diff --git a/Makefile b/Makefile index 4c5fd059..19ae4b3d 100644 --- a/Makefile +++ b/Makefile @@ -14,7 +14,7 @@ all: xgen_gdl90: go get -t -d -v ./main ./test ./linux-mpu9150/mpu ./godump978 ./mpu6050 ./uatparse - go build $(BUILDINFO) main/gen_gdl90.go main/traffic.go main/ry835ai.go main/network.go main/managementinterface.go main/sdr.go main/uibroadcast.go main/monotonic.go + go build $(BUILDINFO) -p 4 main/gen_gdl90.go main/traffic.go main/ry835ai.go main/network.go main/managementinterface.go main/sdr.go main/uibroadcast.go main/monotonic.go main/datalog.go main/equations.go xdump1090: git submodule update --init @@ -39,6 +39,7 @@ install: cp -f gen_gdl90 /usr/bin/gen_gdl90 chmod 755 /usr/bin/gen_gdl90 cp init.d-stratux /etc/init.d/stratux + cp image/10-stratux.rules /etc/udev/rules.d/10-stratux.rules chmod 755 /etc/init.d/stratux ln -sf /etc/init.d/stratux /etc/rc2.d/S01stratux ln -sf /etc/init.d/stratux /etc/rc6.d/K01stratux diff --git a/README.md b/README.md index 3780d142..592bc79c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ [![stratux version](https://img.shields.io/github/tag/cyoung/stratux.svg?style=flat&label=stratux)](https://github.com/cyoung/stratux/releases) [![Build Status](http://circleci-badges-max.herokuapp.com/img/cyoung/stratux/master?token=:circle-ci-token)](https://circleci.com/gh/cyoung/stratux/tree/master) [![BSD3 License](http://img.shields.io/badge/license-BSD3-brightgreen.svg)](https://tldrlegal.com/license/bsd-3-clause-license-%28revised%29) +[![Stratux Slack](http://slack.stratux.me:3000/badge.svg)](http://slack.stratux.me/) + # stratux RTL-SDR UAT tools diff --git a/circle.yml b/circle.yml index 44d36035..925e7573 100644 --- a/circle.yml +++ b/circle.yml @@ -7,7 +7,7 @@ dependencies: pre: - sudo apt-get update; sudo apt-get install libusb-1.0-0-dev; cd ~/; git clone https://github.com/jpoirier/librtlsdr; cd librtlsdr; mkdir build; cd build; cmake ../; make; sudo make install; sudo ldconfig; cd ~/; mkdir gopath; cd ~/; mkdir gopath; wget https://storage.googleapis.com/golang/go1.6.src.tar.gz; tar -zxvf go1.6.src.tar.gz; cd go/src; export GOROOT_BOOTSTRAP=/usr/local/go; ./make.bash; echo $PATH; echo $GOPATH; go version; env override: - - cd .. ; rm -rf stratux ; git clone --recursive https://github.com/cyoung/stratux ; cd stratux ; git fetch origin ; git checkout $CIRCLE_BRANCH ; make + - cd .. ; rm -rf stratux ; git clone --recursive https://github.com/cyoung/stratux ; cd stratux ; git config --add remote.origin.fetch "+refs/pull/*/head:refs/remotes/origin/pr/*" ; git fetch origin ; BRANCH=`echo "$CIRCLE_BRANCH" | sed 's/pull\//pr\//g'` ; git checkout $BRANCH ; make test: override: diff --git a/image/10-stratux.rules b/image/10-stratux.rules new file mode 100644 index 00000000..74b046a2 --- /dev/null +++ b/image/10-stratux.rules @@ -0,0 +1,23 @@ +# To be placed in /etc/udev/rules.d. +# Auto-detect common USB stratux peripherals. + +# u-blox devices. Known devices include +# ublox8: RY835AI, RY836AI +# ublox7: VK-172, RY725AI +# ublox6: VK-162 + +SUBSYSTEMS=="usb", ATTRS{idVendor}=="1546", ATTRS{idProduct}=="01a8", SYMLINK+="ublox8" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="1546", ATTRS{idProduct}=="01a7", SYMLINK+="ublox7" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="1546", ATTRS{idProduct}=="01a6", SYMLINK+="ublox6" +#SUBSYSTEMS=="usb", ATTRS{idVendor}=="1546", ATTRS{idProduct}=="01a7", SYMLINK+="vk172" +#SUBSYSTEMS=="usb", ATTRS{idVendor}=="1546", ATTRS{idProduct}=="01a6", SYMLINK+="vk162" + + + +# pl2303 devices are indistinguishable using idVendor and idProduct. +# Currently the BU-353-S4 and the TU-S9 (serialout) use the pl2303. + +SUBSYSTEMS=="usb", ATTRS{idVendor}=="067b", ATTRS{idProduct}=="2303", SYMLINK+="prolific%n" + +#SUBSYSTEMS=="usb", ATTRS{idVendor}=="067b", ATTRS{idProduct}=="2303", SYMLINK+="bu353s4" +#SUBSYSTEMS=="usb", ATTRS{idVendor}=="067b", ATTRS{idProduct}=="2303", SYMLINK+="tu-s9" diff --git a/image/bashrc.txt b/image/bashrc.txt index 3b4778dd..aad85e3e 100644 --- a/image/bashrc.txt +++ b/image/bashrc.txt @@ -19,3 +19,13 @@ export PATH=/root/go/bin:${PATH} export GOROOT=/root/go export GOPATH=/root/go_path + +# This will allow users to keep an alias file after this file is added +if [ -f /root/.aliases ]; then +. /root/.aliases +fi + +# Uesful aliases for stratux debugging +if [ -f /root/.stxAliases ]; then +. /root/.stxAliases +fi diff --git a/image/config.txt b/image/config.txt index df043d3a..937f43be 100644 --- a/image/config.txt +++ b/image/config.txt @@ -5,3 +5,6 @@ max_usb_current=1 dtparam=i2c1=on dtparam=i2c1_baudrate=400000 dtparam=i2c_arm_baudrate=400000 + +# move RPi3 Bluetooth off of hardware UART to free up connection for GPS +dtoverlay=pi3-miniuart-bt diff --git a/image/hostapd_manager.sh b/image/hostapd_manager.sh new file mode 100644 index 00000000..13963310 --- /dev/null +++ b/image/hostapd_manager.sh @@ -0,0 +1,263 @@ +#!/bin/bash + +###################################################################### +# STRATUX HOSTAPD MANAGER # +###################################################################### + +#Set Script Name variable +SCRIPT=`basename ${BASH_SOURCE[0]}` + +#Initialize variables to default values. +OPT_S=false +OPT_C=false +OPT_E=false +OPT_O=false + +defaultPass="Squawk1200" + +parm="*" +err="####" +att="+++" + +#Set fonts for Help. +BOLD=$(tput bold) +STOT=$(tput smso) +UNDR=$(tput smul) +REV=$(tput rev) +RED=$(tput setaf 1) +GREEN=$(tput setaf 2) +YELLOW=$(tput setaf 3) +MAGENTA=$(tput setaf 5) +WHITE=$(tput setaf 7) +NORM=$(tput sgr0) +NORMAL=$(tput sgr0) + +#Help function +function HELP { + echo -e \\n"Help documentation for ${BOLD}${SCRIPT}.${NORM}"\\n + echo -e "${REV}Basic usage:${NORM} ${BOLD}$SCRIPT -s ssid -c chan -e pass ${NORM}"\\n + echo "Command line switches are optional. The following switches are recognized." + echo "${REV}-s${NORM} --Sets the SSID to ${BOLD}ssid${NORM}. \"-s stratux\"" + echo "${REV}-c${NORM} --Sets the channel to ${BOLD}chan${NORM}. \"-c 1\"" + echo "${REV}-e${NORM} --Turns on encryption with passphrase ${BOLD}pass${NORM}. 8-63 Printable Characters(ascii 32-126). Cannot be used with -o. \"-e password!\"" + echo "${REV}-o${NORM} --Turns off encryption and sets network to open. Cannot be used with -e." + # echo "${REV}-q${NORM} --Run silently." + echo -e "${REV}-h${NORM} --Displays this help message. No further functions are performed."\\n + echo -e "Example: ${BOLD}$SCRIPT -s stratux -c 1 -e N3558D${NORM}"\\n + exit 1 +} +clear +echo "" +echo "#### Stratux HOSTAPD Settings ####" +echo "" + +if [ $(whoami) != 'root' ]; then + echo "${BOLD}${RED}This script must be executed as root, exiting...${WHITE}${NORMAL}" + echo "${BOLD}${RED}USAGE${WHITE}${NORMAL}" + exit 1 +fi + +#Check the number of arguments. If none are passed, print help and exit. +NUMARGS=$# +if [ $NUMARGS -eq 0 ]; then + HELP +fi + +### Start getopts code ### + +#Parse command line flags +#If an option should be followed by an argument, it should be followed by a ":". +#Notice there is no ":" after "oqh". The leading ":" suppresses error messages from +#getopts. This is required to get my unrecognized option code to work. +options=':s:c:e:oqh' +while getopts $options option; do + case $option in + s) #set option "s" + if [[ -z "${OPTARG}" || "${OPTARG}" == *[[:space:]]* || "${OPTARG}" == -* ]]; then + echo "${BOLD}${RED}$err No SSID for -s, exiting...${WHITE}${NORMAL}" + exit 1 + else + OPT_S=$OPTARG + echo "$parm SSID Option -s used: $OPT_S" + echo "${GREEN} SSID will now be ${BOLD}${UNDR}$OPT_S${NORMAL}.${WHITE}" + fi + ;; + c) #set option "c" + if [[ -z "${OPTARG}" || "${OPTARG}" == *[[:space:]]* || "${OPTARG}" == -* ]]; then + echo "${BOLD}${RED}$err Channel option(-c) used without value, exiting... ${WHITE}${NORMAL}" + exit 1 + else + OPT_C=$OPTARG + echo "$parm Channel option -c used: $OPT_C" + if [[ "$OPT_C" =~ ^[0-9]+$ ]] && [ "$OPT_C" -ge 1 -a "$OPT_C" -le 13 ]; then + echo "${GREEN} Channel will now be set to ${BOLD}${UNDR}$OPT_C.${WHITE}${NORMAL}" + else + echo "${BOLD}${RED}$err Channel is not within acceptable values, exiting...${WHITE}${NORMAL}" + exit 1 + fi + fi + ;; + e) #set option "e" + if [[ -z "${OPTARG}" || "${OPTARG}" == *[[:space:]]* || "${OPTARG}" == -* ]]; then + echo "${BOLD}${RED}$err Encryption option(-e) used without passphrase, exiting...${WHITE}${NORMAL}" + exit 1 + else + OPT_E=$OPTARG + echo "$parm Encryption option -e used:" + if [ -z `echo $OPT_E | tr -d "[:print:]"` ] && [ ${#OPT_E} -ge 8 ] && [ ${#OPT_E} -le 63 ]; then + echo "${GREEN} Passphrase will now be ${BOLD}${UNDR}$OPT_E${NORMAL}.${WHITE}${NORMAL}" + else + echo "${BOLD}${RED}$err Invalid PASSWORD: 8 - 63 printable characters, exiting...${WHITE}${NORMAL}" + exit + fi + fi + ;; + o) #set option "o" + if [[ -z "${OPTARG}" || "${OPTARG}" == *[[:space:]]* || "${OPTARG}" == -* ]]; then + echo "$parm Open WiFI Option -o used." + echo "${GREEN} WiFi will be set to ${BOLD}${UNDR}OPEN${NORMAL}${GREEN} or ${BOLD}${UNDR}UNSECURE${WHITE}${NORMAL}" + OPT_O=true + else + echo "${BOLD}${RED}$err Option -o does not require arguement.${WHITE}${NORMAL}" + exit 1 + fi + ;; + h) #show help + HELP + ;; + \?) # invalid option + echo "${BOLD}${RED}$err Invalid option -$OPTARG" >&2 + exit 1 + ;; + :) # Missing Arg + echo "${BOLD}${RED}$err Missing option for argument -$OPTARG ${WHITE}${NORMAL}" >&2 + exit 1 + ;; + *) # Invalid + echo "${BOLD}${RED}$err Unimplemented option -$OPTARG ${WHITE}${NORMAL}" >&2 + exit 1 + ;; + esac +done + +shift $((OPTIND-1)) #This tells getopts to move on to the next argument. + +### End getopts code ### + + +### Main loop to process files ### + +#This is where your main file processing will take place. This example is just +#printing the files and extensions to the terminal. You should place any other +#file processing tasks within the while-do loop. + +if [ $OPT_O = true ] && [ $OPT_E != false ]; then + echo "${BOLD}${RED}$err Option -e and -o cannot be used simultaneously ${WHITE}${NORMAL}" + exit 1 +fi + +echo "" +echo "${BOLD}No errors found. Continuning...${NORMAL}" +echo "" + +# files to edit +HOSTAPD=('/etc/hostapd/hostapd.conf' '/etc/hostapd/hostapd-edimax.conf') + +#### +#### File modification loop +#### +for i in "${HOSTAPD[@]}" +do + if [ -f ${i} ]; then + echo "Working on $i..." + if [ $OPT_S != false ]; then + echo "${MAGENTA}Setting ${YELLOW}SSID${MAGENTA} to ${YELLOW}$OPT_S ${MAGENTA}in $i...${WHITE}" + if grep -q "^ssid=" ${HOSTAPD[$x]}; then + sed -i "s/^ssid=.*/ssid=${OPT_S}/" ${i} + else + echo ${OPT_S} >> ${i} + fi + fi + + if [ $OPT_C != false ]; then + echo "${MAGENTA}Setting ${YELLOW}Channel${MAGENTA} to ${YELLOW}$OPT_C ${MAGENTA}in $i...${WHITE}" + if grep -q "^channel=" ${i}; then + sed -i "s/^channel=.*/channel=${OPT_C}/" ${i} + else + echo ${OPT_C} >> ${i} + fi + fi + + if [ $OPT_E != false ]; then + echo "${MAGENTA}Adding WPA encryption with passphrase: ${YELLOW}$OPT_E ${MAGENTA}to $i...${WHITE}" + if grep -q "^#auth_algs=" ${i}; then + #echo "uncomenting wpa" + sed -i "s/^#auth_algs=.*/auth_algs=1/" ${i} + sed -i "s/^#wpa=.*/wpa=3/" ${i} + sed -i "s/^#wpa_passphrase=.*/wpa_passphrase=$OPT_E/" ${i} + sed -i "s/^#wpa_key_mgmt=.*/wpa_key_mgmt=WPA-PSK/" ${i} + sed -i "s/^#wpa_pairwise=.*/wpa_pairwise=TKIP/" ${i} + sed -i "s/^#rsn_pairwise=.*/rsn_pairwise=CCMP/" ${i} + elif grep -q "^auth_algs=" ${i}; then + #echo "rewriting existing wpa" + sed -i "s/^auth_algs=.*/auth_algs=1/" ${i} + sed -i "s/^wpa=.*/wpa=3/" ${i} + sed -i "s/^wpa_passphrase=.*/wpa_passphrase=$OPT_E/" ${i} + sed -i "s/^wpa_key_mgmt=.*/wpa_key_mgmt=WPA-PSK/" ${i} + sed -i "s/^wpa_pairwise=.*/wpa_pairwise=TKIP/" ${i} + sed -i "s/^rsn_pairwise=.*/rsn_pairwise=CCMP/" ${i} + else + #echo "adding wpa" + echo "" >> ${i} + echo "auth_algs=1" >> ${i} + echo "wpa=3" >> ${i} + echo "wpa_passphrase=$OPT_E" >> ${i} + echo "wpa_key_mgmt=WPA-PSK" >> ${i} + echo "wpa_pairwise=TKIP" >> ${i} + echo "rsn_pairwise=CCMP" >> ${i} + fi + fi + if [ $OPT_O != false ]; then + echo "${MAGENTA}Removing WPA encryption in $i...${WHITE}" + if grep -q "^auth_algs=" ${i}; then + #echo "comenting out wpa" + sed -i "s/^auth_algs=.*/#auth_algs=1/" ${i} + sed -i "s/^wpa=.*/#wpa=3/" ${i} + sed -i "s/^wpa_passphrase=.*/#wpa_passphrase=$defaultPass/" ${i} + sed -i "s/^wpa_key_mgmt=.*/#wpa_key_mgmt=WPA-PSK/" ${i} + sed -i "s/^wpa_pairwise=.*/#wpa_pairwise=TKIP/" ${i} + sed -i "s/^rsn_pairwise=.*/#rsn_pairwise=CCMP/" ${i} + elif grep -q "^#auth_algs=" ${i}; then + #echo "rewriting comentied out wpa" + sed -i "s/^#auth_algs=.*/#auth_algs=1/" ${i} + sed -i "s/^#wpa=.*/#wpa=3/" ${i} + sed -i "s/^#wpa_passphrase=.*/#wpa_passphrase=$defaultPass/" ${i} + sed -i "s/^#wpa_key_mgmt=.*/#wpa_key_mgmt=WPA-PSK/" ${i} + sed -i "s/^#wpa_pairwise=.*/#wpa_pairwise=TKIP/" ${i} + sed -i "s/^#rsn_pairwise=.*/#rsn_pairwise=CCMP/" ${i} + else + #echo "adding commented out WPA" + echo "" >> ${i} + echo "#auth_algs=1" >> ${i} + echo "#wpa=3" >> ${i} + echo "#wpa_passphrase=$defaultPass" >> ${i} + echo "#wpa_key_mgmt=WPA-PSK" >> ${i} + echo "#wpa_pairwise=TKIP" >> ${i} + echo "#rsn_pairwise=CCMP" >> ${i} + fi + + fi + echo "${GREEN}Modified ${i}...done${WHITE}" + echo "" + else + echo "${MAGENTA}No ${i} file found...${WHITE}${NORMAL}" + echo "" + fi +done +echo "${YELLOW}$att Don't forget to reboot... $att ${WHITE}" +echo "" +echo "" + +### End main loop ### + +exit 0 diff --git a/image/interfaces b/image/interfaces index 29680898..96de7bf1 100644 --- a/image/interfaces +++ b/image/interfaces @@ -16,7 +16,7 @@ iface wlan0 inet static ## ## Second Wifi Dongle for local work and internet access -## wifi mist be open for these settings to work +## wifi must be open for these settings to work ## ## uncomment the next two lines to activate the service as well as modify the settings and comments below #allow-hotplug wlan1 diff --git a/image/mkimg.sh b/image/mkimg.sh index 363c49d7..f2160b16 100755 --- a/image/mkimg.sh +++ b/image/mkimg.sh @@ -31,12 +31,18 @@ cp -f root mnt/etc/ssh/authorized_keys/root chown root.root mnt/etc/ssh/authorized_keys/root chmod 644 mnt/etc/ssh/authorized_keys/root +#motd +cp -f motd mnt/etc/motd + #dhcpd config cp -f dhcpd.conf mnt/etc/dhcp/dhcpd.conf #hostapd config cp -f hostapd.conf mnt/etc/hostapd/hostapd.conf cp -f hostapd-edimax.conf mnt/etc/hostapd/hostapd-edimax.conf +#hostapd manager script +cp -f hostapd_manager.sh mnt/usr/sbin/hostapd_manager.sh +chmod 755 mnt/usr/sbin/hostapd_manager.sh #hostapd cp -f hostapd-edimax mnt/usr/sbin/hostapd-edimax chmod 755 mnt/usr/sbin/hostapd-edimax @@ -54,6 +60,9 @@ cp -f isc-dhcp-server mnt/etc/default/isc-dhcp-server #sshd config cp -f sshd_config mnt/etc/ssh/sshd_config +#udev config +cp -f 10-stratux.rules mnt/etc/udev/rules.d + #stratux files cp -f ../libdump978.so mnt/usr/lib/libdump978.so cp -f ../linux-mpu9150/libimu.so mnt/usr/lib/libimu.so @@ -62,6 +71,9 @@ cp -f ../linux-mpu9150/libimu.so mnt/usr/lib/libimu.so cp -rf /root/go mnt/root/ cp -f bashrc.txt mnt/root/.bashrc +#debug aliases +cp -f stxAliases.txt mnt/root/.stxAliases + #rtl-sdr setup cp -f rtl-sdr-blacklist.conf mnt/etc/modprobe.d/ diff --git a/image/motd b/image/motd new file mode 100644 index 00000000..42b505cc --- /dev/null +++ b/image/motd @@ -0,0 +1,18 @@ + + ad88888ba 888888888888 88888888ba db 888888888888 88 88 8b d8 +d8" "8b 88 88 "8b d88b 88 88 88 Y8, ,8P +Y8, 88 88 ,8P d8'`8b 88 88 88 `8b d8' +`Y8aaaaa, 88 88aaaaaa8P' d8' `8b 88 88 88 Y88P + `"""""8b, 88 88""""88' d8YaaaaY8b 88 88 88 d88b + `8b 88 88 `8b d8""""""""8b 88 88 88 ,8P Y8, +Y8a a8P 88 88 `8b d8' `8b 88 Y8a. .a8P d8' `8b + "Y88888P" 88 88 `8b d8' `8b 88 `"Y8888Y"' 8P Y8 + + + +NOTE TO DEVELOPERS: Make sure that your system has an acceptable clock source, i.e., a GPS +with sufficient signal or enable ntpd (internet connection required). + + +Everything here comes with ABSOLUTELY NO WARRANTY, to the extent +permitted by applicable law. diff --git a/image/stratux-wifi.sh b/image/stratux-wifi.sh index c0676b4c..55e6a87a 100755 --- a/image/stratux-wifi.sh +++ b/image/stratux-wifi.sh @@ -11,14 +11,13 @@ DAEMON_CONF=/etc/hostapd/hostapd.conf DAEMON_SBIN=/usr/sbin/hostapd +EW7811Un=$(lsusb | grep EW-7811Un) RPI_REV=`cat /proc/cpuinfo | grep 'Revision' | awk '{print $3}' | sed 's/^1000//'` -if [ "$RPI_REV" = "a01041" ] || [ "$RPI_REV" = "a21041" ] ; then - # This is a RPi2B. +if [ "$RPI_REV" = "a01041" ] || [ "$RPI_REV" = "a21041" ] || [ "$RPI_REV" = "900092" ] || [ "$RPI_REV" = "900093" ] && [ "$EW7811Un" != '' ]; then + # This is a RPi2B or RPi0 with Edimax USB Wifi dongle. DAEMON_CONF=/etc/hostapd/hostapd-edimax.conf DAEMON_SBIN=/usr/sbin/hostapd-edimax -fi -if [ "$RPI_REV" = "a02082" ] || [ "$RPI_REV" = "a22082" ]; then - # This is a RPi3B. +else DAEMON_CONF=/etc/hostapd/hostapd.conf fi diff --git a/image/stxAliases.txt b/image/stxAliases.txt new file mode 100644 index 00000000..e5a36168 --- /dev/null +++ b/image/stxAliases.txt @@ -0,0 +1,56 @@ +alias stratux-off='shutdown -P now' + +alias eeprom='rtl_eeprom' +alias sa='service --status-all' +alias stxrestart='service stratux restart' +alias stxstop='service stratux stop' +alias stxstart='service stratux start' + +alias sdr0='rtl_eeprom -d 0' +alias sdr1='rtl_eeprom -d 1' + +alias sdrs='sdr0 || sdr1' + +alias d90='dump1090' +alias d900='dump1090 --device-index 0 --interactive' +alias d901='dump1090 --device-index 1 --interactive' + +alias kal0='kal -d 0 -s GSM850 -g 47' +alias kal1='kal -d 1 -s GSM850 -g 47' + +alias kalChan0='function _kalChan0 () { kal -d 0 -g 47 -c $1; unset -f _kalChan0; }; _kalChan0' +alias kalChan1='function _kalChan1 () { kal -d 1 -g 47 -c $1; unset -f _kalChan1; }; _kalChan1' + +alias setSerial0='function _setSerial0 () { rtl_eeprom -d 0 -s stx:0:$1; unset -f _setSerial0; }; _setSerial0' +alias setSerial1='function _setSerial0 () { rtl_eeprom -d 1 -s stx:0:$1; unset -f _setSerial1; }; _setSerial1' + + +alias stratux-help='_stxhelp' + +function _stxhelp() { +echo "" +echo "###################################################" +echo "" +echo "These are commands avalible to you to debug Stratux" +echo "" +echo "###################################################" +echo "" +echo "stratux-off Shutdown Stratux" +echo "sa List all services running, check for '[+] stratux'" +echo "stxrestart Restart Startux service only" +echo "stxstop Stop Stratux Service." +echo "stxstart Start Stratux Service." +echo "sdr0 Test to see if sdr0 is in use by Stratux." +echo "sdr1 Test to see if sdr1 is in use by Startux." +echo "sdrs Test to see if both SDRs are in use by Stratux." +echo "d90 List my SDRs with serials" +echo "d900 Run dump1090 on SDR0 in interactive mode." +echo "d901 Run dump1090 on SDR0 in interactive mode." +echo "kal0 Find Channels for calibrating PPM of SDR0." +echo "kal1 Find Channels for calibrating PPM of SDR1." +echo "kalChan0 Use the Chan from above to get ppm pf SDR0. Example: kalChan0 151" +echo "kalChan1 Use the Chan from above to get ppm pf SDR1. Example: kalChan1 151" +echo "setSerial0 Set the PPM error to SDR0. Value from kalChan0. Example: setSerial0 -45" +echo "setSerial1 Set the PPM error to SDR1. Value from kalChan1. Example: setSerial0 23" + +} diff --git a/main/datalog.go b/main/datalog.go new file mode 100644 index 00000000..eda48cd5 --- /dev/null +++ b/main/datalog.go @@ -0,0 +1,635 @@ +/* + Copyright (c) 2015-2016 Christopher Young + Distributable under the terms of The "BSD New"" License + that can be found in the LICENSE file, herein included + as part of this header. + + datalog.go: Log stratux data as it is received. Bucket data into timestamp time slots. + +*/ + +package main + +import ( + "database/sql" + "errors" + "fmt" + _ "github.com/mattn/go-sqlite3" + "log" + "os" + "reflect" + "strconv" + "strings" + "time" +) + +const ( + LOG_TIMESTAMP_RESOLUTION = 250 * time.Millisecond +) + +type StratuxTimestamp struct { + id int64 + Time_type_preference int // 0 = stratuxClock, 1 = gpsClock, 2 = gpsClock extrapolated via stratuxClock. + StratuxClock_value time.Time + GPSClock_value time.Time // The value of this is either from the GPS or extrapolated from the GPS via stratuxClock if pref is 1 or 2. It is time.Time{} if 0. + PreferredTime_value time.Time + StartupID int64 +} + +// 'startup' table creates a new entry each time the daemon is started. This keeps track of sequential starts, even if the +// timestamp is ambiguous (units with no GPS). This struct is just a placeholder for an empty table (other than primary key). +type StratuxStartup struct { + id int64 + Fill string +} + +var dataLogStarted bool +var dataLogReadyToWrite bool + +var stratuxStartupID int64 +var dataLogTimestamps []StratuxTimestamp +var dataLogCurTimestamp int64 // Current timestamp bucket. This is an index on dataLogTimestamps which is not necessarily the db id. + +/* + checkTimestamp(). + Verify that our current timestamp is within the LOG_TIMESTAMP_RESOLUTION bucket. + Returns false if the timestamp was changed, true if it is still valid. + This is where GPS timestamps are extrapolated, if the GPS data is currently valid. +*/ + +func checkTimestamp() bool { + thisCurTimestamp := dataLogCurTimestamp + if stratuxClock.Since(dataLogTimestamps[thisCurTimestamp].StratuxClock_value) >= LOG_TIMESTAMP_RESOLUTION { + var ts StratuxTimestamp + ts.id = 0 + ts.Time_type_preference = 0 // stratuxClock. + ts.StratuxClock_value = stratuxClock.Time + ts.GPSClock_value = time.Time{} + ts.PreferredTime_value = stratuxClock.Time + + // Extrapolate from GPS timestamp, if possible. + if isGPSClockValid() && thisCurTimestamp > 0 { + // Was the last timestamp either extrapolated or GPS time? + last_ts := dataLogTimestamps[thisCurTimestamp] + if last_ts.Time_type_preference == 1 || last_ts.Time_type_preference == 2 { + // Extrapolate via stratuxClock. + timeSinceLastTS := ts.StratuxClock_value.Sub(last_ts.StratuxClock_value) // stratuxClock ticks since last timestamp. + extrapolatedGPSTimestamp := last_ts.PreferredTime_value.Add(timeSinceLastTS) + + // Re-set the preferred timestamp type to '2' (extrapolated time). + ts.Time_type_preference = 2 + ts.PreferredTime_value = extrapolatedGPSTimestamp + ts.GPSClock_value = extrapolatedGPSTimestamp + } + } + + dataLogTimestamps = append(dataLogTimestamps, ts) + dataLogCurTimestamp = int64(len(dataLogTimestamps) - 1) + return false + } + return true +} + +type SQLiteMarshal struct { + FieldType string + Marshal func(v reflect.Value) string +} + +func boolMarshal(v reflect.Value) string { + b := v.Bool() + if b { + return "1" + } + return "0" +} + +func intMarshal(v reflect.Value) string { + return strconv.FormatInt(v.Int(), 10) +} + +func uintMarshal(v reflect.Value) string { + return strconv.FormatUint(v.Uint(), 10) +} + +func floatMarshal(v reflect.Value) string { + return strconv.FormatFloat(v.Float(), 'f', 10, 64) +} + +func stringMarshal(v reflect.Value) string { + return v.String() +} + +func notsupportedMarshal(v reflect.Value) string { + return "" +} + +func structCanBeMarshalled(v reflect.Value) bool { + m := v.MethodByName("String") + if m.IsValid() && !m.IsNil() { + return true + } + return false +} + +func structMarshal(v reflect.Value) string { + if structCanBeMarshalled(v) { + m := v.MethodByName("String") + in := make([]reflect.Value, 0) + ret := m.Call(in) + if len(ret) > 0 { + return ret[0].String() + } + } + return "" +} + +var sqliteMarshalFunctions = map[string]SQLiteMarshal{ + "bool": {FieldType: "INTEGER", Marshal: boolMarshal}, + "int": {FieldType: "INTEGER", Marshal: intMarshal}, + "uint": {FieldType: "INTEGER", Marshal: uintMarshal}, + "float": {FieldType: "REAL", Marshal: floatMarshal}, + "string": {FieldType: "TEXT", Marshal: stringMarshal}, + "struct": {FieldType: "STRING", Marshal: structMarshal}, + "notsupported": {FieldType: "notsupported", Marshal: notsupportedMarshal}, +} + +var sqlTypeMap = map[reflect.Kind]string{ + reflect.Bool: "bool", + reflect.Int: "int", + reflect.Int8: "int", + reflect.Int16: "int", + reflect.Int32: "int", + reflect.Int64: "int", + reflect.Uint: "uint", + reflect.Uint8: "uint", + reflect.Uint16: "uint", + reflect.Uint32: "uint", + reflect.Uint64: "uint", + reflect.Uintptr: "notsupported", + reflect.Float32: "float", + reflect.Float64: "float", + reflect.Complex64: "notsupported", + reflect.Complex128: "notsupported", + reflect.Array: "notsupported", + reflect.Chan: "notsupported", + reflect.Func: "notsupported", + reflect.Interface: "notsupported", + reflect.Map: "notsupported", + reflect.Ptr: "notsupported", + reflect.Slice: "notsupported", + reflect.String: "string", + reflect.Struct: "struct", + reflect.UnsafePointer: "notsupported", +} + +func makeTable(i interface{}, tbl string, db *sql.DB) { + val := reflect.ValueOf(i) + + fields := make([]string, 0) + for i := 0; i < val.NumField(); i++ { + kind := val.Field(i).Kind() + fieldName := val.Type().Field(i).Name + sqlTypeAlias := sqlTypeMap[kind] + + // Check that if the field is a struct that it can be marshalled. + if sqlTypeAlias == "struct" && !structCanBeMarshalled(val.Field(i)) { + continue + } + if sqlTypeAlias == "notsupported" || fieldName == "id" { + continue + } + sqlType := sqliteMarshalFunctions[sqlTypeAlias].FieldType + s := fieldName + " " + sqlType + fields = append(fields, s) + } + + // Add the timestamp_id field to link up with the timestamp table. + if tbl != "timestamp" && tbl != "startup" { + fields = append(fields, "timestamp_id INTEGER") + } + + tblCreate := fmt.Sprintf("CREATE TABLE %s (id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, %s)", tbl, strings.Join(fields, ", ")) + + _, err := db.Exec(tblCreate) + if err != nil { + fmt.Printf("ERROR: %s\n", err.Error()) + } +} + +/* + bulkInsert(). + Reads insertBatch and insertBatchIfs. This is called after a group of insertData() calls. +*/ + +func bulkInsert(tbl string, db *sql.DB) (res sql.Result, err error) { + if _, ok := insertString[tbl]; !ok { + return nil, errors.New("no insert statement") + } + + batchVals := insertBatchIfs[tbl] + numColsPerRow := len(batchVals[0]) + maxRowBatch := int(999 / numColsPerRow) // SQLITE_MAX_VARIABLE_NUMBER = 999. + // log.Printf("table %s. %d cols per row. max batch %d\n", tbl, numColsPerRow, maxRowBatch) + for len(batchVals) > 0 { + // timeInit := time.Now() + i := int(0) // Variable number of rows per INSERT statement. + + stmt := "" + vals := make([]interface{}, 0) + querySize := uint64(0) // Size of the query in bytes. + for len(batchVals) > 0 && i < maxRowBatch && querySize < 750000 { // Maximum of 1,000,000 bytes per query. + if len(stmt) == 0 { // The first set will be covered by insertString. + stmt = insertString[tbl] + querySize += uint64(len(insertString[tbl])) + } else { + addStr := ", (" + strings.Join(strings.Split(strings.Repeat("?", len(batchVals[0])), ""), ",") + ")" + stmt += addStr + querySize += uint64(len(addStr)) + } + for _, val := range batchVals[0] { + querySize += uint64(len(val.(string))) + } + vals = append(vals, batchVals[0]...) + batchVals = batchVals[1:] + i++ + } + // log.Printf("inserting %d rows to %s. querySize=%d\n", i, tbl, querySize) + res, err = db.Exec(stmt, vals...) + // timeBatch := time.Since(timeInit) // debug + // log.Printf("SQLite: bulkInserted %d rows to %s. Took %f msec to build and insert query. querySize=%d\n", i, tbl, 1000*timeBatch.Seconds(), querySize) // debug + if err != nil { + log.Printf("sqlite INSERT error: '%s'\n", err.Error()) + return + } + } + + // Clear the buffers. + delete(insertString, tbl) + delete(insertBatchIfs, tbl) + + return +} + +/* + insertData(). + Inserts an arbitrary struct into an SQLite table. + Inserts the timestamp first, if its 'id' is 0. + +*/ + +// Cached 'VALUES' statements. Indexed by table name. +var insertString map[string]string // INSERT INTO tbl (col1, col2, ...) VALUES(?, ?, ...). Only for one value. +var insertBatchIfs map[string][][]interface{} + +func insertData(i interface{}, tbl string, db *sql.DB, ts_num int64) int64 { + val := reflect.ValueOf(i) + + keys := make([]string, 0) + values := make([]string, 0) + for i := 0; i < val.NumField(); i++ { + kind := val.Field(i).Kind() + fieldName := val.Type().Field(i).Name + sqlTypeAlias := sqlTypeMap[kind] + + if sqlTypeAlias == "notsupported" || fieldName == "id" { + continue + } + + v := sqliteMarshalFunctions[sqlTypeAlias].Marshal(val.Field(i)) + + keys = append(keys, fieldName) + values = append(values, v) + } + + // Add the timestamp_id field to link up with the timestamp table. + if tbl != "timestamp" && tbl != "startup" { + keys = append(keys, "timestamp_id") + if dataLogTimestamps[ts_num].id == 0 { + //FIXME: This is somewhat convoluted. When insertData() is called for a ts_num that corresponds to a timestamp with no database id, + // then it inserts that timestamp via the same interface and the id is updated in the structure via the below lines + // (dataLogTimestamps[ts_num].id = id). + dataLogTimestamps[ts_num].StartupID = stratuxStartupID + insertData(dataLogTimestamps[ts_num], "timestamp", db, ts_num) // Updates dataLogTimestamps[ts_num].id. + } + values = append(values, strconv.FormatInt(dataLogTimestamps[ts_num].id, 10)) + } + + if _, ok := insertString[tbl]; !ok { + // Prepare the statement. + tblInsert := fmt.Sprintf("INSERT INTO %s (%s) VALUES(%s)", tbl, strings.Join(keys, ","), + strings.Join(strings.Split(strings.Repeat("?", len(keys)), ""), ",")) + insertString[tbl] = tblInsert + } + + // Make the values slice into a slice of interface{}. + ifs := make([]interface{}, len(values)) + for i := 0; i < len(values); i++ { + ifs[i] = values[i] + } + + insertBatchIfs[tbl] = append(insertBatchIfs[tbl], ifs) + + if tbl == "timestamp" || tbl == "startup" { // Immediate insert always for "timestamp" and "startup" table. + res, err := bulkInsert(tbl, db) // Bulk insert of 1, always. + if err == nil { + id, err := res.LastInsertId() + if err == nil && tbl == "timestamp" { // Special handling for timestamps. Update the timestamp ID. + ts := dataLogTimestamps[ts_num] + ts.id = id + dataLogTimestamps[ts_num] = ts + } + return id + } + } + + return 0 +} + +type DataLogRow struct { + tbl string + data interface{} + ts_num int64 +} + +var dataLogChan chan DataLogRow +var shutdownDataLog chan bool +var shutdownDataLogWriter chan bool + +var dataLogWriteChan chan DataLogRow + +func dataLogWriter(db *sql.DB) { + dataLogWriteChan = make(chan DataLogRow, 10240) + shutdownDataLogWriter = make(chan bool) + // The write queue. As data comes in via dataLogChan, it is timestamped and stored. + // When writeTicker comes up, the queue is emptied. + writeTicker := time.NewTicker(10 * time.Second) + rowsQueuedForWrite := make([]DataLogRow, 0) + for { + select { + case r := <-dataLogWriteChan: + // Accept timestamped row. + rowsQueuedForWrite = append(rowsQueuedForWrite, r) + case <-writeTicker.C: + // for i := 0; i < 1000; i++ { + // logSituation() + // } + timeStart := stratuxClock.Time + nRows := len(rowsQueuedForWrite) + if globalSettings.DEBUG { + log.Printf("Writing %d rows\n", nRows) + } + // Write the buffered rows. This will block while it is writing. + // Save the names of the tables affected so that we can run bulkInsert() on after the insertData() calls. + tblsAffected := make(map[string]bool) + // Start transaction. + tx, err := db.Begin() + if err != nil { + log.Printf("db.Begin() error: %s\n", err.Error()) + break // from select {} + } + for _, r := range rowsQueuedForWrite { + tblsAffected[r.tbl] = true + insertData(r.data, r.tbl, db, r.ts_num) + } + // Do the bulk inserts. + for tbl, _ := range tblsAffected { + bulkInsert(tbl, db) + } + // Close the transaction. + tx.Commit() + rowsQueuedForWrite = make([]DataLogRow, 0) // Zero the queue. + timeElapsed := stratuxClock.Since(timeStart) + if globalSettings.DEBUG { + rowsPerSecond := float64(nRows) / float64(timeElapsed.Seconds()) + log.Printf("Writing finished. %d rows in %.2f seconds (%.1f rows per second).\n", nRows, float64(timeElapsed.Seconds()), rowsPerSecond) + } + if timeElapsed.Seconds() > 10.0 { + log.Printf("WARNING! SQLite logging is behind. Last write took %.1f seconds.\n", float64(timeElapsed.Seconds())) + dataLogCriticalErr := fmt.Errorf("WARNING! SQLite logging is behind. Last write took %.1f seconds.\n", float64(timeElapsed.Seconds())) + addSystemError(dataLogCriticalErr) + } + case <-shutdownDataLogWriter: // Received a message on the channel to initiate a graceful shutdown, and to command dataLog() to shut down + log.Printf("datalog.go: dataLogWriter() received shutdown message with rowsQueuedForWrite = %d\n", len(rowsQueuedForWrite)) + shutdownDataLog <- true + return + } + } + log.Printf("datalog.go: dataLogWriter() shutting down\n") +} + +func dataLog() { + dataLogStarted = true + log.Printf("datalog.go: dataLog() started\n") + dataLogChan = make(chan DataLogRow, 10240) + shutdownDataLog = make(chan bool) + dataLogTimestamps = make([]StratuxTimestamp, 0) + var ts StratuxTimestamp + ts.id = 0 + ts.Time_type_preference = 0 // stratuxClock. + ts.StratuxClock_value = stratuxClock.Time + ts.GPSClock_value = time.Time{} + ts.PreferredTime_value = stratuxClock.Time + dataLogTimestamps = append(dataLogTimestamps, ts) + dataLogCurTimestamp = 0 + + // Check if we need to create a new database. + createDatabase := false + + if _, err := os.Stat(dataLogFilef); os.IsNotExist(err) { + createDatabase = true + log.Printf("creating new database '%s'.\n", dataLogFilef) + } + + db, err := sql.Open("sqlite3", dataLogFilef) + if err != nil { + log.Printf("sql.Open(): %s\n", err.Error()) + } + + defer func() { + db.Close() + dataLogStarted = false + //close(dataLogChan) + log.Printf("datalog.go: dataLog() has closed DB in %s\n", dataLogFilef) + }() + + _, err = db.Exec("PRAGMA journal_mode=WAL") + if err != nil { + log.Printf("db.Exec('PRAGMA journal_mode=WAL') err: %s\n", err.Error()) + } + _, err = db.Exec("PRAGMA synchronous=OFF") + if err != nil { + log.Printf("db.Exec('PRAGMA journal_mode=WAL') err: %s\n", err.Error()) + } + + //log.Printf("Starting dataLogWriter\n") // REMOVE -- DEBUG + go dataLogWriter(db) + + // Do we need to create the database? + if createDatabase { + makeTable(StratuxTimestamp{}, "timestamp", db) + makeTable(mySituation, "mySituation", db) + makeTable(globalStatus, "status", db) + makeTable(globalSettings, "settings", db) + makeTable(TrafficInfo{}, "traffic", db) + makeTable(msg{}, "messages", db) + makeTable(esmsg{}, "es_messages", db) + makeTable(Dump1090TermMessage{}, "dump1090_terminal", db) + makeTable(StratuxStartup{}, "startup", db) + } + + // The first entry to be created is the "startup" entry. + stratuxStartupID = insertData(StratuxStartup{}, "startup", db, 0) + + dataLogReadyToWrite = true + //log.Printf("Entering dataLog read loop\n") //REMOVE -- DEBUG + for { + select { + case r := <-dataLogChan: + // When data is input, the first step is to timestamp it. + // Check if our time bucket has expired or has never been entered. + checkTimestamp() + // Mark the row with the current timestamp ID, in case it gets entered later. + r.ts_num = dataLogCurTimestamp + // Queue it for the scheduled write. + dataLogWriteChan <- r + case <-shutdownDataLog: // Received a message on the channel to complete a graceful shutdown (see the 'defer func()...' statement above). + log.Printf("datalog.go: dataLog() received shutdown message\n") + return + } + } + log.Printf("datalog.go: dataLog() shutting down\n") + close(shutdownDataLog) +} + +/* + setDataLogTimeWithGPS(). + Create a timestamp entry using GPS time. +*/ + +func setDataLogTimeWithGPS(sit SituationData) { + if isGPSClockValid() { + var ts StratuxTimestamp + // Piggyback a GPS time update from this update. + ts.id = 0 + ts.Time_type_preference = 1 // gpsClock. + ts.StratuxClock_value = stratuxClock.Time + ts.GPSClock_value = sit.GPSTime + ts.PreferredTime_value = sit.GPSTime + + dataLogTimestamps = append(dataLogTimestamps, ts) + dataLogCurTimestamp = int64(len(dataLogTimestamps) - 1) + } +} + +/* + logSituation(), logStatus(), ... pass messages from other functions to the logging + engine. These are only read into `dataLogChan` if the Replay Log is toggled on, + and if the log system is ready to accept writes. +*/ + +func isDataLogReady() bool { + return dataLogReadyToWrite +} + +func logSituation() { + if globalSettings.ReplayLog && isDataLogReady() { + dataLogChan <- DataLogRow{tbl: "mySituation", data: mySituation} + } +} + +func logStatus() { + if globalSettings.ReplayLog && isDataLogReady() { + dataLogChan <- DataLogRow{tbl: "status", data: globalStatus} + } +} + +func logSettings() { + if globalSettings.ReplayLog && isDataLogReady() { + dataLogChan <- DataLogRow{tbl: "settings", data: globalSettings} + } +} + +func logTraffic(ti TrafficInfo) { + if globalSettings.ReplayLog && isDataLogReady() { + dataLogChan <- DataLogRow{tbl: "traffic", data: ti} + } +} + +func logMsg(m msg) { + if globalSettings.ReplayLog && isDataLogReady() { + dataLogChan <- DataLogRow{tbl: "messages", data: m} + } +} + +func logESMsg(m esmsg) { + if globalSettings.ReplayLog && isDataLogReady() { + dataLogChan <- DataLogRow{tbl: "es_messages", data: m} + } +} + +func logDump1090TermMessage(m Dump1090TermMessage) { + if globalSettings.DEBUG && globalSettings.ReplayLog && isDataLogReady() { + dataLogChan <- DataLogRow{tbl: "dump1090_terminal", data: m} + } +} + +func initDataLog() { + //log.Printf("dataLogStarted = %t. dataLogReadyToWrite = %t\n", dataLogStarted, dataLogReadyToWrite) //REMOVE -- DEBUG + insertString = make(map[string]string) + insertBatchIfs = make(map[string][][]interface{}) + go dataLogWatchdog() + + //log.Printf("datalog.go: initDataLog() complete.\n") //REMOVE -- DEBUG +} + +/* + dataLogWatchdog(): Watchdog function to control startup / shutdown of data logging subsystem. + Called by initDataLog as a goroutine. It iterates once per second to determine if + globalSettings.ReplayLog has toggled. If logging was switched from off to on, it starts + datalog() as a goroutine. If the log is running and we want it to stop, it calls + closeDataLog() to turn off the input channels, close the log, and tear down the dataLog + and dataLogWriter goroutines. +*/ + +func dataLogWatchdog() { + for { + if !dataLogStarted && globalSettings.ReplayLog { // case 1: sqlite logging isn't running, and we want to start it + log.Printf("datalog.go: Watchdog wants to START logging.\n") + go dataLog() + } else if dataLogStarted && !globalSettings.ReplayLog { // case 2: sqlite logging is running, and we want to shut it down + log.Printf("datalog.go: Watchdog wants to STOP logging.\n") + closeDataLog() + } + //log.Printf("Watchdog iterated.\n") //REMOVE -- DEBUG + time.Sleep(1 * time.Second) + //log.Printf("Watchdog sleep over.\n") //REMOVE -- DEBUG + } +} + +/* + closeDataLog(): Handler for graceful shutdown of data logging goroutines. It is called by + by dataLogWatchdog(), gracefulShutdown(), and by any other function (disk space monitor?) + that needs to be able to shut down sqlite logging without corrupting data or blocking + execution. + + This function turns off log message reads into the dataLogChan receiver, and sends a + message to a quit channel ('shutdownDataLogWriter`) in dataLogWriter(). dataLogWriter() + then sends a message to a quit channel to 'shutdownDataLog` in dataLog() to close *that* + goroutine. That function sets dataLogStarted=false once the logfile is closed. By waiting + for that signal, closeDataLog() won't exit until the log is safely written. This prevents + data loss on shutdown. +*/ + +func closeDataLog() { + //log.Printf("closeDataLog(): dataLogStarted = %t\n", dataLogStarted) //REMOVE -- DEBUG + dataLogReadyToWrite = false // prevent any new messages from being sent down the channels + log.Printf("datalog.go: Starting data log shutdown\n") + shutdownDataLogWriter <- true // + defer close(shutdownDataLogWriter) // ... and close the channel so subsequent accidental writes don't stall execution + log.Printf("datalog.go: Waiting for shutdown signal from dataLog()") + for dataLogStarted { + //log.Printf("closeDataLog(): dataLogStarted = %t\n", dataLogStarted) //REMOVE -- DEBUG + time.Sleep(50 * time.Millisecond) + } + log.Printf("datalog.go: Data log shutdown successful.\n") +} diff --git a/main/equations.go b/main/equations.go new file mode 100644 index 00000000..8be2bab3 --- /dev/null +++ b/main/equations.go @@ -0,0 +1,324 @@ +/* + Copyright (c) 2016 AvSquirrel (https://github.com/AvSquirrel) + Distributable under the terms of the "BSD New" License + that can be found in the LICENSE file, herein included + as part of this header. + + equations.go: Math and statistics library used to support AHRS + and other fuctions of Stratux package +*/ + +package main + +import ( + "fmt" + "math" +) + +// linReg calculates slope and intercept for a least squares linear regression of y[] vs x[] +// Returns error if fewer than two data points in each series, or if series lengths are different + +func linReg(x, y []float64) (slope, intercept float64, valid bool) { + + n := len(x) + nf := float64(n) + + if n != len(y) { + fmt.Printf("linReg: Lengths not equal\n") + return math.NaN(), math.NaN(), false + } + + if n < 2 { + fmt.Printf("linReg: Lengths too short\n") + return math.NaN(), math.NaN(), false + } + + var Sx, Sy, Sxx, Sxy, Syy float64 + + for i := range x { + Sx += x[i] + Sy += y[i] + Sxx += x[i] * x[i] + Sxy += x[i] * y[i] + Syy += y[i] * y[i] + } + + if nf*Sxx == Sx*Sx { + fmt.Printf("linReg: Infinite slope\n") + return math.NaN(), math.NaN(), false + } + + // Calculate slope and intercept + slope = (nf*Sxy - Sx*Sy) / (nf*Sxx - Sx*Sx) + intercept = Sy/nf - slope*Sx/nf + valid = true + return +} + +// linRegWeighted calculates slope and intercept for a weighted least squares +// linear regression of y[] vs x[], given weights w[] for each point. +// Returns error if fewer than two data points in each series, if series lengths are different, +// if weights sum to zero, or if slope is infinite + +func linRegWeighted(x, y, w []float64) (slope, intercept float64, valid bool) { + + n := len(x) + + if n != len(y) || n != len(w) { + fmt.Printf("linRegWeighted: Lengths not equal\n") + return math.NaN(), math.NaN(), false + } + + if n < 2 { + fmt.Printf("linRegWeighted: Lengths too short\n") + return math.NaN(), math.NaN(), false + } + + //var Sx, Sy, Sxx, Sxy, Syy float64 + var Sw, Swx, Swy, Swxx, Swxy, Swyy float64 + + for i := range x { + Sw += w[i] + Swxy += w[i] * x[i] * y[i] + Swx += w[i] * x[i] + Swy += w[i] * y[i] + Swxx += w[i] * x[i] * x[i] + Swyy += w[i] * y[i] * y[i] + /* + Sx += x[i] + Sy += y[i] + Sxx += x[i]*x[i] + Sxy += x[i]*y[i] + Syy += y[i]*y[i] + */ + } + + if Sw == 0 { + fmt.Printf("linRegWeighted: Sum of weights is zero\n") + return math.NaN(), math.NaN(), false + } + + if Sw*Swxx == Swx*Swx { + fmt.Printf("linRegWeighted: Infinite slope\n") + return math.NaN(), math.NaN(), false + } + + // Calculate slope and intercept + slope = (Sw*Swxy - Swx*Swy) / (Sw*Swxx - Swx*Swx) + intercept = Swy/Sw - slope*Swx/Sw + valid = true + return +} + +// triCubeWeight returns the value of the tricube weight function +// at point x, for the given center and halfwidth. +func triCubeWeight(center, halfwidth, x float64) float64 { + var weight, x_t float64 + x_t = math.Abs((x - center) / halfwidth) + if x_t < 1 { + weight = math.Pow((1 - math.Pow(x_t, 3)), 3) + } else { + weight = 0 + } + return weight +} + +// arrayMin calculates the minimum value in array x +func arrayMin(x []float64) (float64, bool) { + if len(x) < 1 { + fmt.Printf("arrayMin: Length too short\n") + return math.NaN(), false + } + + min := x[0] + for i := range x { + if x[i] < min { + min = x[i] + } + } + return min, true +} + +// arrayMax calculates the maximum value in array x +func arrayMax(x []float64) (float64, bool) { + if len(x) < 1 { + fmt.Printf("arrayMax: Length too short\n") + return math.NaN(), false + } + + max := x[0] + for i := range x { + if x[i] > max { + max = x[i] + } + } + return max, true +} + +// arrayRange calculates the range of values in array x +func arrayRange(x []float64) (float64, bool) { + max, err1 := arrayMax(x) + min, err2 := arrayMin(x) + + if !err1 || !err2 { + fmt.Printf("Error calculating range\n") + return math.NaN(), false + } + + return (max - min), true +} + +// mean returns the arithmetic mean of array x +func mean(x []float64) (float64, bool) { + if len(x) < 1 { + fmt.Printf("mean: Length too short\n") + return math.NaN(), false + } + + sum := 0.0 + nf := float64(len(x)) + + for i := range x { + sum += x[i] + } + + return sum / nf, true +} + +// stdev estimates the sample standard deviation of array x +func stdev(x []float64) (float64, bool) { + if len(x) < 2 { + fmt.Printf("stdev: Length too short\n") + return math.NaN(), false + } + + nf := float64(len(x)) + xbar, xbarValid := mean(x) + + if !xbarValid { + fmt.Printf("stdev: Error calculating xbar\n") + return math.NaN(), false + } + + sumsq := 0.0 + + for i := range x { + sumsq += (x[i] - xbar) * (x[i] - xbar) + } + + return math.Pow(sumsq/(nf-1), 0.5), true +} + +// radians converts angle from degrees, and returns its value in radians +func radians(angle float64) float64 { + return angle * math.Pi / 180.0 +} + +// degrees converts angle from radians, and returns its value in degrees +func degrees(angle float64) float64 { + return angle * 180.0 / math.Pi +} + +// radiansRel converts angle from degrees, and returns its value in radians in the range -Pi to + Pi +func radiansRel(angle float64) float64 { + for angle > 180 { + angle -= 360 + } + for angle < -180 { + angle += 360 + } + return angle * math.Pi / 180.0 +} + +// degreesRel converts angle from radians, and returns its value in the range of -180 to +180 degrees +func degreesRel(angle float64) float64 { + for angle > math.Pi { + angle -= 2 * math.Pi + } + for angle < -math.Pi { + angle += 2 * math.Pi + } + return angle * 180.0 / math.Pi +} + +// degreesHdg converts angle from radians, and returns its value in the range of 0+ to 360 degrees +func degreesHdg(angle float64) float64 { + for angle < 0 { + angle += 2 * math.Pi + } + return angle * 180.0 / math.Pi +} + +/* +Distance functions based on rectangular coordinate systems +Simple calculations and "good enough" on small scale (± 1° of lat / lon) +suitable for relative distance to nearby traffic +*/ + +// distRect returns distance and bearing to target #2 (e.g. traffic) from target #1 (e.g. ownship) +// Inputs are lat / lon of both points in decimal degrees +// Outputs are distance in meters and bearing in degrees (0° = north, 90° = east) +// Secondary outputs are north and east components of distance in meters (north, east positive) + +func distRect(lat1, lon1, lat2, lon2 float64) (dist, bearing, distN, distE float64) { + radius_earth := 6371008.8 // meters; mean radius + dLat := radiansRel(lat2 - lat1) + avgLat := radiansRel((lat2 + lat1) / 2) + dLon := radiansRel(lon2 - lon1) + distN = dLat * radius_earth + distE = dLon * radius_earth * math.Abs(math.Cos(avgLat)) + dist = math.Pow(distN*distN+distE*distE, 0.5) + bearing = math.Atan2(distE, distN) + bearing = degreesHdg(bearing) + return +} + +// distRectNorth returns north-south distance from point 1 to point 2. +// Inputs are lat in decimal degrees. Output is distance in meters (east positive) +func distRectNorth(lat1, lat2 float64) float64 { + var dist float64 + radius_earth := 6371008.8 // meters; mean radius + dLat := radiansRel(lat2 - lat1) + dist = dLat * radius_earth + return dist +} + +// distRectEast returns east-west distance from point 1 to point 2. +// Inputs are lat/lon in decimal degrees. Output is distance in meters (north positive) +func distRectEast(lat1, lon1, lat2, lon2 float64) float64 { + var dist float64 + radius_earth := 6371008.8 // meters; mean radius + //dLat := radiansRel(lat2 - lat1) // unused + avgLat := radiansRel((lat2 + lat1) / 2) + dLon := radiansRel(lon2 - lon1) + dist = dLon * radius_earth * math.Abs(math.Cos(avgLat)) + return dist +} + +/* +Distance functions: Polar coordinate systems +More accurate over longer distances +*/ + +// distance calculates distance between two points using the law of cosines. +// Inputs are lat / lon of both points in decimal degrees +// Outputs are distance in meters and bearing to the target from origin in degrees (0° = north, 90° = east) +func distance(lat1, lon1, lat2, lon2 float64) (dist, bearing float64) { + radius_earth := 6371008.8 // meters; mean radius + + lat1 = radians(lat1) + lon1 = radians(lon1) + lat2 = radians(lat2) + lon2 = radians(lon2) + + dist = math.Acos(math.Sin(lat1)*math.Sin(lat2)+math.Cos(lat1)*math.Cos(lat2)*math.Cos(lon2-lon1)) * radius_earth + + var x, y float64 + + x = math.Cos(lat1)*math.Sin(lat2) - math.Sin(lat1)*math.Cos(lat2)*math.Cos(lon2-lon1) + y = math.Sin(lon2-lon1) * math.Cos(lat2) + + bearing = degreesHdg(math.Atan2(y, x)) + + return +} diff --git a/main/gen_gdl90.go b/main/gen_gdl90.go index 04f50e1f..cd677868 100644 --- a/main/gen_gdl90.go +++ b/main/gen_gdl90.go @@ -26,24 +26,30 @@ import ( "runtime" "strconv" "strings" + "sync" "syscall" "time" - humanize "github.com/dustin/go-humanize" - "../uatparse" + humanize "github.com/dustin/go-humanize" + "github.com/ricochet2200/go-disk-usage/du" ) // http://www.faa.gov/nextgen/programs/adsb/wsa/media/GDL90_Public_ICD_RevA.PDF +var debugLogf string // Set according to OS config. +var dataLogFilef string // Set according to OS config. + const ( - configLocation = "/etc/stratux.conf" - indexFilename = "/var/log/stratux/LOGINDEX" - managementAddr = ":80" - debugLog = "/var/log/stratux.log" + configLocation = "/etc/stratux.conf" + managementAddr = ":80" + debugLog = "/var/log/stratux.log" + dataLogFile = "/var/log/stratux.sqlite" + //FlightBox: log to /root. + debugLog_FB = "/root/stratux.log" + dataLogFile_FB = "/var/log/stratux.sqlite" maxDatagramSize = 8192 maxUserMsgQueueSize = 25000 // About 10MB per port per connected client. - logDirectory = "/var/log/stratux" UPLINK_BLOCK_DATA_BITS = 576 UPLINK_BLOCK_BITS = (UPLINK_BLOCK_DATA_BITS + 160) @@ -64,23 +70,16 @@ const ( MSGTYPE_BASIC_REPORT = 0x1E MSGTYPE_LONG_REPORT = 0x1F - MSGCLASS_UAT = 0 - MSGCLASS_ES = 1 - MSGCLASS_GPS = 3 - MSGCLASS_AHRS = 4 - MSGCLASS_DUMP1090 = 5 + MSGCLASS_UAT = 0 + MSGCLASS_ES = 1 LON_LAT_RESOLUTION = float32(180.0 / 8388608.0) TRACK_RESOLUTION = float32(360.0 / 256.0) ) -var maxSignalStrength int +var usage *du.DiskUsage -var uatReplayLog string -var esReplayLog string -var gpsReplayLog string -var ahrsReplayLog string -var dump1090ReplayLog string +var maxSignalStrength int var stratuxBuild string var stratuxVersion string @@ -101,23 +100,17 @@ type ReadCloser interface { io.Closer } -// File handles for replay logging. -var uatReplayWriter WriteCloser -var esReplayWriter WriteCloser -var gpsReplayWriter WriteCloser -var ahrsReplayWriter WriteCloser -var dump1090ReplayWriter WriteCloser - var developerMode bool type msg struct { MessageClass uint TimeReceived time.Time - Data []byte + Data string Products []uint32 Signal_amplitude int Signal_strength float64 ADSBTowerID string // Index in the 'ADSBTowers' map, if this is a parseable uplink message. + uatMsg *uatparse.UATMsg } // Raw inputs. @@ -134,53 +127,10 @@ type ADSBTower struct { Energy_last_minute uint64 // Summation of power observed for this tower across all messages last minute Signal_strength_last_minute float64 // Average RSSI (dB) observed for this tower last minute Messages_last_minute uint64 - Messages_total uint64 } var ADSBTowers map[string]ADSBTower // Running list of all towers seen. (lat,lng) -> ADSBTower - -func constructFilenames() { - var fileIndexNumber uint - - // First, create the log file directory if it does not exist - os.Mkdir(logDirectory, 0755) - - f, err := os.Open(indexFilename) - if err != nil { - log.Printf("Unable to open index file %s using index of 0\n", indexFilename) - fileIndexNumber = 0 - } else { - _, err := fmt.Fscanf(f, "%d\n", &fileIndexNumber) - if err != nil { - log.Printf("Unable to read index file %s using index of 0\n", indexFilename) - } - f.Close() - fileIndexNumber++ - } - fo, err := os.Create(indexFilename) - if err != nil { - log.Printf("Error creating index file %s\n", indexFilename) - } - _, err2 := fmt.Fprintf(fo, "%d\n", fileIndexNumber) - if err2 != nil { - log.Printf("Error writing to index file %s\n", indexFilename) - } - fo.Sync() - fo.Close() - if developerMode == true { - uatReplayLog = fmt.Sprintf("%s/%04d-uat.log", logDirectory, fileIndexNumber) - esReplayLog = fmt.Sprintf("%s/%04d-es.log", logDirectory, fileIndexNumber) - gpsReplayLog = fmt.Sprintf("%s/%04d-gps.log", logDirectory, fileIndexNumber) - ahrsReplayLog = fmt.Sprintf("%s/%04d-ahrs.log", logDirectory, fileIndexNumber) - dump1090ReplayLog = fmt.Sprintf("%s/%04d-dump1090.log", logDirectory, fileIndexNumber) - } else { - uatReplayLog = fmt.Sprintf("%s/%04d-uat.log.gz", logDirectory, fileIndexNumber) - esReplayLog = fmt.Sprintf("%s/%04d-es.log.gz", logDirectory, fileIndexNumber) - gpsReplayLog = fmt.Sprintf("%s/%04d-gps.log.gz", logDirectory, fileIndexNumber) - ahrsReplayLog = fmt.Sprintf("%s/%04d-ahrs.log.gz", logDirectory, fileIndexNumber) - dump1090ReplayLog = fmt.Sprintf("%s/%04d-dump1090.log.gz", logDirectory, fileIndexNumber) - } -} +var ADSBTowerMutex *sync.Mutex // Construct the CRC table. Adapted from FAA ref above. func crcInit() { @@ -297,7 +247,7 @@ func makeOwnshipReport() bool { msg[11] = byte((alt & 0xFF0) >> 4) // Altitude. msg[12] = byte((alt & 0x00F) << 4) if isGPSGroundTrackValid() { - msg[12] = msg[12] | 0x0B // "Airborne" + "True Heading" + msg[12] = msg[12] | 0x09 // "Airborne" + "True Track" } msg[13] = byte(0x80 | (mySituation.NACp & 0x0F)) //Set NIC = 8 and use NACp from ry835ai.go. @@ -317,12 +267,24 @@ func makeOwnshipReport() bool { msg[15] = msg[15] | byte((verticalVelocity&0x0F00)>>8) msg[16] = byte(verticalVelocity & 0xFF) - // Showing magnetic (corrected) on ForeFlight. Needs to be True Heading. - groundTrack := uint16(0) + // Track is degrees true, set from GPS true course. + groundTrack := float32(0) if isGPSGroundTrackValid() { groundTrack = mySituation.TrueCourse } - trk := uint8(float32(groundTrack) / TRACK_RESOLUTION) // Resolution is ~1.4 degrees. + + tempTrack := groundTrack + TRACK_RESOLUTION/2 // offset by half the 8-bit resolution to minimize binning error + + for tempTrack > 360 { + tempTrack -= 360 + } + for tempTrack < 0 { + tempTrack += 360 + } + + trk := uint8(tempTrack / TRACK_RESOLUTION) // Resolution is ~1.4 degrees. + + //log.Printf("For groundTrack = %.2f°, tempTrack= %.2f, trk = %d (%f°)\n",groundTrack,tempTrack,trk,float32(trk)*TRACK_RESOLUTION) msg[17] = byte(trk) @@ -347,11 +309,10 @@ func makeOwnshipGeometricAltitudeReport() bool { } msg := make([]byte, 5) // See p.28. - msg[0] = 0x0B // Message type "Ownship Geo Alt". - alt := int16(mySituation.Alt) // GPS Altitude. - alt = alt / 5 - msg[1] = byte(alt >> 8) // Altitude. - msg[2] = byte(alt & 0x00FF) // Altitude. + msg[0] = 0x0B // Message type "Ownship Geo Alt". + alt := int16(mySituation.Alt / 5) // GPS Altitude, encoded to 16-bit int using 5-foot resolution + msg[1] = byte(alt >> 8) // Altitude. + msg[2] = byte(alt & 0x00FF) // Altitude. //TODO: "Figure of Merit". 0x7FFF "Not available". msg[3] = 0x00 @@ -417,7 +378,7 @@ func makeStratuxStatus() []byte { // Valid and enabled flags. // Valid/Enabled: GPS portion. if isGPSValid() { - switch mySituation.quality { + switch mySituation.Quality { case 1: // 1 = 3D GPS. msg[13] = 1 case 2: // 2 = DGPS (SBAS /WAAS). @@ -508,7 +469,8 @@ func makeStratuxStatus() []byte { msg[26] = byte((v & 0xFF00) >> 8) msg[27] = byte(v & 0xFF) - // Number of ADS-B towers. + // Number of ADS-B towers. Map structure is protected by ADSBTowerMutex. + ADSBTowerMutex.Lock() num_towers := uint8(len(ADSBTowers)) msg[28] = byte(num_towers) @@ -525,7 +487,7 @@ func makeStratuxStatus() []byte { msg = append(msg, tmp[1]) // Longitude. msg = append(msg, tmp[2]) // Longitude. } - + ADSBTowerMutex.Unlock() return prepareMessage(msg) } @@ -641,7 +603,9 @@ func updateMessageStats() { m := len(MsgLog) UAT_messages_last_minute := uint(0) ES_messages_last_minute := uint(0) - products_last_minute := make(map[string]uint32) + + ADSBTowerMutex.Lock() + defer ADSBTowerMutex.Unlock() // Clear out ADSBTowers stats. for t, tinf := range ADSBTowers { @@ -655,12 +619,20 @@ func updateMessageStats() { t = append(t, MsgLog[i]) if MsgLog[i].MessageClass == MSGCLASS_UAT { UAT_messages_last_minute++ - for _, p := range MsgLog[i].Products { - products_last_minute[getProductNameFromId(int(p))]++ - } if len(MsgLog[i].ADSBTowerID) > 0 { // Update tower stats. tid := MsgLog[i].ADSBTowerID + + if _, ok := ADSBTowers[tid]; !ok { // First time we've seen the tower? Start tracking. + var newTower ADSBTower + newTower.Lat = MsgLog[i].uatMsg.Lat + newTower.Lng = MsgLog[i].uatMsg.Lon + newTower.Signal_strength_max = -999 // dBmax = 0, so this needs to initialize below scale ( << -48 dB) + ADSBTowers[tid] = newTower + } + twr := ADSBTowers[tid] + twr.Signal_strength_now = MsgLog[i].Signal_strength + twr.Energy_last_minute += uint64((MsgLog[i].Signal_amplitude) * (MsgLog[i].Signal_amplitude)) twr.Messages_last_minute++ if MsgLog[i].Signal_strength > twr.Signal_strength_max { // Update alltime max signal strength. @@ -676,7 +648,6 @@ func updateMessageStats() { MsgLog = t globalStatus.UAT_messages_last_minute = UAT_messages_last_minute globalStatus.ES_messages_last_minute = ES_messages_last_minute - globalStatus.uat_products_last_minute = products_last_minute // Update "max messages/min" counters. if globalStatus.UAT_messages_max < UAT_messages_last_minute { @@ -714,42 +685,49 @@ func cpuTempMonitor() { <-timer.C // Update CPUTemp. - globalStatus.CPUTemp = float32(-99.0) // Default value - in case code below hangs. - temp, err := ioutil.ReadFile("/sys/class/thermal/thermal_zone0/temp") tempStr := strings.Trim(string(temp), "\n") + t := float32(-99.0) if err == nil { tInt, err := strconv.Atoi(tempStr) if err == nil { if tInt > 1000 { - globalStatus.CPUTemp = float32(tInt) / float32(1000.0) + t = float32(tInt) / float32(1000.0) } else { - globalStatus.CPUTemp = float32(tInt) // case where Temp is returned as simple integer + t = float32(tInt) // case where Temp is returned as simple integer } } } + if t >= -99.0 { // Only update if valid value was obtained. + globalStatus.CPUTemp = t + } } } func updateStatus() { - if mySituation.quality == 2 { - globalStatus.GPS_solution = "DGPS (SBAS / WAAS)" - } else if mySituation.quality == 1 { + if mySituation.Quality == 2 { + globalStatus.GPS_solution = "GPS + SBAS (WAAS / EGNOS)" + } else if mySituation.Quality == 1 { globalStatus.GPS_solution = "3D GPS" - } else if mySituation.quality == 6 { + } else if mySituation.Quality == 6 { globalStatus.GPS_solution = "Dead Reckoning" - } else if mySituation.quality == 0 { + } else if mySituation.Quality == 0 { globalStatus.GPS_solution = "No Fix" } else { globalStatus.GPS_solution = "Unknown" } if !(globalStatus.GPS_connected) || !(isGPSConnected()) { // isGPSConnected looks for valid NMEA messages. GPS_connected is set by gpsSerialReader and will immediately fail on disconnected USB devices, or in a few seconds after "blocked" comms on ttyAMA0. + + satelliteMutex.Lock() + Satellites = make(map[string]SatelliteInfo) + satelliteMutex.Unlock() + mySituation.Satellites = 0 mySituation.SatellitesSeen = 0 mySituation.SatellitesTracked = 0 - mySituation.quality = 0 + mySituation.Quality = 0 globalStatus.GPS_solution = "Disconnected" globalStatus.GPS_connected = false } @@ -762,51 +740,9 @@ func updateStatus() { globalStatus.Uptime = int64(stratuxClock.Milliseconds) globalStatus.UptimeClock = stratuxClock.Time globalStatus.Clock = time.Now() -} -type ReplayWriter struct { - fp *os.File -} - -func (r ReplayWriter) Write(p []byte) (n int, err error) { - return r.fp.Write(p) -} - -func (r ReplayWriter) Close() error { - return r.fp.Close() -} - -func makeReplayLogEntry(msg string) string { - return fmt.Sprintf("%d,%s\n", time.Since(timeStarted).Nanoseconds(), msg) -} - -func replayLog(msg string, msgclass int) { - if !globalSettings.ReplayLog { // Logging disabled. - return - } - msg = strings.Trim(msg, " \r\n") - if len(msg) == 0 { // Blank message. - return - } - var fp WriteCloser - - switch msgclass { - case MSGCLASS_UAT: - fp = uatReplayWriter - case MSGCLASS_ES: - fp = esReplayWriter - case MSGCLASS_GPS: - fp = gpsReplayWriter - case MSGCLASS_AHRS: - fp = ahrsReplayWriter - case MSGCLASS_DUMP1090: - fp = dump1090ReplayWriter - } - - if fp != nil { - s := makeReplayLogEntry(msg) - fp.Write([]byte(s)) - } + usage = du.NewDiskUsage("/") + globalStatus.DiskBytesFree = usage.Free() } type WeatherMessage struct { @@ -839,8 +775,7 @@ func registerADSBTextMessageReceived(msg string) { } func parseInput(buf string) ([]byte, uint16) { - replayLog(buf, MSGCLASS_UAT) // Log the raw message. - + //FIXME: We're ignoring all invalid format UAT messages (not sending to datalog). x := strings.Split(buf, ";") // Discard everything after the first ';'. s := x[0] if len(s) == 0 { @@ -904,7 +839,7 @@ func parseInput(buf string) ([]byte, uint16) { var thisMsg msg thisMsg.MessageClass = MSGCLASS_UAT thisMsg.TimeReceived = stratuxClock.Time - thisMsg.Data = frame + thisMsg.Data = buf thisMsg.Signal_amplitude = thisSignalStrength thisMsg.Signal_strength = 20 * math.Log10((float64(thisSignalStrength))/1000) thisMsg.Products = make([]uint32, 0) @@ -915,18 +850,6 @@ func parseInput(buf string) ([]byte, uint16) { uatMsg.DecodeUplink() towerid := fmt.Sprintf("(%f,%f)", uatMsg.Lat, uatMsg.Lon) thisMsg.ADSBTowerID = towerid - if _, ok := ADSBTowers[towerid]; !ok { // First time we've seen the tower. Start tracking. - var newTower ADSBTower - newTower.Lat = uatMsg.Lat - newTower.Lng = uatMsg.Lon - newTower.Signal_strength_now = thisMsg.Signal_strength - newTower.Signal_strength_max = -999 // dBmax = 0, so this needs to initialize below scale ( << -48 dB) - ADSBTowers[towerid] = newTower - } - twr := ADSBTowers[towerid] - twr.Messages_total++ - twr.Signal_strength_now = thisMsg.Signal_strength - ADSBTowers[towerid] = twr // Get all of the "product ids". for _, f := range uatMsg.Frames { thisMsg.Products = append(thisMsg.Products, f.Product_id) @@ -936,10 +859,12 @@ func parseInput(buf string) ([]byte, uint16) { for _, r := range textReports { registerADSBTextMessageReceived(r) } + thisMsg.uatMsg = uatMsg } } MsgLog = append(MsgLog, thisMsg) + logMsg(thisMsg) return frame, msgtype } @@ -1017,16 +942,17 @@ func getProductNameFromId(product_id int) string { } type settings struct { - UAT_Enabled bool - ES_Enabled bool - GPS_Enabled bool - NetworkOutputs []networkConnection - AHRS_Enabled bool - DEBUG bool - ReplayLog bool - PPM int - OwnshipModeS string - WatchList string + UAT_Enabled bool + ES_Enabled bool + GPS_Enabled bool + NetworkOutputs []networkConnection + AHRS_Enabled bool + DisplayTrafficSource bool + DEBUG bool + ReplayLog bool + PPM int + OwnshipModeS string + WatchList string } type status struct { @@ -1035,8 +961,8 @@ type status struct { HardwareBuild string Devices uint32 Connected_Users uint + DiskBytesFree uint64 UAT_messages_last_minute uint - uat_products_last_minute map[string]uint32 UAT_messages_max uint ES_messages_last_minute uint ES_messages_max uint @@ -1075,6 +1001,7 @@ func defaultSettings() { } globalSettings.AHRS_Enabled = false globalSettings.DEBUG = false + globalSettings.DisplayTrafficSource = false globalSettings.ReplayLog = false //TODO: 'true' for debug builds. globalSettings.OwnshipModeS = "F00000" } @@ -1123,36 +1050,6 @@ func saveSettings() { log.Printf("wrote settings.\n") } -func replayMark(active bool) { - var t string - if !active { - t = fmt.Sprintf("PAUSE,%d\n", time.Since(timeStarted).Nanoseconds()) - } else { - t = fmt.Sprintf("UNPAUSE,%d\n", time.Since(timeStarted).Nanoseconds()) - } - - if uatReplayWriter != nil { - uatReplayWriter.Write([]byte(t)) - } - - if esReplayWriter != nil { - esReplayWriter.Write([]byte(t)) - } - - if gpsReplayWriter != nil { - gpsReplayWriter.Write([]byte(t)) - } - - if ahrsReplayWriter != nil { - ahrsReplayWriter.Write([]byte(t)) - } - - if dump1090ReplayWriter != nil { - dump1090ReplayWriter.Write([]byte(t)) - } - -} - func openReplay(fn string, compressed bool) (WriteCloser, error) { fp, err := os.OpenFile(fn, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) @@ -1177,18 +1074,27 @@ func openReplay(fn string, compressed bool) (WriteCloser, error) { func printStats() { statTimer := time.NewTicker(30 * time.Second) + diskUsageWarning := false for { <-statTimer.C var memstats runtime.MemStats runtime.ReadMemStats(&memstats) log.Printf("stats [started: %s]\n", humanize.RelTime(time.Time{}, stratuxClock.Time, "ago", "from now")) + log.Printf(" - Disk bytes used = %s (%.1f %%), Disk bytes free = %s (%.1f %%)\n", humanize.Bytes(usage.Used()), 100*usage.Usage(), humanize.Bytes(usage.Free()), 100*(1-usage.Usage())) log.Printf(" - CPUTemp=%.02f deg C, MemStats.Alloc=%s, MemStats.Sys=%s, totalNetworkMessagesSent=%s\n", globalStatus.CPUTemp, humanize.Bytes(uint64(memstats.Alloc)), humanize.Bytes(uint64(memstats.Sys)), humanize.Comma(int64(totalNetworkMessagesSent))) - log.Printf(" - UAT/min %s/%s [maxSS=%.02f%%], ES/min %s/%s\n, Total traffic targets tracked=%s", humanize.Comma(int64(globalStatus.UAT_messages_last_minute)), humanize.Comma(int64(globalStatus.UAT_messages_max)), float64(maxSignalStrength)/10.0, humanize.Comma(int64(globalStatus.ES_messages_last_minute)), humanize.Comma(int64(globalStatus.ES_messages_max)), humanize.Comma(int64(len(seenTraffic)))) + log.Printf(" - UAT/min %s/%s [maxSS=%.02f%%], ES/min %s/%s, Total traffic targets tracked=%s", humanize.Comma(int64(globalStatus.UAT_messages_last_minute)), humanize.Comma(int64(globalStatus.UAT_messages_max)), float64(maxSignalStrength)/10.0, humanize.Comma(int64(globalStatus.ES_messages_last_minute)), humanize.Comma(int64(globalStatus.ES_messages_max)), humanize.Comma(int64(len(seenTraffic)))) log.Printf(" - Network data messages sent: %d total, %d nonqueueable. Network data bytes sent: %d total, %d nonqueueable.\n", globalStatus.NetworkDataMessagesSent, globalStatus.NetworkDataMessagesSentNonqueueable, globalStatus.NetworkDataBytesSent, globalStatus.NetworkDataBytesSentNonqueueable) if globalSettings.GPS_Enabled { - log.Printf(" - Last GPS fix: %s, GPS solution type: %d using %d satellites (%d/%d seen/tracked), NACp: %d, est accuracy %.02f m\n", stratuxClock.HumanizeTime(mySituation.LastFixLocalTime), mySituation.quality, mySituation.Satellites, mySituation.SatellitesSeen, mySituation.SatellitesTracked, mySituation.NACp, mySituation.Accuracy) + log.Printf(" - Last GPS fix: %s, GPS solution type: %d using %d satellites (%d/%d seen/tracked), NACp: %d, est accuracy %.02f m\n", stratuxClock.HumanizeTime(mySituation.LastFixLocalTime), mySituation.Quality, mySituation.Satellites, mySituation.SatellitesSeen, mySituation.SatellitesTracked, mySituation.NACp, mySituation.Accuracy) log.Printf(" - GPS vertical velocity: %.02f ft/sec; GPS vertical accuracy: %v m\n", mySituation.GPSVertVel, mySituation.AccuracyVert) } + // Check if we're using more than 95% of the free space. If so, throw a warning (only once). + if !diskUsageWarning && usage.Usage() > 95.0 { + err_p := fmt.Errorf("Disk bytes used = %s (%.1f %%), Disk bytes free = %s (%.1f %%)", humanize.Bytes(usage.Used()), 100*usage.Usage(), humanize.Bytes(usage.Free()), 100*(1-usage.Usage())) + addSystemError(err_p) + diskUsageWarning = true + } + logStatus() } } @@ -1261,32 +1167,18 @@ func openReplayFile(fn string) ReadCloser { var stratuxClock *monotonic var sigs = make(chan os.Signal, 1) // Signal catch channel (shutdown). -// Close replay log file handles. -func closeReplayLogs() { - if uatReplayWriter != nil { - uatReplayWriter.Close() - } - if esReplayWriter != nil { - esReplayWriter.Close() - } - if gpsReplayWriter != nil { - gpsReplayWriter.Close() - } - if ahrsReplayWriter != nil { - ahrsReplayWriter.Close() - } - if dump1090ReplayWriter != nil { - dump1090ReplayWriter.Close() - } - -} - // Graceful shutdown. func gracefulShutdown() { // Shut down SDRs. sdrKill() + + // Shut down data logging. + if dataLogStarted { + closeDataLog() + } + //TODO: Any other graceful shutdown functions. - closeReplayLogs() + os.Exit(1) } @@ -1307,8 +1199,14 @@ func main() { globalStatus.Version = stratuxVersion globalStatus.Build = stratuxBuild globalStatus.Errors = make([]string, 0) + //FlightBox: detect via presence of /etc/FlightBox file. if _, err := os.Stat("/etc/FlightBox"); !os.IsNotExist(err) { globalStatus.HardwareBuild = "FlightBox" + debugLogf = debugLog_FB + dataLogFilef = dataLogFile_FB + } else { // if not using the FlightBox config, use "normal" log file locations + debugLogf = debugLog + dataLogFilef = dataLogFile } // replayESFilename := flag.String("eslog", "none", "ES Log filename") @@ -1329,9 +1227,9 @@ func main() { } // Duplicate log.* output to debugLog. - fp, err := os.OpenFile(debugLog, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) + fp, err := os.OpenFile(debugLogf, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) if err != nil { - err_log := fmt.Errorf("Failed to open '%s': %s", debugLog, err.Error()) + err_log := fmt.Errorf("Failed to open '%s': %s", debugLogf, err.Error()) addSystemError(err_log) log.Printf("%s\n", err_log.Error()) } else { @@ -1341,9 +1239,9 @@ func main() { } log.Printf("Stratux %s (%s) starting.\n", stratuxVersion, stratuxBuild) - constructFilenames() ADSBTowers = make(map[string]ADSBTower) + ADSBTowerMutex = &sync.Mutex{} MsgLog = make([]msg, 0) crcInit() // Initialize CRC16 table. @@ -1361,45 +1259,8 @@ func main() { globalSettings.ReplayLog = true } - // Set up the replay logs. Keep these files open in any case, even if replay logging is disabled. - - if uatwt, err := openReplay(uatReplayLog, !developerMode); err != nil { - globalSettings.ReplayLog = false - } else { - uatReplayWriter = uatwt - defer uatReplayWriter.Close() - } - // 1090ES replay log. - if eswt, err := openReplay(esReplayLog, !developerMode); err != nil { - globalSettings.ReplayLog = false - } else { - esReplayWriter = eswt - defer esReplayWriter.Close() - } - // GPS replay log. - if gpswt, err := openReplay(gpsReplayLog, !developerMode); err != nil { - globalSettings.ReplayLog = false - } else { - gpsReplayWriter = gpswt - defer gpsReplayWriter.Close() - } - // AHRS replay log. - if ahrswt, err := openReplay(ahrsReplayLog, !developerMode); err != nil { - globalSettings.ReplayLog = false - } else { - ahrsReplayWriter = ahrswt - defer ahrsReplayWriter.Close() - } - // Dump1090 replay log. - if dump1090wt, err := openReplay(dump1090ReplayLog, !developerMode); err != nil { - globalSettings.ReplayLog = false - } else { - dump1090ReplayWriter = dump1090wt - defer dump1090ReplayWriter.Close() - } - - // Mark the files (whether we're logging or not). - replayMark(globalSettings.ReplayLog) + //FIXME: Only do this if data logging is enabled. + initDataLog() initRY835AI() diff --git a/main/managementinterface.go b/main/managementinterface.go index 3388a844..5900fe19 100644 --- a/main/managementinterface.go +++ b/main/managementinterface.go @@ -151,6 +151,8 @@ func handleSituationRequest(w http.ResponseWriter, r *http.Request) { func handleTowersRequest(w http.ResponseWriter, r *http.Request) { setNoCache(w) setJSONHeaders(w) + + ADSBTowerMutex.Lock() towersJSON, err := json.Marshal(&ADSBTowers) if err != nil { log.Printf("Error sending tower JSON data: %s\n", err.Error()) @@ -158,6 +160,20 @@ func handleTowersRequest(w http.ResponseWriter, r *http.Request) { // for testing purposes, we can return a fixed reply // towersJSON = []byte(`{"(38.490880,-76.135554)":{"Lat":38.49087953567505,"Lng":-76.13555431365967,"Signal_strength_last_minute":100,"Signal_strength_max":67,"Messages_last_minute":1,"Messages_total":1059},"(38.978698,-76.309276)":{"Lat":38.97869825363159,"Lng":-76.30927562713623,"Signal_strength_last_minute":495,"Signal_strength_max":32,"Messages_last_minute":45,"Messages_total":83},"(39.179285,-76.668413)":{"Lat":39.17928457260132,"Lng":-76.66841268539429,"Signal_strength_last_minute":50,"Signal_strength_max":24,"Messages_last_minute":1,"Messages_total":16},"(39.666309,-74.315300)":{"Lat":39.66630935668945,"Lng":-74.31529998779297,"Signal_strength_last_minute":9884,"Signal_strength_max":35,"Messages_last_minute":4,"Messages_total":134}}`) fmt.Fprintf(w, "%s\n", towersJSON) + ADSBTowerMutex.Unlock() +} + +// AJAX call - /getSatellites. Responds with all GNSS satellites that are being tracked, along with status information. +func handleSatellitesRequest(w http.ResponseWriter, r *http.Request) { + setNoCache(w) + setJSONHeaders(w) + satelliteMutex.Lock() + satellitesJSON, err := json.Marshal(&Satellites) + if err != nil { + log.Printf("Error sending GNSS satellite JSON data: %s\n", err.Error()) + } + fmt.Fprintf(w, "%s\n", satellitesJSON) + satelliteMutex.Unlock() } // AJAX call - /getSettings. Responds with all stratux.conf data. @@ -205,11 +221,12 @@ func handleSettingsSetRequest(w http.ResponseWriter, r *http.Request) { globalSettings.AHRS_Enabled = val.(bool) case "DEBUG": globalSettings.DEBUG = val.(bool) + case "DisplayTrafficSource": + globalSettings.DisplayTrafficSource = val.(bool) case "ReplayLog": v := val.(bool) if v != globalSettings.ReplayLog { // Don't mark the files unless there is a change. globalSettings.ReplayLog = v - replayMark(v) } case "PPM": globalSettings.PPM = int(val.(float64)) @@ -256,7 +273,11 @@ func doReboot() { } func handleRebootRequest(w http.ResponseWriter, r *http.Request) { - doReboot() + setNoCache(w) + setJSONHeaders(w) + w.Header().Set("Access-Control-Allow-Method", "GET, POST, OPTIONS") + w.Header().Set("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept") + go delayReboot() } // AJAX call - /getClients. Responds with all connected clients. @@ -283,6 +304,11 @@ func handleUpdatePostRequest(w http.ResponseWriter, r *http.Request) { return } defer file.Close() + // Special hardware builds. Don't allow an update unless the filename contains the hardware build name. + if (len(globalStatus.HardwareBuild) > 0) && !strings.Contains(handler.Filename, globalStatus.HardwareBuild) { + w.WriteHeader(404) + return + } updateFile := fmt.Sprintf("/root/%s", handler.Filename) f, err := os.OpenFile(updateFile, os.O_WRONLY|os.O_CREATE, 0666) if err != nil { @@ -378,6 +404,7 @@ type dirlisting struct { ServerUA string } +//FIXME: This needs to be switched to show a "sessions log" from the sqlite database. func viewLogs(w http.ResponseWriter, r *http.Request) { names, err := ioutil.ReadDir("/var/log/stratux/") @@ -448,6 +475,7 @@ func managementInterface() { http.HandleFunc("/getStatus", handleStatusRequest) http.HandleFunc("/getSituation", handleSituationRequest) http.HandleFunc("/getTowers", handleTowersRequest) + http.HandleFunc("/getSatellites", handleSatellitesRequest) http.HandleFunc("/getSettings", handleSettingsGetRequest) http.HandleFunc("/setSettings", handleSettingsSetRequest) http.HandleFunc("/shutdown", handleShutdownRequest) diff --git a/main/monotonic.go b/main/monotonic.go index c880b1f0..c0e27330 100644 --- a/main/monotonic.go +++ b/main/monotonic.go @@ -26,8 +26,8 @@ type monotonic struct { func (m *monotonic) Watcher() { for { <-m.ticker.C - m.Milliseconds += 50 - m.Time = m.Time.Add(50 * time.Millisecond) + m.Milliseconds += 10 + m.Time = m.Time.Add(10 * time.Millisecond) } } @@ -44,7 +44,7 @@ func (m *monotonic) Unix() int64 { } func NewMonotonic() *monotonic { - t := &monotonic{Milliseconds: 0, Time: time.Time{}, ticker: time.NewTicker(50 * time.Millisecond)} + t := &monotonic{Milliseconds: 0, Time: time.Time{}, ticker: time.NewTicker(10 * time.Millisecond)} go t.Watcher() return t } diff --git a/main/network.go b/main/network.go index 12f9e08d..b327d9cc 100644 --- a/main/network.go +++ b/main/network.go @@ -33,11 +33,12 @@ type networkMessage struct { } type networkConnection struct { - Conn *net.UDPConn - Ip string - Port uint32 - Capability uint8 - messageQueue [][]byte // Device message queue. + Conn *net.UDPConn + Ip string + Port uint32 + Capability uint8 + messageQueue [][]byte // Device message queue. + MessageQueueLen int // Length of the message queue. For debugging. /* Sleep mode/throttle variables. "sleep mode" is actually now just a very reduced packet rate, since we don't know positively when a client is ready to accept packets - we just assume so if we don't receive ICMP Unreachable packets in 5 secs. @@ -292,6 +293,8 @@ func messageQueueSender() { outSockets[k] = tmpConn */ } + netconn.MessageQueueLen = len(netconn.messageQueue) + outSockets[k] = netconn } if stratuxClock.Since(lastQueueTimeChange) >= 5*time.Second { @@ -463,11 +466,11 @@ func ffMonitor() { addr := net.UDPAddr{Port: 50113, IP: net.ParseIP("0.0.0.0")} conn, err := net.ListenUDP("udp", &addr) - defer conn.Close() if err != nil { log.Printf("ffMonitor(): error listening on port 50113: %s\n", err.Error()) return } + defer conn.Close() for { buf := make([]byte, 1024) n, addr, err := conn.ReadFrom(buf) diff --git a/main/plugin.go b/main/plugin.go new file mode 100644 index 00000000..bf20f6aa --- /dev/null +++ b/main/plugin.go @@ -0,0 +1,13 @@ +package main + +import ( + "time" +) + +type StratuxPlugin struct { + InitFunc func() bool + ShutdownFunc func() bool + Name string + Clock time.Time + Input chan string +} diff --git a/main/ry835ai.go b/main/ry835ai.go index 29da4427..582d3a27 100644 --- a/main/ry835ai.go +++ b/main/ry835ai.go @@ -30,37 +30,61 @@ import ( "../mpu6050" ) +const ( + SAT_TYPE_UNKNOWN = 0 // default type + SAT_TYPE_GPS = 1 // GPxxx; NMEA IDs 1-32 + SAT_TYPE_GLONASS = 2 // GLxxx; NMEA IDs 65-88 + SAT_TYPE_GALILEO = 3 // GAxxx; NMEA IDs unknown + SAT_TYPE_BEIDOU = 4 // GBxxx; NMEA IDs 201-235 + SAT_TYPE_SBAS = 10 // NMEA IDs 33-54 +) + +type SatelliteInfo struct { + SatelliteNMEA uint8 // NMEA ID of the satellite. 1-32 is GPS, 33-54 is SBAS, 65-88 is Glonass. + SatelliteID string // Formatted code indicating source and PRN code. e.g. S138==WAAS satellite 138, G2==GPS satellites 2 + Elevation int16 // Angle above local horizon, -xx to +90 + Azimuth int16 // Bearing (degrees true), 0-359 + Signal int8 // Signal strength, 0 - 99; -99 indicates no reception + Type uint8 // Type of satellite (GPS, GLONASS, Galileo, SBAS) + TimeLastSolution time.Time // Time (system ticker) a solution was last calculated using this satellite + TimeLastSeen time.Time // Time (system ticker) a signal was last received from this satellite + TimeLastTracked time.Time // Time (system ticker) this satellite was tracked (almanac data) + InSolution bool // True if satellite is used in the position solution (reported by GSA message or PUBX,03) +} + type SituationData struct { mu_GPS *sync.Mutex // From GPS. - lastFixSinceMidnightUTC float32 - Lat float32 - Lng float32 - quality uint8 - HeightAboveEllipsoid float32 // GPS height above WGS84 ellipsoid, ft. This is specified by the GDL90 protocol, but most EFBs use MSL altitude instead. HAE is about 70-100 ft below GPS MSL altitude over most of the US. - GeoidSep float32 // geoid separation, ft, MSL minus HAE (used in altitude calculation) - Satellites uint16 // satellites used in solution - SatellitesTracked uint16 // satellites tracked (almanac data received) - SatellitesSeen uint16 // satellites seen (signal received) - Accuracy float32 // 95% confidence for horizontal position, meters. - NACp uint8 // NACp categories are defined in AC 20-165A - Alt float32 // Feet MSL - AccuracyVert float32 // 95% confidence for vertical position, meters - GPSVertVel float32 // GPS vertical velocity, feet per second - LastFixLocalTime time.Time - TrueCourse uint16 - GroundSpeed uint16 - LastGroundTrackTime time.Time - LastGPSTimeTime time.Time - LastNMEAMessage time.Time // time valid NMEA message last seen + LastFixSinceMidnightUTC float32 + Lat float32 + Lng float32 + Quality uint8 + HeightAboveEllipsoid float32 // GPS height above WGS84 ellipsoid, ft. This is specified by the GDL90 protocol, but most EFBs use MSL altitude instead. HAE is about 70-100 ft below GPS MSL altitude over most of the US. + GeoidSep float32 // geoid separation, ft, MSL minus HAE (used in altitude calculation) + Satellites uint16 // satellites used in solution + SatellitesTracked uint16 // satellites tracked (almanac data received) + SatellitesSeen uint16 // satellites seen (signal received) + Accuracy float32 // 95% confidence for horizontal position, meters. + NACp uint8 // NACp categories are defined in AC 20-165A + Alt float32 // Feet MSL + AccuracyVert float32 // 95% confidence for vertical position, meters + GPSVertVel float32 // GPS vertical velocity, feet per second + LastFixLocalTime time.Time + TrueCourse float32 + GroundSpeed uint16 + LastGroundTrackTime time.Time + GPSTime time.Time + LastGPSTimeTime time.Time // stratuxClock time since last GPS time received. + LastValidNMEAMessageTime time.Time // time valid NMEA message last seen + LastValidNMEAMessage string // last NMEA message processed. mu_Attitude *sync.Mutex // From BMP180 pressure sensor. Temp float64 Pressure_alt float64 - lastTempPressTime time.Time + LastTempPressTime time.Time // From MPU6050 accel/gyro. Pitch float64 @@ -74,6 +98,9 @@ var serialPort *serial.Port var readyToInitGPS bool // TO-DO: replace with channel control to terminate goroutine when complete +var satelliteMutex *sync.Mutex +var Satellites map[string]SatelliteInfo + /* u-blox5_Referenzmanual.pdf Platform settings @@ -128,21 +155,26 @@ func initGPSSerial() bool { baudrate := int(9600) isSirfIV := bool(false) - log.Printf("Configuring GPS\n") - - if _, err := os.Stat("/dev/ttyUSB0"); err == nil { + if _, err := os.Stat("/dev/ublox8"); err == nil { // u-blox 8 (RY83xAI over USB). + device = "/dev/ublox8" + } else if _, err := os.Stat("/dev/ublox7"); err == nil { // u-blox 7 (VK-172, RY725AI over USB). + device = "/dev/ublox7" + } else if _, err := os.Stat("/dev/ublox6"); err == nil { // u-blox 6 (VK-162). + device = "/dev/ublox6" + } else if _, err := os.Stat("/dev/prolific0"); err == nil { // Assume it's a BU-353-S4 SIRF IV. + //TODO: Check a "serialout" flag and/or deal with multiple prolific devices. isSirfIV = true baudrate = 4800 - device = "/dev/ttyUSB0" - } else if _, err := os.Stat("/dev/ttyACM0"); err == nil { - device = "/dev/ttyACM0" - } else if _, err := os.Stat("/dev/ttyAMA0"); err == nil { + device = "/dev/prolific0" + } else if _, err := os.Stat("/dev/ttyAMA0"); err == nil { // ttyAMA0 is PL011 UART (GPIO pins 8 and 10) on all RPi. device = "/dev/ttyAMA0" } else { log.Printf("No suitable device found.\n") return false } - log.Printf("Using %s for GPS\n", device) + if globalSettings.DEBUG { + log.Printf("Using %s for GPS\n", device) + } /* Developer option -- uncomment to allow "hot" configuration of GPS (assuming 38.4 kpbs on warm start) serialConfig = &serial.Config{Name: device, Baud: 38400} @@ -223,13 +255,16 @@ func initGPSSerial() bool { p.Write(makeNMEACmd("PSRF103,04,00,01,01")) // Enable VTG. p.Write(makeNMEACmd("PSRF103,05,00,01,01")) - // Disable GSV. - p.Write(makeNMEACmd("PSRF103,03,00,00,01")) + // Enable GSV (once every 5 position updates) + p.Write(makeNMEACmd("PSRF103,03,00,05,01")) - log.Printf("Finished writing SiRF GPS config to %s. Opening port to test connection.\n", device) + if globalSettings.DEBUG { + log.Printf("Finished writing SiRF GPS config to %s. Opening port to test connection.\n", device) + } } else { - // Set 10Hz update. Little endian order. - p.Write(makeUBXCFG(0x06, 0x08, 6, []byte{0x64, 0x00, 0x01, 0x00, 0x01, 0x00})) + // Set 5 Hz update. Little endian order. + //p.Write(makeUBXCFG(0x06, 0x08, 6, []byte{0x64, 0x00, 0x01, 0x00, 0x01, 0x00})) // 10 Hz + p.Write(makeUBXCFG(0x06, 0x08, 6, []byte{0xc8, 0x00, 0x01, 0x00, 0x01, 0x00})) // 5 Hz // Set navigation settings. nav := make([]byte, 36) @@ -242,17 +277,20 @@ func initGPSSerial() bool { p.Write(makeUBXCFG(0x06, 0x24, 36, nav)) // GNSS configuration CFG-GNSS for ublox 7 higher, p. 125 (v8) - // // NOTE: Max position rate = 5 Hz if GPS+GLONASS used. + + // TESTING: 5Hz unified GPS + GLONASS + // Disable GLONASS to enable 10 Hz solution rate. GLONASS is not used // for SBAS (WAAS), so little real-world impact. cfgGnss := []byte{0x00, 0x20, 0x20, 0x05} - gps := []byte{0x00, 0x08, 0x10, 0x00, 0x01, 0x00, 0x01, 0x01} - sbas := []byte{0x01, 0x02, 0x03, 0x00, 0x01, 0x00, 0x01, 0x01} + gps := []byte{0x00, 0x08, 0x10, 0x00, 0x01, 0x00, 0x01, 0x01} // enable GPS with 8-16 tracking channels + sbas := []byte{0x01, 0x02, 0x03, 0x00, 0x01, 0x00, 0x01, 0x01} // enable SBAS (WAAS) with 2-3 tracking channels beidou := []byte{0x03, 0x00, 0x10, 0x00, 0x00, 0x00, 0x01, 0x01} qzss := []byte{0x05, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x01} - glonass := []byte{0x06, 0x04, 0x0E, 0x00, 0x00, 0x00, 0x01, 0x01} + //glonass := []byte{0x06, 0x04, 0x0E, 0x00, 0x00, 0x00, 0x01, 0x01} // this disables GLONASS + glonass := []byte{0x06, 0x08, 0x0E, 0x00, 0x01, 0x00, 0x01, 0x01} // this enables GLONASS with 8-14 tracking channels cfgGnss = append(cfgGnss, gps...) cfgGnss = append(cfgGnss, sbas...) cfgGnss = append(cfgGnss, beidou...) @@ -263,12 +301,16 @@ func initGPSSerial() bool { // SBAS configuration for ublox 6 and higher p.Write(makeUBXCFG(0x06, 0x16, 8, []byte{0x01, 0x07, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00})) - // Message output configuration -- disable standard NMEA messages except 1Hz GGA + // Message output configuration: UBX,00 (position) on each calculated fix; UBX,03 (satellite info) every 5th fix, + // UBX,04 (timing) every 10th, GGA (NMEA position) every 5th. All other NMEA messages disabled. + // Msg DDC UART1 UART2 USB I2C Res - p.Write(makeUBXCFG(0x06, 0x01, 8, []byte{0xF0, 0x00, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x01})) // GGA - p.Write(makeUBXCFG(0x06, 0x01, 8, []byte{0xF0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01})) // GLL - p.Write(makeUBXCFG(0x06, 0x01, 8, []byte{0xF0, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01})) // GSA - p.Write(makeUBXCFG(0x06, 0x01, 8, []byte{0xF0, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01})) // GSV + p.Write(makeUBXCFG(0x06, 0x01, 8, []byte{0xF0, 0x00, 0x00, 0x05, 0x00, 0x05, 0x00, 0x01})) // GGA enabled every 5th message + p.Write(makeUBXCFG(0x06, 0x01, 8, []byte{0xF0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01})) // GLL disabled + p.Write(makeUBXCFG(0x06, 0x01, 8, []byte{0xF0, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01})) // GSA disabled + //p.Write(makeUBXCFG(0x06, 0x01, 8, []byte{0xF0, 0x02, 0x00, 0x05, 0x00, 0x05, 0x00, 0x01})) // GSA enabled disabled every 5th position (used for testing only) + p.Write(makeUBXCFG(0x06, 0x01, 8, []byte{0xF0, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01})) // GSV disabled + //p.Write(makeUBXCFG(0x06, 0x01, 8, []byte{0xF0, 0x03, 0x00, 0x05, 0x00, 0x05, 0x00, 0x01})) // GSV enabled for every 5th position (used for testing only) p.Write(makeUBXCFG(0x06, 0x01, 8, []byte{0xF0, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01})) // RMC p.Write(makeUBXCFG(0x06, 0x01, 8, []byte{0xF0, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01})) // VGT p.Write(makeUBXCFG(0x06, 0x01, 8, []byte{0xF0, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})) // GRS @@ -279,9 +321,8 @@ func initGPSSerial() bool { p.Write(makeUBXCFG(0x06, 0x01, 8, []byte{0xF0, 0x0D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})) // GNS p.Write(makeUBXCFG(0x06, 0x01, 8, []byte{0xF0, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})) // ??? p.Write(makeUBXCFG(0x06, 0x01, 8, []byte{0xF0, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})) // VLW - p.Write(makeUBXCFG(0x06, 0x01, 8, []byte{0xF1, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00})) // Ublox,0 - p.Write(makeUBXCFG(0x06, 0x01, 8, []byte{0xF1, 0x03, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x00})) // Ublox,3 + p.Write(makeUBXCFG(0x06, 0x01, 8, []byte{0xF1, 0x03, 0x05, 0x05, 0x05, 0x05, 0x05, 0x00})) // Ublox,3 p.Write(makeUBXCFG(0x06, 0x01, 8, []byte{0xF1, 0x04, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x00})) // Ublox,4 // Reconfigure serial port. @@ -324,7 +365,9 @@ func initGPSSerial() bool { // time.Sleep(100* time.Millisecond) // pause and wait for the GPS to finish configuring itself before closing / reopening the port baudrate = 38400 - log.Printf("Finished writing u-blox GPS config to %s. Opening port to test connection.\n", device) + if globalSettings.DEBUG { + log.Printf("Finished writing u-blox GPS config to %s. Opening port to test connection.\n", device) + } } p.Close() @@ -385,10 +428,10 @@ func validateNMEAChecksum(s string) (string, bool) { // changes while on the ground and "movement" is really only changes in GPS fix as it settles down. //TODO: Some more robust checking above current and last speed. //TODO: Dynamic adjust for gain based on groundspeed -func setTrueCourse(groundSpeed, trueCourse uint16) { +func setTrueCourse(groundSpeed uint16, trueCourse float64) { if myMPU6050 != nil && globalStatus.RY835AI_connected && globalSettings.AHRS_Enabled { if mySituation.GroundSpeed >= 7 && groundSpeed >= 7 { - myMPU6050.ResetHeading(float64(trueCourse), 0.10) + myMPU6050.ResetHeading(trueCourse, 0.10) } } } @@ -424,8 +467,16 @@ return is true if parse occurs correctly and position is valid. */ -func processNMEALine(l string) bool { - replayLog(l, MSGCLASS_GPS) +func processNMEALine(l string) (sentenceUsed bool) { + mySituation.mu_GPS.Lock() + + defer func() { + if sentenceUsed || globalSettings.DEBUG { + logSituation() + } + mySituation.mu_GPS.Unlock() + }() + l_valid, validNMEAcs := validateNMEAChecksum(l) if !validNMEAcs { log.Printf("GPS error. Invalid NMEA string: %s\n", l_valid) // remove log message once validation complete @@ -433,58 +484,50 @@ func processNMEALine(l string) bool { } x := strings.Split(l_valid, ",") - mySituation.LastNMEAMessage = stratuxClock.Time + mySituation.LastValidNMEAMessageTime = stratuxClock.Time + mySituation.LastValidNMEAMessage = l if x[0] == "PUBX" { // UBX proprietary message - if x[1] == "00" { // position message + if x[1] == "00" { // Position fix. if len(x) < 20 { return false } - mySituation.mu_GPS.Lock() - defer mySituation.mu_GPS.Unlock() + tmpSituation := mySituation // If we decide to not use the data in this message, then don't make incomplete changes in mySituation. // Do the accuracy / quality fields first to prevent invalid position etc. from being sent downstream - // field 8 = nav status // DR = dead reckoning, G2= 2D GPS, G3 = 3D GPS, D2= 2D diff, D3 = 3D diff, RK = GPS+DR, TT = time only - - okReturn := true - if x[8] == "D2" || x[8] == "D3" { - mySituation.quality = 2 + tmpSituation.Quality = 2 } else if x[8] == "G2" || x[8] == "G3" { - mySituation.quality = 1 + tmpSituation.Quality = 1 } else if x[8] == "DR" || x[8] == "RK" { - mySituation.quality = 6 + tmpSituation.Quality = 6 } else if x[8] == "NF" { - mySituation.quality = 0 - okReturn = false // better to have no data than wrong data + tmpSituation.Quality = 0 // Just a note. + return false } else { - mySituation.quality = 0 - okReturn = false // better to have no data than wrong data + tmpSituation.Quality = 0 // Just a note. + return false } // field 9 = horizontal accuracy, m hAcc, err := strconv.ParseFloat(x[9], 32) if err != nil { - okReturn = false + return false } - mySituation.Accuracy = float32(hAcc * 2) // UBX reports 1-sigma variation; NACp is 95% confidence (2-sigma) + tmpSituation.Accuracy = float32(hAcc * 2) // UBX reports 1-sigma variation; NACp is 95% confidence (2-sigma) // NACp estimate. - mySituation.NACp = calculateNACp(mySituation.Accuracy) + tmpSituation.NACp = calculateNACp(tmpSituation.Accuracy) // field 10 = vertical accuracy, m vAcc, err := strconv.ParseFloat(x[10], 32) if err != nil { - okReturn = false - } - mySituation.AccuracyVert = float32(vAcc * 2) // UBX reports 1-sigma variation; we want 95% confidence - - if !okReturn { return false } + tmpSituation.AccuracyVert = float32(vAcc * 2) // UBX reports 1-sigma variation; we want 95% confidence // field 2 = time if len(x[2]) < 8 { @@ -497,10 +540,9 @@ func processNMEALine(l string) bool { return false } - mySituation.lastFixSinceMidnightUTC = float32(3600*hr+60*min) + float32(sec) + tmpSituation.LastFixSinceMidnightUTC = float32(3600*hr+60*min) + float32(sec) // field 3-4 = lat - if len(x[3]) < 10 { return false } @@ -511,9 +553,9 @@ func processNMEALine(l string) bool { return false } - mySituation.Lat = float32(hr) + float32(minf/60.0) + tmpSituation.Lat = float32(hr) + float32(minf/60.0) if x[4] == "S" { // South = negative. - mySituation.Lat = -mySituation.Lat + tmpSituation.Lat = -tmpSituation.Lat } // field 5-6 = lon @@ -526,9 +568,9 @@ func processNMEALine(l string) bool { return false } - mySituation.Lng = float32(hr) + float32(minf/60.0) + tmpSituation.Lng = float32(hr) + float32(minf/60.0) if x[6] == "W" { // West = negative. - mySituation.Lng = -mySituation.Lng + tmpSituation.Lng = -tmpSituation.Lng } // field 7 = height above ellipsoid, m @@ -537,11 +579,11 @@ func processNMEALine(l string) bool { if err1 != nil { return false } - alt := float32(hae*3.28084) - mySituation.GeoidSep // convert to feet and offset by geoid separation - mySituation.HeightAboveEllipsoid = float32(hae * 3.28084) // feet - mySituation.Alt = alt + alt := float32(hae*3.28084) - tmpSituation.GeoidSep // convert to feet and offset by geoid separation + tmpSituation.HeightAboveEllipsoid = float32(hae * 3.28084) // feet + tmpSituation.Alt = alt - mySituation.LastFixLocalTime = stratuxClock.Time + tmpSituation.LastFixLocalTime = stratuxClock.Time // field 11 = groundspeed, km/h groundspeed, err := strconv.ParseFloat(x[11], 32) @@ -549,30 +591,30 @@ func processNMEALine(l string) bool { return false } groundspeed = groundspeed * 0.540003 // convert to knots - mySituation.GroundSpeed = uint16(groundspeed) + tmpSituation.GroundSpeed = uint16(groundspeed) // field 12 = track, deg - trueCourse := uint16(0) + trueCourse := float32(0.0) tc, err := strconv.ParseFloat(x[12], 32) if err != nil { return false } if groundspeed > 3 { // TO-DO: use average groundspeed over last n seconds to avoid random "jumps" - trueCourse = uint16(tc) - setTrueCourse(uint16(groundspeed), trueCourse) - mySituation.TrueCourse = uint16(trueCourse) + trueCourse = float32(tc) + setTrueCourse(uint16(groundspeed), tc) + tmpSituation.TrueCourse = trueCourse } else { // Negligible movement. Don't update course, but do use the slow speed. // TO-DO: use average course over last n seconds? } - mySituation.LastGroundTrackTime = stratuxClock.Time + tmpSituation.LastGroundTrackTime = stratuxClock.Time // field 13 = vertical velocity, m/s vv, err := strconv.ParseFloat(x[13], 32) if err != nil { return false } - mySituation.GPSVertVel = float32(vv * -3.28084) // convert to ft/sec and positive = up + tmpSituation.GPSVertVel = float32(vv * -3.28084) // convert to ft/sec and positive = up // field 14 = age of diff corrections @@ -581,42 +623,148 @@ func processNMEALine(l string) bool { if err1 != nil { return false } - mySituation.Satellites = uint16(sat) + tmpSituation.Satellites = uint16(sat) // this seems to be reliable. UBX,03 handles >12 satellites solutions correctly. - } else if x[1] == "03" { // satellite status message + // We've made it this far, so that means we've processed "everything" and can now make the change to mySituation. + mySituation = tmpSituation + return true + } else if x[1] == "03" { // satellite status message. Only the first 20 satellites will be reported in this message for UBX firmware older than v3.0. Order seems to be GPS, then SBAS, then GLONASS. + + if len(x) < 3 { // malformed UBX,03 message that somehow passed checksum verification but is missing all of its fields + return false + } // field 2 = number of satellites tracked - satSeen := 0 // satellites seen (signal present) + //satSeen := 0 // satellites seen (signal present) satTracked, err := strconv.Atoi(x[2]) if err != nil { return false } - mySituation.SatellitesTracked = uint16(satTracked) + + if globalSettings.DEBUG { + log.Printf("GPS PUBX,03 message with %d satellites is %d fields long. (Should be %d fields long)\n", satTracked, len(x), satTracked*6+3) + } + + if len(x) < (satTracked*6 + 3) { // malformed UBX,03 message that somehow passed checksum verification but is missing some of its fields + if globalSettings.DEBUG { + log.Printf("GPS PUBX,03 message is missing fields\n") + } + return false + } + + mySituation.SatellitesTracked = uint16(satTracked) // requires UBX M8N firmware v3.01 or later to report > 20 satellites // fields 3-8 are repeated block - for i := 0; i < satTracked; i++ { - j := 7 + 6*i - if j < len(x) { - if x[j] != "" { - satSeen++ - } - } + var sv, elev, az, cno int + var svType uint8 + var svStr string + + /* Reference for constellation tracking + for i:= 0; i < satTracked; i++ { + x[3+6*i] // sv number + x[4+6*i] // status [ U | e | - ] indicates [U]sed in solution, [e]phemeris data only, [-] not used + x[5+6*i] // azimuth, deg, 0-359 + x[6+6*i] // elevation, deg, 0-90 + x[7+6*i] // signal strength dB-Hz + x[8+6*i] // lock time, sec, 0-64 } - - mySituation.SatellitesSeen = uint16(satSeen) - // log.Printf("Satellites with signal: %v\n",mySituation.SatellitesSeen) - - /* Reference for future constellation tracking - for i:= 0; i < satTracked; i++ { - x[3+6*i] // sv number - x[4+6*i] // status [ U | e | - ] for used / ephemeris / not used - x[5+6*i] // azimuth, deg, 0-359 - x[6+6*i] // elevation, deg, 0-90 - x[7+6*i] // signal strength dB-Hz - x[8+6*i] // lock time, sec, 0-64 */ + for i := 0; i < satTracked; i++ { + //field 3+6i is sv number. GPS NMEA = PRN. GLONASS NMEA = PRN + 65. SBAS is PRN; needs to be converted to NMEA for compatiblity with GSV messages. + sv, err = strconv.Atoi(x[3+6*i]) // sv number + if err != nil { + return false + } + if sv < 33 { // indicates GPS + svType = SAT_TYPE_GPS + svStr = fmt.Sprintf("G%d", sv) + } else if sv < 65 { // indicates SBAS: WAAS, EGNOS, MSAS, etc. + svType = SAT_TYPE_SBAS + svStr = fmt.Sprintf("S%d", sv+87) // add 87 to convert from NMEA to PRN. + } else if sv < 97 { // GLONASS + svType = SAT_TYPE_GLONASS + svStr = fmt.Sprintf("R%d", sv-64) // subtract 64 to convert from NMEA to PRN. + } else if (sv >= 120) && (sv < 162) { // indicates SBAS: WAAS, EGNOS, MSAS, etc. + svType = SAT_TYPE_SBAS + svStr = fmt.Sprintf("S%d", sv) + sv -= 87 // subtract 87 to convert to NMEA from PRN. + } else { // TO-DO: Galileo + svType = SAT_TYPE_UNKNOWN + svStr = fmt.Sprintf("U%d", sv) + } + + var thisSatellite SatelliteInfo + + // START OF PROTECTED BLOCK + satelliteMutex.Lock() + + // Retrieve previous information on this satellite code. + if val, ok := Satellites[svStr]; ok { // if we've already seen this satellite identifier, copy it in to do updates + thisSatellite = val + //log.Printf("UBX,03: Satellite %s already seen. Retrieving from 'Satellites'.\n", svStr) // DEBUG + } else { // this satellite isn't in the Satellites data structure + thisSatellite.SatelliteID = svStr + thisSatellite.SatelliteNMEA = uint8(sv) + thisSatellite.Type = uint8(svType) + //log.Printf("UBX,03: Creating new satellite %s\n", svStr) // DEBUG + } + thisSatellite.TimeLastTracked = stratuxClock.Time + + // Field 6+6*i is elevation, deg, 0-90 + elev, err = strconv.Atoi(x[6+6*i]) // elevation + if err != nil { // could be blank if no position fix. Represent as -999. + elev = -999 + } + thisSatellite.Elevation = int16(elev) + + // Field 5+6*i is azimuth, deg, 0-359 + az, err = strconv.Atoi(x[5+6*i]) // azimuth + if err != nil { // could be blank if no position fix. Represent as -999. + az = -999 + } + thisSatellite.Azimuth = int16(az) + + // Field 7+6*i is signal strength dB-Hz + cno, err = strconv.Atoi(x[7+6*i]) // signal + if err != nil { // will be blank if satellite isn't being received. Represent as -99. + cno = -99 + } else if cno > 0 { + thisSatellite.TimeLastSeen = stratuxClock.Time // Is this needed? + } + thisSatellite.Signal = int8(cno) + + // Field 4+6*i is status: [ U | e | - ]: [U]sed in solution, [e]phemeris data only, [-] not used + if x[4+6*i] == "U" { + thisSatellite.InSolution = true + thisSatellite.TimeLastSolution = stratuxClock.Time + } else if x[4+6*i] == "e" { + thisSatellite.InSolution = false + //log.Printf("Satellite %s is no longer in solution but has ephemeris - UBX,03\n", svStr) // DEBUG + // do anything that needs to be done for ephemeris + } else { + thisSatellite.InSolution = false + //log.Printf("Satellite %s is no longer in solution and has no ephemeris - UBX,03\n", svStr) // DEBUG + } + + if globalSettings.DEBUG { + inSolnStr := " " + if thisSatellite.InSolution { + inSolnStr = "+" + } + log.Printf("UBX: Satellite %s%s at index %d. Type = %d, NMEA-ID = %d, Elev = %d, Azimuth = %d, Cno = %d\n", inSolnStr, svStr, i, svType, sv, elev, az, cno) // remove later? + } + + Satellites[thisSatellite.SatelliteID] = thisSatellite // Update constellation with this satellite + updateConstellation() + satelliteMutex.Unlock() + // END OF PROTECTED BLOCK + + // end of satellite iteration loop + } + + return true } else if x[1] == "04" { // clock message // field 5 is UTC week (epoch = 1980-JAN-06). If this is invalid, do not parse date / time utcWeek, err0 := strconv.Atoi(x[5]) @@ -641,7 +789,6 @@ func processNMEALine(l string) bool { if err1 != nil || err2 != nil || err3 != nil { return false } - mySituation.lastFixSinceMidnightUTC = float32(3600*hr+60*min) + float32(sec) // field 3 is date @@ -650,7 +797,10 @@ func processNMEALine(l string) bool { gpsTimeStr := fmt.Sprintf("%s %02d:%02d:%06.3f", x[3], hr, min, sec) gpsTime, err := time.Parse("020106 15:04:05.000", gpsTimeStr) if err == nil { + // We only update ANY of the times if all of the time parsing is complete. mySituation.LastGPSTimeTime = stratuxClock.Time + mySituation.GPSTime = gpsTime + mySituation.LastFixSinceMidnightUTC = float32(3600*hr+60*min) + float32(sec) // log.Printf("GPS time is: %s\n", gpsTime) //debug if time.Since(gpsTime) > 3*time.Second || time.Since(gpsTime) < -3*time.Second { setStr := gpsTime.Format("20060102 15:04:05.000") + " UTC" @@ -661,53 +811,57 @@ func processNMEALine(l string) bool { log.Printf("Time set from GPS. Current time is %v\n", time.Now()) } } + setDataLogTimeWithGPS(mySituation) + return true // All possible successes lead here. } } - } // otherwise parse the NMEA standard messages as a compatibility option for SIRF, generic NMEA, etc. } else if (x[0] == "GNVTG") || (x[0] == "GPVTG") { // Ground track information. - if len(x) < 9 { // Reduce from 10 to 9 to allow parsing by devices pre-NMEA v2.3 + tmpSituation := mySituation // If we decide to not use the data in this message, then don't make incomplete changes in mySituation. + if len(x) < 9 { // Reduce from 10 to 9 to allow parsing by devices pre-NMEA v2.3 return false } - mySituation.mu_GPS.Lock() - defer mySituation.mu_GPS.Unlock() groundspeed, err := strconv.ParseFloat(x[5], 32) // Knots. if err != nil { return false } - mySituation.GroundSpeed = uint16(groundspeed) + tmpSituation.GroundSpeed = uint16(groundspeed) - trueCourse := uint16(0) + trueCourse := float32(0) tc, err := strconv.ParseFloat(x[1], 32) if err != nil { return false } if groundspeed > 3 { // TO-DO: use average groundspeed over last n seconds to avoid random "jumps" - trueCourse = uint16(tc) - setTrueCourse(uint16(groundspeed), trueCourse) - mySituation.TrueCourse = uint16(trueCourse) + trueCourse = float32(tc) + setTrueCourse(uint16(groundspeed), tc) + tmpSituation.TrueCourse = trueCourse } else { // Negligible movement. Don't update course, but do use the slow speed. // TO-DO: use average course over last n seconds? } - mySituation.LastGroundTrackTime = stratuxClock.Time + tmpSituation.LastGroundTrackTime = stratuxClock.Time + + // We've made it this far, so that means we've processed "everything" and can now make the change to mySituation. + mySituation = tmpSituation + return true + + } else if (x[0] == "GNGGA") || (x[0] == "GPGGA") { // Position fix. + tmpSituation := mySituation // If we decide to not use the data in this message, then don't make incomplete changes in mySituation. - } else if (x[0] == "GNGGA") || (x[0] == "GPGGA") { // GPS fix. if len(x) < 15 { return false } - mySituation.mu_GPS.Lock() - defer mySituation.mu_GPS.Unlock() // Quality indicator. q, err1 := strconv.Atoi(x[6]) if err1 != nil { return false } - mySituation.quality = uint8(q) // 1 = 3D GPS; 2 = DGPS (SBAS /WAAS) + tmpSituation.Quality = uint8(q) // 1 = 3D GPS; 2 = DGPS (SBAS /WAAS) // Timestamp. if len(x[1]) < 7 { @@ -720,7 +874,7 @@ func processNMEALine(l string) bool { return false } - mySituation.lastFixSinceMidnightUTC = float32(3600*hr+60*min) + float32(sec) + tmpSituation.LastFixSinceMidnightUTC = float32(3600*hr+60*min) + float32(sec) // Latitude. if len(x[2]) < 4 { @@ -733,9 +887,9 @@ func processNMEALine(l string) bool { return false } - mySituation.Lat = float32(hr) + float32(minf/60.0) + tmpSituation.Lat = float32(hr) + float32(minf/60.0) if x[3] == "S" { // South = negative. - mySituation.Lat = -mySituation.Lat + tmpSituation.Lat = -tmpSituation.Lat } // Longitude. @@ -748,40 +902,17 @@ func processNMEALine(l string) bool { return false } - mySituation.Lng = float32(hr) + float32(minf/60.0) + tmpSituation.Lng = float32(hr) + float32(minf/60.0) if x[5] == "W" { // West = negative. - mySituation.Lng = -mySituation.Lng + tmpSituation.Lng = -tmpSituation.Lng } - /* Satellite count and horizontal accuracy deprecated. Using PUBX,00 with fallback to GSA. - // Satellites. - sat, err1 := strconv.Atoi(x[7]) - if err1 != nil { - return false - } - mySituation.Satellites = uint16(sat) - - // Accuracy. - hdop, err1 := strconv.ParseFloat(x[8], 32) - if err1 != nil { - return false - } - if mySituation.quality == 2 { - mySituation.Accuracy = float32(hdop * 4.0) //Estimate for WAAS / DGPS solution - } else { - mySituation.Accuracy = float32(hdop * 8.0) //Estimate for 3D non-WAAS solution - } - - // NACp estimate. - mySituation.NACp = calculateNACp(mySituation.Accuracy) - */ - // Altitude. alt, err1 := strconv.ParseFloat(x[9], 32) if err1 != nil { return false } - mySituation.Alt = float32(alt * 3.28084) // Convert to feet. + tmpSituation.Alt = float32(alt * 3.28084) // Convert to feet. // Geoid separation (Sep = HAE - MSL) // (needed for proper MSL offset on PUBX,00 altitudes) @@ -790,13 +921,19 @@ func processNMEALine(l string) bool { if err1 != nil { return false } - mySituation.GeoidSep = float32(geoidSep * 3.28084) // Convert to feet. - mySituation.HeightAboveEllipsoid = mySituation.GeoidSep + mySituation.Alt + tmpSituation.GeoidSep = float32(geoidSep * 3.28084) // Convert to feet. + tmpSituation.HeightAboveEllipsoid = tmpSituation.GeoidSep + tmpSituation.Alt // Timestamp. - mySituation.LastFixLocalTime = stratuxClock.Time + tmpSituation.LastFixLocalTime = stratuxClock.Time + + // We've made it this far, so that means we've processed "everything" and can now make the change to mySituation. + mySituation = tmpSituation + return true + + } else if (x[0] == "GNRMC") || (x[0] == "GPRMC") { // Recommended Minimum data. FIXME: Is this needed anymore? + tmpSituation := mySituation // If we decide to not use the data in this message, then don't make incomplete changes in mySituation. - } else if (x[0] == "GNRMC") || (x[0] == "GPRMC") { //$GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*6A /* check RY835 man for NMEA version, if >2.2, add mode field Where: @@ -815,14 +952,10 @@ func processNMEALine(l string) bool { if len(x) < 11 { return false } - mySituation.mu_GPS.Lock() - defer mySituation.mu_GPS.Unlock() if x[2] != "A" { // invalid fix - mySituation.quality = 0 + tmpSituation.Quality = 0 // Just a note. return false - } else if mySituation.quality == 0 { - mySituation.quality = 1 // fallback option; indicate if the position fix is valid even if GGA or PUBX,00 aren't received } // Timestamp. @@ -835,14 +968,15 @@ func processNMEALine(l string) bool { if err1 != nil || err2 != nil || err3 != nil { return false } - mySituation.lastFixSinceMidnightUTC = float32(3600*hr+60*min) + float32(sec) + tmpSituation.LastFixSinceMidnightUTC = float32(3600*hr+60*min) + float32(sec) if len(x[9]) == 6 { // Date of Fix, i.e 191115 = 19 November 2015 UTC field 9 gpsTimeStr := fmt.Sprintf("%s %02d:%02d:%06.3f", x[9], hr, min, sec) gpsTime, err := time.Parse("020106 15:04:05.000", gpsTimeStr) if err == nil { - mySituation.LastGPSTimeTime = stratuxClock.Time + tmpSituation.LastGPSTimeTime = stratuxClock.Time + tmpSituation.GPSTime = gpsTime if time.Since(gpsTime) > 3*time.Second || time.Since(gpsTime) < -3*time.Second { setStr := gpsTime.Format("20060102 15:04:05.000") + " UTC" log.Printf("setting system time to: '%s'\n", setStr) @@ -864,9 +998,9 @@ func processNMEALine(l string) bool { if err1 != nil || err2 != nil { return false } - mySituation.Lat = float32(hr) + float32(minf/60.0) + tmpSituation.Lat = float32(hr) + float32(minf/60.0) if x[4] == "S" { // South = negative. - mySituation.Lat = -mySituation.Lat + tmpSituation.Lat = -tmpSituation.Lat } // Longitude. if len(x[5]) < 5 { @@ -877,38 +1011,45 @@ func processNMEALine(l string) bool { if err1 != nil || err2 != nil { return false } - mySituation.Lng = float32(hr) + float32(minf/60.0) + tmpSituation.Lng = float32(hr) + float32(minf/60.0) if x[6] == "W" { // West = negative. - mySituation.Lng = -mySituation.Lng + tmpSituation.Lng = -tmpSituation.Lng } - mySituation.LastFixLocalTime = stratuxClock.Time + tmpSituation.LastFixLocalTime = stratuxClock.Time // ground speed in kts (field 7) groundspeed, err := strconv.ParseFloat(x[7], 32) if err != nil { return false } - mySituation.GroundSpeed = uint16(groundspeed) + tmpSituation.GroundSpeed = uint16(groundspeed) // ground track "True" (field 8) - trueCourse := uint16(0) + trueCourse := float32(0) tc, err := strconv.ParseFloat(x[8], 32) if err != nil { return false } if groundspeed > 3 { // TO-DO: use average groundspeed over last n seconds to avoid random "jumps" - trueCourse = uint16(tc) - setTrueCourse(uint16(groundspeed), trueCourse) - mySituation.TrueCourse = uint16(trueCourse) + trueCourse = float32(tc) + setTrueCourse(uint16(groundspeed), tc) + tmpSituation.TrueCourse = trueCourse } else { // Negligible movement. Don't update course, but do use the slow speed. // TO-DO: use average course over last n seconds? } - mySituation.LastGroundTrackTime = stratuxClock.Time + tmpSituation.LastGroundTrackTime = stratuxClock.Time + + // We've made it this far, so that means we've processed "everything" and can now make the change to mySituation. + mySituation = tmpSituation + setDataLogTimeWithGPS(mySituation) + return true + + } else if (x[0] == "GNGSA") || (x[0] == "GPGSA") { // Satellite data. + tmpSituation := mySituation // If we decide to not use the data in this message, then don't make incomplete changes in mySituation. - } else if (x[0] == "GNGSA") || (x[0] == "GPGSA") { if len(x) < 18 { return false } @@ -916,32 +1057,83 @@ func processNMEALine(l string) bool { // field 1: operation mode // M: manual forced to 2D or 3D mode // A: automatic switching between 2D and 3D modes - if (x[1] != "A") && (x[1] != "M") { // invalid fix - mySituation.quality = 0 - return false - } + + /* + if (x[1] != "A") && (x[1] != "M") { // invalid fix ... but x[2] is a better indicator of fix quality. Deprecating this. + tmpSituation.Quality = 0 // Just a note. + return false + } + */ // field 2: solution type // 1 = no solution; 2 = 2D fix, 3 = 3D fix. WAAS status is parsed from GGA message, so no need to get here + if (x[2] == "") || (x[2] == "1") { // missing or no solution + tmpSituation.Quality = 0 // Just a note. + return false + } // fields 3-14: satellites in solution + var svStr string + var svType uint8 + var svSBAS bool // used to indicate whether this GSA message contains a SBAS satellite + var svGLONASS bool // used to indicate whether this GSA message contains GLONASS satellites sat := 0 + for _, svtxt := range x[3:15] { - _, err := strconv.Atoi(svtxt) + sv, err := strconv.Atoi(svtxt) if err == nil { sat++ + + if sv < 33 { // indicates GPS + svType = SAT_TYPE_GPS + svStr = fmt.Sprintf("G%d", sv) + } else if sv < 65 { // indicates SBAS: WAAS, EGNOS, MSAS, etc. + svType = SAT_TYPE_SBAS + svStr = fmt.Sprintf("S%d", sv+87) // add 87 to convert from NMEA to PRN. + svSBAS = true + } else if sv < 97 { // GLONASS + svType = SAT_TYPE_GLONASS + svStr = fmt.Sprintf("R%d", sv-64) // subtract 64 to convert from NMEA to PRN. + svGLONASS = true + } else { // TO-DO: Galileo + svType = SAT_TYPE_UNKNOWN + svStr = fmt.Sprintf("U%d", sv) + } + + var thisSatellite SatelliteInfo + + // START OF PROTECTED BLOCK + satelliteMutex.Lock() + + // Retrieve previous information on this satellite code. + if val, ok := Satellites[svStr]; ok { // if we've already seen this satellite identifier, copy it in to do updates + thisSatellite = val + //log.Printf("Satellite %s already seen. Retrieving from 'Satellites'.\n", svStr) + } else { // this satellite isn't in the Satellites data structure, so create it + thisSatellite.SatelliteID = svStr + thisSatellite.SatelliteNMEA = uint8(sv) + thisSatellite.Type = uint8(svType) + //log.Printf("Creating new satellite %s from GSA message\n", svStr) // DEBUG + } + thisSatellite.InSolution = true + thisSatellite.TimeLastSolution = stratuxClock.Time + thisSatellite.TimeLastSeen = stratuxClock.Time // implied, since this satellite is used in the position solution + thisSatellite.TimeLastTracked = stratuxClock.Time // implied, since this satellite is used in the position solution + + Satellites[thisSatellite.SatelliteID] = thisSatellite // Update constellation with this satellite + updateConstellation() + satelliteMutex.Unlock() + // END OF PROTECTED BLOCK + } } - mySituation.Satellites = uint16(sat) - - // Satellites tracked / seen should be parsed from GSV message (TO-DO) ... since we don't have it, just use satellites from solution - if mySituation.SatellitesTracked == 0 { - mySituation.SatellitesTracked = uint16(sat) - } - - if mySituation.SatellitesSeen == 0 { - mySituation.SatellitesSeen = uint16(sat) + if sat < 12 || tmpSituation.Satellites < 13 { // GSA only reports up to 12 satellites in solution, so we don't want to overwrite higher counts based on updateConstellation(). + tmpSituation.Satellites = uint16(sat) + if (tmpSituation.Quality == 2) && !svSBAS && !svGLONASS { // add one to the satellite count if we have a SBAS solution, but the GSA message doesn't track a SBAS satellite + tmpSituation.Satellites++ + } } + //log.Printf("There are %d satellites in solution from this GSA message\n", sat) // TESTING - DEBUG // field 16: HDOP // Accuracy estimate @@ -949,14 +1141,14 @@ func processNMEALine(l string) bool { if err1 != nil { return false } - if mySituation.quality == 2 { - mySituation.Accuracy = float32(hdop * 4.0) // Rough 95% confidence estimate for WAAS / DGPS solution + if tmpSituation.Quality == 2 { + tmpSituation.Accuracy = float32(hdop * 4.0) // Rough 95% confidence estimate for WAAS / DGPS solution } else { - mySituation.Accuracy = float32(hdop * 8.0) // Rough 95% confidence estimate for 3D non-WAAS solution + tmpSituation.Accuracy = float32(hdop * 8.0) // Rough 95% confidence estimate for 3D non-WAAS solution } // NACp estimate. - mySituation.NACp = calculateNACp(mySituation.Accuracy) + tmpSituation.NACp = calculateNACp(tmpSituation.Accuracy) // field 17: VDOP // accuracy estimate @@ -964,9 +1156,150 @@ func processNMEALine(l string) bool { if err1 != nil { return false } - mySituation.AccuracyVert = float32(vdop * 5) // rough estimate for 95% confidence + tmpSituation.AccuracyVert = float32(vdop * 5) // rough estimate for 95% confidence + + // We've made it this far, so that means we've processed "everything" and can now make the change to mySituation. + mySituation = tmpSituation + return true + } - return true + + if (x[0] == "GPGSV") || (x[0] == "GLGSV") { // GPS + SBAS or GLONASS satellites in view message. Galileo is TBD. + if len(x) < 4 { + return false + } + + // field 1 = number of GSV messages of this type + msgNum, err := strconv.Atoi(x[2]) + if err != nil { + return false + } + + // field 2 = index of this GSV message + + msgIndex, err := strconv.Atoi(x[2]) + if err != nil { + return false + } + + // field 3 = number of GPS satellites tracked + /* Is this redundant if parsing from full constellation? + satTracked, err := strconv.Atoi(x[3]) + if err != nil { + return false + } + */ + + //mySituation.SatellitesTracked = uint16(satTracked) // Replaced with parsing of 'Satellites' data structure + + // field 4-7 = repeating block with satellite id, elevation, azimuth, and signal strengh (Cno) + + lenGSV := len(x) + satsThisMsg := (lenGSV - 4) / 4 + + if globalSettings.DEBUG { + log.Printf("%s message [%d of %d] is %v fields long and describes %v satellites\n", x[0], msgIndex, msgNum, lenGSV, satsThisMsg) + } + + var sv, elev, az, cno int + var svType uint8 + var svStr string + + for i := 0; i < satsThisMsg; i++ { + + sv, err = strconv.Atoi(x[4+4*i]) // sv number + if err != nil { + return false + } + if sv < 33 { // indicates GPS + svType = SAT_TYPE_GPS + svStr = fmt.Sprintf("G%d", sv) + } else if sv < 65 { // indicates SBAS: WAAS, EGNOS, MSAS, etc. + svType = SAT_TYPE_SBAS + svStr = fmt.Sprintf("S%d", sv+87) // add 87 to convert from NMEA to PRN. + } else if sv < 97 { // GLONASS + svType = SAT_TYPE_GLONASS + svStr = fmt.Sprintf("R%d", sv-64) // subtract 64 to convert from NMEA to PRN. + } else { // TO-DO: Galileo + svType = SAT_TYPE_UNKNOWN + svStr = fmt.Sprintf("U%d", sv) + } + + var thisSatellite SatelliteInfo + + // START OF PROTECTED BLOCK + satelliteMutex.Lock() + + // Retrieve previous information on this satellite code. + if val, ok := Satellites[svStr]; ok { // if we've already seen this satellite identifier, copy it in to do updates + thisSatellite = val + //log.Printf("Satellite %s already seen. Retrieving from 'Satellites'.\n", svStr) // DEBUG + } else { // this satellite isn't in the Satellites data structure, so create it new + thisSatellite.SatelliteID = svStr + thisSatellite.SatelliteNMEA = uint8(sv) + thisSatellite.Type = uint8(svType) + //log.Printf("Creating new satellite %s\n", svStr) // DEBUG + } + thisSatellite.TimeLastTracked = stratuxClock.Time + + elev, err = strconv.Atoi(x[5+4*i]) // elevation + if err != nil { // some firmwares leave this blank if there's no position fix. Represent as -999. + elev = -999 + } + thisSatellite.Elevation = int16(elev) + + az, err = strconv.Atoi(x[6+4*i]) // azimuth + if err != nil { // UBX allows tracking up to 5(?) degrees below horizon. Some firmwares leave this blank if no position fix. Represent invalid as -999. + az = -999 + } + thisSatellite.Azimuth = int16(az) + + cno, err = strconv.Atoi(x[7+4*i]) // signal + if err != nil { // will be blank if satellite isn't being received. Represent as -99. + cno = -99 + thisSatellite.InSolution = false // resets the "InSolution" status if the satellite disappears out of solution due to no signal. FIXME + //log.Printf("Satellite %s is no longer in solution due to cno parse error - GSV\n", svStr) // DEBUG + } else if cno > 0 { + thisSatellite.TimeLastSeen = stratuxClock.Time // Is this needed? + } + if cno > 127 { // make sure strong signals don't overflow. Normal range is 0-99 so it shouldn't, but take no chances. + cno = 127 + } + thisSatellite.Signal = int8(cno) + + // hack workaround for GSA 12-sv limitation... if this is a SBAS satellite, we have a SBAS solution, and signal is greater than some arbitrary threshold, set InSolution + // drawback is this will show all tracked SBAS satellites as being in solution. + if thisSatellite.Type == SAT_TYPE_SBAS { + if mySituation.Quality == 2 { + if thisSatellite.Signal > 16 { + thisSatellite.InSolution = true + thisSatellite.TimeLastSolution = stratuxClock.Time + } + } else { // quality == 0 or 1 + thisSatellite.InSolution = false + //log.Printf("WAAS satellite %s is marked as out of solution GSV\n", svStr) // DEBUG + } + } + + if globalSettings.DEBUG { + inSolnStr := " " + if thisSatellite.InSolution { + inSolnStr = "+" + } + log.Printf("GSV: Satellite %s%s at index %d. Type = %d, NMEA-ID = %d, Elev = %d, Azimuth = %d, Cno = %d\n", inSolnStr, svStr, i, svType, sv, elev, az, cno) // remove later? + } + + Satellites[thisSatellite.SatelliteID] = thisSatellite // Update constellation with this satellite + updateConstellation() + satelliteMutex.Unlock() + // END OF PROTECTED BLOCK + } + + return true + } + + // if we've gotten this far, the message isn't one that we want to parse + return false } func gpsSerialReader() { @@ -1052,7 +1385,7 @@ func tempAndPressureReader() { } else { mySituation.Temp = temp mySituation.Pressure_alt = alt - mySituation.lastTempPressTime = stratuxClock.Time + mySituation.LastTempPressTime = stratuxClock.Time } } globalStatus.RY835AI_connected = false @@ -1136,12 +1469,59 @@ func attitudeReaderSender() { globalStatus.RY835AI_connected = false } -func isGPSConnected() bool { - return stratuxClock.Since(mySituation.LastNMEAMessage) < 5*time.Second +/* + updateConstellation(): Periodic cleanup and statistics calculation for 'Satellites' + data structure. Calling functions must protect this in a satelliteMutex. + +*/ + +func updateConstellation() { + var sats, tracked, seen uint8 + for svStr, thisSatellite := range Satellites { + if stratuxClock.Since(thisSatellite.TimeLastTracked) > 10*time.Second { // remove stale satellites if they haven't been tracked for 10 seconds + delete(Satellites, svStr) + } else { // satellite almanac data is "fresh" even if it isn't being received. + tracked++ + if thisSatellite.Signal > 0 { + seen++ + } + if stratuxClock.Since(thisSatellite.TimeLastSolution) > 5*time.Second { + thisSatellite.InSolution = false + Satellites[svStr] = thisSatellite + } + if thisSatellite.InSolution { // TESTING: Determine "In solution" from structure (fix for multi-GNSS overflow) + sats++ + } + // do any other calculations needed for this satellite + } + } + //log.Printf("Satellite counts: %d tracking channels, %d with >0 dB-Hz signal\n", tracked, seen) // DEBUG - REMOVE + //log.Printf("Satellite struct: %v\n", Satellites) // DEBUG - REMOVE + mySituation.Satellites = uint16(sats) + mySituation.SatellitesTracked = uint16(tracked) + mySituation.SatellitesSeen = uint16(seen) } +func isGPSConnected() bool { + return stratuxClock.Since(mySituation.LastValidNMEAMessageTime) < 5*time.Second +} + +/* +isGPSValid returns true only if a valid position fix has been seen in the last 15 seconds, +and if the GPS subsystem has recently detected a GPS device. + +If false, 'Quality` is set to 0 ("No fix"), as is the number of satellites in solution. +*/ + func isGPSValid() bool { - return stratuxClock.Since(mySituation.LastFixLocalTime) < 15*time.Second + isValid := false + if (stratuxClock.Since(mySituation.LastFixLocalTime) < 15*time.Second) && globalStatus.GPS_connected && mySituation.Quality > 0 { + isValid = true + } else { + mySituation.Quality = 0 + mySituation.Satellites = 0 + } + return isValid } func isGPSGroundTrackValid() bool { @@ -1157,7 +1537,7 @@ func isAHRSValid() bool { } func isTempPressValid() bool { - return stratuxClock.Since(mySituation.lastTempPressTime) < 15*time.Second + return stratuxClock.Since(mySituation.LastTempPressTime) < 15*time.Second } func initAHRS() error { @@ -1206,6 +1586,8 @@ func pollRY835AI() { func initRY835AI() { mySituation.mu_GPS = &sync.Mutex{} mySituation.mu_Attitude = &sync.Mutex{} + satelliteMutex = &sync.Mutex{} + Satellites = make(map[string]SatelliteInfo) go pollRY835AI() } diff --git a/main/sdr.go b/main/sdr.go index 3a98c8d4..8ef386b0 100644 --- a/main/sdr.go +++ b/main/sdr.go @@ -46,6 +46,11 @@ var UATDev *UAT // ESDev holds a 1090 MHz dongle object var ESDev *ES +type Dump1090TermMessage struct { + Text string + Source string +} + func (e *ES) read() { defer e.wg.Done() log.Println("Entered ES read() ...") @@ -100,7 +105,8 @@ func (e *ES) read() { default: n, err := stdout.Read(stdoutBuf) if err == nil && n > 0 { - replayLog(string(stdoutBuf[:n]), MSGCLASS_DUMP1090) + m := Dump1090TermMessage{Text: string(stdoutBuf[:n]), Source: "stdout"} + logDump1090TermMessage(m) } } } @@ -114,7 +120,8 @@ func (e *ES) read() { default: n, err := stderr.Read(stderrBuf) if err == nil && n > 0 { - replayLog(string(stderrBuf[:n]), MSGCLASS_DUMP1090) + m := Dump1090TermMessage{Text: string(stderrBuf[:n]), Source: "stderr"} + logDump1090TermMessage(m) } } } @@ -222,6 +229,9 @@ func (u *UAT) sdrConfig() (err error) { } log.Printf("\tSetTunerGain Successful\n") + tgain := u.dev.GetTunerGain() + log.Printf("\tGetTunerGain: %d\n", tgain) + //---------- Get/Set Sample Rate ---------- err = u.dev.SetSampleRate(SampleRate) if err != nil { @@ -419,6 +429,8 @@ func configDevices(count int, esEnabled, uatEnabled bool) { for i := 0; i < count; i++ { _, _, s, err := rtl.GetDeviceUsbStrings(i) if err == nil { + //FIXME: Trim NULL from the serial. Best done in gortlsdr, but putting this here for now. + s = strings.Trim(s, "\x00") // no need to check if createXDev returned an error; if it // failed to config the error is logged and we can ignore // it here so it doesn't get queued up again diff --git a/main/traffic.go b/main/traffic.go index 51358605..68a40dea 100644 --- a/main/traffic.go +++ b/main/traffic.go @@ -16,7 +16,7 @@ import ( "log" "math" "net" - //"strconv" + "strconv" "strings" "sync" "time" @@ -81,6 +81,7 @@ type TrafficInfo struct { Addr_type uint8 // UAT address qualifier. Used by GDL90 format, so translations for ES TIS-B/ADS-R are needed. TargetType uint8 // types decribed in const above SignalLevel float64 // Signal level, dB RSSI. + Squawk int // Squawk code Position_valid bool // set when position report received. Unset after n seconds? (To-do) Lat float32 // decimal degrees, north positive Lng float32 // decimal degrees, east positive @@ -95,19 +96,22 @@ type TrafficInfo struct { Vvel int16 // feet per minute Timestamp time.Time // timestamp of traffic message, UTC - // Parameters starting at 'Age' are calculated after message receipt. + // Parameters starting at 'Age' are calculated from last message receipt on each call of sendTrafficUpdates(). // Mode S transmits position and track in separate messages, and altitude can also be // received from interrogations. - Age float64 // seconds ago traffic last seen - Last_seen time.Time // time of last position update, relative to Stratux startup. Used for timing out expired data. - Last_alt time.Time // time of last altitude update, relative to Stratux startup - Last_GnssDiff time.Time // time of last GnssDiffFromBaroAlt update, relative to Stratux startup - Last_GnssDiffAlt int32 // altitude at last GnssDiffFromBaroAlt update - Last_speed time.Time // time of last velocity / track update, relative to Stratux startup - Last_source uint8 // last SDR on which this target was observed + Age float64 // Age of last valid position fix, seconds ago. + AgeLastAlt float64 // Age of last altitude message, seconds ago. + Last_seen time.Time // Time of last position update (stratuxClock). Used for timing out expired data. + Last_alt time.Time // Time of last altitude update (stratuxClock). + Last_GnssDiff time.Time // Time of last GnssDiffFromBaroAlt update (stratuxClock). + Last_GnssDiffAlt int32 // Altitude at last GnssDiffFromBaroAlt update. + Last_speed time.Time // Time of last velocity and track update (stratuxClock). + Last_source uint8 // Last frequency on which this target was received. ExtrapolatedPosition bool // TO-DO: True if Stratux is "coasting" the target from last known position. - Bearing float64 // TO-DO: Bearing in degrees true to traffic from ownship - Distance float64 // TO-DO: Distance to traffic from ownship + Bearing float64 // Bearing in degrees true to traffic from ownship, if it can be calculated. + Distance float64 // Distance to traffic from ownship, if it can be calculated. + //FIXME: Some indicator that Bearing and Distance are valid, since they aren't always available. + //FIXME: Rename variables for consistency, especially "Last_". } type dump1090Data struct { @@ -136,10 +140,17 @@ type dump1090Data struct { Timestamp time.Time // time traffic last seen, UTC } +type esmsg struct { + TimeReceived time.Time + Data string +} + var traffic map[uint32]TrafficInfo var trafficMutex *sync.Mutex var seenTraffic map[uint32]bool // Historical list of all ICAO addresses seen. +var OwnshipTrafficInfo TrafficInfo + func cleanupOldEntries() { for icao_addr, ti := range traffic { if stratuxClock.Since(ti.Last_seen) > 60*time.Second { // keep it in the database for up to 60 seconds, so we don't lose tail number, etc... @@ -157,7 +168,16 @@ func sendTrafficUpdates() { log.Printf("List of all aircraft being tracked:\n") log.Printf("==================================================================\n") } + code, _ := strconv.ParseInt(globalSettings.OwnshipModeS, 16, 32) for icao, ti := range traffic { // TO-DO: Limit number of aircraft in traffic message. ForeFlight 7.5 chokes at ~1000-2000 messages depending on iDevice RAM. Practical limit likely around ~500 aircraft without filtering. + if isGPSValid() { + // func distRect(lat1, lon1, lat2, lon2 float64) (dist, bearing, distN, distE float64) { + dist, bearing := distance(float64(mySituation.Lat), float64(mySituation.Lng), float64(ti.Lat), float64(ti.Lng)) + ti.Distance = dist + ti.Bearing = bearing + } + ti.Age = stratuxClock.Since(ti.Last_seen).Seconds() + ti.AgeLastAlt = stratuxClock.Since(ti.Last_alt).Seconds() // DEBUG: Print the list of all tracked targets (with data) to the log every 15 seconds if "DEBUG" option is enabled if globalSettings.DEBUG && (stratuxClock.Time.Second()%15) == 0 { @@ -169,15 +189,21 @@ func sendTrafficUpdates() { } // end of debug block } - ti.Age = stratuxClock.Since(ti.Last_seen).Seconds() - traffic[icao] = ti + traffic[icao] = ti // write the updated ti back to the map //log.Printf("Traffic age of %X is %f seconds\n",icao,ti.Age) if ti.Age > 2 { // if nothing polls an inactive ti, it won't push to the webUI, and its Age won't update. tiJSON, _ := json.Marshal(&ti) trafficUpdate.Send(tiJSON) } if ti.Position_valid && ti.Age < 6 { // ... but don't pass stale data to the EFB. TO-DO: Coast old traffic? Need to determine how FF, WingX, etc deal with stale targets. - msg = append(msg, makeTrafficReportMsg(ti)...) + logTraffic(ti) // only add to the SQLite log if it's not stale + + if ti.Icao_addr == uint32(code) { // + log.Printf("Ownship target detected for code %X\n", code) // DEBUG - REMOVE + OwnshipTrafficInfo = ti + } else { + msg = append(msg, makeTrafficReportMsg(ti)...) + } } } @@ -188,10 +214,12 @@ func sendTrafficUpdates() { // Send update to attached JSON client. func registerTrafficUpdate(ti TrafficInfo) { - if !ti.Position_valid { // Don't send unless a valid position exists. - return - } - + //logTraffic(ti) // moved to sendTrafficUpdates() to reduce SQLite log size + /* + if !ti.Position_valid { // Don't send unless a valid position exists. + return + } + */ // Send all traffic to the websocket and let JS sort it out. This will provide user indication of why they see 1000 ES messages and no traffic. tiJSON, _ := json.Marshal(&ti) trafficUpdate.Send(tiJSON) } @@ -288,7 +316,7 @@ func makeTrafficReportMsg(ti TrafficInfo) []byte { // msg[19] to msg[26] are "call sign" (tail). for i := 0; i < len(ti.Tail) && i < 8; i++ { c := byte(ti.Tail[i]) - if c != 20 && !((c >= 48) && (c <= 57)) && !((c >= 65) && (c <= 90)) && c != 'e' && c != 'u' { // See p.24, FAA ref. + if c != 20 && !((c >= 48) && (c <= 57)) && !((c >= 65) && (c <= 90)) && c != 'e' && c != 'u' && c != 'a' && c != 'r' && c != 't' { // See p.24, FAA ref. c = byte(20) } msg[19+i] = c @@ -346,13 +374,6 @@ func parseDownlinkReport(s string, signalLevel int) { ti.Tail = tail } - if globalSettings.DEBUG { - // This is a hack to show the source of the traffic in ForeFlight. - if len(ti.Tail) == 0 || (len(ti.Tail) != 0 && len(ti.Tail) < 8 && ti.Tail[0] != 'U') { - ti.Tail = "u" + ti.Tail - } - } - // Extract emitter category. if msg_type == 1 || msg_type == 3 { v := (uint16(frame[17]) << 8) | (uint16(frame[18])) @@ -387,6 +408,28 @@ func parseDownlinkReport(s string, signalLevel int) { } } + // This is a hack to show the source of the traffic on moving maps. + if globalSettings.DisplayTrafficSource { + type_code := " " + switch ti.TargetType { + case TARGET_TYPE_ADSB: + type_code = "a" + case TARGET_TYPE_ADSR, TARGET_TYPE_TISB_S: + type_code = "r" + case TARGET_TYPE_TISB: + type_code = "t" + } + + if len(ti.Tail) == 0 { + ti.Tail = "u" + type_code + } else if len(ti.Tail) < 7 && ti.Tail[0] != 'e' && ti.Tail[0] != 'u' { + ti.Tail = "u" + type_code + ti.Tail + } else if len(ti.Tail) == 7 && ti.Tail[0] != 'e' && ti.Tail[0] != 'u' { + ti.Tail = "u" + type_code + ti.Tail[1:] + } else if len(ti.Tail) > 1 { // bounds checking + ti.Tail = "u" + type_code + ti.Tail[2:] + } + } raw_lat := (uint32(frame[4]) << 15) | (uint32(frame[5]) << 7) | (uint32(frame[6]) >> 1) raw_lon := ((uint32(frame[6]) & 0x01) << 23) | (uint32(frame[7]) << 15) | (uint32(frame[8]) << 7) | (uint32(frame[9]) >> 1) @@ -405,10 +448,13 @@ func parseDownlinkReport(s string, signalLevel int) { lng = lng - 360 } } - ti.Lat = lat - ti.Lng = lng ti.Position_valid = position_valid if ti.Position_valid { + ti.Lat = lat + ti.Lng = lng + if isGPSValid() { + ti.Distance, ti.Bearing = distance(float64(mySituation.Lat), float64(mySituation.Lng), float64(ti.Lat), float64(ti.Lng)) + } ti.Last_seen = stratuxClock.Time ti.ExtrapolatedPosition = false } @@ -555,8 +601,18 @@ func esListen() { break } buf = strings.Trim(buf, "\r\n") - //log.Printf("%s\n", buf) - replayLog(buf, MSGCLASS_ES) // Log the raw message to nnnn-ES.log + + // Log the message to the message counter in any case. + var thisMsg msg + thisMsg.MessageClass = MSGCLASS_ES + thisMsg.TimeReceived = stratuxClock.Time + thisMsg.Data = buf + MsgLog = append(MsgLog, thisMsg) + + var eslog esmsg + eslog.TimeReceived = stratuxClock.Time + eslog.Data = buf + logESMsg(eslog) // log raw dump1090:30006 output to SQLite log var newTi *dump1090Data err = json.Unmarshal([]byte(buf), &newTi) @@ -565,18 +621,19 @@ func esListen() { continue } - if (newTi.Icao_addr & 0xFF000000) != 0 { //24-bit overflow is used to signal heartbeat - log.Printf("No traffic last 60 seconds. Heartbeat message from dump1090: %s\n", buf) - continue + if newTi.Icao_addr == 0x07FFFFFF { // used to signal heartbeat + if globalSettings.DEBUG { + log.Printf("No traffic last 60 seconds. Heartbeat message from dump1090: %s\n", buf) + } + continue // don't process heartbeat messages } - // Log the message to the message counter as a valid ES if it unmarshalles. - var thisMsg msg - thisMsg.MessageClass = MSGCLASS_ES - thisMsg.TimeReceived = stratuxClock.Time - thisMsg.Data = []byte(buf) - MsgLog = append(MsgLog, thisMsg) - + if (newTi.Icao_addr & 0x01000000) != 0 { // bit 25 used by dump1090 to signal non-ICAO address + newTi.Icao_addr = newTi.Icao_addr & 0x00FFFFFF + if globalSettings.DEBUG { + log.Printf("Non-ICAO address %X sent by dump1090. This is typical for TIS-B.\n", newTi.Icao_addr) + } + } icao := uint32(newTi.Icao_addr) var ti TrafficInfo @@ -589,8 +646,10 @@ func esListen() { } else { //log.Printf("New target %X created for ES update\n",newTi.Icao_addr) ti.Last_seen = stratuxClock.Time // need to initialize to current stratuxClock so it doesn't get cut before we have a chance to populate a position message + ti.Last_alt = stratuxClock.Time // ditto. ti.Icao_addr = icao ti.ExtrapolatedPosition = false + ti.Last_source = TRAFFIC_SOURCE_1090ES } ti.SignalLevel = 10 * math.Log10(newTi.SignalLevel) @@ -666,6 +725,9 @@ func esListen() { if valid_position { ti.Lat = lat ti.Lng = lng + if isGPSValid() { + ti.Distance, ti.Bearing = distance(float64(mySituation.Lat), float64(mySituation.Lng), float64(ti.Lat), float64(ti.Lng)) + } ti.Position_valid = true ti.ExtrapolatedPosition = false ti.Last_seen = stratuxClock.Time // only update "last seen" data on position updates @@ -754,6 +816,9 @@ func esListen() { ti.Emitter_category = uint8(*newTi.Emitter_category) // validate dump1090 on live traffic } + if newTi.Squawk != nil { + ti.Squawk = int(*newTi.Squawk) // only provided by Mode S messages, so we don't do this in parseUAT. + } // Set the target type. DF=18 messages are sent by ground station, so we look at CA // (repurposed to Control Field in DF18) to determine if it's ADS-R or TIS-B. if newTi.DF == 17 { @@ -776,19 +841,41 @@ func esListen() { ti.OnGround = bool(*newTi.OnGround) } - if newTi.Tail != nil { // DF=17 or DF=18, Type Code 1-4 + if (newTi.Tail != nil) && ((newTi.DF == 17) || (newTi.DF == 18)) { // DF=17 or DF=18, Type Code 1-4 ti.Tail = *newTi.Tail - // This is a hack to show the source of the traffic in ForeFlight. - ti.Tail = strings.Trim(ti.Tail, " ") - if globalSettings.DEBUG { - if len(ti.Tail) == 0 || (len(ti.Tail) != 0 && len(ti.Tail) < 8 && ti.Tail[0] != 'E') { - ti.Tail = "e" + ti.Tail - } + ti.Tail = strings.Trim(ti.Tail, " ") // remove extraneous spaces + } + + // This is a hack to show the source of the traffic on moving maps. + + if globalSettings.DisplayTrafficSource { + type_code := " " + switch ti.TargetType { + case TARGET_TYPE_ADSB: + type_code = "a" + case TARGET_TYPE_ADSR: + type_code = "r" + case TARGET_TYPE_TISB: + type_code = "t" + } + + if len(ti.Tail) == 0 { + ti.Tail = "e" + type_code + } else if len(ti.Tail) < 7 && ti.Tail[0] != 'e' && ti.Tail[0] != 'u' { + ti.Tail = "e" + type_code + ti.Tail + } else if len(ti.Tail) == 7 && ti.Tail[0] != 'e' && ti.Tail[0] != 'u' { + ti.Tail = "e" + type_code + ti.Tail[1:] + } else if len(ti.Tail) > 1 { // bounds checking + ti.Tail = "e" + type_code + ti.Tail[2:] + } } + if newTi.DF == 17 || newTi.DF == 18 { + ti.Last_source = TRAFFIC_SOURCE_1090ES // only update traffic source on ADS-B messages. Prevents source on UAT ADS-B targets with Mode S transponders from "flickering" every time we get an altitude or DF11 update. + } ti.Timestamp = newTi.Timestamp // only update "last seen" data on position updates - ti.Last_source = TRAFFIC_SOURCE_1090ES + /* s_out, err := json.Marshal(ti) if err != nil { @@ -824,6 +911,16 @@ and speed invalid flag is set for headings 135-150 to allow testing of response func updateDemoTraffic(icao uint32, tail string, relAlt float32, gs float64, offset int32) { var ti TrafficInfo + // Retrieve previous information on this ICAO code. + if val, ok := traffic[icao]; ok { // if we've already seen it, copy it in to do updates + ti = val + //log.Printf("Existing target %X imported for ES update\n", icao) + } else { + //log.Printf("New target %X created for ES update\n",newTi.Icao_addr) + ti.Last_seen = stratuxClock.Time // need to initialize to current stratuxClock so it doesn't get cut before we have a chance to populate a position message + ti.Icao_addr = icao + ti.ExtrapolatedPosition = false + } hdg := float64((int32(stratuxClock.Milliseconds/1000)+offset)%720) / 2 // gs := float64(220) // knots radius := gs * 0.2 / (2 * math.Pi) @@ -862,6 +959,9 @@ func updateDemoTraffic(icao uint32, tail string, relAlt float32, gs float64, off ti.Emitter_category = 1 ti.Lat = float32(lat + traffRelLat) ti.Lng = float32(lng + traffRelLng) + + ti.Distance, ti.Bearing = distance(float64(lat), float64(lng), float64(ti.Lat), float64(ti.Lng)) + ti.Position_valid = true ti.ExtrapolatedPosition = false ti.Alt = int32(mySituation.Alt + relAlt) diff --git a/selfupdate/makeupdate.sh b/selfupdate/makeupdate.sh index bb85ac8b..004b931f 100755 --- a/selfupdate/makeupdate.sh +++ b/selfupdate/makeupdate.sh @@ -26,6 +26,10 @@ cp image/config.txt work/bin/ cp image/rtl-sdr-blacklist.conf work/bin/ cp image/bashrc.txt work/bin/ cp image/modules.txt work/bin/ +cp image/stxAliases.txt work/bin/ +cp image/hostapd_manager.sh work/bin/ +cp image/10-stratux.rules work/bin/ +cp image/motd work/bin/ #TODO: librtlsdr. cd work/ diff --git a/selfupdate/process_builds.sh b/selfupdate/process_builds.sh index c665e3f3..7fddd3fd 100755 --- a/selfupdate/process_builds.sh +++ b/selfupdate/process_builds.sh @@ -8,7 +8,11 @@ ssh -i ~/.ssh/id_rsa.updates stratux-updates@updates.stratux.me 'ls -1 queue/' | cd selfupdate ./makeupdate.sh cd .. - scp -i ~/.ssh/id_rsa.updates work/update*.sh stratux-updates@updates.stratux.me:finished/ + for fl in `ls -1 work/update*.sh | cut -d/ -f2` + do + scp -i ~/.ssh/id_rsa.updates work/${fl} stratux-updates@updates.stratux.me:uploading/ + ssh -i ~/.ssh/id_rsa.updates stratux-updates@updates.stratux.me "mv uploading/${fl} finished/" + done cd .. ssh -i ~/.ssh/id_rsa.updates stratux-updates@updates.stratux.me "rm -f queue/${git_hash}" done \ No newline at end of file diff --git a/selfupdate/update_footer.sh b/selfupdate/update_footer.sh index 4b5a95c2..655c707d 100755 --- a/selfupdate/update_footer.sh +++ b/selfupdate/update_footer.sh @@ -11,6 +11,10 @@ ln -fs /etc/init.d/stratux /etc/rc6.d/K01stratux #wifi config cp -f hostapd.conf /etc/hostapd/hostapd.conf +cp -f hostapd-edimax.conf /etc/hostapd/hostapd-edimax.conf + +#WiFi Config Manager +cp -f hostapd_manager.sh /usr/sbin/ #boot config cp -f config.txt /boot/config.txt @@ -18,13 +22,24 @@ cp -f config.txt /boot/config.txt #modprobe.d blacklist cp -f rtl-sdr-blacklist.conf /etc/modprobe.d/ +#udev config +cp -f 10-stratux.rules /etc/udev/rules.d + #go setup cp -f bashrc.txt /root/.bashrc +cp -f stxAliases /root/.stxAliases # /etc/modules cp -f modules.txt /etc/modules +#motd +cp -f motd /etc/motd + cp -f dump1090 /usr/bin/ # Web files install. -cd web/ && make stratuxBuild=${stratuxBuild} \ No newline at end of file +cd web/ && make stratuxBuild=${stratuxBuild} + +# Remove old Wi-Fi watcher script. +rm -f /usr/sbin/wifi_watch.sh +sed -i "/\bwifi_watch\b/d" /etc/rc.local diff --git a/selfupdate/update_header.sh b/selfupdate/update_header.sh index a601ad20..8f5dec58 100755 --- a/selfupdate/update_header.sh +++ b/selfupdate/update_header.sh @@ -3,4 +3,5 @@ rm -rf /root/stratux-update mkdir -p /root/stratux-update cd /root/stratux-update - +mv -f /var/log/stratux.sqlite /var/log/stratux.sqlite.`date +%s` +rm -f /var/log/stratux.sqlite-wal /var/log/stratux.sqlite-shm diff --git a/test/es_dump_csv.go b/test/es_dump_csv.go new file mode 100644 index 00000000..f926640d --- /dev/null +++ b/test/es_dump_csv.go @@ -0,0 +1,134 @@ +package main + +import ( + "database/sql" + "encoding/csv" + "encoding/json" + "fmt" + _ "github.com/mattn/go-sqlite3" + "os" + "time" +) + +type dump1090Data struct { + Icao_addr uint32 + DF int // Mode S downlink format. + CA int // Lowest 3 bits of first byte of Mode S message (DF11 and DF17 capability; DF18 control field, zero for all other DF types) + TypeCode int // Mode S type code + SubtypeCode int // Mode S subtype code + SBS_MsgType int // type of SBS message (used in "old" 1090 parsing) + SignalLevel float64 // Decimal RSSI (0-1 nominal) as reported by dump1090-mutability. Convert to dB RSSI before setting in TrafficInfo. + Tail *string + Squawk *int // 12-bit squawk code in octal format + Emitter_category *int + OnGround *bool + Lat *float32 + Lng *float32 + Position_valid bool + NACp *int + Alt *int + AltIsGNSS bool // + GnssDiffFromBaroAlt *int16 // GNSS height above baro altitude in feet; valid range is -3125 to 3125. +/- 3138 indicates larger difference. + Vvel *int16 + Speed_valid bool + Speed *uint16 + Track *uint16 + Timestamp time.Time // time traffic last seen, UTC +} + +func main() { + if len(os.Args) < 2 { + fmt.Printf("es_dump_csv \n") + return + } + db, err := sql.Open("sqlite3", os.Args[1]) + if err != nil { + fmt.Printf("sql.Open(): %s\n", err.Error()) + return + } + defer db.Close() + rows, err := db.Query("SELECT Data FROM es_messages") + if err != nil { + fmt.Printf("db.Exec(): %s\n", err.Error()) + return + } + defer rows.Close() + + csvOut := make([][]string, 0) + + for rows.Next() { + var Data string + if err := rows.Scan(&Data); err != nil { + fmt.Printf("rows.Scan(): %s\n", err.Error()) + continue + } + var d dump1090Data + err := json.Unmarshal([]byte(Data), &d) + if err != nil { + fmt.Printf("json.Unmarshal(): %s\n", err.Error()) + continue + } + + r := make([]string, 23) + r[0] = fmt.Sprintf("%06x", d.Icao_addr) + r[1] = fmt.Sprintf("%d", d.DF) + r[2] = fmt.Sprintf("%d", d.CA) + r[3] = fmt.Sprintf("%d", d.TypeCode) + r[4] = fmt.Sprintf("%d", d.SubtypeCode) + r[5] = fmt.Sprintf("%d", d.SBS_MsgType) + + r[6] = fmt.Sprintf("%f", d.SignalLevel) + if d.Tail != nil { + r[7] = fmt.Sprintf("%s", *d.Tail) + } + if d.Squawk != nil { + r[8] = fmt.Sprintf("%d", *d.Squawk) + } + if d.Emitter_category != nil { + r[9] = fmt.Sprintf("%d", *d.Emitter_category) + } + if d.OnGround != nil { + r[10] = fmt.Sprintf("%t", *d.OnGround) + } + if d.Lat != nil { + r[11] = fmt.Sprintf("%f", *d.Lat) + } + if d.Lng != nil { + r[12] = fmt.Sprintf("%f", *d.Lng) + } + + r[13] = fmt.Sprintf("%t", d.Position_valid) + if d.NACp != nil { + r[14] = fmt.Sprintf("%d", *d.NACp) + } + if d.Alt != nil { + r[15] = fmt.Sprintf("%d", *d.Alt) + } + + r[16] = fmt.Sprintf("%t", d.AltIsGNSS) + if d.GnssDiffFromBaroAlt != nil { + r[17] = fmt.Sprintf("%d", *d.GnssDiffFromBaroAlt) + } + if d.Vvel != nil { + r[18] = fmt.Sprintf("%d", *d.Vvel) + } + r[19] = fmt.Sprintf("%t", d.Speed_valid) + if d.Speed != nil { + r[20] = fmt.Sprintf("%d", *d.Speed) + } + if d.Track != nil { + r[21] = fmt.Sprintf("%d", *d.Track) + } + r[22] = fmt.Sprintf("%s", d.Timestamp) + + csvOut = append(csvOut, r) + } + + w := csv.NewWriter(os.Stdout) + w.WriteAll(csvOut) + + if err := rows.Err(); err != nil { + fmt.Printf("rows.Scan(): %s\n", err.Error()) + return + } +} diff --git a/test/metar-to-text/mdsplib-code-1/Makefile b/test/metar-to-text/mdsplib-code-1/Makefile new file mode 100644 index 00000000..72c9e793 --- /dev/null +++ b/test/metar-to-text/mdsplib-code-1/Makefile @@ -0,0 +1,3 @@ +all: + gcc -o metar_to_text src/*.c + diff --git a/test/metar-to-text/mdsplib-code-1/include/metar.h b/test/metar-to-text/mdsplib-code-1/include/metar.h new file mode 100644 index 00000000..6075aa4d --- /dev/null +++ b/test/metar-to-text/mdsplib-code-1/include/metar.h @@ -0,0 +1,382 @@ + +/* ref: http://limulus.net/mdsplib */ +/* +METAR Decoder Software Package Library: Parses Aviation Routine Weather Reports +Copyright (C) 2003 Eric McCarthy + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +/********************************************************************/ +/* */ +/* Title: metar.h */ +/* Organization: W/OSO242 - GRAPHICS AND DISPLAY SECTION */ +/* Date: 19 Jan 1996 */ +/* Programmer: CARL MCCALLA */ +/* Language: C/370 */ +/* */ +/* Abstract: METAR Decoder Header File. */ +/* */ +/* Modification History: */ +/* 7 Jul 2001 by Eric McCarthy: Made suitable for */ +/* use as header for the metar.a library. */ +/* */ +/********************************************************************/ + +/* Used in the METAR structs. */ +typedef unsigned short int MDSP_BOOL; + + +/*********************************************/ +/* */ +/* RUNWAY VISUAL RANGE STRUCTURE DECLARATION */ +/* AND VARIABLE TYPE DEFINITION */ +/* */ +/*********************************************/ + +typedef struct runway_VisRange { + char runway_designator[6]; + MDSP_BOOL vrbl_visRange; + MDSP_BOOL below_min_RVR; + MDSP_BOOL above_max_RVR; + int visRange; + int Max_visRange; + int Min_visRange; +} Runway_VisRange; + +/***********************************************/ +/* */ +/* DISPATCH VISUAL RANGE STRUCTURE DECLARATION */ +/* AND VARIABLE TYPE DEFINITION */ +/* */ +/***********************************************/ + +typedef struct dispatch_VisRange { + MDSP_BOOL vrbl_visRange; + MDSP_BOOL below_min_DVR; + MDSP_BOOL above_max_DVR; + int visRange; + int Max_visRange; + int Min_visRange; +} Dispatch_VisRange; + +/*****************************************/ +/* */ +/* CLOUD CONDITION STRUCTURE DECLARATION */ +/* AND VARIABLE TYPE DEFINITION */ +/* */ +/*****************************************/ + +typedef struct cloud_Conditions { + char cloud_type[5]; + char cloud_hgt_char[4]; + char other_cld_phenom[4]; + int cloud_hgt_meters; +} Cloud_Conditions; + +/*****************************************/ +/* */ +/* WIND GROUP DATA STRUCTURE DECLARATION */ +/* AND VARIABLE TYPE DEFINITION */ +/* */ +/*****************************************/ + +typedef struct windstruct { + char windUnits[ 4 ]; + MDSP_BOOL windVRB; + int windDir; + int windSpeed; + int windGust; +} WindStruct; + +/*****************************************/ +/* */ +/* RECENT WX GROUP STRUCTURE DECLARATION */ +/* AND VARIABLE TYPE DEFINITION */ +/* */ +/*****************************************/ + +typedef struct recent_wx { + char Recent_weather[ 5 ]; + int Bhh; + int Bmm; + int Ehh; + int Emm; +} Recent_Wx; + +/***************************************/ +/* */ +/* DECODED METAR STRUCTURE DECLARATION */ +/* AND VARIABLE TYPE DEFINITION */ +/* */ +/***************************************/ + +typedef struct decoded_METAR { + char synoptic_cloud_type[ 6 ]; + char snow_depth_group[ 6 ]; + char codeName[ 6 ]; + char stnid[5]; + char horiz_vsby[5]; + char dir_min_horiz_vsby[3]; + char vsby_Dir[ 3 ]; + char WxObstruct[10][8]; + char autoIndicator[5]; + char VSBY_2ndSite_LOC[10]; + char SKY_2ndSite_LOC[10]; + char SKY_2ndSite[10]; + char SectorVsby_Dir[ 3 ]; + char ObscurAloft[ 12 ]; + char ObscurAloftSkyCond[ 12 ]; + char VrbSkyBelow[ 4 ]; + char VrbSkyAbove[ 4 ]; + char LTG_DIR[ 3 ]; + char CloudLow; + char CloudMedium; + char CloudHigh; + char CIG_2ndSite_LOC[10]; + char VIRGA_DIR[3]; + char TornadicType[15]; + char TornadicLOC[10]; + char TornadicDIR[4]; + char TornadicMovDir[3]; + char CHINO_LOC[6]; + char VISNO_LOC[6]; + char PartialObscurationAmt[2][7]; + char PartialObscurationPhenom[2][12]; + char SfcObscuration[6][10]; + char charPrevailVsby[12]; + char charVertVsby[10]; + char TS_LOC[3]; + char TS_MOVMNT[3]; + + MDSP_BOOL Indeterminant3_6HrPrecip; + MDSP_BOOL Indeterminant_24HrPrecip; + MDSP_BOOL CIGNO; + MDSP_BOOL SLPNO; + MDSP_BOOL ACFTMSHP; + MDSP_BOOL NOSPECI; + MDSP_BOOL FIRST; + MDSP_BOOL LAST; + MDSP_BOOL SunSensorOut; + MDSP_BOOL AUTO; + MDSP_BOOL COR; + MDSP_BOOL NIL_rpt; + MDSP_BOOL CAVOK; + MDSP_BOOL RVRNO; + MDSP_BOOL A_altstng; + MDSP_BOOL Q_altstng; + MDSP_BOOL VIRGA; + MDSP_BOOL VOLCASH; + MDSP_BOOL GR; + MDSP_BOOL CHINO; + MDSP_BOOL VISNO; + MDSP_BOOL PNO; + MDSP_BOOL PWINO; + MDSP_BOOL FZRANO; + MDSP_BOOL TSNO; + MDSP_BOOL DollarSign; + MDSP_BOOL PRESRR; + MDSP_BOOL PRESFR; + MDSP_BOOL Wshft_FROPA; + MDSP_BOOL OCNL_LTG; + MDSP_BOOL FRQ_LTG; + MDSP_BOOL CNS_LTG; + MDSP_BOOL CG_LTG; + MDSP_BOOL IC_LTG; + MDSP_BOOL CC_LTG; + MDSP_BOOL CA_LTG; + MDSP_BOOL DSNT_LTG; + MDSP_BOOL AP_LTG; + MDSP_BOOL VcyStn_LTG; + MDSP_BOOL OVHD_LTG; + MDSP_BOOL LightningVCTS; + MDSP_BOOL LightningTS; + + int TornadicDistance; + int ob_hour; + int ob_minute; + int ob_date; + int minWnDir; + int maxWnDir; + int VertVsby; + int temp; + int dew_pt_temp; + int QFE; + int hectoPasc_altstng; + int char_prestndcy; + int minCeiling; + int maxCeiling; + int WshfTime_hour; + int WshfTime_minute; + int min_vrbl_wind_dir; + int max_vrbl_wind_dir; + int PKWND_dir; + int PKWND_speed; + int PKWND_hour; + int PKWND_minute; + int SKY_2ndSite_Meters; + int Ceiling; + int Estimated_Ceiling; + int SNINCR; + int SNINCR_TotalDepth; + int SunshineDur; + int ObscurAloftHgt; + int VrbSkyLayerHgt; + int Num8thsSkyObscured; + int CIG_2ndSite_Meters; + int snow_depth; + int BTornadicHour; + int BTornadicMinute; + int ETornadicHour; + int ETornadicMinute; + + + float SectorVsby; + float WaterEquivSnow; + float VSBY_2ndSite; + float prevail_vsbySM; + float prevail_vsbyM; + float prevail_vsbyKM; + float prestndcy; + float precip_amt; + float precip_24_amt; + float maxtemp; + float mintemp; + float max24temp; + float min24temp; + float minVsby; + float maxVsby; + float hourlyPrecip; + float TWR_VSBY; + float SFC_VSBY; + float Temp_2_tenths; + float DP_Temp_2_tenths; + float SLP; + float GR_Size; + + double inches_altstng; + + Runway_VisRange RRVR[12]; + Dispatch_VisRange DVR; + Recent_Wx ReWx[3]; + WindStruct winData; + Cloud_Conditions cldTypHgt[6]; + +} Decoded_METAR; + + +/********************************************************************/ +/* */ +/* Title: DcdMETAR */ +/* Organization: W/OSO242 - GRAPHICS AND DISPLAY SECTION */ +/* Date: 14 Sep 1994 */ +/* Programmer: CARL MCCALLA */ +/* Language: C/370 */ +/* */ +/* Abstract: DcdMETAR takes a pointer to a METAR report char- */ +/* acter string as input, decodes the report, and */ +/* puts the individual decoded/parsed groups into */ +/* a structure that has the variable type */ +/* Decoded_METAR. */ +/* */ +/* Input: string - a pointer to a METAR report character */ +/* string. */ +/* */ +/* Output: Mptr - a pointer to a structure that has the */ +/* variable type Decoded_METAR. */ +/* */ +/* Modification History: */ +/* 3 Jul 2001 by Eric McCarthy: Added stringCpy */ +/* so cosnt char *'s could be passed in. */ +/* */ +/********************************************************************/ + +int DcdMETAR( char *string , Decoded_METAR *Mptr ); + + + +/********************************************************************/ +/* */ +/* Title: prtDMETR */ +/* Organization: W/OSO242 - GRAPHICS AND DISPLAY SECTION */ +/* Date: 15 Sep 1994 */ +/* Programmer: CARL MCCALLA */ +/* Language: C/370 */ +/* */ +/* Abstract: prtDMETR prints, in order of the ASOS METAR */ +/* format, all non-initialized members of the structure */ +/* addressed by the Decoded_METAR pointer. */ +/* */ +/* External Functions Called: */ +/* None. */ +/* */ +/* Input: Mptr - ptr to a decoded_METAR structure. */ +/* */ +/* Output: NONE */ +/* */ +/* Modification History: */ +/* None. */ +/* */ +/********************************************************************/ + +void prtDMETR( Decoded_METAR *Mptr ); + + +/********************************************************************/ +/* */ +/* Title: dcdNetMETAR */ +/* Date: 24 Jul 2001 */ +/* Programmer: Eric McCarthy */ +/* Language: C */ +/* */ +/* Abstract: dcdNetMETAR */ +/* The METARs supplied by the NWS server need to */ +/* be reformatted before they can be sent through */ +/* dcdMETAR. This calls dcdMETAR on the correctly */ +/* formated METAR. */ +/* */ +/* Input: a pointer to a METAR string from a NWS server */ +/* */ +/* Output: Mptr - a pointer to a structure that has the */ +/* variable type Decoded_METAR. */ +/* */ +/* Modification History: */ +/* None. */ +/* */ +/********************************************************************/ + +int dcdNetMETAR (char *string, Decoded_METAR *Mptr); + + +/********************************************************************/ +/* */ +/* Title: sprint_metar */ +/* Date: 24 Jul 2001 */ +/* Programmer: Eric McCarthy */ +/* Language: C */ +/* */ +/* Abstract: sprtDMETR */ +/* Does what prtDMETR does, but into a string. */ +/* */ +/* Input: string containing the printout, decoded METAR */ +/* */ +/* Modification History: */ +/* None. */ +/* */ +/********************************************************************/ + +void sprint_metar( char *string, Decoded_METAR *Mptr ); + + diff --git a/test/metar-to-text/mdsplib-code-1/src/antoi.c b/test/metar-to-text/mdsplib-code-1/src/antoi.c new file mode 100644 index 00000000..29d001e8 --- /dev/null +++ b/test/metar-to-text/mdsplib-code-1/src/antoi.c @@ -0,0 +1,92 @@ +/* +METAR Decoder Software Package Library: Parses Aviation Routine Weather Reports +Copyright (C) 2003 Eric McCarthy + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#pragma comment(compiler) +#pragma comment(date) +#pragma comment(timestamp) + +#include + +#pragma title("antoi - char array to integer") +#pragma pagesize (80) + +#pragma page(1) +/********************************************************************/ +/* */ +/* Title: antoi */ +/* Date: Jan 28, 1991 */ +/* Organization: W/OSO242 - Graphics and Display Section */ +/* Programmer: Allan Darling */ +/* Language: C/370 */ +/* */ +/* Abstract: This function will convert a character array */ +/* (string) of length (len) into an integer. */ +/* The integer is created via a call to the */ +/* function atoi. This function extends the */ +/* functionality of atoi by removing the */ +/* requirement for a sentinal delimited string */ +/* as input. */ +/* */ +/* Input: - Pointer to an array of characters. */ +/* - Integer indicating the number of character to include */ +/* in the conversion. */ +/* */ +/* Output:- An integer corresponding to the value in the character */ +/* array or MAXNEG (-2147483648) if the function is */ +/* unable to acquire system storage. */ +/* */ +/* Modification History: */ +/* None */ +/* */ +/********************************************************************/ + +int antoi(char * string, int len) +{ + + /*******************/ + /* local variables */ + /*******************/ + + char * tmpstr; + int i, + retval; + + + /*****************/ + /* function body */ + /*****************/ + + tmpstr = malloc((len+1) * sizeof(char)); + + if (tmpstr == NULL) return (-2147483648); + + for (i = 0; i < len; i++) + tmpstr[i] = string[i]; + + tmpstr[len] = '\0'; + + retval = atoi(tmpstr); + + free(tmpstr); + + return(retval); + +} /* end antoi */ + +#pragma page(1) diff --git a/test/metar-to-text/mdsplib-code-1/src/charcmp.c b/test/metar-to-text/mdsplib-code-1/src/charcmp.c new file mode 100644 index 00000000..6b81f08e --- /dev/null +++ b/test/metar-to-text/mdsplib-code-1/src/charcmp.c @@ -0,0 +1,191 @@ +/* +METAR Decoder Software Package Library: Parses Aviation Routine Weather Reports +Copyright (C) 2003 Eric McCarthy + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#pragma comment (compiler) +#pragma comment (date) +#pragma comment (timestamp) +#pragma pagesize(80) + +#include "local.h" /* standard header file */ + +#pragma subtitle(" ") +#pragma page(1) +#pragma subtitle("charcmp - characters compare with patterns ") +/********************************************************************/ +/* */ +/* Title: charcmp */ +/* Organization: W/OSO242 - GRAPHICS AND DISPLAY SECTION */ +/* Date: 12 Dec 1995 */ +/* Programmer: CINDY L. CHONG */ +/* Language: C/370 */ +/* */ +/* Abstract: This function will compare each character in the */ +/* string match with each character in the pattern */ +/* which is made up of characters. The str can */ +/* be longer than the pattern. */ +/* */ +/* External Functions Called: */ +/* None. */ +/* */ +/* Input: str is a pointer to char */ +/* pattern is a pointer to char */ +/* */ +/* Output: Return true if str matches pattern, */ +/* otherwise, return false */ +/* */ +/* Modification History: */ +/* None. */ +/* */ +/********************************************************************/ +#pragma page(1) + +MDSP_BOOL charcmp(char *str, char *pattern) +{ + + + /**********************************************************/ + /* Loop while str and pattern is not equal to null, then */ + /* inscreases str and pattern by one */ + /**********************************************************/ + + for (; *pattern != '\0'; pattern++) + { + if (*str == '\0') + return FALSE; + + /************************************************************/ + /* If pattern match str, then increase str and jump out the */ + /* case and read next char of the str and pattern */ + /************************************************************/ + + if ( isspace(*pattern) ) + pattern = nxtalpha(pattern); + + switch( *pattern ) + { + case 'c': + if ( !isalnum(*str++) ) + { + return FALSE; + } + break; + + case 'a': + if ( !isalpha(*str) ) + { + return FALSE; + } + str++; + break; + + case 'n': + if ( !iscntrl(*str++) ) + { + return FALSE; + } + break; + + case 'd': + if ( !isdigit(*str) ) + { + return FALSE; + } + str++; + break; + + case 'g': + if ( !isgraph(*str++) ) + { + return FALSE; + } + break; + + case 'i': + if ( !islower(*str++) ) + { + return FALSE; + } + break; + + case 'p': + if ( !isprint(*str++) ) + { + return FALSE; + } + break; + + case 't': + if ( !ispunct(*str++) ) + { + return FALSE; + } + break; + + case 'w': + if ( !isspace(*str++) ) + { + return FALSE; + } + break; + + case 'u': + if ( !isupper(*str++) ) + { + return FALSE; + } + break; + + case 's': + if (*str++ != ' ') + { + return FALSE; + } + break; + + case 'm': + if ( !isspace(*str) ) + { + return FALSE; + } + else + { + while ( isspace(*str) ) + str++; + } + break; + + case '\'': + pattern++; + if (*pattern != *str) + { + return FALSE; + } + pattern++; + str++; + break; + + default: + return FALSE; + + } /* end switch */ + + } /* end for */ + + return (TRUE); +} diff --git a/test/metar-to-text/mdsplib-code-1/src/dcdmetar.c b/test/metar-to-text/mdsplib-code-1/src/dcdmetar.c new file mode 100644 index 00000000..67b661e7 --- /dev/null +++ b/test/metar-to-text/mdsplib-code-1/src/dcdmetar.c @@ -0,0 +1,2792 @@ +/* +METAR Decoder Software Package Library: Parses Aviation Routine Weather Reports +Copyright (C) 2003 Eric McCarthy + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#pragma comment (compiler) +#pragma comment (date) +#pragma comment (timestamp) +#pragma pagesize(80) + + +#include +#include "metar_structs.h" /* standard header file */ + +float fracPart( char * ); +void DcdMTRmk( char **, Decoded_METAR * ); +void prtDMETR( Decoded_METAR * ); + +#pragma page(1) +#pragma subtitle(" ") +#pragma subtitle("subtitle - Decode METAR report. ") +/********************************************************************/ +/* */ +/* Title: SaveTokenString */ +/* Organization: W/OSO242 - GRAPHICS AND DISPLAY SECTION */ +/* Date: 14 Sep 1994 */ +/* Programmer: CARL MCCALLA */ +/* Language: C/370 */ +/* */ +/* Abstract: SaveTokenString tokenizes the input character */ +/* string based upon the delimeter set supplied */ +/* by the calling routine. The elements tokenized */ +/* from the input character string are saved in an */ +/* array of pointers to characters. The address of */ +/* this array is the output from this function. */ +/* */ +/* Input: string - a pointer to a character string. */ +/* */ +/* delimeters - a pointer to a string of 1 or more */ +/* characters that are used for token- */ +/* izing the input character string. */ +/* */ +/* Output: token - the address of a pointer to an array of */ +/* pointers to character strings. The */ +/* array of pointers are the addresses of */ +/* the character strings that are token- */ +/* ized from the input character string. */ +/* */ +/* */ +/* Modification History: */ +/* None. */ +/* */ +/********************************************************************/ +#pragma page(1) + +static char **SaveTokenString ( char *string , char *delimeters ) +{ + + /***************************/ + /* DECLARE LOCAL VARIABLES */ + /***************************/ + + int NDEX; + static char *token[ MAXTOKENS ], + *TOKEN; + + + /*********************************/ + /* BEGIN THE BODY OF THE ROUTINE */ + /*********************************/ + + /******************************************/ + /* TOKENIZE THE INPUT CHARACTER STRING */ + /* AND SAVE THE TOKENS TO THE token ARRAY */ + /******************************************/ + + NDEX = 0; + TOKEN = strtok(string, delimeters); + + if( TOKEN == NULL ) + return NULL; + + token[NDEX] = (char *) malloc(sizeof(char)*(strlen(TOKEN)+1)); + strcpy( token[ NDEX ], TOKEN ); + + + while ( token[NDEX] != NULL ) + { + NDEX++; + TOKEN = strtok(NULL, delimeters); + + if( TOKEN != NULL ) + { + token[NDEX] = (char *) + malloc(sizeof(char)*(strlen(TOKEN)+1)); + strcpy( token[NDEX], TOKEN ); + } + else + token[ NDEX ] = TOKEN; + + } + + + return token; + +} +#pragma page(1) +#pragma subtitle(" ") +#pragma subtitle("subtitle - Decode METAR report. ") +/********************************************************************/ +/* */ +/* Title: freeTokens */ +/* Organization: W/OSO242 - GRAPHICS AND DISPLAY SECTION */ +/* Date: 14 Sep 1994 */ +/* Programmer: CARL MCCALLA */ +/* Language: C/370 */ +/* */ +/* Abstract: freeTokens frees the storage allocated for the */ +/* character strings stored in the token array. */ +/* */ +/* Input: token - the address of a pointer to an array */ +/* of string tokens. */ +/* */ +/* */ +/* Output: None. */ +/* */ +/* */ +/* Modification History: */ +/* None. */ +/* */ +/********************************************************************/ +#pragma page(1) + +static void freeTokens( char **token ) +{ + int NDEX; + + NDEX = 0; + while( *(token+NDEX) != NULL ) + { + free( *(token+NDEX) ); + NDEX++; + } + return; +} +#pragma subtitle(" ") +#pragma page(1) +#pragma subtitle("subtitle - description ") +/********************************************************************/ +/* */ +/* Title: InitDcdMETAR */ +/* Organization: W/OSO242 - GRAPHICS AND DISPLAY SECTION */ +/* Date: 15 Sep 1994 */ +/* Programmer: CARL MCCALLA */ +/* Language: C/370 */ +/* */ +/* Abstract: InitDcdMETAR initializes every member of the */ +/* structure addressed by the pointer Mptr. */ +/* */ +/* External Functions Called: */ +/* None. */ +/* */ +/* Input: Mptr - ptr to a decoded_METAR structure. */ +/* */ +/* Output: NONE */ +/* */ +/* Modification History: */ +/* None. */ +/* */ +/********************************************************************/ +#pragma page(1) +static void InitDcdMETAR( Decoded_METAR *Mptr ) +{ + + /***************************/ + /* DECLARE LOCAL VARIABLES */ + /***************************/ + + + int i, + j; + + + + /*************************/ + /* START BODY OF ROUTINE */ + /*************************/ + + memset(Mptr->TS_LOC,'\0',3); + memset(Mptr->TS_MOVMNT,'\0',3); + + + memset(Mptr->TornadicType,'\0',15); + memset(Mptr->TornadicLOC,'\0',10); + memset(Mptr->TornadicDIR,'\0',4); + memset(Mptr->TornadicMovDir,'\0',3); + Mptr->BTornadicHour = MAXINT; + Mptr->BTornadicMinute = MAXINT; + Mptr->ETornadicHour = MAXINT; + Mptr->ETornadicMinute = MAXINT; + Mptr->TornadicDistance = MAXINT; + + memset( Mptr->autoIndicator,'\0', 5 ); + + Mptr->RVRNO = FALSE; + Mptr->GR = FALSE; + Mptr->GR_Size = (float) MAXINT; + + Mptr->CHINO = FALSE; + memset(Mptr->CHINO_LOC, '\0', 6); + + Mptr->VISNO = FALSE; + memset(Mptr->VISNO_LOC, '\0', 6); + + Mptr->PNO = FALSE; + Mptr->PWINO = FALSE; + Mptr->FZRANO = FALSE; + Mptr->TSNO = FALSE; + Mptr->DollarSign = FALSE; + Mptr->hourlyPrecip = (float) MAXINT; + + Mptr->ObscurAloftHgt = MAXINT; + memset(Mptr->ObscurAloft, '\0', 12); + memset(Mptr->ObscurAloftSkyCond, '\0', 12); + + memset(Mptr->VrbSkyBelow, '\0', 4); + memset(Mptr->VrbSkyAbove, '\0', 4); + Mptr->VrbSkyLayerHgt = MAXINT; + + Mptr->SectorVsby = (float) MAXINT; + memset( Mptr->SectorVsby_Dir, '\0', 3); + + memset(Mptr->codeName, '\0', 6); + memset(Mptr->stnid, '\0', 5); + Mptr->ob_hour = MAXINT; + Mptr->ob_minute = MAXINT; + Mptr->ob_date = MAXINT; + + memset(Mptr->synoptic_cloud_type, '\0', 6); + + Mptr->CloudLow = '\0'; + Mptr->CloudMedium = '\0'; + Mptr->CloudHigh = '\0'; + + memset(Mptr->snow_depth_group, '\0', 6); + Mptr->snow_depth = MAXINT; + + Mptr->Temp_2_tenths = (float) MAXINT; + Mptr->DP_Temp_2_tenths = (float) MAXINT; + + Mptr->OCNL_LTG = FALSE; + Mptr->FRQ_LTG = FALSE; + Mptr->CNS_LTG = FALSE; + Mptr->CG_LTG = FALSE; + Mptr->IC_LTG = FALSE; + Mptr->CC_LTG = FALSE; + Mptr->CA_LTG = FALSE; + Mptr->AP_LTG = FALSE; + Mptr->OVHD_LTG = FALSE; + Mptr->DSNT_LTG = FALSE; + Mptr->VcyStn_LTG = FALSE; + Mptr->LightningVCTS = FALSE; + Mptr->LightningTS = FALSE; + + memset( Mptr->LTG_DIR, '\0', 3); + + + for( i = 0; i < 3; i++) + { + memset(Mptr->ReWx[i].Recent_weather, '\0', 5); + + Mptr->ReWx[i].Bhh = MAXINT; + Mptr->ReWx[i].Bmm = MAXINT; + + Mptr->ReWx[i].Ehh = MAXINT; + Mptr->ReWx[i].Emm = MAXINT; + + } + + Mptr->NIL_rpt = FALSE; + Mptr->AUTO = FALSE; + Mptr->COR = FALSE; + + Mptr->winData.windDir = MAXINT; + Mptr->winData.windSpeed = MAXINT; + Mptr->winData.windGust = MAXINT; + Mptr->winData.windVRB = FALSE; + memset(Mptr->winData.windUnits, '\0', 4); + + Mptr->minWnDir = MAXINT; + Mptr->maxWnDir = MAXINT; + + memset(Mptr->horiz_vsby, '\0', 5); + memset(Mptr->dir_min_horiz_vsby, '\0', 3); + + Mptr->prevail_vsbySM = (float) MAXINT; + Mptr->prevail_vsbyM = (float) MAXINT; + Mptr->prevail_vsbyKM = (float) MAXINT; + + memset(Mptr->vsby_Dir, '\0', 3); + + Mptr->CAVOK = FALSE; + + for ( i = 0; i < 12; i++ ) + { + memset(Mptr->RRVR[ i ].runway_designator, + '\0', 6); + + Mptr->RRVR[ i ].visRange = MAXINT; + + Mptr->RRVR[ i ].vrbl_visRange = FALSE; + Mptr->RRVR[ i ].below_min_RVR = FALSE; + Mptr->RRVR[ i ].above_max_RVR = FALSE; + + + Mptr->RRVR[ i ].Max_visRange = MAXINT; + Mptr->RRVR[ i ].Min_visRange = MAXINT; + } + + Mptr->DVR.visRange = MAXINT; + Mptr->DVR.vrbl_visRange = FALSE; + Mptr->DVR.below_min_DVR = FALSE; + Mptr->DVR.above_max_DVR = FALSE; + Mptr->DVR.Max_visRange = MAXINT; + Mptr->DVR.Min_visRange = MAXINT; + + for ( i = 0; i < MAXWXSYMBOLS; i++ ) + { + for( j = 0; j < 8; j++ ) + Mptr->WxObstruct[i][j] = '\0'; + } + + /***********************/ + /* PARTIAL OBSCURATION */ + /***********************/ + + memset( &(Mptr->PartialObscurationAmt[0][0]), '\0', 7 ); + memset( &(Mptr->PartialObscurationPhenom[0][0]), '\0',12); + + memset( &(Mptr->PartialObscurationAmt[1][0]), '\0', 7 ); + memset( &(Mptr->PartialObscurationPhenom[1][0]), '\0',12); + + + /***************************************************/ + /* CLOUD TYPE, CLOUD LEVEL, AND SIGNIFICANT CLOUDS */ + /***************************************************/ + + + for ( i = 0; i < 6; i++ ) + { + memset(Mptr->cldTypHgt[ i ].cloud_type, + '\0', 5); + + memset(Mptr->cldTypHgt[ i ].cloud_hgt_char, + '\0', 4); + + Mptr->cldTypHgt[ i ].cloud_hgt_meters = MAXINT; + + memset(Mptr->cldTypHgt[ i ].other_cld_phenom, + '\0', 4); + } + + Mptr->VertVsby = MAXINT; + + Mptr->temp = MAXINT; + Mptr->dew_pt_temp = MAXINT; + Mptr->QFE = MAXINT; + + Mptr->SLPNO = FALSE; + Mptr->SLP = (float) MAXINT; + + Mptr->A_altstng = FALSE; + Mptr->inches_altstng = (double) MAXINT; + + Mptr->Q_altstng = FALSE; + Mptr->hectoPasc_altstng = MAXINT; + + Mptr->char_prestndcy = MAXINT; + Mptr->prestndcy = (float) MAXINT; + + Mptr->precip_amt = (float) MAXINT; + + Mptr->precip_24_amt = (float) MAXINT; + Mptr->maxtemp = (float) MAXINT; + Mptr->mintemp = (float) MAXINT; + Mptr->max24temp = (float) MAXINT; + Mptr->min24temp = (float) MAXINT; + + Mptr->VIRGA = FALSE; + memset( Mptr->VIRGA_DIR, '\0', 3 ); + + Mptr->VOLCASH = FALSE; + + Mptr->minCeiling = MAXINT; + Mptr->maxCeiling = MAXINT; + + Mptr->CIG_2ndSite_Meters = MAXINT; + memset(Mptr->CIG_2ndSite_LOC, '\0', 10 ); + + Mptr->minVsby = (float) MAXINT; + Mptr->maxVsby = (float) MAXINT; + Mptr->VSBY_2ndSite = (float) MAXINT; + memset(Mptr->VSBY_2ndSite_LOC,'\0',10); + + for( i = 0; i < 6; i++ ) + memset (&(Mptr->SfcObscuration[i][0]), '\0', 10); + + Mptr->Num8thsSkyObscured = MAXINT; + + Mptr->Indeterminant3_6HrPrecip = FALSE; + Mptr->Indeterminant_24HrPrecip = FALSE; + Mptr->CIGNO = FALSE; + Mptr->Ceiling = MAXINT; + Mptr->Estimated_Ceiling = MAXINT; + + Mptr->NOSPECI = FALSE; + Mptr->LAST = FALSE; + + Mptr->SNINCR = MAXINT; + Mptr->SNINCR_TotalDepth = MAXINT; + + Mptr->WaterEquivSnow = (float) MAXINT; + + Mptr->SunshineDur = MAXINT; + Mptr->SunSensorOut = FALSE; + + + Mptr->WshfTime_hour = MAXINT; + Mptr->WshfTime_minute = MAXINT; + Mptr->Wshft_FROPA = FALSE; + Mptr->min_vrbl_wind_dir = MAXINT; + Mptr->max_vrbl_wind_dir = MAXINT; + + Mptr->PRESRR = FALSE; + Mptr->PRESFR = FALSE; + + Mptr->TWR_VSBY = (float) MAXINT; + Mptr->SFC_VSBY = (float) MAXINT; + + Mptr->PKWND_dir = MAXINT; + Mptr->PKWND_speed = MAXINT; + Mptr->PKWND_hour = MAXINT; + Mptr->PKWND_minute = MAXINT; + + return; + +} +#pragma subtitle(" ") +#pragma page(1) +#pragma subtitle("subtitle - description ") +/********************************************************************/ +/* */ +/* Title: ResetMETARGroup */ +/* Organization: W/OSO242 - GRAPHICS AND DISPLAY SECTION */ +/* Date: 15 Sep 1994 */ +/* Programmer: CARL MCCALLA */ +/* Language: C/370 */ +/* */ +/* Abstract: ResetMETARGroup returns a METAR_obGroup enumerated */ +/* variable that indicates which METAR reporting group */ +/* might next appear in the METAR report and should be */ +/* considered for decoding. */ +/* */ +/* External Functions Called: */ +/* None. */ +/* */ +/* Input: StartGroup - a METAR_obGroup variable that */ +/* indicates where or on what group */ +/* METAR Decoding began. */ +/* */ +/* SaveStartGroup - a METAR_obGroup variable that */ +/* indicates the reporting group */ +/* in the METAR report that was */ +/* successfully decoded. */ +/* */ +/* Output: A METAR_obGroup variable that indicates which */ +/* reporting group in the METAR report should next */ +/* be considered for decoding */ +/* */ +/* Modification History: */ +/* None. */ +/* */ +/********************************************************************/ +#pragma page(1) +static int ResetMETARGroup( int StartGroup, + int SaveStartGroup ) +{ + + enum METAR_obGroup { codename, stnid, NIL1, COR1, obDateTime, NIL2, + AUTO, COR, windData, MinMaxWinDir, + CAVOK, visibility, + RVR, presentWX, skyCond, tempGroup, + altimStng, NotIDed = 99}; + + if( StartGroup == NotIDed && SaveStartGroup == NotIDed ) + return NotIDed; + else if( StartGroup == NotIDed && SaveStartGroup != NotIDed && + SaveStartGroup != altimStng ) + return (++SaveStartGroup); + else + return (++SaveStartGroup); + +} + +#pragma page(1) +#pragma subtitle(" ") +#pragma page(1) +#pragma subtitle("subtitle - description ") +/********************************************************************/ +/* */ +/* Title: CodedHgt2Meters */ +/* Organization: W/OSO242 - GRAPHICS AND DISPLAY SECTION */ +/* Date: 15 Sep 1994 */ +/* Programmer: CARL MCCALLA */ +/* Language: C/370 */ +/* */ +/* Abstract: CodedHgt2Meters converts a coded cloud height into */ +/* meters. */ +/* */ +/* External Functions Called: */ +/* None. */ +/* */ +/* Input: token - a pointer to a METAR report group. */ +/* Mptr - a pointer to a decoded_METAR structure. */ +/* */ +/* Output: Cloud height in meters */ +/* */ +/* Modification History: */ +/* None. */ +/* */ +/********************************************************************/ +#pragma page(1) + +static int CodedHgt2Meters( char *token, Decoded_METAR *Mptr ) +{ + int hgt; + static int maxhgt = 30000; + + + if( (hgt = atoi(token)) == 999 ) + return maxhgt; + else + return (hgt*30); +} + +#pragma page(1) +#pragma subtitle(" ") +#pragma page(1) +#pragma subtitle("subtitle - description ") +/********************************************************************/ +/* */ +/* Title: isPartObscur */ +/* Organization: W/OSO242 - GRAPHICS AND DISPLAY SECTION */ +/* Date: 15 Sep 1994 */ +/* Programmer: CARL MCCALLA */ +/* Language: C/370 */ +/* */ +/* Abstract: isPartObscur determines whether or not the METAR */ +/* report element that is passed to it is or is not */ +/* a partial obscuration indicator for an amount of */ +/* obscuration. */ +/* */ +/* */ +/* External Functions Called: */ +/* None. */ +/* */ +/* Input: token - the address of a pointer to the group */ +/* in the METAR report that isPartObscur */ +/* determines is or is not a partial */ +/* obscuration indicator. */ +/* */ +/* */ +/* Mptr - a pointer to a decoded_METAR structure. */ +/* */ +/* Output: TRUE, if the group is a partial obscuration */ +/* indicator and FALSE, if it is not. */ +/* */ +/* */ +/* Modification History: */ +/* None. */ +/* */ +/********************************************************************/ +#pragma page(1) +static MDSP_BOOL isPartObscur( char **string, Decoded_METAR *Mptr, + int *NDEX ) +{ + + if( *string == NULL ) + return FALSE; + + if( strcmp( *string, "FEW///" ) == 0 || + strcmp( *string, "SCT///" ) == 0 || + strcmp( *string, "BKN///" ) == 0 || + strcmp( *string, "FEW000" ) == 0 || + strcmp( *string, "SCT000" ) == 0 || + strcmp( *string, "BKN000" ) == 0 ) { + strcpy( &(Mptr->PartialObscurationAmt[0][0]), *string ); + (*NDEX)++; + string++; + + if( *string == NULL ) + return TRUE; + + if( strcmp( (*string+3), "///") ) { + if( strcmp( *string, "FEW000" ) == 0 || + strcmp( *string, "SCT000" ) == 0 || + strcmp( *string, "BKN000" ) == 0 ) { + strcpy( &(Mptr->PartialObscurationAmt[1][0]), *string ); + (*NDEX)++; + } + } + else { + if( strcmp( *string, "FEW///" ) == 0 || + strcmp( *string, "SCT///" ) == 0 || + strcmp( *string, "BKN///" ) == 0 ) { + strcpy( &(Mptr->PartialObscurationAmt[1][0]), *string ); + (*NDEX)++; + } + } + return TRUE; + } + else + return FALSE; +} + +#pragma page(1) +#pragma subtitle(" ") +#pragma page(1) +#pragma subtitle("subtitle - description ") +/********************************************************************/ +/* */ +/* Title: isCldLayer */ +/* Organization: W/OSO242 - GRAPHICS AND DISPLAY SECTION */ +/* Date: 15 Sep 1994 */ +/* Programmer: CARL MCCALLA */ +/* Language: C/370 */ +/* */ +/* Abstract: isCldLayer determines whether or not the */ +/* current group has a valid cloud layer */ +/* identifier. */ +/* */ +/* */ +/* External Functions Called: */ +/* None. */ +/* */ +/* Input: token - pointer to a METAR report group. */ +/* */ +/* Output: TRUE, if the report group is a valid cloud */ +/* layer indicator. */ +/* */ +/* FALSE, if the report group is not a valid cloud */ +/* layer indicator. */ +/* */ +/* */ +/* Modification History: */ +/* None. */ +/* */ +/********************************************************************/ +#pragma page(1) + +static MDSP_BOOL isCldLayer( char *token ) +{ + if( token == NULL ) + return FALSE; + + if( strlen(token) < 6 ) + return FALSE; + else + return ((strncmp(token,"OVC",3) == 0 || + strncmp(token,"SCT",3) == 0 || + strncmp(token,"FEW",3) == 0 || + strncmp(token,"BKN",3) == 0 || + (isdigit(*token) && + strncmp(token+1,"CU",2) == 0) || + (isdigit(*token) && + strncmp(token+1,"SC",2) == 0) ) && + nisdigit((token+3),3)) ? TRUE:FALSE; +} + +#pragma page(1) +#pragma subtitle(" ") +#pragma page(1) +#pragma subtitle("subtitle - description ") +/********************************************************************/ +/* */ +/* Title: isCAVOK */ +/* Organization: W/OSO242 - GRAPHICS AND DISPLAY SECTION */ +/* Date: 09 May 1996 */ +/* Programmer: CARL MCCALLA */ +/* Language: C/370 */ +/* */ +/* Abstract: isCAVOK determines whether or not the current */ +/* group is a valid CAVOK indicator. */ +/* */ +/* */ +/* External Functions Called: */ +/* None. */ +/* */ +/* Input: token - pointer to a METAR report group. */ +/* */ +/* Output: TRUE, if the input group is a valid CAVOK */ +/* indicator. FALSE, otherwise. */ +/* */ +/* */ +/* */ +/* Modification History: */ +/* None. */ +/* */ +/********************************************************************/ +#pragma page(1) + +static MDSP_BOOL isCAVOK( char *token, Decoded_METAR *Mptr, int *NDEX ) +{ + + if( token == NULL ) + return FALSE; + + if( strcmp(token, "CAVOK") != 0 ) + return FALSE; + else { + (*NDEX)++; + Mptr->CAVOK = TRUE; + return TRUE; + } +} + +#pragma subtitle(" ") +#pragma page(1) +#pragma subtitle("subtitle - description ") +/********************************************************************/ +/* */ +/* Title: parseCldData */ +/* Organization: W/OSO242 - GRAPHICS AND DISPLAY SECTION */ +/* Date: 15 Sep 1994 */ +/* Programmer: CARL MCCALLA */ +/* Language: C/370 */ +/* */ +/* Abstract: */ +/* */ +/* External Functions Called: */ +/* None. */ +/* */ +/* Input: x */ +/* */ +/* Output: x */ +/* */ +/* Modification History: */ +/* None. */ +/* */ +/********************************************************************/ +#pragma page(1) + +static void parseCldData( char *token, Decoded_METAR *Mptr, int next) +{ + + + if( token == NULL ) + return; + + if( strlen(token) > 6 ) + strncpy(Mptr->cldTypHgt[next].other_cld_phenom,token+6, + (strlen(token)-6)); + + strncpy(Mptr->cldTypHgt[next].cloud_type,token,3); + + strncpy(Mptr->cldTypHgt[next].cloud_hgt_char,token+3,3); + + Mptr->cldTypHgt[next].cloud_hgt_meters = + CodedHgt2Meters( token+3, Mptr ); + + return; +} + + +#pragma subtitle(" ") +#pragma page(1) +#pragma subtitle("subtitle - description ") +/********************************************************************/ +/* */ +/* Title: isSkyCond */ +/* Organization: W/OSO242 - GRAPHICS AND DISPLAY SECTION */ +/* Date: 15 Sep 1994 */ +/* Programmer: CARL MCCALLA */ +/* Language: C/370 */ +/* */ +/* Abstract: */ +/* */ +/* External Functions Called: */ +/* None. */ +/* */ +/* Input: x */ +/* */ +/* Output: x */ +/* */ +/* Modification History: */ +/* None. */ +/* */ +/********************************************************************/ +#pragma page(1) +static MDSP_BOOL isSkyCond( char **skycond, Decoded_METAR *Mptr, + int *NDEX ) +{ + + MDSP_BOOL first_layer, + second_layer, + third_layer, + fourth_layer, + fifth_layer, + sixth_layer; + int next; + + /********************************************************/ + /* INTERROGATE skycond TO DETERMINE IF "CLR" IS PRESENT */ + /********************************************************/ + + if( *skycond == NULL ) + return FALSE; + + + if( strcmp(*skycond,"CLR") == 0) + { + strcpy(Mptr->cldTypHgt[0].cloud_type,"CLR"); +/* + memset(Mptr->cldTypHgt[0].cloud_hgt_char,'\0',1); + memset(Mptr->cldTypHgt[0].other_cld_phenom, + '\0', 1); +*/ + (*NDEX)++; + return TRUE; + } + + /********************************************************/ + /* INTERROGATE skycond TO DETERMINE IF "SKC" IS PRESENT */ + /********************************************************/ + + else if( strcmp(*skycond,"SKC") == 0) + { + strcpy(Mptr->cldTypHgt[0].cloud_type,"SKC"); +/* + memset(Mptr->cldTypHgt[0].cloud_hgt_char,'\0',1); + memset(Mptr->cldTypHgt[0].other_cld_phenom, + '\0', 1); +*/ + (*NDEX)++; + return TRUE; + } + + /****************************************/ + /* INTERROGATE skycond TO DETERMINE IF */ + /* VERTICAL VISIBILITY IS PRESENT */ + /****************************************/ + + else if( strncmp(*skycond,"VV",2) == 0 + && strlen(*skycond) == 5 && + nisdigit((*skycond+2),3) ) + { + Mptr->VertVsby = CodedHgt2Meters( (*skycond+2), Mptr); + strncpy(Mptr->cldTypHgt[0].cloud_type,*skycond,2); + (*NDEX)++; + return TRUE; + } + + /****************************************/ + /* INTERROGATE skycond TO DETERMINE IF */ + /* CLOUD LAYER DATA IS PRESENT */ + /****************************************/ + + else if( isCldLayer( *skycond )) + { + next = 0; + + parseCldData( *skycond , Mptr, next ); + first_layer = TRUE; + next++; + (++skycond); + + if( *skycond == NULL ) + return TRUE; + + second_layer = FALSE; + third_layer = FALSE; + fourth_layer = FALSE; + fifth_layer = FALSE; + sixth_layer = FALSE; + + + if( isCldLayer( *skycond ) && first_layer ) + { + parseCldData( *skycond, Mptr, next ); + second_layer = TRUE; + next++; + (++skycond); + + if( *skycond == NULL ) + return TRUE; + + } + + if( isCldLayer( *skycond ) && first_layer && + second_layer ) + { + parseCldData( *skycond , Mptr, next ); + third_layer = TRUE; + next++; + (++skycond); + + if( *skycond == NULL ) + return TRUE; + + } + + if( isCldLayer( *skycond ) && first_layer && second_layer && + third_layer ) + { + parseCldData( *skycond, Mptr, next ); + fourth_layer = TRUE; + next++; + (++skycond); + + if( *skycond == NULL ) + return TRUE; + + } + + if( isCldLayer( *skycond ) && first_layer && second_layer && + third_layer && fourth_layer ) + { + parseCldData( *skycond , Mptr, next ); + fifth_layer = TRUE; + next++; + (++skycond); + + if( *skycond == NULL ) + return TRUE; + + } + + if( isCldLayer( *skycond ) && first_layer && second_layer && + third_layer && fourth_layer && fifth_layer ) + { + parseCldData( *skycond , Mptr, next ); + sixth_layer = TRUE; + } + + + + if( sixth_layer ) + { + (*NDEX)++; + (*NDEX)++; + (*NDEX)++; + (*NDEX)++; + (*NDEX)++; + (*NDEX)++; + return TRUE; + } + else if( fifth_layer ) + { + (*NDEX)++; + (*NDEX)++; + (*NDEX)++; + (*NDEX)++; + (*NDEX)++; + return TRUE; + } + else if( fourth_layer ) + { + (*NDEX)++; + (*NDEX)++; + (*NDEX)++; + (*NDEX)++; + return TRUE; + } + else if( third_layer ) + { + (*NDEX)++; + (*NDEX)++; + (*NDEX)++; + return TRUE; + } + else if( second_layer ) + { + (*NDEX)++; + (*NDEX)++; + return TRUE; + } + else if( first_layer ) + { + (*NDEX)++; + return TRUE; + } + else + return FALSE; + + } + else + return FALSE; +} +#pragma subtitle(" ") +#pragma page(1) +#pragma subtitle("subtitle - description ") +/********************************************************************/ +/* */ +/* Title: prevailVSBY */ +/* Organization: W/OSO242 - GRAPHICS AND DISPLAY SECTION */ +/* Date: 15 Sep 1994 */ +/* Programmer: CARL MCCALLA */ +/* Language: C/370 */ +/* */ +/* Abstract: */ +/* */ +/* External Functions Called: */ +/* None. */ +/* */ +/* Input: x */ +/* */ +/* Output: x */ +/* */ +/* Modification History: */ +/* None. */ +/* */ +/********************************************************************/ +#pragma page(1) +static float prevailVSBY( char *visibility ) +{ + float Miles_vsby; + char *temp, + *Slash_ptr, + *SM_KM_ptr; + char numerator[3], + denominator[3]; + + + if( (SM_KM_ptr = strstr( visibility, "SM" )) == NULL ) + SM_KM_ptr = strstr(visibility, "KM"); + + Slash_ptr = strchr( visibility, '/' ); + + if( Slash_ptr == NULL ) + { + temp = (char *) malloc(sizeof(char) * + ((SM_KM_ptr - visibility)+1)); + memset( temp, '\0', (SM_KM_ptr-visibility)+1); + strncpy( temp, visibility, (SM_KM_ptr-visibility) ); + Miles_vsby = (float) (atoi(temp)); + free( temp ); + return Miles_vsby; + } + else + { + memset(numerator, '\0', 3); + memset(denominator, '\0', 3); + + strncpy(numerator, visibility, (Slash_ptr - visibility)); + +/*>>>>>>>>>>>>>>>>>>>>>> + if( (SM_KM_ptr - (Slash_ptr+1)) == 0 ) + strcpy(denominator, "4"); + else +<<<<<<<<<<<<<<<<<<<<<<*/ + + strncpy(denominator, + Slash_ptr+1, (SM_KM_ptr - Slash_ptr)); + + return ( ((float)(atoi(numerator)))/ + ((float)(atoi(denominator))) ); + } + +} + +#pragma subtitle(" ") +#pragma page(1) +#pragma subtitle("subtitle - description ") +/********************************************************************/ +/* */ +/* Title: isVisibility */ +/* Organization: W/OSO242 - GRAPHICS AND DISPLAY SECTION */ +/* Date: 15 Sep 1994 */ +/* Programmer: CARL MCCALLA */ +/* Language: C/370 */ +/* */ +/* Abstract: */ +/* */ +/* External Functions Called: */ +/* None. */ +/* */ +/* Input: x */ +/* */ +/* Output: x */ +/* */ +/* Modification History: */ +/* None. */ +/* */ +/********************************************************************/ + +#pragma page(1) + +static MDSP_BOOL isVisibility( char **visblty, Decoded_METAR *Mptr, + int *NDEX ) +{ + char *achar, + *astring, + *save_token; + + + /****************************************/ + /* CHECK FOR VISIBILITY MEASURED <1/4SM */ + /****************************************/ + + if( *visblty == NULL ) + return FALSE; + + + if( strcmp(*visblty,"M1/4SM") == 0 || + strcmp(*visblty,"<1/4SM") == 0 ) { + Mptr->prevail_vsbySM = 0.0; + (*NDEX)++; + return TRUE; + } + + /***********************************************/ + /* CHECK FOR VISIBILITY MEASURED IN KILOMETERS */ + /***********************************************/ + + if( (achar = strstr(*visblty, "KM")) != NULL ) + { + if( nisdigit(*visblty,(achar - *visblty)) && + (achar - *visblty) > 0 ) + { + Mptr->prevail_vsbyKM = prevailVSBY( *visblty ); + (*NDEX)++; + return TRUE; + } + else + return FALSE; + } + + /***********************************/ + /* CHECK FOR VISIBILITY MEASURED */ + /* IN A FRACTION OF A STATUTE MILE */ + /***********************************/ + + else if( (achar = strchr( *visblty, '/' )) != + NULL && + (astring = strstr( *visblty, "SM")) != NULL ) + { + if( nisdigit(*visblty,(achar - *visblty)) + && + (achar - *visblty) > 0 && + (astring - (achar+1)) > 0 && + nisdigit(achar+1, (astring - (achar+1))) ) + { + Mptr->prevail_vsbySM = prevailVSBY (*visblty); + (*NDEX)++; + return TRUE; + } + else + return FALSE; + } + + /***********************************/ + /* CHECK FOR VISIBILITY MEASURED */ + /* IN WHOLE STATUTE MILES */ + /***********************************/ + + else if( (astring = strstr(*visblty,"SM") ) != NULL ) + { + if( nisdigit(*visblty,(astring - *visblty)) && + (astring- *visblty) > 0 ) + { + Mptr->prevail_vsbySM = prevailVSBY (*visblty); + (*NDEX)++; + return TRUE; + } + else + return FALSE; + } + + /***********************************/ + /* CHECK FOR VISIBILITY MEASURED */ + /* IN WHOLE AND FRACTIONAL STATUTE */ + /* MILES */ + /***********************************/ + + else if( nisdigit( *visblty, + strlen(*visblty)) && + strlen(*visblty) < 4 ) + { + save_token = (char *) malloc(sizeof(char)* + (strlen(*visblty)+1)); + strcpy(save_token,*visblty); + if( *(++visblty) == NULL) + { + free( save_token ); + return FALSE; + } + + if( (achar = strchr( *visblty, '/' ) ) != NULL && + (astring = strstr( *visblty, "SM") ) != NULL ) + { + if( nisdigit(*visblty, + (achar - *visblty)) && + (achar - *visblty) > 0 && + (astring - (achar+1)) > 0 && + nisdigit(achar+1, (astring - (achar+1))) ) + { + Mptr->prevail_vsbySM = prevailVSBY (*visblty); + Mptr->prevail_vsbySM += + (float) (atoi(save_token)); + free( save_token); + + (*NDEX)++; + (*NDEX)++; + + return TRUE; + + } + else + return FALSE; + } + else + return FALSE; + + } + + /***********************************/ + /* CHECK FOR VISIBILITY MEASURED */ + /* IN METERS WITH OR WITHOUT DI- */ + /* RECTION OF OBSERVATION */ + /***********************************/ + + else if( nisdigit(*visblty,4) && + strlen(*visblty) >= 4) + { + if( strcmp(*visblty+4,"NE") == 0 ) + { + memset(Mptr->vsby_Dir,'\0',3); + strcpy(Mptr->vsby_Dir,*visblty+4); + } + if( strcmp(*visblty+4,"NW") == 0 ) + { + memset(Mptr->vsby_Dir,'\0',3); + strcpy(Mptr->vsby_Dir,*visblty+4); + } + if( strcmp(*visblty+4,"SE") == 0 ) + { + memset(Mptr->vsby_Dir,'\0',3); + strcpy(Mptr->vsby_Dir,*visblty+4); + } + if( strcmp(*visblty+4,"SW") == 0 ) + { + memset(Mptr->vsby_Dir,'\0',3); + strcpy(Mptr->vsby_Dir,*visblty+4); + } + if( strcmp(*visblty+4,"N") == 0 ) + { + memset(Mptr->vsby_Dir,'\0',3); + strcpy(Mptr->vsby_Dir,*visblty+4); + } + if( strcmp(*visblty+4,"S") == 0 ) + { + memset(Mptr->vsby_Dir,'\0',3); + strcpy(Mptr->vsby_Dir,*visblty+4); + } + if( strcmp(*visblty+4,"E") == 0 ) + { + memset(Mptr->vsby_Dir,'\0',3); + strcpy(Mptr->vsby_Dir,*visblty+4); + } + if( strcmp(*visblty+4,"W") == 0 ) + { + memset(Mptr->vsby_Dir,'\0',3); + strcpy(Mptr->vsby_Dir,*visblty+4); + } + + if( antoi(*visblty, + strlen(*visblty)) >= 50 && + antoi(*visblty, + strlen(*visblty)) <= 500 && + (antoi(*visblty, + strlen(*visblty)) % 50) == 0 ) + { + Mptr->prevail_vsbyM = + (float) (antoi(*visblty, + strlen(*visblty))); + (*NDEX)++; + return TRUE; + } + else if( antoi(*visblty, + strlen(*visblty)) >= 500 && + antoi(*visblty, + strlen(*visblty)) <= 3000 && + (antoi(*visblty, + strlen(*visblty)) % 100) == 0 ) + { + Mptr->prevail_vsbyM = + (float) (antoi(*visblty, + strlen(*visblty))); + (*NDEX)++; + return TRUE; + } + else if( antoi(*visblty, + strlen(*visblty)) >= 3000 && + antoi(*visblty, + strlen(*visblty)) <= 5000 && + (antoi(*visblty, + strlen(*visblty)) % 500) == 0 ) + { + Mptr->prevail_vsbyM = + (float) (antoi(*visblty, + strlen(*visblty))); + (*NDEX)++; + return TRUE; + } + else if( antoi(*visblty, + strlen(*visblty)) >= 5000 && + antoi(*visblty, + strlen(*visblty)) <= 9999 && + (antoi(*visblty, + strlen(*visblty)) % 500) == 0 || + antoi(*visblty, + strlen(*visblty)) == 9999 ) + { + Mptr->prevail_vsbyM = + (float) (antoi(*visblty, + strlen(*visblty))); + (*NDEX)++; + return TRUE; + } + else + return FALSE; + + } + else + return FALSE; + +} + +#pragma subtitle(" ") +#pragma page(1) +#pragma subtitle("subtitle - description ") +/********************************************************************/ +/* */ +/* Title: vrblVsby */ +/* Organization: W/OSO242 - GRAPHICS AND DISPLAY SECTION */ +/* Date: 15 Sep 1994 */ +/* Programmer: CARL MCCALLA */ +/* Language: C/370 */ +/* */ +/* Abstract: */ +/* */ +/* External Functions Called: */ +/* None. */ +/* */ +/* Input: x */ +/* */ +/* Output: x */ +/* */ +/* Modification History: */ +/* None. */ +/* */ +/********************************************************************/ +#pragma page(1) +static MDSP_BOOL vrblVsby( char *string1, char *string2, + Decoded_METAR *Mptr, int *NDEX ) +{ + char buf[ 6 ]; + int numerator, + denominator; + char *slash, + *V_char, + *temp; + + if( string1 == NULL ) + return FALSE; + + V_char = strchr(string1,'V'); + slash = strchr(string1,'/'); + + if(slash == NULL) + { + if(nisdigit(string1,V_char-string1)) + { + memset(buf, '\0', 6); + strncpy(buf, string1, V_char-string1); + + if( Mptr->minVsby != (float) MAXINT ) + Mptr->minVsby += (float) atoi(buf); + else + Mptr->minVsby = (float) atoi(buf); + + memset(buf, '\0', 6); + strncpy(buf, V_char+1, 5); + Mptr->maxVsby = (float) atoi(buf); + + } + else + return FALSE; + } + else + { + temp = (char *) malloc(sizeof(char)*((V_char-string1)+1)); + memset(temp, '\0', (V_char-string1) +1); + strncpy(temp, string1, V_char-string1); + if( Mptr->minVsby != MAXINT ) + Mptr->minVsby += fracPart(temp); + else + Mptr->minVsby = fracPart(temp); + + free( temp ); + + if( strchr(V_char+1,'/') != NULL) + Mptr->maxVsby = fracPart(V_char+1); + else + Mptr->maxVsby = (float) atoi(V_char+1); + } + + if( string2 == NULL ) + return TRUE; + else + { + slash = strchr( string2, '/' ); + + if( slash == NULL ) + return TRUE; + else + { + if( nisdigit(string2,slash-string2) && + nisdigit(slash+1,strlen(slash+1)) ) + { + Mptr->maxVsby += fracPart(string2); + (*NDEX)++; + } + return TRUE; + } + } + +} + + +#pragma subtitle(" ") +#pragma page(1) +#pragma subtitle("subtitle - description ") +/********************************************************************/ +/* */ +/* Title: isMinMaxWinDir */ +/* Organization: W/OSO242 - GRAPHICS AND DISPLAY SECTION */ +/* Date: 15 Sep 1994 */ +/* Programmer: CARL MCCALLA */ +/* Language: C/370 */ +/* */ +/* Abstract: */ +/* */ +/* External Functions Called: */ +/* None. */ +/* */ +/* Input: x */ +/* */ +/* Output: x */ +/* */ +/* Modification History: */ +/* None. */ +/* */ +/********************************************************************/ +#pragma page(1) +static MDSP_BOOL isMinMaxWinDir( char *string, Decoded_METAR *Mptr, + int *NDEX ) +{ +#define buf_len 50 + char buf[ buf_len ]; + char *V_char; + + if( string == NULL ) + return FALSE; + + if( (V_char = strchr(string,'V')) == NULL ) + return FALSE; + else + { + if( nisdigit(string,(V_char - string)) && + nisdigit(V_char+1,3) ) + { + memset( buf, '\0', buf_len); + strncpy( buf, string, V_char - string); + Mptr->minWnDir = atoi( buf ); + + memset( buf, '\0', buf_len); + strcpy( buf, V_char+1 ); + Mptr->maxWnDir = atoi( buf ); + + (*NDEX)++; + return TRUE; + } + else + return FALSE; + } +} +#pragma subtitle(" ") +#pragma page(1) +#pragma subtitle("subtitle - description ") +/********************************************************************/ +/* */ +/* Title: isRVR */ +/* Organization: W/OSO242 - GRAPHICS AND DISPLAY SECTION */ +/* Date: 15 Sep 1994 */ +/* Programmer: CARL MCCALLA */ +/* Language: C/370 */ +/* */ +/* Abstract: */ +/* */ +/* External Functions Called: */ +/* None. */ +/* */ +/* Input: x */ +/* */ +/* Output: x */ +/* */ +/* Modification History: */ +/* None. */ +/* */ +/********************************************************************/ +#pragma page(1) + +static MDSP_BOOL isRVR( char *token, Decoded_METAR *Mptr, int *NDEX, + int ndex ) +{ + char *slashPtr, *FT_ptr; + char *vPtr; + int length; + + if( token == NULL ) + return FALSE; + + if( *token != 'R' || (length = strlen(token)) < 7 || + (slashPtr = strchr(token,'/')) == NULL || + nisdigit(token+1,2) == FALSE ) + return FALSE; + + if( (slashPtr - (token+3)) > 0 ) + if( !nisalpha(token+3,(slashPtr - (token+3))) ) + return FALSE; + + if( strcmp(token+(strlen(token)-2),"FT") != 0 ) + return FALSE; + else + FT_ptr = token + (strlen(token)-2); + + if( strchr(slashPtr+1, 'P' ) != NULL ) + Mptr->RRVR[ndex].above_max_RVR = TRUE; + + if( strchr(slashPtr+1, 'M' ) != NULL ) + Mptr->RRVR[ndex].below_min_RVR = TRUE; + + + strncpy(Mptr->RRVR[ndex].runway_designator, token+1, + (slashPtr-(token+1))); + + if( (vPtr = strchr(slashPtr, 'V' )) != NULL ) + { + Mptr->RRVR[ndex].vrbl_visRange = TRUE; + Mptr->RRVR[ndex].Min_visRange = antoi(slashPtr+1, + (vPtr-(slashPtr+1)) ); + Mptr->RRVR[ndex].Max_visRange = antoi(vPtr+1, + (FT_ptr - (vPtr+1)) ); + (*NDEX)++; + return TRUE; + } + else + { + if( Mptr->RRVR[ndex].below_min_RVR || + Mptr->RRVR[ndex].above_max_RVR ) + Mptr->RRVR[ndex].visRange = antoi(slashPtr+2, + (FT_ptr - (slashPtr+2)) ); + else + Mptr->RRVR[ndex].visRange = antoi(slashPtr+1, + (FT_ptr - (slashPtr+1)) ); + + (*NDEX)++; + return TRUE; + } + +} + + +#pragma subtitle(" ") +#pragma page(1) +#pragma subtitle("subtitle - description ") +/********************************************************************/ +/* */ +/* Title: isAltimStng */ +/* Organization: W/OSO242 - GRAPHICS AND DISPLAY SECTION */ +/* Date: 15 Sep 1994 */ +/* Programmer: CARL MCCALLA */ +/* Language: C/370 */ +/* */ +/* Abstract: */ +/* */ +/* External Functions Called: */ +/* None. */ +/* */ +/* Input: x */ +/* */ +/* Output: x */ +/* */ +/* Modification History: */ +/* None. */ +/* */ +/********************************************************************/ +#pragma page(1) + +static MDSP_BOOL isAltimStng( char *token, Decoded_METAR *Mptr, int *NDEX ) +{ + char dummy[6]; + + + if( token == NULL ) + return FALSE; + + if( strlen(token) < 5 ) + return FALSE; + else + { + Mptr->A_altstng = FALSE; + Mptr->Q_altstng = FALSE; + + if( (*token == 'A' || *token == 'Q') && + (nisdigit(token+1, strlen(token)-1) || + nisdigit(token+1,strlen(token)-3)) ) + { + if( *token == 'A' ) + { + Mptr->A_altstng = TRUE; + Mptr->inches_altstng = atof(token+1) * 0.01; + } + else + { + Mptr->Q_altstng = TRUE; + + if( strchr(token,'.') != NULL) + { + memset(dummy, '\0', 6); + strncpy(dummy,token+1,4); + Mptr->hectoPasc_altstng = atoi(dummy); + } + else + Mptr->hectoPasc_altstng = atoi(token+1); + } + + (*NDEX)++; + return TRUE; + + } + return FALSE; + } +} + + +#pragma subtitle(" ") +#pragma page(1) +#pragma subtitle("subtitle - description ") +/********************************************************************/ +/* */ +/* Title: isTempGroup */ +/* Organization: W/OSO242 - GRAPHICS AND DISPLAY SECTION */ +/* Date: 15 Sep 1994 */ +/* Programmer: CARL MCCALLA */ +/* Language: C/370 */ +/* */ +/* Abstract: */ +/* */ +/* External Functions Called: */ +/* None. */ +/* */ +/* Input: x */ +/* */ +/* Output: x */ +/* */ +/* Modification History: */ +/* None. */ +/* */ +/********************************************************************/ +#pragma page(1) + +static MDSP_BOOL isTempGroup( char *token, Decoded_METAR *Mptr, int *NDEX) +{ + + /***************************/ + /* DECLARE LOCAL VARIABLES */ + /***************************/ + + char *slash; + + if( token == NULL ) + return FALSE; + + if( (slash = strchr(token,'/')) == NULL) + return FALSE; + else + { + if( charcmp(token,"aa'/'dd") ) { + Mptr->dew_pt_temp = atoi(slash+1); + (*NDEX)++; + return TRUE; + } + else if( charcmp(token,"aa'/''M'dd") ) { + Mptr->dew_pt_temp = atoi(slash+2) * -1; + (*NDEX)++; + return TRUE; + } + else if( charcmp(token,"dd'/'aa") ) { + Mptr->temp = antoi(token,(slash-token)); + (*NDEX)++; + return TRUE; + } + else if( charcmp(token,"'M'dd'/'aa") ) { + Mptr->temp = antoi(token+1,(slash-(token+1))) * -1; + (*NDEX)++; + return TRUE; + } + else if( nisdigit(token,(slash-token)) && + nisdigit(slash+1,strlen(slash+1)) ) + { + Mptr->temp = antoi(token,(slash-token)); + Mptr->dew_pt_temp = atoi(slash+1); + (*NDEX)++; + return TRUE; + } + else if( *token == 'M' && nisdigit(token+1,(slash-(token+1))) + && *(slash+1) != '\0' && + *(slash+1) == 'M' && nisdigit(slash+2,strlen(slash+2)) ) + { + Mptr->temp = antoi(token+1,(slash-(token+1))) * -1; + Mptr->dew_pt_temp = atoi(slash+2) * -1; + (*NDEX)++; + return TRUE; + } + else if( *token == 'M' && nisdigit(token+1,(slash-(token+1))) + && *(slash+1) != '\0' && + nisdigit(slash+1,strlen(slash+1)) ) + { + Mptr->temp = antoi(token+1,(slash-(token+1))) * -1; + Mptr->dew_pt_temp = atoi(slash+1); + (*NDEX)++; + return TRUE; + } + else if( nisdigit(token,(slash - token)) && + *(slash+1) != '\0' && + nisdigit(slash+2,strlen(slash+2)) ) + { + Mptr->temp = antoi(token,(slash-token)); + Mptr->dew_pt_temp = atoi(slash+2) * -1; + (*NDEX)++; + return TRUE; + } + else if( nisdigit(token,(slash-token)) && + strlen(token) <= 3) + { + Mptr->temp = antoi(token,(slash-token)); + (*NDEX)++; + return TRUE; + } + else if( *token == 'M' && + nisdigit(token+1,(slash-(token+1))) && + strlen(token) <= 4) + { + Mptr->temp = antoi(token+1,(slash-(token+1))) * -1; + (*NDEX)++; + return TRUE; + } + else + return FALSE; + } + +} + + + +#pragma subtitle(" ") +#pragma page(1) +#pragma subtitle("subtitle - description ") +/********************************************************************/ +/* */ +/* Title: isWxToken */ +/* Organization: W/OSO242 - GRAPHICS AND DISPLAY SECTION */ +/* Date: 15 Sep 1994 */ +/* Programmer: CARL MCCALLA */ +/* Language: C/370 */ +/* */ +/* Abstract: */ +/* */ +/* External Functions Called: */ +/* None. */ +/* */ +/* Input: x */ +/* */ +/* Output: x */ +/* */ +/* Modification History: */ +/* None. */ +/* */ +/********************************************************************/ +#pragma page(1) + +static MDSP_BOOL isWxToken( char *token ) +{ + int i; + + if( token == NULL ) + return FALSE; + for( i = 0; i < strlen(token); i++ ) + { + if( !(isalpha(*(token+i)) || *(token+i) == '+' || + *(token+i) == '-' ) ) + return FALSE; + } + return TRUE; +} + + +#pragma subtitle(" ") +#pragma page(1) +#pragma subtitle("subtitle - description ") +/********************************************************************/ +/* */ +/* Title: isPresentWX */ +/* Organization: W/OSO242 - GRAPHICS AND DISPLAY SECTION */ +/* Date: 15 Sep 1994 */ +/* Programmer: CARL MCCALLA */ +/* Language: C/370 */ +/* */ +/* Abstract: */ +/* */ +/* External Functions Called: */ +/* None. */ +/* */ +/* Input: x */ +/* */ +/* Output: x */ +/* */ +/* Modification History: */ +/* None. */ +/* */ +/********************************************************************/ +#pragma page(1) + +static MDSP_BOOL isPresentWX( char *token, Decoded_METAR *Mptr, + int *NDEX, int *next ) +{ + static char *WxSymbols[] = {"BCFG", "BLDU", "BLSA", "BLPY", + "BLSN", "FZBR", "VCBR", "TSGR", "VCTS", + "DRDU", "DRSA", "DRSN", "FZFG", "FZDZ", "FZRA", + "PRFG", "MIFG", + "SHRA", "SHSN", "SHPE", "SHPL", "SHGS", + "SHGR", + "VCFG", "VCFC", + "VCSS", "VCDS", "TSRA", "TSPE", "TSPL", "TSSN", + "VCSH", "VCPO", "VCBLDU", "VCBLSA", "VCBLSN", + + "BR", "DU", + "DZ", "DS", + "FG", "FC", "FU", "GS", "GR", "HZ", "IC", + "PE", "PL", "PO", "RA", + "SN", "SG", "SQ", "SA", "SS", "TS", + "VA", + "PY", NULL}; + + int i; + char *ptr, + *temp_token, + *save_token, + *temp_token_orig; + + if( token == NULL) + return FALSE; + + temp_token_orig = temp_token = + (char *) malloc(sizeof(char)*(strlen(token)+1)); + strcpy(temp_token, token); + while( temp_token != NULL && (*next) < MAXWXSYMBOLS ) + { + i = 0; + save_token = NULL; + + if( *temp_token == '+' || *temp_token == '-' ) + { + save_token = temp_token; + temp_token++; + } + + while( WxSymbols[i] != NULL ) + if( strncmp(temp_token, WxSymbols[i], + strlen(WxSymbols[i])) != 0 ) + i++; + else + break; + + if( WxSymbols[i] == NULL ) { + free( temp_token_orig ); + return FALSE; + } + else + { + + if( save_token != NULL ) + { + strncpy( Mptr->WxObstruct[*next], save_token, 1); + strcpy( (Mptr->WxObstruct[*next])+1, + WxSymbols[i]); + (*next)++; + } + else + { + strcpy( Mptr->WxObstruct[*next], WxSymbols[i]); + (*next)++; + } + + + if( strcmp(temp_token, WxSymbols[i]) != 0) + { + ptr = strstr(temp_token, WxSymbols[i]); + temp_token = ptr + strlen(WxSymbols[i]); + } + else + { + free( temp_token_orig ); + temp_token = NULL; + (*NDEX)++; + return TRUE; + } + + } + + } + + free( temp_token_orig ); + return FALSE; + +} + +#pragma subtitle(" ") +#pragma page(1) +#pragma subtitle("subtitle - description ") +/********************************************************************/ +/* */ +/* Title: isStnID */ +/* Organization: W/OSO242 - GRAPHICS AND DISPLAY SECTION */ +/* Date: 15 Sep 1994 */ +/* Programmer: CARL MCCALLA */ +/* Language: C/370 */ +/* */ +/* Abstract: */ +/* */ +/* External Functions Called: */ +/* None. */ +/* */ +/* Input: x */ +/* */ +/* Output: x */ +/* */ +/* Modification History: */ +/* None. */ +/* */ +/********************************************************************/ +#pragma page(1) + +static MDSP_BOOL isStnId( char *stnID, Decoded_METAR *Mptr, int *NDEX) +{ + + if( stnID == NULL ) + return FALSE; + +#ifdef CMCPRT + printf("isStnId: stnID = %s\n",stnID); +#endif + + if( strlen(stnID) == 4 ) + { + if( nisalpha(stnID,1) != 0 && nisalnum(stnID+1,3) != 0 ) { + strcpy(Mptr->stnid,stnID); + (*NDEX)++; + return TRUE; + } + else + return FALSE; + } + else + return FALSE; + +} + +#pragma subtitle(" ") +#pragma page(1) +#pragma subtitle("subtitle - description ") +/********************************************************************/ +/* */ +/* Title: isCodeName */ +/* Organization: W/OSO242 - GRAPHICS AND DISPLAY SECTION */ +/* Date: 15 Sep 1994 */ +/* Programmer: CARL MCCALLA */ +/* Language: C/370 */ +/* */ +/* Abstract: */ +/* */ +/* External Functions Called: */ +/* None. */ +/* */ +/* Input: x */ +/* */ +/* Output: x */ +/* */ +/* Modification History: */ +/* None. */ +/* */ +/********************************************************************/ +#pragma page(1) + +static MDSP_BOOL isCodeName( char *codename, Decoded_METAR *Mptr, int *NDEX) +{ + if( codename == NULL ) + return FALSE; + + if( strcmp(codename,"METAR") == 0 || + strcmp(codename,"SPECI") == 0 ) + { + strcpy(Mptr->codeName, codename ); + (*NDEX)++; + return TRUE; + } + else + return FALSE; + +} + + +#pragma subtitle(" ") +#pragma page(1) +#pragma subtitle("subtitle - description ") +/********************************************************************/ +/* */ +/* Title: isNIL */ +/* Organization: W/OSO242 - GRAPHICS AND DISPLAY SECTION */ +/* Date: 15 Sep 1994 */ +/* Programmer: CARL MCCALLA */ +/* Language: C/370 */ +/* */ +/* Abstract: */ +/* */ +/* External Functions Called: */ +/* None. */ +/* */ +/* Input: x */ +/* */ +/* Output: x */ +/* */ +/* Modification History: */ +/* None. */ +/* */ +/********************************************************************/ +#pragma page(1) + +static MDSP_BOOL isNIL( char *token, Decoded_METAR *Mptr, int *NDEX) +{ + + if( token == NULL ) + return FALSE; + + if( strcmp(token, "NIL") == 0 ) + { + Mptr->NIL_rpt = TRUE; + (*NDEX)++; + return TRUE; + } + else + return FALSE; + +} + +#pragma subtitle(" ") +#pragma page(1) +#pragma subtitle("subtitle - description ") +/********************************************************************/ +/* */ +/* Title: isAUTO */ +/* Organization: W/OSO242 - GRAPHICS AND DISPLAY SECTION */ +/* Date: 15 Sep 1994 */ +/* Programmer: CARL MCCALLA */ +/* Language: C/370 */ +/* */ +/* Abstract: */ +/* */ +/* External Functions Called: */ +/* None. */ +/* */ +/* Input: x */ +/* */ +/* Output: x */ +/* */ +/* Modification History: */ +/* None. */ +/* */ +/********************************************************************/ +#pragma page(1) + +static MDSP_BOOL isAUTO( char *token, Decoded_METAR *Mptr, int *NDEX) +{ + + if( token == NULL ) + return FALSE; + + if( strcmp(token, "AUTO") == 0 ) + { + Mptr->AUTO = TRUE; + (*NDEX)++; + return TRUE; + } + else + return FALSE; + +} + +#pragma subtitle(" ") +#pragma page(1) +#pragma subtitle("subtitle - description ") +/********************************************************************/ +/* */ +/* Title: isCOR */ +/* Organization: W/OSO242 - GRAPHICS AND DISPLAY SECTION */ +/* Date: 24 Apr 1996 */ +/* Programmer: CARL MCCALLA */ +/* Language: C/370 */ +/* */ +/* Abstract: */ +/* */ +/* External Functions Called: */ +/* None. */ +/* */ +/* Input: x */ +/* */ +/* Output: x */ +/* */ +/* Modification History: */ +/* None. */ +/* */ +/********************************************************************/ +#pragma page(1) + +static MDSP_BOOL isCOR ( char *token, Decoded_METAR *Mptr, int *NDEX) +{ + + if( token == NULL ) + return FALSE; + + if( strcmp(token, "COR") == 0 ) + { + Mptr->COR = TRUE; + (*NDEX)++; + return TRUE; + } + else + return FALSE; + +} + +#pragma subtitle(" ") +#pragma page(1) +#pragma subtitle("subtitle - description ") +/********************************************************************/ +/* */ +/* Title: isTimeUTC */ +/* Organization: W/OSO242 - GRAPHICS AND DISPLAY SECTION */ +/* Date: 15 Sep 1994 */ +/* Programmer: CARL MCCALLA */ +/* Language: C/370 */ +/* */ +/* Abstract: */ +/* */ +/* External Functions Called: */ +/* None. */ +/* */ +/* Input: x */ +/* */ +/* Output: x */ +/* */ +/* Modification History: */ +/* None. */ +/* */ +/********************************************************************/ +#pragma page(1) + +static MDSP_BOOL isTimeUTC( char *UTC, Decoded_METAR *Mptr, int *NDEX ) +{ + + if( UTC == NULL ) + return FALSE; + + if( strlen( UTC ) == 4 ) { + if(nisdigit(UTC,4) ) { + Mptr->ob_hour = antoi(UTC,2); + Mptr->ob_minute = antoi(UTC+2,2); + (*NDEX)++; + return TRUE; + } + else + return FALSE; + } + else if( strlen( UTC ) == 6 ) { + if(nisdigit(UTC,6) ) { + Mptr->ob_date = antoi(UTC,2); + Mptr->ob_hour = antoi(UTC+2,2); + Mptr->ob_minute = antoi(UTC+4,2); + (*NDEX)++; + return TRUE; + } + else + return FALSE; + } + if( strlen( UTC ) == 5 ) { + if(nisdigit(UTC,4) && (*(UTC+4) == 'Z') ) { + Mptr->ob_hour = antoi(UTC,2); + Mptr->ob_minute = antoi(UTC+2,2); + (*NDEX)++; + return TRUE; + } + else + return FALSE; + } + else if( strlen( UTC ) == 7 ) { + if(nisdigit(UTC,6) && (*(UTC+6) == 'Z') ) { + Mptr->ob_date = antoi(UTC,2); + Mptr->ob_hour = antoi(UTC+2, 2); + Mptr->ob_minute = antoi(UTC+4, 2 ); + (*NDEX)++; + return TRUE; + } + else + return FALSE; + } + else + return FALSE; +} + + +#pragma subtitle(" ") +#pragma page(1) +#pragma subtitle("subtitle - description ") +/********************************************************************/ +/* */ +/* Title: isWindData */ +/* Organization: W/OSO242 - GRAPHICS AND DISPLAY SECTION */ +/* Date: 15 Sep 1994 */ +/* Programmer: CARL MCCALLA */ +/* Language: C/370 */ +/* */ +/* Abstract: */ +/* */ +/* External Functions Called: */ +/* None. */ +/* */ +/* Input: x */ +/* */ +/* Output: x */ +/* */ +/* Modification History: */ +/* None. */ +/* */ +/********************************************************************/ +#pragma page(1) + +static MDSP_BOOL isWindData( char *wind, Decoded_METAR *Mptr, int *NDEX ) +{ + + char *GustPtr, + *unitsPtr; + char dummy[8]; + + if( wind == NULL ) + return FALSE; + + if( strlen(wind) < 7 ) + return FALSE; + + memset(dummy,'\0',8); + + /***************************************/ + /* CHECK FOR WIND SPEED UNITS OF KNOTS */ + /***************************************/ + +/* + if( ( unitsPtr = strstr( wind, "KMH" ) ) != NULL ) + strcpy( dummy, "KMH" ); + else if( (unitsPtr = strstr( wind, "MPS") ) != NULL ) + strcpy( dummy, "MPS" ); +*/ + + if( (unitsPtr = strstr( wind, "KT") ) != NULL ) + strcpy( dummy, "KT" ); + else + return FALSE; + + /*****************************************/ + /* CHECK FOR VARIABLE ("VRB") WIND SPEED */ + /*****************************************/ + + if( charcmp(wind,"'V''R''B'dd'K''T'")) { + Mptr->winData.windVRB = TRUE; + Mptr->winData.windSpeed = antoi(wind+3,2); + memset(Mptr->winData.windUnits, '\0', 4); + strcpy(Mptr->winData.windUnits,"KT"); + (*NDEX)++; +/* +printf("isWindData: Passed VRBddKT test - wind = %s\n",wind); +*/ + return TRUE; + } + + if( charcmp(wind,"'V''R''B'ddd'K''T'")) { + Mptr->winData.windVRB = TRUE; + Mptr->winData.windSpeed = antoi(wind+3,3); + memset(Mptr->winData.windUnits, '\0', 4); + strcpy(Mptr->winData.windUnits,"KT"); + (*NDEX)++; +/* +printf("isWindData: Passed VRBdddKT test - wind = %s\n",wind); +*/ + return TRUE; + } + + if( charcmp(wind,"'V''R''B'ddd'G'ddd'K''T'")) { + Mptr->winData.windVRB = TRUE; + Mptr->winData.windSpeed = antoi(wind+3,3); + Mptr->winData.windGust = antoi(wind+7,3); + + memset(Mptr->winData.windUnits, '\0', 4); + strcpy(Mptr->winData.windUnits,"KT"); + (*NDEX)++; + return TRUE; + } + + if( charcmp(wind,"'V''R''B'dd'G'dd'K''T'")) { + Mptr->winData.windVRB = TRUE; + Mptr->winData.windSpeed = antoi(wind+3,2); + Mptr->winData.windGust = antoi(wind+6,2); + + memset(Mptr->winData.windUnits, '\0', 4); + strcpy(Mptr->winData.windUnits,"KT"); + (*NDEX)++; + return TRUE; + } + + if( charcmp(wind,"'V''R''B'dd'G'ddd'K''T'")) { + Mptr->winData.windVRB = TRUE; + Mptr->winData.windSpeed = antoi(wind+3,2); + Mptr->winData.windGust = antoi(wind+6,3); + + memset(Mptr->winData.windUnits, '\0', 4); + strcpy(Mptr->winData.windUnits,"KT"); + (*NDEX)++; + return TRUE; + } + + /************************/ + /* CHECK FOR WIND GUSTS */ + /************************/ + + if( (GustPtr = strchr( wind, 'G' )) != NULL ) + { +/* +printf("isWindData: Passed 1st GUST test - wind = %s\n",wind); +*/ + if( nisdigit(wind,(GustPtr-wind)) && + nisdigit(GustPtr+1,(unitsPtr-(GustPtr+1))) && + ((GustPtr-wind) >= 5 && (GustPtr-wind) <= 6) && + ((unitsPtr-(GustPtr+1)) >= 2 && + (unitsPtr-(GustPtr+1)) <= 3) ) + { + Mptr->winData.windDir = antoi(wind,3); + + Mptr->winData.windSpeed = antoi(wind+3, (GustPtr-(wind+3))); + Mptr->winData.windGust = antoi(GustPtr+1,(unitsPtr- + (GustPtr+1))); + strcpy( Mptr->winData.windUnits, dummy ); +/* +printf("isWindData: Passed 2nd GUST test - wind = %s\n",wind); +*/ + (*NDEX)++; + return TRUE; + } + else { +/* +printf("isWindData: Failed 2nd GUST test - wind = %s\n",wind); +*/ + return FALSE; + } + } + else if( nisdigit(wind,(unitsPtr-wind)) && + ((unitsPtr-wind) >= 5 && (unitsPtr-wind) <= 6) ) + { + Mptr->winData.windDir = antoi(wind, 3); + + Mptr->winData.windSpeed = antoi(wind+3,(unitsPtr-(wind+3))); + strcpy( Mptr->winData.windUnits, dummy ); + (*NDEX)++; +/* +printf("isWindData: Passed dddff(f) test - wind = %s\n",wind); +*/ + return TRUE; + } + else + return FALSE; + +} +#pragma page(1) +#pragma subtitle(" ") +#pragma subtitle("subtitle - Decode METAR report. ") +/********************************************************************/ +/* */ +/* Title: DcdMETAR */ +/* Organization: W/OSO242 - GRAPHICS AND DISPLAY SECTION */ +/* Date: 14 Sep 1994 */ +/* Programmer: CARL MCCALLA */ +/* Language: C/370 */ +/* */ +/* Abstract: DcdMETAR takes a pointer to a METAR report char- */ +/* acter string as input, decodes the report, and */ +/* puts the individual decoded/parsed groups into */ +/* a structure that has the variable type */ +/* Decoded_METAR. */ +/* */ +/* Input: string - a pointer to a METAR report character */ +/* string. */ +/* */ +/* Output: Mptr - a pointer to a structure that has the */ +/* variable type Decoded_METAR. */ +/* */ +/* Modification History: */ +/* 3 Jul 2001 by Eric McCarthy: Added stringCpy */ +/* so const char *'s could be passed in. */ +/* */ +/********************************************************************/ +#pragma page(1) + + +int DcdMETAR( char *string , Decoded_METAR *Mptr ) +{ + + /***************************/ + /* DECLARE LOCAL VARIABLES */ + /***************************/ + + + enum METAR_obGroup { codename, stnid, NIL1, COR1, obDateTime, NIL2, + AUTO, COR, windData, MinMaxWinDir, + CAVOK, visibility, + RVR, presentWX, PartialObscur, + skyCond, tempGroup, + altimStng, NotIDed = 99} StartGroup, + SaveStartGroup, + MetarGroup; + + WindStruct *WinDataPtr; + + int ndex, + NDEX, + i, + jkj, + j; + + + char **token, + *delimeters = {" "}, + *stringCpy; + + MDSP_BOOL IS_NOT_RMKS; + +/*********************************/ +/* BEGIN THE BODY OF THE ROUTINE */ +/*********************************/ + + /********************************************************/ + /* ONLY PARSE OR DECOCODE NON-NULL METAR REPORT STRINGS */ + /********************************************************/ + + if( string == NULL ) + return 8; + + + /*****************************************/ + /* INITIALIZE STRUCTURE THAT HAS THE */ + /* VARIABLE TYPE Decoded_METAR */ + /*****************************************/ + + InitDcdMETAR( Mptr ); + +#ifdef DEBUGZZ + printf("DcdMETAR: Returned from InitDcdMETAR\n"); +#endif + + /* Copy the string since it may be const, and functions + * strtok() don't like that. + */ + + stringCpy = calloc(strlen(string) + 1, sizeof(char)); + strcpy(stringCpy, string); + + + /****************************************************/ + /* TOKENIZE AND STORE THE INPUT METAR REPORT STRING */ + /****************************************************/ +#ifdef DEBUGZZ + printf("DcdMETAR: Before start of tokenizing, string = %s\n", + stringCpy); +#endif + + token = SaveTokenString( stringCpy, delimeters ); + + + + /*********************************************************/ + /* DECODE THE METAR REPORT (POSITIONAL ORDER PRECEDENCE) */ + /*********************************************************/ + + NDEX = 0; + MetarGroup = codename; + IS_NOT_RMKS = TRUE; + +#ifdef DEBUGZZ +printf("DcdMETAR: token[0] = %s\n",token[0]); +#endif + + while( token[NDEX] != NULL && IS_NOT_RMKS ) { + +#ifdef DEBUGZZ +if( strcmp(token[0],"OPKC") == 0 || strcmp(token[0],"TAPA") == 0 ) { + printf("DcdMETAR: token[%d] = %s\n",NDEX,token[NDEX]); + printf("DcdMETAR: Token[%d] = %s\n",NDEX,token[NDEX]); + printf("DcdMETAR: MetarGroup = %d\n",MetarGroup); +} +#endif + + if( strcmp( token[NDEX], "RMK" ) != 0 ) { + + StartGroup = NotIDed; + +#ifdef DEBUGZZ +if( strcmp(token[0],"OPKC") == 0 || strcmp(token[0],"TAPA") == 0 ) { + printf("DcdMETAR: StartGroup = %d\n",StartGroup); + printf("DcdMETAR: SaveStartGroup = %d\n",SaveStartGroup); +} +#endif + + /**********************************************/ + /* SET ID_break_CODE TO ITS DEFAULT VALUE OF */ + /* 99, WHICH MEANS THAT NO SUCCESSFUL ATTEMPT */ + /* WAS MADE TO DECODE ANY METAR CODED GROUP */ + /* FOR THIS PASS THROUGH THE DECODING LOOP */ + /**********************************************/ + switch( MetarGroup ) { + case( codename ): + if( isCodeName( token[NDEX], Mptr, &NDEX ) ) + SaveStartGroup = StartGroup = codename; + MetarGroup = stnid; + break; + case( stnid ): + if( isStnId( token[NDEX], Mptr, &NDEX ) ) { + SaveStartGroup = StartGroup = stnid; + MetarGroup = NIL1; + } + else { +#ifdef DEBUGZX +printf("DcdMETAR: token[%d] = %s\n",NDEX,token[NDEX]); +#endif + freeTokens( token ); + return 12; + } + break; + case( NIL1 ): + if( isNIL( token[NDEX], Mptr, &NDEX ) ) + SaveStartGroup = StartGroup = NIL1; + MetarGroup = COR1; + break; + case( COR1 ): + if( isCOR( token[NDEX], Mptr, &NDEX ) ) + SaveStartGroup = StartGroup = COR1; + MetarGroup = obDateTime; + break; + case( obDateTime ): + if( isTimeUTC( token[NDEX], Mptr, &NDEX ) ) + SaveStartGroup = StartGroup = obDateTime; + MetarGroup = NIL2; + break; + case( NIL2 ): + if( isNIL( token[NDEX], Mptr, &NDEX ) ) + SaveStartGroup = StartGroup = NIL2; + MetarGroup = AUTO; + break; + case( AUTO ): + if( isAUTO( token[NDEX], Mptr, &NDEX ) ) + SaveStartGroup = StartGroup = AUTO; + MetarGroup = COR; + break; + case( COR ): + if( isCOR( token[NDEX], Mptr, &NDEX ) ) + SaveStartGroup = StartGroup = COR; + MetarGroup = windData; + break; + case( windData ): + if( isWindData( token[NDEX], Mptr, &NDEX ) ) + SaveStartGroup = StartGroup = windData; + MetarGroup = MinMaxWinDir; + break; + case( MinMaxWinDir ): + if( isMinMaxWinDir( token[NDEX], Mptr, &NDEX ) ) + SaveStartGroup = StartGroup = MinMaxWinDir; + MetarGroup = CAVOK; + break; + case( CAVOK ): + if( isCAVOK( token[NDEX], Mptr, &NDEX ) ) + SaveStartGroup = StartGroup = CAVOK; + MetarGroup = visibility; + break; + case( visibility ): + if( isVisibility( &(token[NDEX]), Mptr, &NDEX ) ) + SaveStartGroup = StartGroup = visibility; + MetarGroup = RVR; + break; + case( RVR ): + ndex = 0; + MetarGroup = presentWX; + + while (isRVR( token[NDEX], Mptr, &NDEX, ndex ) && + ndex < 12 ) { + ndex++; + SaveStartGroup = StartGroup = RVR; + MetarGroup = presentWX; + } + break; + case( presentWX ): + ndex = 0; + MetarGroup = skyCond; + + while( isPresentWX( token[NDEX], Mptr, &NDEX, + &ndex ) && ndex < MAXWXSYMBOLS) { + SaveStartGroup = StartGroup = presentWX; + MetarGroup = PartialObscur; + } + break; + case( PartialObscur ): + if( isPartObscur( &(token[NDEX]), Mptr, &NDEX ) ) + SaveStartGroup = StartGroup = PartialObscur; + MetarGroup = skyCond; + break; + case( skyCond ): + if( isSkyCond( &(token[NDEX]), Mptr, &NDEX ) ) + SaveStartGroup = StartGroup = skyCond; + MetarGroup = tempGroup; + break; + case( tempGroup ): + if( isTempGroup( token[NDEX], Mptr, &NDEX ) ) + SaveStartGroup = StartGroup = tempGroup; + MetarGroup = altimStng; + break; + case( altimStng ): + if( isAltimStng( token[NDEX], Mptr, &NDEX ) ) + SaveStartGroup = StartGroup = altimStng; + MetarGroup = NotIDed; + break; + default: + NDEX++; +/* MetarGroup = SaveStartGroup; */ + MetarGroup = ResetMETARGroup( StartGroup, + SaveStartGroup ); + break; + } + } + else + IS_NOT_RMKS = FALSE; + + } + + +#ifdef DEBUGZZ +if( strcmp(token[0],"OPKC") == 0 || strcmp(token[0],"TAPA") == 0 ) { + printf("DcdMETAR: while loop exited, Token[%d] = %s\n", + NDEX,token[NDEX]); +} +#endif + /******************************/ + /* DECODE GROUPS FOUND IN THE */ + /* REMARKS SECTION OF THE */ + /* METAR REPORT */ + /******************************/ +#ifdef PRTMETAR +printf("DCDMETAR: Print DECODED METAR, before leaving " + "DCDMETAR Routine, but before possible call to DcdMTRmk\n\n"); +prtDMETR( Mptr ); +#endif + + if( token[NDEX] != NULL ) + if( strcmp( token[NDEX], "RMK" ) == 0 ) + DcdMTRmk( token, Mptr ); + +#ifdef PRTMETAR +printf("DCDMETAR: Print DECODED METAR, after possible DcdMTRmk " + "call\n\n"); +prtDMETR( Mptr ); +#endif + + /****************************************/ + freeTokens( token ); /* FREE THE STORAGE ALLOCATED FOR THE */ + /* ARRAY USED TO HOLD THE METAR REPORT */ + /* GROUPS */ + /****************************************/ + free(stringCpy); + + return 0; + +} + + + + +/********************************************************************/ +/* */ +/* Title: dcdNetMETAR */ +/* Date: 24 Jul 2001 */ +/* Programmer: Eric McCarthy */ +/* Language: C/370 */ +/* */ +/* Abstract: dcdNetMETAR */ +/* The METARs supplied by the NWS server need to */ +/* be reformatted before they can be sent through */ +/* dcdMETAR. This calls dcdMETAR on the correctly */ +/* formated METAR. */ +/* */ +/* Input: a pointer to a METAR string from a NWS server */ +/* */ +/* Output: Mptr - a pointer to a structure that has the */ +/* variable type Decoded_METAR. */ +/* */ +/* Modification History: */ +/* None. */ +/* */ +/********************************************************************/ + + +int dcdNetMETAR (char *string, Decoded_METAR *Mptr) +{ + char *string_cpy, *ptr; + int result; + + /* Strip the date, which is the first line. */ + while (*string != '\n') + { + ++string; + } + ++string; + + /* make a copy of the string without the date */ + string_cpy = (char *) calloc(strlen(string), sizeof(char)); + strcpy(string_cpy, string); + + /* replace all carrage returns with spaces */ + ptr = string_cpy; + while (*ptr != '\0') + { + if (*ptr == '\n') + *ptr = ' '; + ++ptr; + } + + /* decode the METAR */ + result = DcdMETAR(string_cpy, Mptr); + + free(string_cpy); + return result; +} + + diff --git a/test/metar-to-text/mdsplib-code-1/src/dcdmtrmk.c b/test/metar-to-text/mdsplib-code-1/src/dcdmtrmk.c new file mode 100644 index 00000000..0d2e0182 --- /dev/null +++ b/test/metar-to-text/mdsplib-code-1/src/dcdmtrmk.c @@ -0,0 +1,5372 @@ +/* +METAR Decoder Software Package Library: Parses Aviation Routine Weather Reports +Copyright (C) 2003 Eric McCarthy + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "metar_structs.h" + +#define SKY1_len 50 +float fracPart( char * ); + +#pragma subtitle(" ") +#pragma page(1) +#pragma subtitle("subtitle - description ") +/********************************************************************/ +/* */ +/* Title: isTS_LOC */ +/* Organization: W/OSO242 - GRAPHICS AND DISPLAY SECTION */ +/* Date: 06 May 1996 */ +/* Programmer: CARL MCCALLA */ +/* Language: C/370 */ +/* */ +/* Abstract: Identify the input character string as a thunder- */ +/* storm location. If the input string is a thunder- */ +/* storm location, then return TRUE. Otherwise, */ +/* return FALSE. */ +/* */ +/* */ +/* External Functions Called: */ +/* None. */ +/* */ +/* Input: string - a pointer to a pointer to a charac- */ +/* ter string from a METAR report. */ +/* */ +/* Mptr - a pointer to a structure that has the */ +/* data type Decoded_METAR. */ +/* */ +/* NDEX - a pointer to an integer that is the index */ +/* into an array that contains the indi- */ +/* vidual groups of the METAR report being */ +/* decoded. Upon entry, NDEX is the index */ +/* of the current group of the METAR report */ +/* that is to be indentified. */ +/* */ +/* Output: TRUE - if the input string is a thunderstorm */ +/* location. */ +/* FALSE - the input string is not a thunderstorm */ +/* location. */ +/* */ +/* Modification History: */ +/* None. */ +/* */ +/********************************************************************/ +#pragma page(1) + +static MDSP_BOOL isTS_LOC( char **string, Decoded_METAR *Mptr, + int *NDEX ) +{ + + /***************************/ + /* DECLARE LOCAL VARIABLES */ + /***************************/ + + int i; + + /*************************/ + /* START BODY OF ROUTINE */ + /*************************/ + /*******************************************/ + /* COMPARE THE INPUT CHARACTER STRING WITH */ + /* VALID AUTOMATED STATION CODE TYPE. IF */ + /* A MATCH IS FOUND, RETURN TRUE. OTHER- */ + /* WISE, RETURN FALSE */ + /*******************************************/ + + if( *string == NULL ) + return FALSE; + + i = 0; + + if( strcmp( *string, "TS") != 0 ) + return FALSE; + else { + string++; + + if( *string == NULL ) + return FALSE; + + if( strcmp(*string,"N") == 0 || + strcmp(*string,"NE") == 0 || + strcmp(*string,"NW") == 0 || + strcmp(*string,"S") == 0 || + strcmp(*string,"SE") == 0 || + strcmp(*string,"SW") == 0 || + strcmp(*string,"E") == 0 || + strcmp(*string,"W") == 0 ) { + strcpy( Mptr->TS_LOC, *string ); + (*NDEX)++; + (*NDEX)++; + string++; + + if( *string == NULL ) + return TRUE; + + if( strcmp( *string, "MOV" ) == 0 ) { + string++; + + if( *string == NULL ) { + (*NDEX)++; + return TRUE; + } + + if( strcmp(*string,"N") == 0 || + strcmp(*string,"NE") == 0 || + strcmp(*string,"NW") == 0 || + strcmp(*string,"S") == 0 || + strcmp(*string,"SE") == 0 || + strcmp(*string,"SW") == 0 || + strcmp(*string,"E") == 0 || + strcmp(*string,"W") == 0 ) { + strcpy( Mptr->TS_MOVMNT, *string ); + (*NDEX)++; + (*NDEX)++; + string++; + return TRUE; + } + } + else + return TRUE; + + } + else + return FALSE; + + } + +} + +#pragma subtitle(" ") +#pragma page(1) +#pragma subtitle("subtitle - description ") +/********************************************************************/ +/* */ +/* Title: isDVR */ +/* Organization: W/OSO242 - GRAPHICS AND DISPLAY SECTION */ +/* Date: 15 Sep 1994 */ +/* Programmer: CARL MCCALLA */ +/* Language: C/370 */ +/* */ +/* Abstract: */ +/* */ +/* External Functions Called: */ +/* None. */ +/* */ +/* Input: x */ +/* */ +/* Output: x */ +/* */ +/* Modification History: */ +/* None. */ +/* */ +/********************************************************************/ +#pragma page(1) + +static MDSP_BOOL isDVR( char *token, Decoded_METAR *Mptr, int *NDEX ) +{ + char *slashPtr, *FT_ptr; + char *vPtr; + int length; + + if( token == NULL ) + return FALSE; + + if( (length = strlen( token )) < 4 ) + return FALSE; + + if( strncmp( token, "DVR", 3 ) != 0 ) + return FALSE; + + if( *(slashPtr = token+3) != '/' ) { + (*NDEX)++; + return FALSE; + } + + if( strcmp(token+(strlen(token)-2),"FT") != 0 ) + return FALSE; + else + FT_ptr = token + (strlen(token)-2); + + if( strchr(slashPtr+1, 'P' ) != NULL ) + Mptr->DVR.above_max_DVR = TRUE; + + if( strchr(slashPtr+1, 'M' ) != NULL ) + Mptr->DVR.below_min_DVR = TRUE; + + + if( (vPtr = strchr(slashPtr, 'V' )) != NULL ) + { + Mptr->DVR.vrbl_visRange = TRUE; + Mptr->DVR.Min_visRange = antoi(slashPtr+1, + (vPtr-(slashPtr+1)) ); + Mptr->DVR.Max_visRange = antoi(vPtr+1, + (FT_ptr - (vPtr+1)) ); + (*NDEX)++; + return TRUE; + } + else + { + if( Mptr->DVR.below_min_DVR || + Mptr->DVR.above_max_DVR ) + Mptr->DVR.visRange = antoi(slashPtr+2, + (FT_ptr - (slashPtr+2)) ); + else + Mptr->DVR.visRange = antoi(slashPtr+1, + (FT_ptr - (slashPtr+1)) ); + + (*NDEX)++; + return TRUE; + } + +} +#pragma subtitle(" ") +#pragma page(1) +#pragma subtitle("subtitle - description ") +/********************************************************************/ +/* */ +/* Title: isRADAT */ +/* Organization: W/OSO242 - GRAPHICS AND DISPLAY SECTION */ +/* Date: 07 Nov 1996 */ +/* Programmer: CARL MCCALLA */ +/* Language: C/370 */ +/* */ +/* Abstract: Determines whether or not the input string is */ +/* the 'RADAT' group elevation indicator. If it is, */ +/* then skip past the 'RADAT' indicator and also the */ +/* next group which is the RADAT elevation informa- */ +/* tion. */ +/* */ +/* */ +/* External Functions Called: */ +/* None. */ +/* */ +/* Input: string - the address of a pointer to a charac- */ +/* ter string that may or may not be the */ +/* RADAT group. */ +/* */ +/* Mptr - a pointer to a structure that has the */ +/* data type Decoded_METAR. */ +/* */ +/* NDEX - a pointer to an integer that is the index */ +/* into an array that contains the indi- */ +/* vidual groups of the METAR report being */ +/* decoded. Upon entry, NDEX is the index */ +/* of the current group of the METAR report */ +/* that is to be indentified. */ +/* */ +/* Output: TRUE - if a RADAT group is found. */ +/* FALSE - if no RADAT group is found. */ +/* */ +/* Modification History: */ +/* None. */ +/* */ +/********************************************************************/ +#pragma page(1) + +static MDSP_BOOL isRADAT( char **string, Decoded_METAR *Mptr, + int *NDEX ) +{ + if( strcmp( *string, "RADAT" ) != 0 ) + return FALSE; + else { + + (*NDEX)++; + (++string); + + if( *string == NULL ) + return TRUE; + else { + (*NDEX)++; + (++string); + + return TRUE; + } + + } + +} +#pragma subtitle(" ") +#pragma page(1) +#pragma subtitle("subtitle - description ") +/********************************************************************/ +/* */ +/* Title: isTornadicActiv */ +/* Organization: W/OSO242 - GRAPHICS AND DISPLAY SECTION */ +/* Date: 15 Sep 1994 */ +/* Programmer: CARL MCCALLA */ +/* Language: C/370 */ +/* */ +/* Abstract: Determines whether or not the input character */ +/* string is signals the beginning of TORNADIC */ +/* ACTIVITY data. If it is, then interrogate subse- */ +/* quent report groups for time, location, and movement*/ +/* of tornado. Return TRUE, if TORNADIC ACTIVITY is */ +/* found. Otherwise, return FALSE. */ +/* */ +/* */ +/* External Functions Called: */ +/* None. */ +/* */ +/* Input: string - the address of a pointer to a charac- */ +/* ter string that may or may not signal */ +/* TORNADIC ACTIVITY. */ +/* */ +/* Mptr - a pointer to a structure that has the */ +/* data type Decoded_METAR. */ +/* */ +/* NDEX - a pointer to an integer that is the index */ +/* into an array that contains the indi- */ +/* vidual groups of the METAR report being */ +/* decoded. Upon entry, NDEX is the index */ +/* of the current group of the METAR report */ +/* that is to be indentified. */ +/* */ +/* Output: TRUE - if TORNADIC ACTIVITY is found. */ +/* FALSE - if no TORNADIC ACTIVITY is found. */ +/* */ +/* Modification History: */ +/* None. */ +/* */ +/********************************************************************/ +#pragma page(1) + +static MDSP_BOOL isTornadicActiv( char **string, Decoded_METAR *Mptr, + int *NDEX ) +{ + int saveNdex, + TornadicTime; + MDSP_BOOL Completion_flag; + char *B_stringPtr, + *E_stringPtr; + + + /*************************/ + /* START BODY OF ROUTINE */ + /*************************/ + + + saveNdex = *NDEX; + + B_stringPtr = NULL; + E_stringPtr = NULL; + + if( *string == NULL ) + return FALSE; + + if( !( strcmp(*string, "TORNADO") == 0 || + strcmp(*string, "TORNADOS") == 0 || + strcmp(*string, "TORNADOES") == 0 || + strcmp(*string, "WATERSPOUT") == 0 || + strcmp(*string, "WATERSPOUTS") == 0 || + strcmp(*string, "FUNNEL") == 0 ) ) + return FALSE; + else { + if( strcmp(*string, "FUNNEL") == 0 ) { + (++string); + + if( *string == NULL ) + return FALSE; + + if( !(strcmp(*string,"CLOUD") == 0 || + strcmp(*string,"CLOUDS") == 0 ) ) { + (*NDEX)++; + return FALSE; + } + else + strcpy(Mptr->TornadicType,"FUNNEL CLOUD"); + } + else { + strcpy(Mptr->TornadicType, *string); + (*NDEX)++; + (++string); + } + + Completion_flag = FALSE; + + if( *string == NULL ) + return FALSE; + + while( !Completion_flag ) { + +/* printf("isTornadicActivity: current *string = %s\n", + *string); */ + + if( *(*string) =='B' || *(*string) == 'E') { + if( *(*string) == 'B' ) { + B_stringPtr = *string; + E_stringPtr = strchr((*string)+1,'E'); + } + else { + B_stringPtr = strchr((*string)+1,'B'); + E_stringPtr = *string; + } +/* + if( B_stringPtr != NULL ) + printf("isTornadicActivity: B_stringPtr = %x\n", + B_stringPtr); + else + printf("isTornadicActivity: B_stringPtr = NULL\n"); + + if( E_stringPtr != NULL ) + printf("isTornadicActivity: E_stringPtr = %x\n", + E_stringPtr); + else + printf("isTornadicActivity: E_stringPtr = NULL\n"); +*/ + if( B_stringPtr != NULL && E_stringPtr == NULL ) { + if( nisdigit((*string)+1, strlen((*string)+1)) && + strlen((*string)+1) <= 4 ) { + TornadicTime = antoi((*string)+1, + strlen((*string)+1)); + if( TornadicTime > 99 ) { + Mptr->BTornadicHour = TornadicTime / 100; + Mptr->BTornadicMinute = TornadicTime % 100; + (*NDEX)++; + (++string); + } + else { + Mptr->BTornadicHour = TornadicTime; + (*NDEX)++; + (++string); + } + } + else { + (*NDEX)++; + (++string); + } + } + else if( B_stringPtr == NULL && E_stringPtr != NULL ) { + if( nisdigit((*string)+1,strlen((*string)+1)) && + strlen((*string)+1) <= 4 ) { + TornadicTime = antoi((*string)+1, + strlen((*string)+1)); + if( TornadicTime > 99 ) { + Mptr->ETornadicHour = TornadicTime / 100; + Mptr->ETornadicMinute = TornadicTime % 100; + (*NDEX)++; + (++string); + } + else { + Mptr->ETornadicHour = TornadicTime; + (*NDEX)++; + (++string); + } + } + else { + (*NDEX)++; + (++string); + } + } + else { +/* printf("isTornadicActivity: B_stringPtr != NULL" + " and E_stringPtr != NULL\n"); */ + if( nisdigit((B_stringPtr+1),(E_stringPtr - + (B_stringPtr+1)))) { + TornadicTime = antoi(( B_stringPtr+1), + (E_stringPtr-(B_stringPtr+1))); + if( TornadicTime > 99 ) { + Mptr->BTornadicHour = TornadicTime / 100; + Mptr->BTornadicMinute = TornadicTime % 100; + (*NDEX)++; + (++string); + } + else { + Mptr->BTornadicHour = TornadicTime; + (*NDEX)++; + (++string); + } + + TornadicTime = antoi(( E_stringPtr+1), + strlen(E_stringPtr+1)); + + if( TornadicTime > 99 ) { + Mptr->ETornadicHour = TornadicTime / 100; + Mptr->ETornadicMinute = TornadicTime % 100; + (*NDEX)++; + (++string); + } + else { + Mptr->ETornadicHour = TornadicTime; + (*NDEX)++; + (++string); + } + } + else { + (*NDEX)++; + (++string); + } + } + } + else if( nisdigit(*string, strlen(*string))) { + (++string); + + if( *string == NULL ) + return FALSE; + + if( strcmp(*string,"N") == 0 || + strcmp(*string,"NE") == 0 || + strcmp(*string,"NW") == 0 || + strcmp(*string,"S") == 0 || + strcmp(*string,"SE") == 0 || + strcmp(*string,"SW") == 0 || + strcmp(*string,"E") == 0 || + strcmp(*string,"W") == 0 ) { + (--string); + Mptr->TornadicDistance = antoi(*string, + strlen(*string)); + (*NDEX)++; + (++string); + } + else { + (--string); + + if( saveNdex == *NDEX ) + return FALSE; + else + return TRUE; + } + + } + else if(strcmp(*string,"DSNT") == 0 || + strcmp(*string,"VC") == 0 || + strcmp(*string,"VCY") == 0 ) { + if( strcmp(*string,"VCY") == 0 || + strcmp(*string,"VC") == 0 ) { + (++string); + + if( *string == NULL ) + return FALSE; + + if( strcmp(*string,"STN") == 0 ){ + strcpy(Mptr->TornadicLOC,"VC STN"); + (*NDEX)++; + (*NDEX)++; + (++string); + } + else { + strcpy(Mptr->TornadicLOC,"VC"); + (*NDEX)++; + } + } + else { + strcpy(Mptr->TornadicLOC,"DSNT"); + (*NDEX)++; + (++string); + } + } + else if(strcmp(*string,"N") == 0 || + strcmp(*string,"NE") == 0 || + strcmp(*string,"NW") == 0 || + strcmp(*string,"S") == 0 || + strcmp(*string,"SE") == 0 || + strcmp(*string,"SW") == 0 || + strcmp(*string,"E") == 0 || + strcmp(*string,"W") == 0 ) { + strcpy(Mptr->TornadicDIR, *string); + (*NDEX)++; + (++string); + } + else if( strcmp(*string, "MOV" ) == 0 ) { + (*NDEX)++; + (++string); + + if( *string == NULL ) + return FALSE; + + if( strcmp(*string, "N") == 0 || + strcmp(*string, "S") == 0 || + strcmp(*string, "E") == 0 || + strcmp(*string, "W") == 0 || + strcmp(*string, "NE") == 0 || + strcmp(*string, "NW") == 0 || + strcmp(*string, "SE") == 0 || + strcmp(*string, "SW") == 0 ) { + strcpy( Mptr->TornadicMovDir, *string ); + (*NDEX)++; + (++string); + + } + } + else + Completion_flag = TRUE; + } + + if( saveNdex == *NDEX ) + return FALSE; + else + return TRUE; + + } + +} +#pragma subtitle(" ") +#pragma page(1) +#pragma subtitle("subtitle - description ") +/********************************************************************/ +/* */ +/* Title: isPartObscur */ +/* Organization: W/OSO242 - GRAPHICS AND DISPLAY SECTION */ +/* Date: 15 Sep 1994 */ +/* Programmer: CARL MCCALLA */ +/* Language: C/370 */ +/* */ +/* Abstract: Determine whether or not the input character string */ +/* is a partial obscuration phenomenon. If a partial */ +/* obscuration is found, then take the preceding group */ +/* as the obscuring phenomenon. If a partial obscura- */ +/* tion is found, then return TRUE. Otherwise, return */ +/* false. */ +/* */ +/* */ +/* External Functions Called: */ +/* None. */ +/* */ +/* Input: string - the address of a pointer to a group */ +/* in a METAR report that may or may not */ +/* be a partial obscuration indicator. */ +/* */ +/* Mptr - a pointer to a structure that has the */ +/* data type Decoded_METAR. */ +/* */ +/* NDEX - a pointer to an integer that is the index */ +/* into an array that contains the indi- */ +/* vidual groups of the METAR report being */ +/* decoded. Upon entry, NDEX is the index */ +/* of the current group of the METAR report */ +/* that is to be indentified. */ +/* */ +/* Output: TRUE - if the input string is a partial obscura- */ +/* tion. */ +/* FALSE - if the input string is not a partial ob- */ +/* scuration. */ +/* */ +/* Modification History: */ +/* None. */ +/* */ +/********************************************************************/ +#pragma page(1) +static MDSP_BOOL isPartObscur( char **string, Decoded_METAR *Mptr, + int ndex, int *NDEX ) +{ + + /***************************/ + /* DECLARE LOCAL VARIABLES */ + /***************************/ + + int i; + + static char *phenom[] = {"-DZ", "DZ", "+DZ", + "FZDZ", "-RA", "RA", "+RA", + "SHRA", "TSRA", "FZRA", "-SN", "SN", "+SN", "DRSN", + "SHSN", "TSSN", "-SG", "SG", "+SG", "IC", "-PE", "PE", "+PE", + "SHPE", "TSPE", "GR", "SHGR", "TSGR", "GS", "SHGS", "TSGS", "-GS", + "+GS", "TS", "VCTS", "-TSRA", "TSRA", "+TSRA", "-TSSN", "TSSN", + "+TSSN", "-TSPE", "TSPE", "+TSPE", "-TSGS", "TSGS", "+TSGS", + "VCSH", "-SHRA", "+SHRA", "-SHSN", "+SHSN", "-SHPE", "+SHPE", + "-SHGS", "+SHGS", "-FZDZ", "+FZDZ", "-FZRA", "+FZRA", "FZFG", + "+FZFG", "BR", "FG", "VCFG", "MIFG", "PRFG", "BCFG", "FU", + "VA", "DU", "DRDU", "BLDU", "SA", "DRSA", "BLSA", "HZ", + "BLPY", "BLSN", "+BLSN", "VCBLSN", "BLSA", "+BLSA", + "VCBLSA", "+BLDU", "VCBLDU", "PO", "VCPO", "SQ", "FC", "+FC", + "VCFC", "SS", "+SS", "VCSS", "DS", "+DS", "VCDS", NULL}; + + +#ifdef DEBUGXX + printf("isPartObscur: Routine Entered...\n"); + printf("isPartObscur: *string = %s\n",*string); + if( Mptr->PartialObscurationAmt[ndex][0] != '\0' ) { + printf("PartialObscurationAmt = %s\n", + &(Mptr->PartialObscurationAmt[ndex][0])); + if( strcmp( *string, "FEW///" ) == 0 || + strcmp( *string, "SCT///" ) == 0 || + strcmp( *string, "BKN///" ) == 0 || + strcmp( *string, "FEW000" ) == 0 || + strcmp( *string, "SCT000" ) == 0 || + strcmp( *string, "BKN000" ) == 0 ) { + + --string; + printf("isPartObscur: Preceding group = %s\n", + *string); + ++string; + } + } +#endif + + if( *string == NULL ) + return FALSE; + + if( strcmp( *string, "FEW///" ) == 0 || + strcmp( *string, "SCT///" ) == 0 || + strcmp( *string, "BKN///" ) == 0 || + strcmp( *string, "FEW000" ) == 0 || + strcmp( *string, "SCT000" ) == 0 || + strcmp( *string, "BKN000" ) == 0 ) { + if( Mptr->PartialObscurationAmt[ndex][0] == '\0' ) + { + (*NDEX)++; + return FALSE; + } + else { + if( strcmp( *string, + &(Mptr->PartialObscurationAmt[ndex][0]) ) == 0 ) + { + --string; + + if( *string == NULL ) + return FALSE; + + i = 0; + while( phenom[i] != NULL ) { + if( strcmp( *string, phenom[i] ) == 0 ) { + strcpy(&(Mptr->PartialObscurationPhenom[ndex][0]), + *string); + + (*NDEX)++; + return TRUE; + } + else + i++; + } + + (*NDEX)++; + return FALSE; + + } + else { + (*NDEX)++; + return FALSE; + } + + } + + } + else + return FALSE; + +} + +#pragma subtitle(" ") +#pragma page(1) +#pragma subtitle("subtitle - description ") +/********************************************************************/ +/* */ +/* Title: isA0indicator */ +/* Organization: W/OSO242 - GRAPHICS AND DISPLAY SECTION */ +/* Date: 15 Sep 1994 */ +/* Programmer: CARL MCCALLA */ +/* Language: C/370 */ +/* */ +/* Abstract: Identify the input character string as an automated */ +/* station code type. If the input character string */ +/* is an automated station code type, then return */ +/* TRUE. Otherwise, return FALSE. */ +/* */ +/* */ +/* External Functions Called: */ +/* None. */ +/* */ +/* Input: indicator - a pointer to a character string */ +/* that may or may not be an ASOS */ +/* automated station code type. */ +/* */ +/* Mptr - a pointer to a structure that has the */ +/* data type Decoded_METAR. */ +/* */ +/* NDEX - a pointer to an integer that is the index */ +/* into an array that contains the indi- */ +/* vidual groups of the METAR report being */ +/* decoded. Upon entry, NDEX is the index */ +/* of the current group of the METAR report */ +/* that is to be indentified. */ +/* */ +/* Output: TRUE - if the input string matches one of the */ +/* valid ASOS automated station indicators. */ +/* FALSE - the input string did not match one of the*/ +/* valid ASOS automated station indicators. */ +/* */ +/* Modification History: */ +/* None. */ +/* */ +/********************************************************************/ +#pragma page(1) + +static MDSP_BOOL isA0indicator( char *indicator, Decoded_METAR *Mptr, + int *NDEX ) +{ + + /***************************/ + /* DECLARE LOCAL VARIABLES */ + /***************************/ + + char *autoFlag[] = {"A01", "A01A", "A02", "A02A", "AOA", + "A0A", "AO1", "AO1A", "AO2", "AO2A", NULL}; + int i; + + /*************************/ + /* START BODY OF ROUTINE */ + /*************************/ + /*******************************************/ + /* COMPARE THE INPUT CHARACTER STRING WITH */ + /* VALID AUTOMATED STATION CODE TYPE. IF */ + /* A MATCH IS FOUND, RETURN TRUE. OTHER- */ + /* WISE, RETURN FALSE */ + /*******************************************/ + + if( indicator == NULL ) + return FALSE; + + i = 0; + + while( autoFlag[ i ] != NULL ) + { + if( strcmp( indicator, autoFlag[ i ]) == 0 ) + { + (*NDEX)++; + strcpy(Mptr->autoIndicator, indicator); + return TRUE; + } + i++; + } + + return FALSE; +} + +#pragma subtitle(" ") +#pragma page(1) +#pragma subtitle("subtitle - description ") +/********************************************************************/ +/* */ +/* Title: isPeakWind */ +/* Organization: W/OSO242 - GRAPHICS AND DISPLAY SECTION */ +/* Date: 15 Sep 1994 */ +/* Programmer: CARL MCCALLA */ +/* Language: C/370 */ +/* */ +/* Abstract: Determine whether or not the current and subsequent */ +/* groups from the METAR report make up a valid report */ +/* of peak wind. */ +/* */ +/* */ +/* Input: string - the addr of a ptr to a character string */ +/* that may or may not be the indicator */ +/* for a peak wind data group. */ +/* */ +/* Mptr - a pointer to a structure that has the */ +/* data type Decoded_METAR. */ +/* */ +/* NDEX - a pointer to an integer that is the index */ +/* into an array that contains the indi- */ +/* vidual groups of the METAR report being */ +/* decoded. Upon entry, NDEX is the index */ +/* of the current group of the METAR report */ +/* that is to be indentified. */ +/* */ +/* Output: TRUE - if the input string (and subsequent grps) */ +/* are decoded as peak wind. */ +/* FALSE - if the input string (and subsequent grps)*/ +/* are not decoded as peak wind. */ +/* External Functions Called: */ +/* nisdigit */ +/* */ +/* */ +/* Modification History: */ +/* None. */ +/* */ +/********************************************************************/ +#pragma page(1) +static MDSP_BOOL isPeakWind( char **string, Decoded_METAR *Mptr, + int *NDEX ) +{ + + /***************************/ + /* DECLARE LOCAL VARIABLES */ + /***************************/ + + char buf[ 6 ]; + char *slash; + int temp; + MDSP_BOOL PK_WND_FLAG; + + /*************************/ + /* START BODY OF ROUTINE */ + /*************************/ + + + + /******************************************************/ + /* IF THE CURRENT AND NEXT GROUPS ARE "PK WND", THEN */ + /* DETERMINE WHETHER OR NOT THE GROUP THAT FOLLOWS IS */ + /* A VALID PK WND GROUP. IF IT IS, THEN DECODE THE */ + /* GROUP AND RETURN TRUE. OTHERWISE, RETURN FALSE. */ + /******************************************************/ + + PK_WND_FLAG = TRUE; + + if( *string == NULL ) + return FALSE; + + + if( !(strcmp(*string,"PK") == 0 || + strcmp(*string,"PKWND") == 0 ) ) + return FALSE; + else + (++string); + + if( *string == NULL ) + return FALSE; + + if( strcmp(*string,"WND") == 0 ) + (++string); + else + PK_WND_FLAG = FALSE; + + if( *string == NULL ) + return FALSE; + + if( (slash = strchr(*string,'/')) == NULL ) { + /********************************/ + /* INVALID PEAK WIND. BUMP PAST */ + /* PK AND WND GROUP AND RETURN */ + /* FALSE. */ + /********************************/ + (*NDEX)++; + + if( PK_WND_FLAG ) + (*NDEX)++; + + return FALSE; + } + else if( strlen(*string) >= 8 && strlen(*string) <= 11 && + nisdigit(slash+1,strlen(slash+1)) && + nisdigit(*string, (slash - *string)) && + (slash - *string) <= 6 ) + { + memset( buf, '\0', 4); + strncpy( buf, *string, 3 ); + Mptr->PKWND_dir = atoi( buf ); + + memset( buf, '\0', 4); + strncpy( buf, *string+3, slash-(*string+3) ); + Mptr->PKWND_speed = atoi( buf ); + + memset( buf, '\0', 5); + strcpy( buf, slash+1 ); + temp = atoi( buf ); + + if( temp > 99 ) + { + Mptr->PKWND_hour = atoi(buf)/100; + Mptr->PKWND_minute = (atoi(buf)) % 100; + } + else + Mptr->PKWND_minute = atoi( buf ); + /********************************/ + /* VALID PEAK WIND FOUND. BUMP */ + /* PAST PK, WND, AND PEAK WIND */ + /* GROUPS AND RETURN TRUE. */ + /********************************/ + (*NDEX)++; + (*NDEX)++; + + if( PK_WND_FLAG ) + (*NDEX)++; + + return TRUE; + } + else + return FALSE; +} +#pragma subtitle(" ") +#pragma page(1) +#pragma subtitle("subtitle - description ") +/********************************************************************/ +/* */ +/* Title: isWindShift */ +/* Organization: W/OSO242 - GRAPHICS AND DISPLAY SECTION */ +/* Date: 15 Sep 1994 */ +/* Programmer: CARL MCCALLA */ +/* Language: C/370 */ +/* */ +/* Abstract: Determine whether or not the current and subsequent */ +/* groups from the METAR report make up a valid report */ +/* of wind shift and frontal passage, if included. */ +/* */ +/* */ +/* Input: string - the addr of a ptr to a character string */ +/* that may or may not be the indicator */ +/* for a wind shift data group. */ +/* */ +/* Mptr - a pointer to a structure that has the */ +/* data type Decoded_METAR. */ +/* */ +/* NDEX - a pointer to an integer that is the index */ +/* into an array that contains the indi- */ +/* vidual groups of the METAR report being */ +/* decoded. Upon entry, NDEX is the index */ +/* of the current group of the METAR report */ +/* that is to be indentified. */ +/* */ +/* Output: TRUE - if the input string (and subsequent grps) */ +/* are decoded as wind shift. */ +/* FALSE - if the input string (and subsequent grps)*/ +/* are not decoded as wind shift. */ +/* External Functions Called: */ +/* nisdigit */ +/* */ +/* */ +/* Modification History: */ +/* None. */ +/* */ +/********************************************************************/ +#pragma page(1) +static MDSP_BOOL isWindShift( char **string, Decoded_METAR *Mptr, + int *NDEX) +{ + + /***************************/ + /* DECLARE LOCAL VARIABLES */ + /***************************/ + + int temp; + + /*************************/ + /* START BODY OF ROUTINE */ + /*************************/ + + + /****************************************************/ + /* IF THE CURRENT GROUP IS "WSHFT", THEN DETERMINE */ + /* WHETHER OR NOT THE GROUP THAT FOLLOWS IS A VALID */ + /* WSHFT GROUP. IF IT IS, THEN DECODE THE GROUP */ + /* GROUP AND RETURN TRUE. OTHERWISE, RETURN FALSE. */ + /****************************************************/ + + if( *string == NULL ) + return FALSE; + + if( strcmp( *string, "WSHFT" ) != 0 ) + return FALSE; + else + (++string); + + if( *string == NULL ) + return FALSE; + + if( nisdigit(*string,strlen(*string)) && strlen(*string) <= 4) + { + temp = atoi( *string ); + + if( temp > 100 ) + { + Mptr->WshfTime_hour = (atoi(*string))/100; + Mptr->WshfTime_minute = (atoi(*string)) % 100; + } + else + Mptr->WshfTime_minute = (atoi(*string)) % 100; + + (++string); + + if( *string == NULL ) + return FALSE; + + + if( **string == '\0') { + (*NDEX)++; + (*NDEX)++; + return TRUE; + } + else if( strcmp( *string, "FROPA") == 0 ) + { + Mptr->Wshft_FROPA = TRUE; + /********************************/ + /* VALID WIND SHIFT FOUND. BUMP */ + /* PAST WSHFT, WSHFT GROUP, AND */ + /* FROPA GROUPS AND RETURN TRUE.*/ + /********************************/ + (*NDEX)++; + (*NDEX)++; + (*NDEX)++; + return TRUE; + } + else { + /********************************/ + /* VALID WIND SHIFT FOUND. BUMP */ + /* PAST WSHFT AND WSHFT GROUP */ + /* AND RETURN TRUE. */ + /********************************/ + (*NDEX)++; + (*NDEX)++; + return TRUE; + } + } + else { + /**********************************/ + /* INVALID WIND SHIFT FOUND. BUMP */ + /* PAST WSHFT AND RETURN FALSE. */ + /********************************/ + (*NDEX)++; + return FALSE; + } +} +#pragma subtitle(" ") +#pragma page(1) +#pragma subtitle("subtitle - description ") +/********************************************************************/ +/* */ +/* Title: isTowerVsby */ +/* Organization: W/OSO242 - GRAPHICS AND DISPLAY SECTION */ +/* Date: 15 Sep 1994 */ +/* Programmer: CARL MCCALLA */ +/* Language: C/370 */ +/* */ +/* Abstract: Determine whether or not the current and subsequent */ +/* groups from the METAR report make up a valid report */ +/* of tower visibility. */ +/* */ +/* */ +/* Input: string - the addr of a ptr to a character string */ +/* that may or may not be the indicator */ +/* for tower visibility. */ +/* */ +/* Mptr - a pointer to a structure that has the */ +/* data type Decoded_METAR. */ +/* */ +/* NDEX - a pointer to an integer that is the index */ +/* into an array that contains the indi- */ +/* vidual groups of the METAR report being */ +/* decoded. Upon entry, NDEX is the index */ +/* of the current group of the METAR report */ +/* that is to be indentified. */ +/* */ +/* Output: TRUE - if the input string (and subsequent grps) */ +/* are decoded as tower visibility. */ +/* FALSE - if the input string (and subsequent grps)*/ +/* are not decoded as tower visibility */ +/* External Functions Called: */ +/* nisdigit */ +/* */ +/* Modification History: */ +/* None. */ +/* */ +/********************************************************************/ +#pragma page(1) +static MDSP_BOOL isTowerVsby( char **token, Decoded_METAR *Mptr, int *NDEX) +{ + + /***************************/ + /* DECLARE LOCAL VARIABLES */ + /***************************/ + + char *slash; + float T_vsby; + + /*************************/ + /* START BODY OF ROUTINE */ + /*************************/ + + + /****************************************************************/ + /* IF THE CURRENT AND NEXT GROUPS ARE "TWR VIS", THEN DETERMINE */ + /* WHETHER OR NOT THE GROUP(S) THAT FOLLOWS IS(ARE) A VALID */ + /* TOWER VISIBILITY GROUP. IF IT IS, THEN DECODE THE GROUP */ + /* GROUP AND RETURN TRUE. OTHERWISE, RETURN FALSE. */ + /****************************************************************/ + + if( *token == NULL ) + return FALSE; + + if(strcmp(*token,"TWR") != 0) + return FALSE; + else + (++token); + + if( *token == NULL ) + return FALSE; + + if( strcmp(*token,"VIS") != 0) { + (*NDEX)++; + return FALSE; + } + else + (++token); + + if( *token == NULL ) + return FALSE; + + if( nisdigit(*token, + strlen(*token))) + { + Mptr->TWR_VSBY = (float) atoi(*token); + (++token); + if( *token != NULL ) + { + if( (slash = strchr(*token, '/')) + != NULL ) + { + if( nisdigit(slash+1,strlen(slash+1)) && + nisdigit(*token, + (slash-*token))) + { + T_vsby = fracPart(*token); + Mptr->TWR_VSBY += T_vsby; + (*NDEX)++; + (*NDEX)++; + (*NDEX)++; + (*NDEX)++; + return TRUE; + } + else { + (*NDEX)++; + (*NDEX)++; + (*NDEX)++; + return TRUE; + } + + } + else { + (*NDEX)++; + (*NDEX)++; + (*NDEX)++; + return TRUE; + } + } + else { + (*NDEX)++; + (*NDEX)++; + (*NDEX)++; + return TRUE; + } + + } + else if( (slash = strchr(*token, '/')) + != NULL ) + { + if( nisdigit(slash+1,strlen(slash+1)) && + nisdigit(*token, + (slash-*token))) + { + Mptr->TWR_VSBY = fracPart(*token); + (*NDEX)++; + (*NDEX)++; + (*NDEX)++; + return TRUE; + } + else { + (*NDEX)++; + (*NDEX)++; + return FALSE; + } + } + else { + (*NDEX)++; + (*NDEX)++; + return FALSE; + } + +} +#pragma subtitle(" ") +#pragma page(1) +#pragma subtitle("subtitle - description ") +/********************************************************************/ +/* */ +/* Title: isSurfaceVsby */ +/* Organization: W/OSO242 - GRAPHICS AND DISPLAY SECTION */ +/* Date: 15 Sep 1994 */ +/* Programmer: CARL MCCALLA */ +/* Language: C/370 */ +/* */ +/* Abstract: Determine whether or not the current and subsequent */ +/* groups from the METAR report make up a valid report */ +/* of surface visibility. */ +/* */ +/* */ +/* Input: string - the addr of a ptr to a character string */ +/* that may or may not be the indicator */ +/* for surface visibility. */ +/* */ +/* Mptr - a pointer to a structure that has the */ +/* data type Decoded_METAR. */ +/* */ +/* NDEX - a pointer to an integer that is the index */ +/* into an array that contains the indi- */ +/* vidual groups of the METAR report being */ +/* decoded. Upon entry, NDEX is the index */ +/* of the current group of the METAR report */ +/* that is to be indentified. */ +/* */ +/* Output: TRUE - if the input string (and subsequent grps) */ +/* are decoded as surface visibility. */ +/* FALSE - if the input string (and subsequent grps)*/ +/* are not decoded as surface visibility. */ +/* External Functions Called: */ +/* nisdigit */ +/* */ +/* Modification History: */ +/* None. */ +/* */ +/********************************************************************/ +#pragma page(1) +static MDSP_BOOL isSurfaceVsby( char **token, Decoded_METAR *Mptr, + int *NDEX) +{ + + /***************************/ + /* DECLARE LOCAL VARIABLES */ + /***************************/ + + char *slash; + float S_vsby; + + + /*************************/ + /* START BODY OF ROUTINE */ + /*************************/ + + + /****************************************************************/ + /* IF THE CURRENT AND NEXT GROUPS ARE "SFC VIS", THEN DETERMINE */ + /* WHETHER OR NOT THE GROUP(S) THAT FOLLOWS IS(ARE) A VALID */ + /* SURFACE VISIBILITY GROUP. IF IT IS, THEN DECODE THE GROUP */ + /* GROUP AND RETURN TRUE. OTHERWISE, RETURN FALSE. */ + /****************************************************************/ + + if( *token == NULL ) + return FALSE; + + if(strcmp(*token,"SFC") != 0) + return FALSE; + else + (++token); + + if( strcmp(*token,"VIS") != 0) { + (*NDEX)++; + return FALSE; + } + else + (++token); + + + if( *token == NULL ) + return FALSE; + + + if( nisdigit(*token, + strlen(*token))) + { + Mptr->SFC_VSBY = (float) atoi(*token); + (++token); + if( *token != NULL ) + { + if( (slash = strchr(*token, '/')) + != NULL ) + { + if( nisdigit(slash+1,strlen(slash+1)) && + nisdigit(*token, + (slash-*token))) + { + S_vsby = fracPart(*token); + Mptr->SFC_VSBY += S_vsby; + (*NDEX)++; + (*NDEX)++; + (*NDEX)++; + (*NDEX)++; + return TRUE; + } + else { + (*NDEX)++; + (*NDEX)++; + (*NDEX)++; + return TRUE; + } + + } + else { + (*NDEX)++; + (*NDEX)++; + (*NDEX)++; + return TRUE; + } + } + else { + (*NDEX)++; + (*NDEX)++; + (*NDEX)++; + return TRUE; + } + + } + else if( (slash = strchr(*token, '/')) + != NULL ) + { + if( nisdigit(slash+1,strlen(slash+1)) && + nisdigit(*token, + (slash-*token))) + { + Mptr->SFC_VSBY = fracPart(*token); + (*NDEX)++; + (*NDEX)++; + (*NDEX)++; + return TRUE; + } + else { + (*NDEX)++; + (*NDEX)++; + return FALSE; + } + } + else { + (*NDEX)++; + (*NDEX)++; + return FALSE; + } + +} + +#pragma subtitle(" ") +#pragma page(1) +#pragma subtitle("subtitle - description ") +/********************************************************************/ +/* */ +/* Title: isVariableVsby */ +/* Organization: W/OSO242 - GRAPHICS AND DISPLAY SECTION */ +/* Date: 21 Nov 1994 */ +/* Programmer: CARL MCCALLA */ +/* Language: C/370 */ +/* */ +/* Abstract: Determine whether or not the current and subsequent */ +/* groups from the METAR report make up a valid report */ +/* of variable prevailing visibility. */ +/* */ +/* */ +/* Input: string - the addr of a ptr to a character string */ +/* that may or may not be the indicator */ +/* for variable prevailing visibility. */ +/* */ +/* Mptr - a pointer to a structure that has the */ +/* data type Decoded_METAR. */ +/* */ +/* NDEX - a pointer to an integer that is the index */ +/* into an array that contains the indi- */ +/* vidual groups of the METAR report being */ +/* decoded. Upon entry, NDEX is the index */ +/* of the current group of the METAR report */ +/* that is to be indentified. */ +/* */ +/* Output: TRUE - if the input string (and subsequent grps) */ +/* are decoded as variable prevailing vsby. */ +/* FALSE - if the input string (and subsequent grps)*/ +/* are not decoded as variable prevailing */ +/* vsby. */ +/* External Functions Called: */ +/* nisdigit */ +/* */ +/* Modification History: */ +/* None. */ +/* */ +/********************************************************************/ +#pragma page(1) +static MDSP_BOOL isVariableVsby( char **string, Decoded_METAR *Mptr, + int *NDEX ) +{ + + /***************************/ + /* DECLARE LOCAL VARIABLES */ + /***************************/ + + char *slash, + *slash1, + *slash2, + buf[ 5 ], + *V_char; + float minimumVsby, + maximumVsby; + + + + /*************************/ + /* START BODY OF ROUTINE */ + /*************************/ + + + /***************************************************/ + /* IF THE CURRENT GROUP IS "VIS", THEN DETERMINE */ + /* WHETHER OR NOT THE GROUPS THAT FOLLOW ARE VALID */ + /* VARIABLE PREVAILING VSBY. IF THEY ARE, THEN */ + /* DECODE THE GROUPS AND RETURN TRUE. OTHERWISE, */ + /* RETURN FALSE. */ + /***************************************************/ + + if( *string == NULL ) + return FALSE; + + if( strcmp(*string, "VIS") != 0 ) + return FALSE; + else + (++string); + + if( *string == NULL ) + return FALSE; + + if( !((V_char = strchr(*string, 'V')) != NULL || + nisdigit(*string,strlen(*string))) ) + return FALSE; + else if( nisdigit(*string,strlen(*string)) ) { + minimumVsby = (float) atoi(*string); + (++string); + + if( *string == NULL ) + return FALSE; + + if( (V_char = strchr(*string,'V')) == NULL ) + return FALSE; + else { + if( (slash = strchr(*string,'/')) == NULL ) + return FALSE; + else { + if( nisdigit(*string,(slash - *string)) && + nisdigit(slash+1,(V_char-(slash+1))) && + nisdigit(V_char+1,strlen(V_char+1)) ) { + if( (V_char - *string) > 4 ) + return FALSE; + else { + memset(buf,'\0',5); + strncpy(buf,*string,(V_char - *string)); + Mptr->minVsby = minimumVsby + fracPart(buf); + maximumVsby = (float) atoi(V_char+1); + } + + (++string); + + if( *string == NULL ) + return FALSE; + + if( (slash = strchr(*string,'/')) == NULL ) { + Mptr->maxVsby = maximumVsby; + (*NDEX)++; + (*NDEX)++; + (*NDEX)++; + return TRUE; + } + else if( nisdigit(*string,(slash - *string)) && + nisdigit(slash+1, strlen(slash+1)) ) { + Mptr->maxVsby = maximumVsby + fracPart(*string); + (*NDEX)++; + (*NDEX)++; + (*NDEX)++; + (*NDEX)++; + return TRUE; + } + else { + Mptr->maxVsby = maximumVsby; + (*NDEX)++; + (*NDEX)++; + (*NDEX)++; + return TRUE; + } + } + else + return FALSE; + } + } + } + else { + if( (V_char = strchr(*string,'V')) == NULL ) + return FALSE; + if(nisdigit(*string,(V_char - *string)) && + nisdigit(V_char+1,strlen(V_char+1)) ) { + Mptr->minVsby = (float) antoi(*string,(V_char - *string)); + maximumVsby = (float) atoi(V_char+1); + + (++string); + + if( *string == NULL ) + return FALSE; + + if( (slash = strchr(*string,'/')) == NULL ) { + Mptr->maxVsby = maximumVsby; + (*NDEX)++; + (*NDEX)++; + return TRUE; + } + else if( nisdigit(*string, (slash - *string)) && + nisdigit(slash+1,strlen(slash+1)) ) { + Mptr->maxVsby = maximumVsby + fracPart( *string ); + (*NDEX)++; + (*NDEX)++; + (*NDEX)++; + return TRUE; + } + else { + Mptr->maxVsby = maximumVsby; + (*NDEX)++; + (*NDEX)++; + return TRUE; + } + } + else { + if( (slash2 = strchr(V_char+1,'/')) == NULL && + (slash1 = strchr(*string,'/')) == NULL ) + return FALSE; + else if( slash1 == NULL ) + return FALSE; + else if( slash == slash2 ) + return FALSE; + else if( nisdigit(*string,(slash1 - *string)) && + nisdigit((slash1+1),(V_char-(slash1+1))) ) { + if( (V_char - *string) > 4 ) + return FALSE; + else { + memset(buf,'\0',5); + strncpy(buf,*string,(V_char - *string)); + minimumVsby = fracPart(buf); + } + if( slash2 == NULL) { + if( nisdigit(V_char+1, strlen(V_char+1)) ) { + maximumVsby = (float) atoi(V_char+1); + + (++string); + + if( *string == NULL ) + return FALSE; + + if( (slash = strchr(*string,'/')) == NULL ) { + Mptr->minVsby = minimumVsby; + Mptr->maxVsby = maximumVsby; + (*NDEX)++; + (*NDEX)++; + return TRUE; + } + else if( nisdigit(*string,(slash-*string)) && + nisdigit((slash+1),strlen(slash+1)) ) { + Mptr->minVsby = minimumVsby; + Mptr->maxVsby = maximumVsby + + fracPart(*string); + (*NDEX)++; + (*NDEX)++; + (*NDEX)++; + return TRUE; + } + else{ + Mptr->minVsby = minimumVsby; + Mptr->maxVsby = maximumVsby; + (*NDEX)++; + (*NDEX)++; + return TRUE; + } + } + else + return FALSE; + } + else { + if( nisdigit(V_char+1,(slash2-V_char+1)) && + nisdigit((slash2+1),strlen(slash2+1)) ) { + Mptr->minVsby = minimumVsby; + Mptr->maxVsby = fracPart(V_char+1); + (*NDEX)++; + (*NDEX)++; + return TRUE; + } + else + return FALSE; + } + } + } + } + return FALSE; +} +#pragma subtitle(" ") +#pragma page(1) +#pragma subtitle("subtitle - description ") +/********************************************************************/ +/* */ +/* Title: isVsby2ndSite */ +/* Organization: W/OSO242 - GRAPHICS AND DISPLAY SECTION */ +/* Date: 15 Sep 1994 */ +/* Programmer: CARL MCCALLA */ +/* Language: C/370 */ +/* */ +/* Abstract: Determine whether or not the current and subsequent */ +/* groups from the METAR report make up a valid report */ +/* of visibility at a secondary site. */ +/* */ +/* */ +/* Input: token - the addr of a ptr to a character string */ +/* that may or may not be the indicator */ +/* for visibility at a secondary site. */ +/* */ +/* Mptr - a pointer to a structure that has the */ +/* data type Decoded_METAR. */ +/* */ +/* NDEX - a pointer to an integer that is the index */ +/* into an array that contains the indi- */ +/* vidual groups of the METAR report being */ +/* decoded. Upon entry, NDEX is the index */ +/* of the current group of the METAR report */ +/* that is to be indentified. */ +/* */ +/* Output: TRUE - if the input string (and subsequent grps) */ +/* are decoded as visibility at a 2ndry site.*/ +/* FALSE - if the input string (and subsequent grps)*/ +/* are not decoded as visibility at a 2ndry */ +/* site. */ +/* */ +/* External Functions Called: */ +/* nisalnum */ +/* fracPart */ +/* nisdigit */ +/* */ +/* */ +/* Modification History: */ +/* None. */ +/* */ +/********************************************************************/ +#pragma page(1) +static MDSP_BOOL isVsby2ndSite( char **token, Decoded_METAR *Mptr, + int *NDEX) +{ + + /***************************/ + /* DECLARE LOCAL VARIABLES */ + /***************************/ + + char *slash; + float S_vsby, + VSBY_2ndSite; + + + /*************************/ + /* START BODY OF ROUTINE */ + /*************************/ + + + /***************************************************/ + /* IF THE CURRENT GROUP IS "VIS", THEN DETERMINE */ + /* WHETHER OR NOT THE GROUPS THAT FOLLOW ARE VALID */ + /* VSBILITY AT A 2NDRY SITE. IF THEY ARE, THEN */ + /* DECODE THE GROUPS AND RETURN TRUE. OTHERWISE, */ + /* RETURN FALSE. */ + /***************************************************/ + + if( *token == NULL ) + return FALSE; + + if(strcmp(*token,"VIS") != 0) + return FALSE; + else + (++token); + + if( *token == NULL ) + return FALSE; + + if( nisdigit(*token, + strlen(*token))) + { + VSBY_2ndSite = (float) atoi(*token); + (++token); + if( *token != NULL ) + { + if( (slash = strchr(*token, '/')) + != NULL ) + { + if( nisdigit(slash+1,strlen(slash+1)) && + nisdigit(*token, + (slash-*token))) + { + S_vsby = fracPart(*token); + + (++token); + + if( *token == NULL ) + return FALSE; + + if( strncmp( *token, "RMY", 3 ) == 0) { + if( nisalnum( *token, strlen(*token) ) ) { + strcpy(Mptr->VSBY_2ndSite_LOC, *token); + Mptr->VSBY_2ndSite = VSBY_2ndSite + S_vsby; + (*NDEX)++; + (*NDEX)++; + (*NDEX)++; + (*NDEX)++; + return TRUE; + } + else + return FALSE; + } + else + return FALSE; + } + else { + if( strncmp( *token, "RMY", 3 ) == 0) { + if( nisalnum( *token, strlen(*token) ) ) { + strcpy(Mptr->VSBY_2ndSite_LOC, *token); + Mptr->VSBY_2ndSite = VSBY_2ndSite; + (*NDEX)++; + (*NDEX)++; + (*NDEX)++; + return TRUE; + } + else + return FALSE; + } + else + return FALSE; + } + + } + else { + if( strncmp( *token, "RMY", 3 ) == 0) { + if( nisalnum( *token, strlen(*token) ) ) { + strcpy(Mptr->VSBY_2ndSite_LOC, *token); + Mptr->VSBY_2ndSite = VSBY_2ndSite; + (*NDEX)++; + (*NDEX)++; + (*NDEX)++; + return TRUE; + } + else + return TRUE; + } + else + return FALSE; + } + } + else { + if(*token != NULL && strncmp( *token, "RMY", 3 ) == 0) { + if( nisalnum( *token, strlen(*token) ) ) { + strcpy(Mptr->VSBY_2ndSite_LOC, *token); + Mptr->VSBY_2ndSite = VSBY_2ndSite; + (*NDEX)++; + (*NDEX)++; + (*NDEX)++; + return TRUE; + } + else + return FALSE; + } + else + return FALSE; + } + + } + else if( (slash = strchr(*token, '/')) + != NULL ) + { + if( nisdigit(slash+1,strlen(slash+1)) && + nisdigit(*token, + (slash-*token))) + { + VSBY_2ndSite = fracPart(*token); + if( strncmp( *(++token), "RMY", 3 ) == 0) { + if( nisalnum( *token, strlen(*token) ) ) { + Mptr->VSBY_2ndSite = VSBY_2ndSite; + strcpy(Mptr->VSBY_2ndSite_LOC, *token); + (*NDEX)++; + (*NDEX)++; + (*NDEX)++; + return TRUE; + } + else + return FALSE; + } + else + return FALSE; + } + else + return FALSE; + } + else + return FALSE; + +} + +#pragma subtitle(" ") +#pragma page(1) +#pragma subtitle("subtitle - description ") +/********************************************************************/ +/* */ +/* Title: isLTGfreq */ +/* Organization: W/OSO242 - GRAPHICS AND DISPLAY SECTION */ +/* Date: 15 Sep 1994 */ +/* Programmer: CARL MCCALLA */ +/* Language: C/370 */ +/* */ +/* Abstract: Determine whether or not the current and subsequent */ +/* groups from the METAR report make up a valid report */ +/* of lightning. */ +/* */ +/* */ +/* Input: string - the addr of a ptr to a character string */ +/* that may or may not be the indicator */ +/* for lightning. */ +/* */ +/* Mptr - a pointer to a structure that has the */ +/* data type Decoded_METAR. */ +/* */ +/* NDEX - a pointer to an integer that is the index */ +/* into an array that contains the indi- */ +/* vidual groups of the METAR report being */ +/* decoded. Upon entry, NDEX is the index */ +/* of the current group of the METAR report */ +/* that is to be indentified. */ +/* */ +/* Output: TRUE - if the input string (and subsequent grps) */ +/* are decoded as lightning. */ +/* FALSE - if the input string (and subsequent grps)*/ +/* are not decoded as lightning. */ +/* */ +/* External Functions Called: */ +/* NONE. */ +/* */ +/* */ +/* Modification History: */ +/* 09 May 1996: Software modified to properly */ +/* decode lightning types. */ +/* */ +/* */ +/* */ +/********************************************************************/ +#pragma page(1) + +MDSP_BOOL static isLTGfreq( char **string, Decoded_METAR *Mptr, int *NDEX ) +{ + + /***************************/ + /* DECLARE LOCAL VARIABLES */ + /***************************/ + + MDSP_BOOL LTG_FREQ_FLAG, + LTG_TYPE_FLAG, + LTG_LOC_FLAG, + LTG_DIR_FLAG, + TYPE_NOT_FOUND; + char *temp; + + /*************************/ + /* START BODY OF ROUTINE */ + /*************************/ + + + + /***************************************************/ + /* IF THE CURRENT GROUP IS "LTG", THEN DETERMINE */ + /* WHETHER OR NOT THE PREVIOUS GROUP AS WELL AS */ + /* GROUPS THAT FOLLOW ARE VALID LIGHTNING REPORT */ + /* PARAMETERS. IF THEY ARE, THEN DECODE THE */ + /* GROUPS AND RETURN TRUE. OTHERWISE, RETURN */ + /* FALSE. */ + /***************************************************/ + + if( *string == NULL ) + return FALSE; + + if( strcmp(*string,"VCTS") == 0 ) { + Mptr->LightningVCTS = TRUE; + (++string); + (*NDEX)++; + return TRUE; + } + + if( *string == NULL ) + return FALSE; + + if( strncmp( *string, "LTG", 3 ) != 0 ) { + return FALSE; + } + else { + + if( *string == NULL ) + return FALSE; + + (--string); + + + LTG_FREQ_FLAG = FALSE; + /*-- CHECK FOR LIGHTNING FREQUENCY -----------*/ + if( strcmp( *string, "OCNL" ) == 0 ) { + Mptr->OCNL_LTG = TRUE; + LTG_FREQ_FLAG = TRUE; + } + else if( strcmp( *string, "FRQ" ) == 0 ) { + Mptr->FRQ_LTG = TRUE; + LTG_FREQ_FLAG = TRUE; + } + else if( strcmp( *string, "CONS" ) == 0 ) { + Mptr->CNS_LTG = TRUE; + LTG_FREQ_FLAG = TRUE; + } + + + (++string); + + if( *string == NULL ) + return FALSE; + + + if( strcmp( *string, "LTG") == 0 ) { + (++string); + + if( *string == NULL ) + return FALSE; + + (*NDEX)++; + + LTG_LOC_FLAG = FALSE; + /*-- CHECK FOR LIGHTNING LOCATION ------------*/ + if( strcmp( *string, "DSNT" ) == 0 ) { + Mptr->DSNT_LTG = TRUE; + LTG_LOC_FLAG = TRUE; + } + else if( strcmp( *string, "AP" ) == 0 ) { + Mptr->AP_LTG = TRUE; + LTG_LOC_FLAG = TRUE; + } + else if( strcmp( *string, "VCY" ) == 0 || + strcmp( *string, "VC" ) == 0 ) { + Mptr->VcyStn_LTG = TRUE; + LTG_LOC_FLAG = TRUE; + } + else if( strcmp( *string, "OVHD" ) == 0 || + strcmp( *string, "OHD" ) == 0 ) { + Mptr->OVHD_LTG = TRUE; + LTG_LOC_FLAG = TRUE; + } + + if( LTG_LOC_FLAG ) + (++string); + + if( *string == NULL ) { + if( LTG_LOC_FLAG ) + (*NDEX)++; + return TRUE; + } + + LTG_DIR_FLAG = FALSE; + /*-- CHECK FOR LIGHTNING DIRECTION -----------*/ + if( strcmp( *string, "N" ) == 0 || + strcmp( *string, "NE" ) == 0 || + strcmp( *string, "NW" ) == 0 || + strcmp( *string, "S" ) == 0 || + strcmp( *string, "SE" ) == 0 || + strcmp( *string, "SW" ) == 0 || + strcmp( *string, "E" ) == 0 || + strcmp( *string, "W" ) == 0 ) { + strcpy( Mptr->LTG_DIR, *string); + LTG_DIR_FLAG = TRUE; + } + + + if( LTG_LOC_FLAG ) + (*NDEX)++; + if( LTG_DIR_FLAG ) + (*NDEX)++; + + return TRUE; + } + else { + + LTG_TYPE_FLAG = FALSE; + /*-- CHECK FOR LIGHTNING TYPE ----------------*/ + TYPE_NOT_FOUND = FALSE; + temp = (*string) + 3; + while( *temp != '\0' && !TYPE_NOT_FOUND ) { + if( strncmp( temp, "CG", 2 ) == 0 ) { + Mptr->CG_LTG = TRUE; + LTG_TYPE_FLAG = TRUE; + temp++; + temp++; + } + else if( strncmp( temp, "IC", 2 ) == 0 ) { + Mptr->IC_LTG = TRUE; + LTG_TYPE_FLAG = TRUE; + temp++; + temp++; + } + else if( strncmp( temp, "CC", 2 ) == 0 ) { + Mptr->CC_LTG = TRUE; + LTG_TYPE_FLAG = TRUE; + temp++; + temp++; + } + else if( strncmp( temp, "CA", 2 ) == 0 ) { + Mptr->CA_LTG = TRUE; + LTG_TYPE_FLAG = TRUE; + temp++; + temp++; + } + else + TYPE_NOT_FOUND = TRUE; + } + + (++string); + + if( *string == NULL ) { + (*NDEX)++; + return TRUE; + } +/* else + (*NDEX)++; TURNED OFF 07-24-97 */ + + LTG_LOC_FLAG = FALSE; + /*-- CHECK FOR LIGHTNING LOCATION ------------*/ + if( strcmp( *string, "DSNT" ) == 0 ) { + Mptr->DSNT_LTG = TRUE; + LTG_LOC_FLAG = TRUE; + } + else if( strcmp( *string, "AP" ) == 0 ) { + Mptr->AP_LTG = TRUE; + LTG_LOC_FLAG = TRUE; + } + else if( strcmp( *string, "VCY" ) == 0 || + strcmp( *string, "VC" ) == 0 ) { + Mptr->VcyStn_LTG = TRUE; + LTG_LOC_FLAG = TRUE; + } + else if( strcmp( *string, "OVHD" ) == 0 ) { + Mptr->OVHD_LTG = TRUE; + LTG_LOC_FLAG = TRUE; + } + + if( LTG_LOC_FLAG ) + (++string); + + if( *string == NULL ) { + if( LTG_LOC_FLAG ) + (*NDEX)++; + if( LTG_TYPE_FLAG ) + (*NDEX)++; + return TRUE; + } + + LTG_DIR_FLAG = FALSE; + /*-- CHECK FOR LIGHTNING DIRECTION -----------*/ + if( strcmp( *string, "N" ) == 0 || + strcmp( *string, "NE" ) == 0 || + strcmp( *string, "NW" ) == 0 || + strcmp( *string, "S" ) == 0 || + strcmp( *string, "SE" ) == 0 || + strcmp( *string, "SW" ) == 0 || + strcmp( *string, "E" ) == 0 || + strcmp( *string, "W" ) == 0 ) { + strcpy( Mptr->LTG_DIR, *string); + LTG_DIR_FLAG = TRUE; + } + + + if( LTG_TYPE_FLAG ) + (*NDEX)++; + if( LTG_LOC_FLAG ) + (*NDEX)++; + if( LTG_DIR_FLAG ) + (*NDEX)++; + + if( !(LTG_TYPE_FLAG) && /* Added on 02/23/98 to prevent */ + !(LTG_LOC_FLAG) && /* infinite looping when 'LTG' */ + !(LTG_DIR_FLAG) ) /* is present in the input, but */ + (*NDEX)++; /* all other related parameters */ + /* are missing or invalid */ + return TRUE; + } + } +} + + +#pragma comment (compiler) +#pragma comment (date) +#pragma comment (timestamp) +#pragma pagesize(80) + +#include "metar_structs.h" /* standard header file */ + +#pragma subtitle(" ") +#pragma page(1) +#pragma subtitle("subtitle - description ") +/********************************************************************/ +/* */ +/* Title: isRecentWx */ +/* Organization: W/OSO242 - GRAPHICS AND DISPLAY SECTION */ +/* Date: 15 Sep 1994 */ +/* Programmer: CARL MCCALLA */ +/* Language: C/370 */ +/* */ +/* Abstract: Determine whether or not the current and subsequent */ +/* groups from the METAR report make up a valid report */ +/* recent weather. */ +/* */ +/* Input: token - the addr of a ptr to a character token */ +/* that may or may not be a recent weather */ +/* group. */ +/* */ +/* Mptr - a pointer to a structure that has the */ +/* data type Decoded_METAR. */ +/* */ +/* NDEX - a pointer to an integer that is the i*NDEX */ +/* into an array that contains the indi- */ +/* vidual groups of the METAR report being */ +/* decoded. Upon entry, NDEX is the i*NDEX */ +/* of the current group of the METAR report */ +/* that is to be indentified. */ +/* */ +/* Output: TRUE - if the input token (and possibly subse- */ +/* quent groups) are decoded as recent wx. */ +/* FALSE - if the input token (and possibly subse- */ +/* quent groups) are not decoded as recent */ +/* wx. */ +/* */ +/* External Functions Called: */ +/* nisdigit */ +/* */ +/* Input: x */ +/* */ +/* Output: x */ +/* */ +/* Modification History: */ +/* None. */ +/* */ +/********************************************************************/ +#pragma page(1) +static MDSP_BOOL isRecentWX( char **token, Decoded_METAR *Mptr, + int *NDEX ) +{ + static char *phenom[] = {"FCB", "-DZB", "DZB", "+DZB", + "FZDZB", "-RAB", "RAB", "+RAB", + "SHRAB", "TSRAB", "FZRAB", "-SNB", + "SNB", "+SNB", "DRSNB", "BLSNB", + "SHSNB", "TSSNB", "-SGB", "SGB", + "+SGB", "ICB", "-PEB", "PEB", "+PEB", + "SHPEB", "TSPEB", "GRB", "SHGRB", + "TSGRB", "GSB", "SHGSB", "TSGSB", "-GSB", + "+GSB", "TSB", "VCTSB", "-TSRAB", + "TSRAB", "+TSRAB", "-TSSNB", "TSSNB", + "+TSSNB", "-TSPEB", "TSPEB", "+TSPEB", + "-TSGSB", "TSGSB", "+TSGSB", + "VCSHB", "-SHRAB", "+SHRAB", "-SHSNB", + "+SHSNB", "-SHPEB", "+SHPEB", + "-SHGSB", "+SHGSB", "-FZDZB", "+FZDZB", + "-FZRAB", "+FZRAB", "FZFGB", + "+FZFGB", "BRB", "FGB", "VCFGB", "MIFGB", + "PRFGB", "BCFGB", "FUB", + "VAB", "DUB", "DRDUB", "BLDUB", "SAB", + "DRSAB", "BLSAB", "HZB", + "BLPYB", "BLSNB", "+BLSNB", "VCBLSNB", + "BLSAB", "+BLSAB", + "VCBLSAB", "+BLDUB", "VCBLDUB", "POB", + "VCPOB", "SQB", "FCB", "+FCB", + "VCFCB", "SSB", "+SSB", "VCSSB", "DSB", + "+DSB", "VCDSB", + + + "FCE", "-DZE", "DZE", "+DZE", + "FZDZE", "-RAE", "RAE", "+RAE", + "SHRAE", "TSRAE", "FZRAE", "-SNE", + "SNE", "+SNE", "DRSNE", "BLSNE", + "SHSNE", "TSSNE", "-SGE", "SGE", + "+SGE", "ICE", "-PEE", "PEE", "+PEE", + "SHPEE", "TSPEE", "GRE", "SHGRE", + "TSGRE", "GSE", "SHGSE", "TSGSE", "-GSE", + "+GSE", "TSE", "VCTSE", "-TSRAE", + "TSRAE", "+TSRAE", "-TSSNE", "TSSNE", + "+TSSNE", "-TSPEE", "TSPEE", "+TSPEE", + "-TSGSE", "TSGSE", "+TSGSE", + "VCSHE", "-SHRAE", "+SHRAE", "-SHSNE", + "+SHSNE", "-SHPEE", "+SHPEE", + "-SHGSE", "+SHGSE", "-FZDZE", "+FZDZE", + "-FZRAE", "+FZRAE", "FZFGE", + "+FZFGE", "BRE", "FGE", "VCFGE", "MIFGE", + "PRFGE", "BCFGE", "FUE", + "VAE", "DUE", "DRDUE", "BLDUE", "SAE", + "DRSAE", "BLSAE", "HZE", + "BLPYE", "BLSNE", "+BLSNE", "VCBLSNE", + "BLSAE", "+BLSAE", + "VCBLSAE", "+BLDUE", "VCBLDUE", "POE", + "VCPOE", "SQE", "FCE", "+FCE", + "VCFCE", "SSE", "+SSE", "VCSSE", "DSE", + "+DSE", "VCDSE", "4-Zs"}; + + int i, + beg_hour, + beg_min, + end_hour, + end_min; + + char *temp, + *free_temp, + *numb_char, + *C_char; + + + if( *token == NULL ) + return FALSE; + + + if( (free_temp = temp = (char *) malloc(sizeof(char) * + (strlen(*token) + 1))) == NULL ) { + return FALSE; + } + else + strcpy(temp,*token); + + + + while ( *temp != '\0' ) { +/* +printf("isRecentWX: JUST inside while-loop, *NDEX = %d\n",*NDEX); +printf("isRecentWX: JUST inside while-loop, temp = %s\n",temp); +*/ + i = 0; + + beg_hour = beg_min = end_hour = end_min = MAXINT; + + while( strncmp(temp, phenom[i],strlen(phenom[i])) != 0 && + strcmp(phenom[i],"4-Zs") != 0 ) + i++; + + if( strcmp(phenom[i],"4-Zs") != 0 ) { + + C_char = (strlen(phenom[i]) - 1) + temp; + numb_char = C_char + 1; + + if( *numb_char == '\0') + return FALSE; + + if( nisdigit(numb_char,4) && strlen(numb_char) >= 4) { + if( *C_char == 'B' ) { + beg_hour = antoi( numb_char, 2 ); + beg_min = antoi( numb_char+2,2 ); + temp = numb_char+4; + + if( *NDEX < 3 ) { + Mptr->ReWx[*NDEX].Bmm = beg_min; + Mptr->ReWx[*NDEX].Bhh = beg_hour; + } + + temp = numb_char + 4; + + if( *(numb_char+4) == 'E' ) { + numb_char += 5; + if( nisdigit(numb_char,4) && + strlen(numb_char) >= 4 ) { + end_hour = antoi( numb_char, 2 ); + end_min = antoi( numb_char+2,2 ); + temp = numb_char+4; + + if( *NDEX < 3 ) { + Mptr->ReWx[*NDEX].Emm = end_min; + Mptr->ReWx[*NDEX].Ehh = end_hour; + } + + temp = numb_char + 4; + + } + else if( nisdigit(numb_char,2) && + strlen(numb_char) >= 2 ) { + end_min = antoi( numb_char,2 ); + + if( *NDEX < 3 ) { + Mptr->ReWx[*NDEX].Emm = end_min; + } + temp = numb_char+2; + } + + } + + if( *NDEX < 3 ) { + strncpy(Mptr->ReWx[*NDEX].Recent_weather, + phenom[i], (strlen(phenom[i])-1) ); + (*NDEX)++; + } + if( *temp == '\0' ) { + free( free_temp ); + return TRUE; + } + + } + else { + end_hour = antoi( numb_char, 2 ); + end_min = antoi( numb_char+2,2 ); + + temp = numb_char + 4; + + if( *NDEX < 3 ) { + Mptr->ReWx[*NDEX].Emm = end_min; + Mptr->ReWx[*NDEX].Ehh = end_hour; + + } + + temp = numb_char+4; + + if( *(numb_char+4) == 'B' ) { + numb_char += 5; + if( nisdigit(numb_char,4) && + strlen(numb_char) >= 4 ) { + + beg_hour = antoi(numb_char,2); + beg_min = antoi(numb_char+2,2); + temp = numb_char + 4; + + if( *NDEX < 3 ) { + Mptr->ReWx[*NDEX].Bmm = beg_min; + Mptr->ReWx[*NDEX].Bhh = beg_hour; + + } + + temp = numb_char+4; + } + else if( nisdigit(numb_char,2) && + strlen(numb_char) >= 2 ) { + beg_min = antoi( numb_char,2 ); + + if( *NDEX < 3 ) { + Mptr->ReWx[*NDEX].Bmm = beg_min; + } + + temp = numb_char+2; + } + + } + + if( *NDEX < 3 ) { + strncpy(Mptr->ReWx[*NDEX].Recent_weather, + phenom[i], (strlen(phenom[i])-1) ); + (*NDEX)++; + } + + if( *temp == '\0' ) { + free( free_temp ); + return TRUE; + } + + } + + } + else if(nisdigit(numb_char,2) && strlen(numb_char) >= 2 ) { + if( *C_char == 'B' ) { + beg_min = antoi( numb_char,2 ); + + if( *NDEX < 3 ) { + Mptr->ReWx[*NDEX].Bmm = beg_min; + + + } + + temp = numb_char+2; + + if( *(numb_char+2) == 'E' ) { + numb_char += 3; + if( nisdigit(numb_char,4) && + strlen(numb_char) >= 4 ) { + end_hour = antoi( numb_char,2 ); + end_min = antoi( numb_char+2,2 ); + + if( *NDEX < 3 ) { + Mptr->ReWx[*NDEX].Emm = end_min; + Mptr->ReWx[*NDEX].Ehh = end_hour; + + + } + + temp = numb_char+4; + } + else if( nisdigit(numb_char,2) && + strlen(numb_char) >= 2 ) { + end_min = antoi( numb_char,2 ); + + if( *NDEX < 3 ) + Mptr->ReWx[*NDEX].Emm = end_min; + + + temp = numb_char+2; + } + } + if( *NDEX < 3 ) { + strncpy( Mptr->ReWx[*NDEX].Recent_weather, + phenom[i], (strlen(phenom[i])-1) ); + + (*NDEX)++; + } + if( *temp == '\0' ) { + free( free_temp ); + return TRUE; + } + } + else { + end_min = antoi( numb_char, 2 ); + + if( *NDEX < 3 ) + Mptr->ReWx[*NDEX].Emm = end_min; + + temp = numb_char+2; + + if( *(numb_char+2) == 'B' ) { + numb_char += 3; + if( nisdigit(numb_char,4) && + strlen(numb_char) >= 4 ) { + beg_hour = antoi( numb_char,2 ); + beg_min = antoi( numb_char+2,2 ); + + if( *NDEX < 3 ) { + Mptr->ReWx[*NDEX].Bmm = beg_min; + Mptr->ReWx[*NDEX].Bhh = beg_hour; + + } + + temp = numb_char+4; + } + else if( nisdigit(numb_char,2) && + strlen(numb_char) >= 2 ) { + beg_min = antoi( numb_char,2 ); + + if( *NDEX < 3 ) + Mptr->ReWx[*NDEX].Bmm = beg_min; + + + temp = numb_char+2; + } + + } + if( *NDEX < 3 ) { + strncpy( Mptr->ReWx[*NDEX].Recent_weather, + phenom[i], (strlen(phenom[i])-1) ); + (*NDEX)++; + } + if( *temp == '\0' ) { + free( free_temp ); + return TRUE; + } + + } + + } + else { + free( free_temp ); + + if( *NDEX > 0 && *NDEX < 3 ) + return TRUE; + else + return FALSE; + } + + } + else { + free( free_temp ); + return FALSE; + } + + } + +} + + +#pragma subtitle(" ") +#pragma page(1) +#pragma subtitle("subtitle - description ") +/********************************************************************/ +/* */ +/* Title: isVariableCIG */ +/* Organization: W/OSO242 - GRAPHICS AND DISPLAY SECTION */ +/* Date: 21 Nov 1994 */ +/* Programmer: CARL MCCALLA */ +/* Language: C/370 */ +/* */ +/* Abstract: isVariableCIG determines whether or not the */ +/* current group in combination with the next */ +/* one or more groups is a report of variable */ +/* ceiling. */ +/* */ +/* External Functions Called: */ +/* nisdigit */ +/* */ +/* Input: token - a pointer to an array of METAR report */ +/* groups. */ +/* Mptr - a pointer to a decoded_METAR structure */ +/* NDEX - the index value of the current METAR */ +/* report group array element. */ +/* */ +/* Output: TRUE, if the token is currently pointing to */ +/* METAR report group(s) that a report of vari- */ +/* ble ceiling. */ +/* */ +/* Modification History: */ +/* None. */ +/* */ +/********************************************************************/ +#pragma page(1) +static MDSP_BOOL isVariableCIG( char **token, Decoded_METAR *Mptr, + int *NDEX ) +{ + char *V_char; + + if( *token == NULL ) + return FALSE; + + if( strcmp(*token, "CIG") != 0 ) + return FALSE; + else + (++token); + + if( *token == NULL ) + return FALSE; + + if( (V_char = strchr(*token,'V')) != NULL ) { + if( nisdigit(*token, (V_char - *token)) && + nisdigit( V_char+1, strlen(V_char+1)) ) { + Mptr->minCeiling = antoi(*token, (V_char - *token)); + Mptr->maxCeiling = atoi(V_char+1); + + (*NDEX)++; + (*NDEX)++; + return TRUE; + } + else + return FALSE; + } + else + return FALSE; +} +#pragma subtitle(" ") +#pragma page(1) +#pragma subtitle("subtitle - description ") +/********************************************************************/ +/* */ +/* Title: isCeil2ndSite */ +/* Organization: W/OSO242 - GRAPHICS AND DISPLAY SECTION */ +/* Date: 15 Sep 1994 */ +/* Programmer: CARL MCCALLA */ +/* Language: C/370 */ +/* */ +/* Abstract: isCeil2ndSite determines whether or not the */ +/* current group in combination with the next */ +/* one or more groups is a report of a ceiling */ +/* at a secondary site. */ +/* */ +/* External Functions Called: */ +/* nisdigit */ +/* */ +/* Input: token - a pointer to an array of METAR report */ +/* groups. */ +/* Mptr - a pointer to a decoded_METAR structure */ +/* NDEX - the index value of the current METAR */ +/* report group array element. */ +/* */ +/* Output: TRUE, if the token is currently pointing to */ +/* METAR report group(s) that are reporting */ +/* ceiling at a secondary site. */ +/* */ +/* External Functions Called: */ +/* nisdigit */ +/* */ +/* */ +/* Modification History: */ +/* None. */ +/* */ +/********************************************************************/ +#pragma page(1) +static MDSP_BOOL isCIG2ndSite( char **token, Decoded_METAR *Mptr, + int *NDEX) +{ + int CIG2ndSite; + + if( (*token) == NULL ) + return FALSE; + + if(strcmp(*token,"CIG") != 0) + return FALSE; + else + (++token); + + if( (*token) == NULL ) + return FALSE; + + if( strlen(*token) != 3 ) + return FALSE; + + if( nisdigit(*token,3) ) + { + CIG2ndSite = atoi(*token ) * 10; + + if( strncmp(*(++token),"RY",2) != 0) + return FALSE; + else { + strcpy(Mptr->CIG_2ndSite_LOC, *token ); + Mptr->CIG_2ndSite_Meters = CIG2ndSite; + (*NDEX)++; + (*NDEX)++; + (*NDEX)++; + return TRUE; + } + } + else + return FALSE; +} + +#pragma subtitle(" ") +#pragma page(1) +#pragma subtitle("subtitle - description ") +/********************************************************************/ +/* */ +/* Title: isPRESFR */ +/* Organization: W/OSO242 - GRAPHICS AND DISPLAY SECTION */ +/* Date: 20 Nov 1994 */ +/* Programmer: CARL MCCALLA */ +/* Language: C/370 */ +/* */ +/* Abstract: x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* */ +/* External Functions Called: */ +/* None. */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* */ +/* Input: x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* */ +/* Output: x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* */ +/* Modification History: */ +/* None. */ +/* */ +/********************************************************************/ +#pragma page(1) + +static MDSP_BOOL isPRESFR( char *string, Decoded_METAR *Mptr, int *NDEX) +{ + + if( string == NULL ) + return FALSE; + + if( strcmp(string, "PRESFR") != 0 ) + return FALSE; + else { + Mptr->PRESFR = TRUE; + (*NDEX)++; + return TRUE; + } + +} +#pragma subtitle(" ") +#pragma page(1) +#pragma subtitle("subtitle - description ") +/********************************************************************/ +/* */ +/* Title: isPRESRR */ +/* Organization: W/OSO242 - GRAPHICS AND DISPLAY SECTION */ +/* Date: 20 Nov 1994 */ +/* Programmer: CARL MCCALLA */ +/* Language: C/370 */ +/* */ +/* Abstract: x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* */ +/* External Functions Called: */ +/* None. */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* */ +/* Input: x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* */ +/* Output: x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* */ +/* Modification History: */ +/* None. */ +/* */ +/********************************************************************/ +#pragma page(1) + +static MDSP_BOOL isPRESRR( char *string, Decoded_METAR *Mptr, int *NDEX) +{ + + if( string == NULL ) + return FALSE; + + if( strcmp(string, "PRESRR") != 0 ) + return FALSE; + else { + Mptr->PRESRR = TRUE; + (*NDEX)++; + return TRUE; + } + +} + +#pragma subtitle(" ") +#pragma page(1) +#pragma subtitle("subtitle - description ") +/********************************************************************/ +/* */ +/* Title: isSLP */ +/* Organization: W/OSO242 - GRAPHICS AND DISPLAY SECTION */ +/* Date: 15 Sep 1994 */ +/* Programmer: CARL MCCALLA */ +/* Language: C/370 */ +/* */ +/* Abstract: */ +/* */ +/* External Functions Called: */ +/* None. */ +/* */ +/* Input: x */ +/* */ +/* Output: x */ +/* */ +/* Modification History: */ +/* None. */ +/* */ +/********************************************************************/ +#pragma page(1) + +static MDSP_BOOL isSLP( char **token, Decoded_METAR *Mptr, int *NDEX ) +{ + + /***************************/ + /* DECLARE LOCAL VARIABLES */ + /***************************/ + + int pressure, + ndex; + + /*************************/ + /* BEGIN BODY OF ROUTINE */ + /*************************/ + + if( *token == NULL ) + return FALSE; + + if( strcmp(*token, "SLPNO") == 0 ) { + Mptr->SLPNO = TRUE; + (*NDEX)++; + return TRUE; + } + + + if( strncmp(*token, "SLP", 3) != 0 ) + return FALSE; + else + { + if( strncmp(*token, "SLP", 3) == 0 && + strcmp(*token,"SLP") != 0 ) + { + if( nisdigit( *token+3, 3) ) + { + pressure = atoi(*token+3); + + if(pressure >= 550 ) + Mptr->SLP = ((float) pressure)/10. + 900.; + else + Mptr->SLP = ((float) pressure)/10. + 1000.; + (*NDEX)++; + return TRUE; + } + else + return FALSE; + } + else + { + (++token); + + if( *token == NULL ) + return FALSE; + + if( nisdigit( *token, 3) ) + { + pressure = atoi(*token); + + if(pressure >= 550 ) + Mptr->SLP = ((float) pressure)/10. + 900.; + else + Mptr->SLP = ((float) pressure)/10. + 1000.; + + (*NDEX)++; + (*NDEX)++; + return TRUE; + } + else + return FALSE; + } + + } + +} +#pragma page(1) +static MDSP_BOOL isSectorVsby( char **string, Decoded_METAR *Mptr, + int *NDEX ) +{ + /***************************/ + /* DECLARE LOCAL VARIABLES */ + /***************************/ + + int result, + tempstrlen = 20; + + float vsby; + char dd[3], + temp[20], + *slash; + + /*************************/ + /* START BODY OF ROUTINE */ + /*************************/ + + if( *string == NULL ) + return FALSE; + + memset( dd, '\0', 3 ); + + if( strcmp(*string, "VIS") != 0 ) + return FALSE; + else { + ++string; + + if( *string == NULL ) + return FALSE; + + if( strncmp(*string,"NE", 2) == 0 ) + strncpy(dd,*string,2); + else if( strncmp(*string,"SE",2) == 0 ) + strncpy(dd,*string,2); + else if( strncmp(*string,"NW",2) == 0 ) + strncpy(dd,*string,2); + else if( strncmp(*string,"SW",2) == 0 ) + strncpy(dd,*string,2); + else if( strncmp(*string,"N",1) == 0 ) + strncpy(dd,*string,1); + else if( strncmp(*string,"E",1) == 0 ) + strncpy(dd,*string,1); + else if( strncmp(*string,"S",1) == 0 ) + strncpy(dd,*string,1); + else if( strncmp(*string,"W",1) == 0 ) + strncpy(dd,*string,1); + else + return FALSE; + + (++string); + if( *string == NULL ) + return FALSE; +/* +printf("DCDMTRMK result = %d\n", + strspn(*string,"0123456789/M")); +*/ + if( (result = strspn(*string,"0123456789/M")) == 0 ) + return FALSE; + else if(nisdigit(*string,result) ) + vsby = antoi(*string,result); + else if(result >= tempstrlen-1) + return FALSE; + else { + memset( temp, '\0', tempstrlen ); + strncpy(temp, *string, result); +/* +printf("DCDMTRMK temp = %s\n",temp); +*/ + if( strcmp(temp, "M1/4") == 0) { + strcpy(Mptr->SectorVsby_Dir,dd); + Mptr->SectorVsby = 0.0; + (*NDEX)++; + (*NDEX)++; + (*NDEX)++; + return TRUE; + } + if( strchr(temp,'M') != NULL ) + return FALSE; + if( (slash = strchr(temp,'/')) == NULL ) + return FALSE; + else if(nisdigit(temp,(slash-temp)) && + nisdigit(slash+1,strlen(slash+1)) ) { + vsby = fracPart(temp); + if(vsby > 0.875) + return FALSE; + else { + Mptr->SectorVsby = vsby; + strcpy(Mptr->SectorVsby_Dir,dd); + (*NDEX)++; + (*NDEX)++; + (*NDEX)++; + return TRUE; + } + + } + + } + + + (++string); + if( *string == NULL ) { + Mptr->SectorVsby = vsby; + strcpy(Mptr->SectorVsby_Dir,dd); + (*NDEX)++; + (*NDEX)++; + (*NDEX)++; + return TRUE; + } + else if( (result = strspn(*string,"0123456789/")) == 0 ) { + Mptr->SectorVsby = vsby; + strcpy(Mptr->SectorVsby_Dir,dd); + (*NDEX)++; + (*NDEX)++; + (*NDEX)++; + return TRUE; + } + else if( (slash = strchr(*string,'/')) == NULL ) { + Mptr->SectorVsby = vsby; + strcpy(Mptr->SectorVsby_Dir,dd); + (*NDEX)++; + (*NDEX)++; + (*NDEX)++; + return TRUE; + } + else { + if( fracPart(*string) > 0.875 ) { + Mptr->SectorVsby = vsby; + strcpy(Mptr->SectorVsby_Dir,dd); + (*NDEX)++; + (*NDEX)++; + (*NDEX)++; + return TRUE; + } + else { + vsby += fracPart(*string); + Mptr->SectorVsby = vsby; + strcpy(Mptr->SectorVsby_Dir,dd); + (*NDEX)++; + (*NDEX)++; + (*NDEX)++; + (*NDEX)++; + return TRUE; + } + + } + + } + +} + +#pragma subtitle(" ") +#pragma page(1) +#pragma subtitle("subtitle - description ") +/********************************************************************/ +/* */ +/* Title: isGR */ +/* Organization: W/OSO242 - GRAPHICS AND DISPLAY SECTION */ +/* Date: 20 Nov 1994 */ +/* Programmer: CARL MCCALLA */ +/* Language: C/370 */ +/* */ +/* Abstract: x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* */ +/* External Functions Called: */ +/* None. */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* */ +/* Input: x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* */ +/* Output: x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* */ +/* Modification History: */ +/* None. */ +/* */ +/********************************************************************/ +#pragma page(1) +static MDSP_BOOL isGR( char **string, Decoded_METAR *Mptr, int *NDEX) +{ + char *slash; + + if( *string == NULL ) + return FALSE; + + if( strcmp(*string, "GS") == 0 ) { + Mptr->GR = TRUE; + (*NDEX)++; + return TRUE; + } + + + if( strcmp(*string, "GR") != 0 ) + return FALSE; + else { + (++string); + + if( *string == NULL ) + return FALSE; + + if( (slash = strchr( *string, '/' )) != NULL ) { + if( strcmp( *string, "M1/4" ) == 0 ) { + Mptr->GR_Size = 1./8.; + Mptr->GR = TRUE; + (*NDEX)++; + (*NDEX)++; + return TRUE; + } + else if( nisdigit( *string, (slash - *string) ) && + nisdigit( slash+1, strlen(slash+1)) ) { + Mptr->GR_Size = fracPart( *string ); + Mptr->GR = TRUE; + (*NDEX)++; + (*NDEX)++; + return TRUE; + } + else { + Mptr->GR = TRUE; + (*NDEX)++; + return TRUE; + } + } + else if( nisdigit( *string, strlen(*string) ) ) { + Mptr->GR_Size = antoi( *string, strlen(*string) ); + Mptr->GR = TRUE; + + (++string); + + if( *string == NULL ) + return FALSE; + + if( (slash = strchr( *string, '/' )) != NULL ) { + if( nisdigit( *string, (slash - *string) ) && + nisdigit( slash+1, strlen(slash+1)) ) { + Mptr->GR_Size += fracPart( *string ); + (*NDEX)++; + (*NDEX)++; + (*NDEX)++; + return TRUE; + } + else { + (*NDEX)++; + (*NDEX)++; + return TRUE; + } + } + else { + (*NDEX)++; + (*NDEX)++; + return TRUE; + } + } + else { + Mptr->GR = TRUE; + (*NDEX)++; + return TRUE; + } + } +} + +#pragma subtitle(" ") +#pragma page(1) +#pragma subtitle("subtitle - description ") +/********************************************************************/ +/* */ +/* Title: isVIRGA */ +/* Organization: W/OSO242 - GRAPHICS AND DISPLAY SECTION */ +/* Date: 20 Nov 1994 */ +/* Programmer: CARL MCCALLA */ +/* Language: C/370 */ +/* */ +/* Abstract: x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* */ +/* External Functions Called: */ +/* None. */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* */ +/* Input: x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* */ +/* Output: x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* */ +/* Modification History: */ +/* None. */ +/* */ +/********************************************************************/ +#pragma page(1) + +static MDSP_BOOL isVIRGA( char **string, Decoded_METAR *Mptr, int *NDEX) +{ + + if( *string == NULL ) + return FALSE; + + if( strcmp(*string, "VIRGA") != 0 ) + return FALSE; + else { + Mptr->VIRGA = TRUE; + (*NDEX)++; + + (++string); + + if( *string == NULL ) + return FALSE; + + + if( strcmp( *string, "N" ) == 0 || + strcmp( *string, "S" ) == 0 || + strcmp( *string, "E" ) == 0 || + strcmp( *string, "W" ) == 0 || + strcmp( *string, "NE" ) == 0 || + strcmp( *string, "NW" ) == 0 || + strcmp( *string, "SE" ) == 0 || + strcmp( *string, "SW" ) == 0 ) { + strcpy(Mptr->VIRGA_DIR, *string); + (*NDEX)++; + } + return TRUE; + } + +} + +#pragma page(1) +static MDSP_BOOL isSfcObscuration( char *string, Decoded_METAR *Mptr, + int *NDEX ) +{ + /***************************/ + /* DECLARE LOCAL VARIABLES */ + /***************************/ + + static char *WxSymbols[] = {"BCFG", "BLDU", "BLSA", "BLPY", + "DRDU", "DRSA", "DRSN", "DZ", "DS", "FZFG", "FZDZ", "FZRA", + "FG", "FC", "FU", "GS", "GR", "HZ", "IC", "MIFG", + "PE", "PO", "RA", "SHRA", "SHSN", "SHPE", "SHGS", + "SHGR", "SN", "SG", "SQ", "SA", "SS", "TSRA", + "TSSN", "TSPE", "TSGS", "TSGR", "TS", + "VCSH", "VCPO", "VCBLDU", "VCBLSA", "VCBLSN", + "VCFG", "VCFC","VA", NULL}; + int i, + ndex; + char *numLoc, + ww[12], + *temp; + + MDSP_BOOL IS_NOT_FOUND; + + /*************************/ + /* START BODY OF ROUTINE */ + /*************************/ + + if( string == NULL ) + return FALSE; + + memset( ww, '\0', sizeof(ww) ); + + if( strlen(string) < 4 ) + return FALSE; + + if( strncmp(string, "-X",2 ) != 0 ) + return FALSE; + + if( !(nisdigit(string+(strlen(string)-1), 1)) ) + return FALSE; + else { + temp = string + 2; + strncpy( ww, temp, (strlen(string)-2) ); + + ndex = 0; + temp = ww; + numLoc = temp + (strlen(temp) - 1 ); + + while( temp < numLoc && ndex < 6 ) { + i = 0; + + IS_NOT_FOUND = TRUE; + + while( WxSymbols[i] != NULL && IS_NOT_FOUND ) { + if( strncmp( WxSymbols[i], temp, strlen(WxSymbols[i])) + != 0 ) + i++; + else + IS_NOT_FOUND = FALSE; + } + + if( WxSymbols[i] == NULL ) { + (*NDEX)++; + return FALSE; + } + else { + strcpy(&(Mptr->SfcObscuration[ndex][0]),WxSymbols[i]); + temp += strlen(WxSymbols[i]); + ndex++; + } + + } + + if( ndex > 0 ) { + Mptr->Num8thsSkyObscured = antoi( numLoc,1 ); + (*NDEX)++; + return TRUE; + } + else { + (*NDEX)++; + return FALSE; + } + + } + +} + +#pragma page(1) +static MDSP_BOOL isCeiling( char *string, Decoded_METAR *Mptr, int *NDEX ) +{ + + if( string == NULL ) + return FALSE; + + if( !(strncmp(string,"CIG",3) == 0 && strlen(string) >= 5) ) + return FALSE; + else { + if( strcmp(string, "CIGNO") == 0 ) { + Mptr->CIGNO = TRUE; + (*NDEX)++; + return TRUE; + } + else if( strlen( string+3 ) == 3 ) { + if( nisdigit(string+3, strlen(string+3)) && + strlen(string+3) == 3 ) { + Mptr->Ceiling = atoi(string+3) * 100; + (*NDEX)++; + return TRUE; + } + else + return FALSE; + } + else if( strlen(string+3) == 4 ) { + if( *(string+3) == 'E' && nisdigit(string+4,3) ) { + Mptr->Estimated_Ceiling = antoi(string+4,3) * 100; + (*NDEX)++; + return TRUE; + } + else + return FALSE; + } + else + return FALSE; + + } + +} +#pragma page(1) +static MDSP_BOOL isVrbSky( char **string, Decoded_METAR *Mptr, int *NDEX ) +{ + static char *cldPtr[] = {"FEW", "SCT", "BKN", "OVC", NULL }; + MDSP_BOOL IS_NOT_FOUND; + int i; + char SKY1[ SKY1_len ]; + + + if( *string == NULL ) + return FALSE; + + + memset( SKY1, '\0', SKY1_len ); + i = 0; + IS_NOT_FOUND = TRUE; + + while( cldPtr[i] != NULL && IS_NOT_FOUND ) { +#ifdef DEBUGQQ + printf("isVrbSky: *string = %s cldPtr[%d] = %s\n", + *string,i,cldPtr[i]); +#endif + if( strncmp(*string, cldPtr[i], strlen(cldPtr[i])) != 0 ) + i++; + else + IS_NOT_FOUND = FALSE; + } + + if( cldPtr[i] == NULL ) + return FALSE; + else { +#ifdef DEBUGQQ + printf("isVrbSky: *string = %s = cldPtr[%d] = %s\n", + *string,i,cldPtr[i]); +#endif + strcpy( SKY1, cldPtr[i] ); + + (++string); + + if( *string == NULL ) + return FALSE; + + + if( strcmp(*string, "V") != 0 ) + return FALSE; + else { + (++string); + + if( *string == NULL ) + return FALSE; + + i = 0; + IS_NOT_FOUND = TRUE; + while( cldPtr[i] != NULL && IS_NOT_FOUND ) { +#ifdef DEBUGQQ + printf("isVrbSky: *string = %s cldPtr[%d] = %s\n", + *string,i,cldPtr[i]); +#endif + if( strncmp(*string, cldPtr[i], strlen(cldPtr[i])) != 0 ) + i++; + else + IS_NOT_FOUND = FALSE; + } + + if( cldPtr[i] == NULL ) { + (*NDEX)++; + (*NDEX)++; + return FALSE; + } + else { + if(strlen(SKY1) == 6 ) { + if( nisdigit(SKY1+3,3)) { + strncpy(Mptr->VrbSkyBelow,SKY1,3); + strcpy(Mptr->VrbSkyAbove,cldPtr[i]); + Mptr->VrbSkyLayerHgt = antoi(SKY1+3,3)*100; + (*NDEX)++; + (*NDEX)++; + (*NDEX)++; + return TRUE; + } + else { + (*NDEX)++; + (*NDEX)++; + (*NDEX)++; + return TRUE; + } + } + else { + strcpy(Mptr->VrbSkyBelow,SKY1); + strcpy(Mptr->VrbSkyAbove,cldPtr[i]); + (*NDEX)++; + (*NDEX)++; + (*NDEX)++; + return TRUE; + } + + } + + } + + } + +} + +#pragma page(1) +static MDSP_BOOL isObscurAloft( char **string, Decoded_METAR *Mptr, + int *NDEX ) +{ + + /***************************/ + /* DECLARE LOCAL VARIABLES */ + /***************************/ + + static char *WxSymbols[] = {"BCFG", "BLDU", "BLSA", "BLPY", + "DRDU", "DRSA", "DRSN", "DZ", "DS", "FZFG", "FZDZ", "FZRA", + "FG", "FC", "FU", "GS", "GR", "HZ", "IC", "MIFG", + "PE", "PO", "RA", "SHRA", "SHSN", "SHPE", "SHGS", + "SHGR", "SN", "SG", "SQ", "SA", "SS", "TSRA", + "TSSN", "TSPE", "TSGS", "TSGR", "TS", + "VCSH", "VCPO", "VCBLDU", "VCBLSA", "VCBLSN", + "VCFG", "VCFC","VA", NULL}; + int i; + char *saveTemp, + *temp; + + MDSP_BOOL IS_NOT_FOUND; + + /*************************/ + /* START BODY OF ROUTINE */ + /*************************/ + + if( *string == NULL ) + return FALSE; + + saveTemp = temp = *string; + + if( *temp == '\0' ) + return FALSE; + + while( *temp != '\0' ) { + i = 0; + + IS_NOT_FOUND = TRUE; + + while( WxSymbols[i] != NULL && IS_NOT_FOUND ) { + if( strncmp(temp,WxSymbols[i],strlen(WxSymbols[i])) != 0 ) + i++; + else + IS_NOT_FOUND = FALSE; + } + + if( WxSymbols[i] == NULL ) { + return FALSE; + } + else + temp += strlen(WxSymbols[i]); + } + + (++string); + + if( *string == NULL ) + return FALSE; + + if( strlen(*string) != 6 ) + return FALSE; + else { + if((strncmp(*string,"FEW",3) == 0 || + strncmp(*string,"SCT",3) == 0 || + strncmp(*string,"BKN",3) == 0 || + strncmp(*string,"OVC",3) == 0 ) && + (nisdigit(*string+3,3) && + strcmp(*string+3,"000") != 0 )) { + strcpy(Mptr->ObscurAloft,saveTemp); + strncpy(Mptr->ObscurAloftSkyCond, *string,3); + Mptr->ObscurAloftHgt = atoi(*string+3)*100; + (*NDEX)++; + (*NDEX)++; + (*NDEX)++; + return TRUE; + } + else { + (*NDEX)++; + return TRUE; + } + + } + +} +#pragma page(1) +static MDSP_BOOL isNOSPECI( char *string, Decoded_METAR *Mptr, int *NDEX ) +{ + + if( string == NULL ) + return FALSE; + + if( strcmp(string,"NOSPECI") != 0 ) + return FALSE; + else { + Mptr->NOSPECI = TRUE; + (*NDEX)++; + return TRUE; + } +} +#pragma page(1) +static MDSP_BOOL isLAST( char *string, Decoded_METAR *Mptr, int *NDEX ) +{ + + if( string == NULL ) + return FALSE; + + if( strcmp(string,"LAST") != 0 ) + return FALSE; + else { + Mptr->LAST = TRUE; + (*NDEX)++; + return TRUE; + } +} +#pragma subtitle(" ") +#pragma page(1) +#pragma subtitle("subtitle - description ") +/********************************************************************/ +/* */ +/* Title: isSynopClouds */ +/* Organization: W/OSO242 - GRAPHICS AND DISPLAY SECTION */ +/* Date: 15 Sep 1994 */ +/* Programmer: CARL MCCALLA */ +/* Language: C/370 */ +/* */ +/* Abstract: */ +/* */ +/* External Functions Called: */ +/* None. */ +/* */ +/* Input: x */ +/* */ +/* Output: x */ +/* */ +/* Modification History: */ +/* None. */ +/* */ +/********************************************************************/ +#pragma page(1) +static MDSP_BOOL isSynopClouds( char *token, Decoded_METAR *Mptr, + int *NDEX ) +{ + + + if( token == NULL ) + return FALSE; + + if(strlen(token) != 5) + return FALSE; + + if( *token == '8' && + *(token+1) == '/' && + ((*(token+2) <= '9' && *(token+2) >= '0') || *(token+2) == '/') + && + ((*(token+3) <= '9' && *(token+3) >= '0') || *(token+3) == '/') + && + ((*(token+4) <= '9' && *(token+4) >= '0') || *(token+4) == '/')) + { + strcpy(Mptr->synoptic_cloud_type,token); + + Mptr->CloudLow = *(token+2); + Mptr->CloudMedium = *(token+3); + Mptr->CloudHigh = *(token+4); + + (*NDEX)++; + return TRUE; + } + else + return FALSE; +} + +#pragma page(1) +static MDSP_BOOL isSNINCR( char **string, Decoded_METAR *Mptr, int *NDEX ) +{ + + char *slash; + + if( *string == NULL ) + return FALSE; + + if( strcmp( *string, "SNINCR") != 0 ) + return FALSE; + else { + (++string); + + if( *string == NULL ) + return FALSE; + + + if( (slash = strchr(*string,'/')) == NULL ) { + (*NDEX)++; + return FALSE; + } + else if( nisdigit (*string,(slash-*string)) && + nisdigit(slash+1,strlen(slash+1)) ) { + Mptr->SNINCR = antoi(*string,(slash-*string)); + Mptr->SNINCR_TotalDepth = antoi(slash+1,strlen(slash+1)); + (*NDEX)++; + (*NDEX)++; + return TRUE; + } + else { + (*NDEX)++; + return FALSE; + } + + } + +} + +#pragma subtitle(" ") +#pragma page(1) +#pragma subtitle("subtitle - description ") +/********************************************************************/ +/* */ +/* Title: isSnowDepth */ +/* Organization: W/OSO242 - GRAPHICS AND DISPLAY SECTION */ +/* Date: 15 Sep 1994 */ +/* Programmer: CARL MCCALLA */ +/* Language: C/370 */ +/* */ +/* Abstract: */ +/* */ +/* External Functions Called: */ +/* None. */ +/* */ +/* Input: x */ +/* */ +/* Output: x */ +/* */ +/* Modification History: */ +/* None. */ +/* */ +/********************************************************************/ +#pragma page(1) +static MDSP_BOOL isSnowDepth( char *token, Decoded_METAR *Mptr, + int *NDEX ) +{ + + if( token == NULL ) + return FALSE; + + if(strlen(token) != 5) + return FALSE; + + if( *token == '4' && + *(token+1) == '/' && + nisdigit( (token+2),3) ) + { + strcpy(Mptr->snow_depth_group,token); + Mptr->snow_depth = antoi(token+2,3); + (*NDEX)++; + return TRUE; + } + else + return FALSE; +} + +#pragma page(1) +static MDSP_BOOL isWaterEquivSnow( char *string, + Decoded_METAR *Mptr, + int *NDEX ) +{ + + if( string == NULL ) + return FALSE; + + if( strlen(string) != 6 ) + return FALSE; + else if( !(nisdigit(string,6)) ) + return FALSE; + else if( strncmp(string, "933", 3) != 0 ) + return FALSE; + else { + Mptr->WaterEquivSnow = ((float) atoi(string+3))/10.; + (*NDEX)++; + return TRUE; + } + +} +#pragma page(1) +static MDSP_BOOL isSunshineDur( char *string, Decoded_METAR *Mptr, + int *NDEX ) +{ + + if( string == NULL ) + return FALSE; + + if( strlen(string) != 5 ) + return FALSE; + else if( strncmp(string, "98", 2) != 0 ) + return FALSE; + else if(nisdigit(string+2,3)) { + Mptr->SunshineDur = atoi(string+2); + (*NDEX)++; + return TRUE; + } + else if( strncmp(string+2, "///", 3) == 0 ) { + Mptr->SunSensorOut = TRUE; + (*NDEX)++; + return TRUE; + } + else + return FALSE; +} + +#pragma subtitle(" ") +#pragma page(1) +#pragma subtitle("subtitle - description ") +/********************************************************************/ +/* */ +/* Title: isHourlyPrecip */ +/* Organization: W/OSO242 - GRAPHICS AND DISPLAY SECTION */ +/* Date: 20 Nov 1994 */ +/* Programmer: CARL MCCALLA */ +/* Language: C/370 */ +/* */ +/* Abstract: x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* */ +/* External Functions Called: */ +/* None. */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* */ +/* Input: x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* */ +/* Output: x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* */ +/* Modification History: */ +/* None. */ +/* */ +/********************************************************************/ +#pragma page(1) + +static MDSP_BOOL isHourlyPrecip( char **string, Decoded_METAR *Mptr, + int *NDEX) +{ + + + if( *string == NULL ) + return FALSE; + + if( !(strcmp(*string, "P") == 0 || charcmp(*string, "'P'dddd") || + charcmp(*string, "'P'ddd") ) ) + return FALSE; + else if( strcmp(*string, "P") != 0 ) { + if( nisdigit((*string+1), strlen(*string+1)) ) { + Mptr->hourlyPrecip = ((float) + atoi(*string+1)) * 0.01; + (*NDEX)++; + return TRUE; + } + else { + (*NDEX)++; + return FALSE; + } + } + else { + + (++string); + + if( *string == NULL ) + return FALSE; + + + if( nisdigit(*string,strlen(*string)) ) { + Mptr->hourlyPrecip = ((float) + atoi(*string)) * 0.01; + (*NDEX)++; + (*NDEX)++; + return TRUE; + } + else { + (*NDEX)++; + return FALSE; + } + } +} + +#pragma subtitle(" ") +#pragma page(1) +#pragma subtitle("subtitle - description ") +/********************************************************************/ +/* */ +/* Title: isP6Precip */ +/* Organization: W/OSO242 - GRAPHICS AND DISPLAY SECTION */ +/* Date: 20 Nov 1994 */ +/* Programmer: CARL MCCALLA */ +/* Language: C/370 */ +/* */ +/* Abstract: x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* */ +/* External Functions Called: */ +/* None. */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* */ +/* Input: x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* */ +/* Output: x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* */ +/* Modification History: */ +/* None. */ +/* */ +/********************************************************************/ +#pragma page(1) + +static MDSP_BOOL isP6Precip( char *string, Decoded_METAR *Mptr, + int *NDEX ) +{ + + if( string == NULL ) + return FALSE; + + if( strlen(string) != 5 ) + return FALSE; + + + if( charcmp(string,"'6'dddd") || + charcmp(string,"'6''/''/''/''/'") ) { + if( strcmp(string+1, "////") == 0 ) { + Mptr->precip_amt = (float) MAXINT; + Mptr->Indeterminant3_6HrPrecip = TRUE; + (*NDEX)++; + return TRUE; + } + else { + Mptr->precip_amt = ((float) atoi(string+1)) / 100; + (*NDEX)++; + return TRUE; + } + } + else + return FALSE; + +} + +#pragma subtitle(" ") +#pragma page(1) +#pragma subtitle("subtitle - description ") +/********************************************************************/ +/* */ +/* Title: isP24Precip */ +/* Organization: W/OSO242 - GRAPHICS AND DISPLAY SECTION */ +/* Date: 20 Nov 1994 */ +/* Programmer: CARL MCCALLA */ +/* Language: C/370 */ +/* */ +/* Abstract: x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* */ +/* External Functions Called: */ +/* None. */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* */ +/* Input: x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* */ +/* Output: x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* */ +/* Modification History: */ +/* None. */ +/* */ +/********************************************************************/ +#pragma page(1) + +static MDSP_BOOL isP24Precip( char *string, Decoded_METAR *Mptr, + int *NDEX ) +{ + + if( string == NULL ) + return FALSE; + + if( strlen(string) != 5 ) + return FALSE; + + if( charcmp(string,"'7'dddd") || + charcmp(string,"'7''/''/''/''/'") ) { + if( strcmp(string+1, "////") == 0 ) { + Mptr->precip_24_amt = (float) MAXINT; + Mptr->Indeterminant_24HrPrecip = TRUE; + (*NDEX)++; + return TRUE; + } + else { + Mptr->precip_24_amt = ((float) atoi(string+1)) / 100.; + (*NDEX)++; + return TRUE; + } + } + else + return FALSE; + +} +#pragma subtitle(" ") +#pragma page(1) +#pragma subtitle("subtitle - description ") +/********************************************************************/ +/* */ +/* Title: isTTdTenths */ +/* Organization: W/OSO242 - GRAPHICS AND DISPLAY SECTION */ +/* Date: 16 Nov 1994 */ +/* Programmer: CARL MCCALLA */ +/* Language: C/370 */ +/* */ +/* Abstract: x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* */ +/* External Functions Called: */ +/* None. */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* */ +/* Input: x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* */ +/* Output: x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* */ +/* Modification History: */ +/* None. */ +/* */ +/********************************************************************/ +#pragma page(1) + +static MDSP_BOOL isTTdTenths( char *token, Decoded_METAR *Mptr, int *NDEX) +{ + + /***************************/ + /* DECLARE LOCAL VARIABLES */ + /***************************/ + + MDSP_BOOL returnFlag = FALSE; + float sign; + + if( token == NULL ) + return FALSE; + + if( *token != 'T' ) + return FALSE; + else if( !(strlen(token) == 5 || strlen(token) == 9) ) + return FALSE; + else + { + if( (*(token+1) == '0' || *(token+1) == '1') && + nisdigit(token+2,3) ) + { + if( *(token+1) == '0' ) + sign = 0.1; + else + sign = -0.1; + + Mptr->Temp_2_tenths = sign * ((float) antoi(token+2,3)); + returnFlag = TRUE; + } + else + return FALSE; + + if( (*(token+5) == '0' || *(token+5) == '1') && + nisdigit(token+6,3) ) + { + if( *(token+5) == '0' ) + sign = 0.1; + else + sign = -0.1; + + Mptr->DP_Temp_2_tenths = sign * ((float) atoi(token+6)); + (*NDEX)++; + return TRUE; + + } + else + { + if( returnFlag ) + { + (*NDEX)++; + return TRUE; + } + else + return FALSE; + } + } +} + +#pragma subtitle(" ") +#pragma page(1) +#pragma subtitle("subtitle - description ") +/********************************************************************/ +/* */ +/* Title: isMaxTemp */ +/* Organization: W/OSO242 - GRAPHICS AND DISPLAY SECTION */ +/* Date: 15 Sep 1994 */ +/* Programmer: CARL MCCALLA */ +/* Language: C/370 */ +/* */ +/* Abstract: */ +/* */ +/* External Functions Called: */ +/* None. */ +/* */ +/* Input: x */ +/* */ +/* Output: x */ +/* */ +/* Modification History: */ +/* None. */ +/* */ +/********************************************************************/ +#pragma page(1) +static MDSP_BOOL isMaxTemp(char *string, Decoded_METAR *Mptr, int *NDEX) +{ + char buf[ 6 ]; + + if( string == NULL ) + return FALSE; + + if(strlen(string) != 5 ) + return FALSE; + else if(*string == '1' && (*(string+1) == '0' || + *(string+1) == '1' || + *(string+1) == '/' ) && + (nisdigit((string+2),3) || + strncmp(string+2,"///",3) == 0) ) + { + if(nisdigit(string+2,3)) + { + memset(buf,'\0',6); + strncpy(buf,string+2,3); + Mptr->maxtemp = ( (float) atoi(buf))/10.; + + if( *(string+1) == '1' ) + Mptr->maxtemp *= (-1.0); + + (*NDEX)++; + return TRUE; + } + else + { + Mptr->maxtemp = (float) MAXINT; + (*NDEX)++; + return TRUE; + } + } + else + return FALSE; + +} + +#pragma subtitle(" ") +#pragma page(1) +#pragma subtitle("subtitle - description ") +/********************************************************************/ +/* */ +/* Title: isMinTemp */ +/* Organization: W/OSO242 - GRAPHICS AND DISPLAY SECTION */ +/* Date: 15 Sep 1994 */ +/* Programmer: CARL MCCALLA */ +/* Language: C/370 */ +/* */ +/* Abstract: */ +/* */ +/* External Functions Called: */ +/* None. */ +/* */ +/* Input: x */ +/* */ +/* Output: x */ +/* */ +/* Modification History: */ +/* None. */ +/* */ +/********************************************************************/ +#pragma page(1) +static MDSP_BOOL isMinTemp(char *string, Decoded_METAR *Mptr, int *NDEX) +{ + char buf[ 6 ]; + + if( string == NULL ) + return FALSE; + + if(strlen(string) != 5 ) + return FALSE; + else if(*string == '2' && (*(string+1) == '0' || + *(string+1) == '1' || + *(string+1) == '/' ) && + (nisdigit((string+2),3) || + strncmp(string+2,"///",3) == 0) ) + { + if(nisdigit(string+2,3)) + { + memset(buf,'\0',6); + strncpy(buf,string+2,3); + Mptr->mintemp = ( (float) atoi(buf) )/10.; + + if( *(string+1) == '1' ) + Mptr->mintemp *= (-1.0); + (*NDEX)++; + return TRUE; + } + else + { + Mptr->mintemp = (float) MAXINT; + (*NDEX)++; + return TRUE; + } + } + else + return FALSE; + +} + +#pragma subtitle(" ") +#pragma page(1) +#pragma subtitle("subtitle - description ") +/********************************************************************/ +/* */ +/* Title: isT24MaxMinTemp */ +/* Organization: W/OSO242 - GRAPHICS AND DISPLAY SECTION */ +/* Date: 15 Sep 1994 */ +/* Programmer: CARL MCCALLA */ +/* Language: C/370 */ +/* */ +/* Abstract: */ +/* */ +/* External Functions Called: */ +/* None. */ +/* */ +/* Input: x */ +/* */ +/* Output: x */ +/* */ +/* Modification History: */ +/* None. */ +/* */ +/********************************************************************/ +#pragma page(1) +static MDSP_BOOL isT24MaxMinTemp( char *string, Decoded_METAR *Mptr, + int *NDEX ) +{ + char buf[ 6 ]; + + if( string == NULL ) + return FALSE; + + if( strlen(string) != 9 ) + return FALSE; + else if( (*string == '4' && (*(string+1) == '0' || + *(string+1) == '1' || + *(string+1) == '/') && + (nisdigit((string+2),3) || strncmp(string+2,"///",3))) + && + ((*(string+5) == '0' || *(string+5) == '1' || + *(string+5) == '/') && + (nisdigit((string+6),3) || + strncmp(string+6,"///",3) == 0 )) ) + { + if(nisdigit(string+1,4) && (*(string+1) == '0' || + *(string+1) == '1') ) + { + memset(buf, '\0', 6); + strncpy(buf, string+2, 3); + Mptr->max24temp = ( (float) atoi( buf ) )/10.; + + if( *(string+1) == '1' ) + Mptr->max24temp *= -1.; + } + else + Mptr->max24temp = (float) MAXINT; + + + if(nisdigit(string+5,4) && (*(string+5) == '0' || + *(string+5) == '1' ) ) + { + memset(buf, '\0', 6); + strncpy(buf, string+6, 3); + Mptr->min24temp = ( (float) atoi(buf) )/10.; + + if( *(string+5) == '1' ) + Mptr->min24temp *= -1.; + } + else + Mptr->min24temp = (float) MAXINT; + + (*NDEX)++; + return TRUE; + + } + else + return FALSE; +} + +#pragma subtitle(" ") +#pragma page(1) +#pragma subtitle("subtitle - description ") +/********************************************************************/ +/* */ +/* Title: isPtendency */ +/* Organization: W/OSO242 - GRAPHICS AND DISPLAY SECTION */ +/* Date: 15 Sep 1994 */ +/* Programmer: CARL MCCALLA */ +/* Language: C/370 */ +/* */ +/* Abstract: */ +/* */ +/* External Functions Called: */ +/* None. */ +/* */ +/* Input: x */ +/* */ +/* Output: x */ +/* */ +/* Modification History: */ +/* None. */ +/* */ +/********************************************************************/ +#pragma page(1) + +static MDSP_BOOL isPtendency(char *string, Decoded_METAR *Mptr, int *NDEX) +{ + char buf[ 6 ]; + + if( string == NULL ) + return FALSE; + + if(strlen(string) != 5) + return FALSE; + else if(*string == '5' && ('0' <= *(string+1) <= '8') && + (nisdigit(string+2,3) || strncmp(string+2,"///",3) + == 0) ) + { + if( !(nisdigit(string+2,3)) ) + { + memset(buf,'\0',6); + strncpy(buf,(string+1),1); + Mptr->char_prestndcy = atoi(buf); + (*NDEX)++; + return TRUE; + } + else + { + memset(buf,'\0',6); + strncpy(buf,(string+1),1); + Mptr->char_prestndcy = atoi(buf); + + Mptr->prestndcy = ((float) atoi(string+2)) * 0.1; + + (*NDEX)++; + return TRUE; + } + + } + else + return FALSE; + +} + +#pragma subtitle(" ") +#pragma page(1) +#pragma subtitle("subtitle - description ") +/********************************************************************/ +/* */ +/* Title: isPWINO */ +/* Organization: W/OSO242 - GRAPHICS AND DISPLAY SECTION */ +/* Date: 20 Nov 1994 */ +/* Programmer: CARL MCCALLA */ +/* Language: C/370 */ +/* */ +/* Abstract: x */ +/* */ +/* External Functions Called: */ +/* None. */ +/* */ +/* Input: x */ +/* */ +/* Output: x */ +/* */ +/* Modification History: */ +/* None. */ +/* */ +/********************************************************************/ +#pragma page(1) + +static MDSP_BOOL isPWINO( char *string, Decoded_METAR *Mptr, int *NDEX) +{ + + if( string == NULL ) + return FALSE; + + + if( strcmp(string, "PWINO") != 0 ) + return FALSE; + else { + Mptr->PWINO = TRUE; + (*NDEX)++; + return TRUE; + } + +} + +#pragma subtitle(" ") +#pragma page(1) +#pragma subtitle("subtitle - description ") +/********************************************************************/ +/* */ +/* Title: isPNO */ +/* Organization: W/OSO242 - GRAPHICS AND DISPLAY SECTION */ +/* Date: 20 Nov 1994 */ +/* Programmer: CARL MCCALLA */ +/* Language: C/370 */ +/* */ +/* Abstract: x */ +/* */ +/* External Functions Called: */ +/* None. */ +/* */ +/* Input: x */ +/* */ +/* Output: x */ +/* */ +/* Modification History: */ +/* None. */ +/* */ +/********************************************************************/ +#pragma page(1) + +static MDSP_BOOL isPNO( char *string, Decoded_METAR *Mptr, int *NDEX) +{ + + + if( string == NULL ) + return FALSE; + + if( strcmp(string, "PNO") != 0 ) + return FALSE; + else { + Mptr->PNO = TRUE; + (*NDEX)++; + return TRUE; + } + +} + +#pragma subtitle(" ") +#pragma page(1) +#pragma subtitle("subtitle - description ") +/********************************************************************/ +/* */ +/* Title: isRVRNO */ +/* Organization: W/OSO242 - GRAPHICS AND DISPLAY SECTION */ +/* Date: 20 Nov 1994 */ +/* Programmer: CARL MCCALLA */ +/* Language: C/370 */ +/* */ +/* Abstract: x */ +/* */ +/* External Functions Called: */ +/* None. */ +/* */ +/* Input: x */ +/* */ +/* Output: x */ +/* */ +/* Modification History: */ +/* None. */ +/* */ +/********************************************************************/ +#pragma page(1) + +static MDSP_BOOL isRVRNO( char *string, Decoded_METAR *Mptr, int *NDEX) +{ + + if( string == NULL ) + return FALSE; + + if( strcmp(string, "RVRNO") != 0 ) + return FALSE; + else { + Mptr->RVRNO = TRUE; + (*NDEX)++; + return TRUE; + } + +} + +#pragma subtitle(" ") +#pragma page(1) +#pragma subtitle("subtitle - description ") +/********************************************************************/ +/* */ +/* Title: isCHINO */ +/* Organization: W/OSO242 - GRAPHICS AND DISPLAY SECTION */ +/* Date: 20 Nov 1994 */ +/* Programmer: CARL MCCALLA */ +/* Language: C/370 */ +/* */ +/* Abstract: x */ +/* */ +/* External Functions Called: */ +/* None. */ +/* */ +/* Input: x */ +/* */ +/* Output: x */ +/* */ +/* Modification History: */ +/* None. */ +/* */ +/********************************************************************/ +#pragma page(1) + +static MDSP_BOOL isCHINO( char **string, Decoded_METAR *Mptr, int *NDEX) +{ + + + if( *string == NULL ) + return FALSE; + + if( strcmp(*string, "CHINO") != 0 ) + return FALSE; + else + string++; + + if( *string == NULL ) + return FALSE; + + if( strlen(*string) <= 2 ) { + (*NDEX)++; + return FALSE; + } + else { + if( strncmp( *string, "RY", 2 ) == 0 && + nisdigit(*string+2,strlen(*string+2)) ) { + Mptr->CHINO = TRUE; + strcpy(Mptr->CHINO_LOC, *string); + (*NDEX)++; + (*NDEX)++; + return TRUE; + } + else { + (*NDEX)++; + return FALSE; + } + } + +} + +#pragma subtitle(" ") +#pragma page(1) +#pragma subtitle("subtitle - description ") +/********************************************************************/ +/* */ +/* Title: isVISNO */ +/* Organization: W/OSO242 - GRAPHICS AND DISPLAY SECTION */ +/* Date: 20 Nov 1994 */ +/* Programmer: CARL MCCALLA */ +/* Language: C/370 */ +/* */ +/* Abstract: x */ +/* */ +/* External Functions Called: */ +/* None. */ +/* */ +/* Input: x */ +/* */ +/* Output: x */ +/* */ +/* Modification History: */ +/* None. */ +/* */ +/********************************************************************/ +#pragma page(1) + +static MDSP_BOOL isVISNO( char **string, Decoded_METAR *Mptr, int *NDEX) +{ + + if( *string == NULL ) + return FALSE; + + if( strcmp(*string, "VISNO") != 0 ) + return FALSE; + else + string++; + + if( *string == NULL ) + return FALSE; + + if( strlen(*string) <= 2 ) { + (*NDEX)++; + return FALSE; + } + else { + if( strncmp( *string, "RY", 2 ) == 0 && + nisdigit(*string+2,strlen(*string+2))) { + Mptr->VISNO = TRUE; + strcpy(Mptr->VISNO_LOC, *string); + (*NDEX)++; + (*NDEX)++; + return TRUE; + } + else { + (*NDEX)++; + return FALSE; + } + } + +} + +#pragma subtitle(" ") +#pragma page(1) +#pragma subtitle("subtitle - description ") +/********************************************************************/ +/* */ +/* Title: isFZRANO */ +/* Organization: W/OSO242 - GRAPHICS AND DISPLAY SECTION */ +/* Date: 20 Nov 1994 */ +/* Programmer: CARL MCCALLA */ +/* Language: C/370 */ +/* */ +/* Abstract: x */ +/* */ +/* External Functions Called: */ +/* None. */ +/* */ +/* Input: x */ +/* */ +/* Output: x */ +/* */ +/* Modification History: */ +/* None. */ +/* */ +/********************************************************************/ +#pragma page(1) + +static MDSP_BOOL isFZRANO( char *string, Decoded_METAR *Mptr, int *NDEX) +{ + + + if( string == NULL ) + return FALSE; + + if( strcmp(string, "FZRANO") != 0 ) + return FALSE; + else { + Mptr->FZRANO = TRUE; + (*NDEX)++; + return TRUE; + } + +} + +#pragma subtitle(" ") +#pragma page(1) +#pragma subtitle("subtitle - description ") +/********************************************************************/ +/* */ +/* Title: isTSNO */ +/* Organization: W/OSO242 - GRAPHICS AND DISPLAY SECTION */ +/* Date: 20 Nov 1994 */ +/* Programmer: CARL MCCALLA */ +/* Language: C/370 */ +/* */ +/* Abstract: x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* */ +/* External Functions Called: */ +/* None. */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* */ +/* Input: x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* */ +/* Output: x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* x */ +/* */ +/* Modification History: */ +/* None. */ +/* */ +/********************************************************************/ +#pragma page(1) + +static MDSP_BOOL isTSNO( char *string, Decoded_METAR *Mptr, int *NDEX) +{ + + if( string == NULL ) + return FALSE; + + if( strcmp(string, "TSNO") != 0 ) + return FALSE; + else { + Mptr->TSNO = TRUE; + (*NDEX)++; + return TRUE; + } + +} + +#pragma subtitle(" ") +#pragma page(1) +#pragma subtitle("subtitle - description ") +/********************************************************************/ +/* */ +/* Title: isDollarSign */ +/* Organization: W/OSO242 - GRAPHICS AND DISPLAY SECTION */ +/* Date: 15 Sep 1994 */ +/* Programmer: CARL MCCALLA */ +/* Language: C/370 */ +/* */ +/* Abstract: */ +/* */ +/* External Functions Called: */ +/* None. */ +/* */ +/* Input: x */ +/* */ +/* Output: x */ +/* */ +/* Modification History: */ +/* None. */ +/* */ +/********************************************************************/ +#pragma page(1) + +static MDSP_BOOL isDollarSign( char *indicator, Decoded_METAR *Mptr, + int *NDEX ) +{ + + if( indicator == NULL ) + return FALSE; + + if( strcmp(indicator,"$") != 0 ) + return FALSE; + else + { + (*NDEX)++; + Mptr->DollarSign = TRUE; + return TRUE; + } +} + +#pragma page(1) +#pragma subtitle(" ") +#pragma subtitle("subtitle - description ") +/********************************************************************/ +/* */ +/* Title: DcdMTRmk */ +/* Organization: W/OSO242 - GRAPHICS AND DISPLAY SECTION */ +/* Date: 15 Sep 1994 */ +/* Programmer: CARL MCCALLA */ +/* Language: C/370 */ +/* */ +/* Abstract: DcdMTRmk takes a pointer to a METAR */ +/* report and parses/decodes data elements from */ +/* the remarks section of the report. */ +/* */ +/* */ +/* */ +/* External Functions Called: */ +/* None. */ +/* */ +/* Input: token - the address of a pointer to a METAR */ +/* report character string. */ +/* Mptr - a pointer to a structure of the vari- */ +/* able type Decoded_METAR. */ +/* */ +/* */ +/* Output: x */ +/* */ +/* Modification History: */ +/* None. */ +/* */ +/********************************************************************/ +#pragma page(1) +void DcdMTRmk( char **token, Decoded_METAR *Mptr ) +{ + + /***************************/ + /* DECLARE LOCAL VARIABLES */ + /***************************/ + + int TornadicActvty = 0, A0indicator = 0, + peakwind = 0, windshift = 0, towerVsby = 0, surfaceVsby = 0, + variableVsby = 0, LTGfreq = 0, + TS_LOC = 0, + recentWX = 0, variableCIG = 0, PRESFR = 0, + Vsby2ndSite = 0, CIG2ndSite = 0, + PRESRR = 0, SLP = 0, PartObscur = 0, + SectorVsby = 0, GR = 0, Virga = 0, + SfcObscur = 0, Ceiling = 0, VrbSkyCond = 0, ObscurAloft = 0, + NoSPECI = 0, Last = 0, SynopClouds = 0, Snincr = 0, + SnowDepth = 0, WaterEquivSnow = 0, SunshineDur = 0, + hourlyPrecip = 0, P6Precip = 0, P24Precip = 0, + TTdTenths = 0, MaxTemp = 0, MinTemp = 0, T24MaxMinTemp = 0, + Ptendency = 0, PWINO = 0, + FZRANO = 0, TSNO = 0, maintIndicator = 0, CHINO = 0, RVRNO = 0, + VISNO = 0, PNO = 0, DVR = 0; + + int NDEX, + ndex, + i; + char *slash, + *tokenX, + *V_char, + *temp_token; + + MDSP_BOOL extra_token, + IS_NOT_RMKS; + + float T_vsby; + + /*************************/ + /* START BODY OF ROUTINE */ + /*************************/ + + NDEX = 0; + + /*************************************************/ + /* LOCATE THE START OF THE METAR REMARKS SECTION */ + /*************************************************/ + + IS_NOT_RMKS = TRUE; + + while( token[ NDEX ] != NULL && IS_NOT_RMKS) { +#ifdef DEBUGZZ + printf("DcdMTRmk: token[%d] = %s\n",NDEX,token[NDEX]); +#endif + if( strcmp(token[ NDEX ], "RMK") != 0 ) + NDEX++; + else + IS_NOT_RMKS = FALSE; + } + + /***********************************************/ + /* IF THE METAR REPORT CONTAINS NO REMARKS */ + /* SECTION, THEN RETURN TO THE CALLING ROUTINE */ + /***********************************************/ + + if( token[ NDEX ] != NULL ) { +#ifdef DEBUGZZ + printf("DcdMTRmk: RMK found, token[%d] = %s\n", + NDEX,token[NDEX]); +#endif + NDEX++; +#ifdef DEBUGZZ + printf("DcdMTRmk: Bump NDEX, token[%d] = %s\n", + NDEX,token[NDEX]); +#endif + } + else { +#ifdef DEBUGZZ + printf("DcdMTRmk: No RMK found. NULL ptr encountered\n"); +#endif + return; + } + /*****************************************/ + /* IDENTIFY AND VALIDATE REMARKS SECTION */ + /* DATA GROUPS FOR PARSING/DECODING */ + /*****************************************/ + + while(token[NDEX] != NULL) { + +#ifdef DEBUGZZ + printf("DcdMTRmk: DECODE RMKS: token[%d] = %s\n",NDEX,token[NDEX]); +#endif + + isRADAT( &(token[NDEX]), Mptr, &NDEX ); + + if( isTornadicActiv( &(token[NDEX]), Mptr, &NDEX ) ) { + TornadicActvty++; + if( TornadicActvty > 1 ) { + memset(Mptr->TornadicType,'\0',15); + memset(Mptr->TornadicLOC,'\0',10); + memset(Mptr->TornadicDIR,'\0',4); + Mptr->BTornadicHour = MAXINT; + Mptr->BTornadicMinute = MAXINT; + Mptr->ETornadicHour = MAXINT; + Mptr->ETornadicMinute = MAXINT; + } + } + else if( isA0indicator( token[NDEX], Mptr, &NDEX ) ) { + A0indicator++; + if( A0indicator > 1 ) + memset(Mptr->autoIndicator,'\0',5); + } + else if( isPeakWind( &(token[NDEX]), Mptr, &NDEX ) ) { + peakwind++; + if( peakwind > 1 ) { + Mptr->PKWND_dir = MAXINT; + Mptr->PKWND_speed = MAXINT; + Mptr->PKWND_hour = MAXINT; + Mptr->PKWND_minute = MAXINT; + } + } + else if( isWindShift( &(token[NDEX]), Mptr, &NDEX ) ) { + windshift++; + if( windshift > 1 ) { + Mptr->WshfTime_hour = MAXINT; + Mptr->WshfTime_minute = MAXINT; + } + } + else if( isTowerVsby( &(token[NDEX]), Mptr, &NDEX ) ) { + towerVsby++; + if( towerVsby > 1 ) + Mptr->TWR_VSBY = (float) MAXINT; + } + else if( isSurfaceVsby( &(token[NDEX]), Mptr, &NDEX ) ) { + surfaceVsby++; + if( surfaceVsby > 1 ) + Mptr->SFC_VSBY = (float) MAXINT; + } + else if( isVariableVsby( &(token[NDEX]), Mptr, &NDEX ) ) { + variableVsby++; + if( variableVsby > 1 ) { + Mptr->minVsby = (float) MAXINT; + Mptr->maxVsby = (float) MAXINT; + } + } + else if( isVsby2ndSite( &(token[NDEX]), Mptr, &NDEX ) ) { + Vsby2ndSite++; + if( Vsby2ndSite > 1 ) { + Mptr->VSBY_2ndSite = (float) MAXINT; + memset(Mptr->VSBY_2ndSite_LOC,'\0',10); + } + } + else if( isLTGfreq( &(token[NDEX]), Mptr, &NDEX ) ) { + LTGfreq++; + if( LTGfreq > 1 ) { + Mptr->OCNL_LTG = FALSE; + Mptr->FRQ_LTG = FALSE; + Mptr->CNS_LTG = FALSE; + Mptr->CG_LTG = FALSE; + Mptr->IC_LTG = FALSE; + Mptr->CC_LTG = FALSE; + Mptr->CA_LTG = FALSE; + Mptr->DSNT_LTG = FALSE; + Mptr->OVHD_LTG = FALSE; + Mptr->VcyStn_LTG = FALSE; + Mptr->LightningVCTS = FALSE; + Mptr->LightningTS = FALSE; + memset(Mptr->LTG_DIR,'\0',3 ); + } + } + else if( isTS_LOC( &(token[NDEX]), Mptr, &NDEX ) ) { + TS_LOC++; + if( TS_LOC > 1 ) { + memset(Mptr->TS_LOC, '\0', 3); + memset(Mptr->TS_MOVMNT, '\0', 3); + } + } + else if( isRecentWX( &(token[NDEX]), Mptr, &recentWX ) ) { + NDEX++; + } + else if( isVariableCIG( &(token[NDEX]), Mptr, &NDEX ) ) { + variableCIG++; + if( variableCIG > 1) { + Mptr->minCeiling = MAXINT; + Mptr->maxCeiling = MAXINT; + } + } + else if( isCIG2ndSite( &(token[NDEX]), Mptr, &NDEX ) ) { + CIG2ndSite++; + if( CIG2ndSite > 1) { + Mptr->CIG_2ndSite_Meters = MAXINT; + memset( Mptr->CIG_2ndSite_LOC, '\0', 10); + } + } + else if( isPRESFR( token[NDEX], Mptr, &NDEX ) ) { + PRESFR++; + if( PRESFR > 1 ) + Mptr->PRESFR = FALSE; + } + else if( isPRESRR( token[NDEX], Mptr, &NDEX ) ) { + PRESRR++; + if( PRESRR > 1 ) + Mptr->PRESRR = FALSE; + } + else if( isSLP( &(token[NDEX]), Mptr, &NDEX ) ) { + SLP++; + if( SLP > 1 ) + Mptr->SLP = (float) MAXINT; + } + else if( isPartObscur( &(token[NDEX]), Mptr, PartObscur, + &NDEX ) ) { + PartObscur++; + if( PartObscur > 2 ) { + memset(&(Mptr->PartialObscurationAmt[0][0]), '\0', 7 ); + memset(&(Mptr->PartialObscurationPhenom[0][0]),'\0',12 ); + + memset(&(Mptr->PartialObscurationAmt[1][0]), '\0', 7 ); + memset(&(Mptr->PartialObscurationPhenom[1][0]),'\0',12 ); + } + } + else if( isSectorVsby( &(token[NDEX]), Mptr, &NDEX ) ) { + SectorVsby++; + if( SectorVsby > 1 ) { + Mptr->SectorVsby = (float) MAXINT; + memset(Mptr->SectorVsby_Dir, '\0', 3); + } + } + else if( isGR( &(token[NDEX]), Mptr, &NDEX ) ) { + GR++; + if( GR > 1 ) { + Mptr->GR_Size = (float) MAXINT; + Mptr->GR = FALSE; + } + } + else if( isVIRGA( &(token[NDEX]), Mptr, &NDEX ) ) { + Virga++; + if( Virga > 1 ) { + Mptr->VIRGA = FALSE; + memset(Mptr->VIRGA_DIR, '\0', 3); + } + } + else if( isSfcObscuration( token[NDEX], Mptr, &NDEX ) ) { + SfcObscur++; + if( SfcObscur > 1 ) { + for( i = 0; i < 6; i++ ) { + memset(&(Mptr->SfcObscuration[i][0]), '\0', 10); + Mptr->Num8thsSkyObscured = MAXINT; + } + } + } + else if( isCeiling( token[NDEX], Mptr, &NDEX ) ) { + Ceiling++; + if( Ceiling > 1 ) { + Mptr->CIGNO = FALSE; + Mptr->Ceiling = MAXINT; + Mptr->Estimated_Ceiling = FALSE; + } + } + else if( isVrbSky( &(token[NDEX]), Mptr, &NDEX ) ) { + VrbSkyCond++; + if( VrbSkyCond > 1 ) { + memset(Mptr->VrbSkyBelow, '\0', 4); + memset(Mptr->VrbSkyAbove, '\0', 4); + Mptr->VrbSkyLayerHgt = MAXINT; + } + } + else if( isObscurAloft( &(token[NDEX]), Mptr, &NDEX ) ) { + ObscurAloft++; + if( ObscurAloft > 1 ) { + Mptr->ObscurAloftHgt = MAXINT; + memset( Mptr->ObscurAloft, '\0', 12 ); + memset( Mptr->ObscurAloftSkyCond, '\0', 12 ); + } + } + else if( isNOSPECI( token[NDEX], Mptr, &NDEX ) ) { + NoSPECI++; + if( NoSPECI > 1 ) + Mptr->NOSPECI = FALSE; + } + else if( isLAST( token[NDEX], Mptr, &NDEX ) ) { + Last++; + if( Last > 1 ) + Mptr->LAST = FALSE; + } + else if( isSynopClouds( token[NDEX], Mptr, &NDEX ) ) { + SynopClouds++; + if( SynopClouds > 1 ) { + memset( Mptr->synoptic_cloud_type, '\0', 6 ); + Mptr->CloudLow = '\0'; + Mptr->CloudMedium = '\0'; + Mptr->CloudHigh = '\0'; + } + } + else if( isSNINCR( &(token[NDEX]), Mptr, &NDEX ) ) { + Snincr++; + if( Snincr > 1 ) { + Mptr->SNINCR = MAXINT; + Mptr->SNINCR_TotalDepth = MAXINT; + } + } + else if( isSnowDepth( token[NDEX], Mptr, &NDEX ) ) { + SnowDepth++; + if( SnowDepth > 1 ) { + memset( Mptr->snow_depth_group, '\0', 6 ); + Mptr->snow_depth = MAXINT; + } + } + else if( isWaterEquivSnow( token[NDEX], Mptr, &NDEX ) ) { + WaterEquivSnow++; + if( WaterEquivSnow > 1 ) + Mptr->WaterEquivSnow = (float) MAXINT; + } + else if( isSunshineDur( token[NDEX], Mptr, &NDEX ) ) { + SunshineDur++; + if( SunshineDur > 1 ) { + Mptr->SunshineDur = MAXINT; + Mptr->SunSensorOut = FALSE; + } + } + else if( isHourlyPrecip( &(token[NDEX]), Mptr, &NDEX ) ) { + hourlyPrecip++; + if( hourlyPrecip > 1 ) + Mptr->hourlyPrecip = (float) MAXINT; + } + else if( isP6Precip( token[NDEX], Mptr, &NDEX ) ) { + P6Precip++; + if( P6Precip > 1 ) + Mptr->precip_amt = (float) MAXINT; + } + else if( isP24Precip( token[NDEX], Mptr, &NDEX ) ) { + P24Precip++; + if( P24Precip > 1 ) + Mptr->precip_24_amt = (float) MAXINT; + } + else if( isTTdTenths( token[NDEX], Mptr, &NDEX ) ) { + TTdTenths++; + if( TTdTenths > 1 ) { + Mptr->Temp_2_tenths = (float) MAXINT; + Mptr->DP_Temp_2_tenths = (float) MAXINT; + } + } + else if( isMaxTemp( token[NDEX], Mptr, &NDEX ) ) { + MaxTemp++; + if( MaxTemp > 1 ) + Mptr->maxtemp = (float) MAXINT; + } + else if( isMinTemp( token[NDEX], Mptr, &NDEX ) ) { + MinTemp++; + if( MinTemp > 1 ) + Mptr->mintemp = (float) MAXINT; + } + else if( isT24MaxMinTemp( token[NDEX], + Mptr, &NDEX ) ) { + T24MaxMinTemp++; + if( T24MaxMinTemp > 1 ) { + Mptr->max24temp = (float) MAXINT; + Mptr->min24temp = (float) MAXINT; + } + } + else if( isPtendency( token[NDEX], Mptr, &NDEX ) ) { + Ptendency++; + if( Ptendency > 1 ) { + Mptr->char_prestndcy = MAXINT; + Mptr->prestndcy = (float) MAXINT; + } + } + else if( isPWINO( token[NDEX], Mptr, &NDEX ) ) { + PWINO++; + if( PWINO > 1 ) + Mptr->PWINO = FALSE; + } + else if( isFZRANO( token[NDEX], Mptr, &NDEX ) ) { + FZRANO++; + if( FZRANO > 1 ) + Mptr->FZRANO = FALSE; + } + else if( isTSNO( token[NDEX], Mptr, &NDEX ) ) { + TSNO++; + if( TSNO > 1 ) + Mptr->TSNO = FALSE; + } + else if( isDollarSign( token[NDEX], Mptr, &NDEX ) ) { + maintIndicator++; + if( maintIndicator > 1 ) + Mptr->DollarSign = FALSE; + } + else if( isRVRNO( token[NDEX], Mptr, &NDEX ) ) { + RVRNO++; + if( RVRNO > 1 ) + Mptr->RVRNO = FALSE; + } + else if( isPNO( token[NDEX], Mptr, &NDEX ) ) { + PNO++; + if( PNO > 1 ) + Mptr->PNO = FALSE; + } + else if( isVISNO( &(token[NDEX]), Mptr, &NDEX ) ) { + VISNO++; + if( VISNO > 1 ) { + Mptr->VISNO = FALSE; + memset(Mptr->VISNO_LOC, '\0', 6); + } + } + else if( isCHINO( &(token[NDEX]), Mptr, &NDEX ) ) { + CHINO++; + if( CHINO > 1 ) { + Mptr->CHINO = FALSE; + memset(Mptr->CHINO_LOC, '\0', 6); + } + } + else if( isDVR( token[NDEX], Mptr, &NDEX ) ) { + DVR++; + if( DVR > 1 ) { + Mptr->DVR.Min_visRange = MAXINT; + Mptr->DVR.Max_visRange = MAXINT; + Mptr->DVR.visRange = MAXINT; + Mptr->DVR.vrbl_visRange = FALSE; + Mptr->DVR.below_min_DVR = FALSE; + Mptr->DVR.above_max_DVR = FALSE; + } + } + else + NDEX++; + + } + + return; +} diff --git a/test/metar-to-text/mdsplib-code-1/src/drvmetar.c b/test/metar-to-text/mdsplib-code-1/src/drvmetar.c new file mode 100644 index 00000000..a32388a7 --- /dev/null +++ b/test/metar-to-text/mdsplib-code-1/src/drvmetar.c @@ -0,0 +1,685 @@ +/* +METAR Decoder Software Package Library: Parses Aviation Routine Weather Reports +Copyright (C) 2003 Eric McCarthy + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "metar_structs.h" +#ifdef SYNOPTIC + +char *BldSynop( Decoded_METAR * , char * ); +/*char *Sec0MeSm(Decoded_METAR *);*/ +/*char *Sec1MeSm(Decoded_METAR *, char *);*/ +/*char *Sec3MeSm(Decoded_METAR *, char *);*/ +/*char *Sec5MeSm(Decoded_METAR *, char *);*/ +#endif + +void prtDMETR( Decoded_METAR *); +int DcdMETAR( char *, Decoded_METAR * ); + +#pragma page(1) +#pragma subtitle(" ") +#pragma subtitle("subtitle - description ") +/********************************************************************/ +/* */ +/* Title: dRVMETAR */ +/* Organization: W/OSO242 - GRAPHICS AND DISPLAY SECTION */ +/* Date: 28 Oct 1994 */ +/* Programmer: CARL MCCALLA */ +/* Language: C/370 */ +/* */ +/* Abstract: DRVMETAR is a main routine that acts a driver */ +/* for testing the METAR Decoder function. */ +/* */ +/* External Functions Called: */ +/* None. */ +/* DcdMETAR */ +/* prtDcdMetar */ +/* Sec0MTSm */ +/* Sec1MTSm */ +/* */ +/* Input: None */ +/* */ +/* Output: None */ +/* */ +/* Modification History: */ +/* None. */ +/* */ +/********************************************************************/ +#pragma page(1) +main() +{ + static char *string[] = + { + +"KMKG 18022G29KT 3SM BR BKN018 BR 24/22 A2995 RMK A02 VIS 2", + + +"KPIT 132351Z 33013KT 4SM +TSRA BR BKN018CB OVC031 12/11 A2977 RMK " +"AO2 PK WND 31041/2305 WSHFT 2300 PRESSRR SLP090 FRQ LTGCGCC OHD " +"TS OHD MOV E CB OHD MOV E 8/3// P0051 60052 T01170111 10222 20122 " +"53030", + + +"KCAK 132351Z 28016G22KT 10SM BKN021 OVC030 11/09 A2981 RMK AO2 " +"TSE00RAE10 PRESRR SLP093 TS MOV NE CIG RGD 8/5// P0002 60066 " +"T01110094 10217 20111 51053", + +"KBUF 132354Z 21007KT 3SM +TSRA BR FEW009 OVC 12/11 A2959 RMK " +"AO2 PRESFR SLP021 8/9// TS ALQDS MOV E OCNL LTGICCCCG P0031 " +"60073 T01170111 10233 20111 50000 0", + + +"KPIT 132356Z 32012G21KT 4SM TSRA BR BKN018CB OVC031 12/11 A2978 " +"RMK AO2 WSHFT 2338 PRESFR FRQ LTGCGCC OHD TS OHD MOV E CB OHD MOV " +"E P0001", + + +"KCAK 132358Z 28015G22KT 10SM BKN013 OVC023 11/10 A2982 RMK AO2", + +"KBUF 140001Z 22008KT 3SM +TSRA BR BKN009 BKN013 OVC022 12/12 A2959 " +"RMK AO2 P0003", + + + +"KRIL 031853Z AUTO 33008KT 10SM SCT022 BKN032 OVC060 07/01 A3004 " +"RMK AO2 SLP157 T00720006 TSNO", + + +"METAR KCLE 091657Z COR 35021KT 3SM -PL SHPL VV004 M03/M04 A2964 " +"RMK VIS S M1/4=", + +"METAR KCLE 091657Z COR 35021KT 3SM -PE SHPE VV004 M03/M04 A2964 " +"RMK VIS S M1/4=", + +"METAR KCLE 091657Z COR 35021KT 3SM -PE TSPL VV004 M03/M04 A2964 " +"RMK VIS S M1/4=", + +"METAR KCLE 091657Z COR 35021KT 3SM -PL TSPE VV004 M03/M04 A2964 " +"RMK VIS S M1/4=", + + +"KMLB 200450Z 18006KT 7SM OVC100 23/22 A2986 RMK FQT LTGIG W-N", + +"KMLB 200450Z 18006KT 7SM OVC100 23/22 A2986 RMK FQT LTGIG W-N=", + +"KMLB 200450Z 18006KT 7SM OVC100 23/22 A2986 RMK FRQ LTGIC NW", + +"KMLB 200450Z 18006KT 7SM OVC100 23/22 A2986 RMK FRQ LTGCC NW=", + + +"SPECI KEKO 151609Z 00000KT 5SM BR FEW003 SCT013 M04/M06 A3018 " +"RMK VIS N-NE M1/4", + + "KPIT 1935Z 22015G25KT 1 3/4SM R22L/2700FT " + "TSRA -DZ FG +SNPE SCT005 BKN010 OVC250CB 18/MM A2992", + + "KPIT 1935Z 22015G25KT 1 3/4SM R22L/2700FT " + "TSRA -DZ FG +SNPE SCT005 BKN010 OVC250CB MM/M12 A2992", + + "METAR KLAX 281156Z AUTO VRB100G135KT 130V210 3 9999 " + "R15L/0500FT R22L/2700FT R16/1200FT R34/1000V1600FT R01L/P6000FT FC " + "+TS VCTS FEW/// SCT000 BKN050 SCT150 OVC250 3/M1 A2991 RMK " + "TORNADO B13 DSNT NE A01 PK WND 18515/45 " + "WSHFT 1350 FROPA TWR VIS 1 1/2 SFC VIS 1/4 VIS 1/4V1 1/4 " + "VIS 2 1/2 RWY11 " + "DVR/1000V1600FT " + "SHRAB05E30SHSNB20E55 FZDZB1057E1059 CIG 1000V1500 CIG 020 RWY11 " + "PRESFR PRESRR SLP013 FG FEW/// HZ SCT000 VIS NW 2 1/2 GR 3/4 " + "VIRGA SE -XRAFG3 CIGE005 BKN014 V OVC " + "FU BKN020 NOSPECI LAST 8/365 SNINCR 2/10 4/178 " + "933125 98096 P0125 60225 70565 " + "T00261015 10369 21026 " + "404800360 52101 VISNO RWY05 CHINO RWY27 PNO RVRNO " + "PWINO FZRANO TSNO $", + + +"METAR KAFF 091657Z COR 35021KT 3SM -SG BR VV004 M03/M04 A2964 " +"RMK SLP046 ESTMD SLP VIS SW-NW 2 " + "PWINO FZRANO TSNO $", + +"METAR KAFF 091657Z COR 35021KT 3SM -SG BR VV004 M03/M04 A2964 " +"RMK SLP046 ESTMD SLP VIS SW-NW 2 1/2 " + "PWINO FZRANO TSNO $", + +"METAR KAFF 091657Z COR 35021KT 3SM -SG BR VV004 M03/M04 A2964 " +"RMK SLP046 ESTMD SLP VIS SW-NW 2", + +"METAR KAFF 091657Z COR 35021KT 3SM -SG BR VV004 M03/M04 A2964 " +"RMK SLP046 ESTMD SLP VIS SW-NW 2 1/2=", +"METAR KAFF 091657Z COR 35021KT 3SM -SG BR VV004 M03/M04 A2964 " +"RMK SLP046 ESTMD SLP VIS SW-NW 2", + +"METAR KAFF 091657Z COR 35021KT 3SM -SG BR VV004 M03/M04 A2964 " +"RMK CIG 003V026 SLP046 ESTMD SLP VIS SW-NW 2", + +"METAR KAFF 091657Z COR 35021KT 3SM -SG BR VV004 M03/M04 A2964 " +"RMK VIS S 2", + +"SPECI KEKO 151609Z 00000KT 5SM BR FEW003 SCT013 M04/M06 A3018 " +"RMK VIS N-NE 1", + + + "KPIT 1935Z 22015G25KT 1 3/4SM R22L/2700FT " + "TSRA -DZ FG +SNPE SCT005 BKN010 OVC250CB 18/MM A2992", + + "KPIT 1935Z 22015G25KT 1 3/4SM R22L/2700FT " + "TSRA -DZ FG +SNPE SCT005 BKN010 OVC250CB MM/M12 A2992", + + "KPIT 1935Z 22015G25KT 1 3/4SM R22L/2700FT " + "TSRA -DZ FG +SNPE SCT005 BKN010 OVC250CB M18/MM A2992", + + + + + +"METAR KAFF 091657Z COR 35021KT 3SM -SG BR VV004 M03/M04 A2964 " +"RMK CIG 003V026 SLP046 ESTMD SLP VIS SW-NW 2=", + +"METAR KAFF 091657Z COR 35021KT 3SM -SG BR VV004 M03/M04 A2964 " +"RMK VIS S 2=", + +"SPECI KEKO 151609Z 00000KT 5SM BR FEW003 SCT013 M04/M06 A3018 " +"RMK VIS N-NE 1=", + + +"METAR KAFF 091657Z COR 35021KT 3SM -SG BR VV004 M03/M04 A2964 " +"RMK SLP046 ESTMD SLP VIS SW-NW 2", + +"METAR KAFF 091657Z COR 35021KT 3SM -SG BR VV004 M03/M04 A2964 " +"RMK CIG 003V026 SLP046 ESTMD SLP VIS SW-NW 2", + +"METAR KAFF 091657Z COR 35021KT 3SM -SG BR VV004 M03/M04 A2964 " +"RMK VIS S 2", + +"SPECI KEKO 151609Z 00000KT 5SM BR FEW003 SCT013 M04/M06 A3018 " +"RMK VIS N-NE 1", + + + +"METAR KAFF 091657Z COR 35021KT 3SM -SG BR VV004 M03/M04 A2964 " +"RMK SLP046 ESTMD SLP VIS SW 2", + +"METAR KAFF 091657Z COR 35021KT 3SM -SG BR VV004 M03/M04 A2964 " +"RMK CIG 003V026 SLP046 ESTMD SLP VIS NW 2", + +"METAR KAFF 091657Z COR 35021KT 3SM -SG BR VV004 M03/M04 A2964 " +"RMK VIS S 2", + +"SPECI KEKO 151609Z 00000KT 5SM BR FEW003 SCT013 M04/M06 A3018 " +"RMK VIS NE 1", + + + "KPIT 1935Z 22015G25KT 1/8SM R22L/2700FT " + "TSRA -DZ FG +SNPE SCT005 BKN010 OVC250CB 18/12 A2992", + + "KPIT 1935Z 22015G25KT 6SM R22L/2700FT " + "TSRA -DZ FG +SNPE SCT005 BKN010 OVC250CB M12/M18 A2992", + + "KPIT 1935Z 22015G25KT 8SM R22L/2700FT " + "TSRA -DZ FG +SNPE SCT005 BKN010 OVC250CB M18/12 A2992", + + "KPIT 1935Z 22015G25KT 9SM R22L/2700FT " + "TSRA -DZ FG +SNPE SCT005 BKN010 OVC250CB 18/M01 A2992", + + "KPIT 1935Z 22015G25KT 1 3/4SM R22L/2700FT " + "TSRA -DZ FG +SNPE SCT005TCU BKN010ACSL OVC250CB MM/12 A2992", + + "KPIT 1935Z 22015G25KT 1 3/4SM R22L/2700FT " + "TSRA -DZ FG +SNPE SCT005 BKN010 OVC250CB 18/MM A2992", + + "KPIT 1935Z 22015G25KT 1 3/4SM R22L/2700FT " + "TSRA -DZ FG +SNPE SCT005 BKN010 OVC250CB MM/M12 A2992", + + "KPIT 1935Z 22015G25KT 1 3/4SM R22L/2700FT " + "TSRA -DZ FG +SNPE SCT005 BKN010 OVC250CB M18/MM A2992", + + "KPIT 1935Z 22015G25KT 1 3/4SM R22L/2700FT " + "TSRA -DZ FG +SNPE SCT005 BKN010 OVC250CB MM/MM A2992", + + "SPECI KGFI 041430Z 18045G56KT M1/4SM R15/0200FT FC +TS VV010 20/18 " + "A2900 RMK A02A PK WND 18056/28 OCNL LTG AP " + "RAB15E25TSB20 FCB1430 PRESFR " + "SLP 701 P 0254 T01990182", + + + "METAR KLAX 281156Z AUTO VRB100G135KT 130V210 3 9999 " + "R15L/0500FT R22L/2700FT R16/1200FT R34/1000V1600FT R01L/P6000FT FC " + "+TS VCTS FEW/// SCT000 BKN050 SCT150 OVC250 3/M1 A2991 RMK " + "TORNADO B13 DSNT NE A01 PK WND 18515/45 " + "WSHFT 1350 FROPA TWR VIS 1 1/2 SFC VIS 1/4 VIS 1/4V1 1/4 " + "VIS 2 1/2 RWY11 " + "DVR/1000V1600FT " + "SHRAB05E30SHSNB20E55 FZDZB1057E1059 CIG 1000V1500 CIG 020 RWY11 " + "PRESFR PRESRR SLP013 FG FEW/// HZ SCT000 VIS NW 2 1/2 GR 3/4 " + "VIRGA SE -XRAFG3 CIGE005 BKN014 V OVC " + "FU BKN020 NOSPECI LAST 8/365 SNINCR 2/10 4/178 " + "933125 98096 P0125 60225 70565 " + "T00261015 10369 21026 " + "404800360 52101 VISNO RWY05 CHINO RWY27 PNO RVRNO " + "PWINO FZRANO TSNO $", + + "KP88 1919Z 09001KT 14/03 RMK AO / PKWND 002/RNO 158 Z T01440034", + + "K40B 1924Z 29004KT 15/M07 RMK AO PKWND 011/RM MV263 T01501072", + + + + "SPECI KGFI 041430Z COR 18045G56KT " + "M1/4SM R15/0200FT R01L/0600V1000FT R01L/M0600FT R27/P6000FT " + "+FC +TS -FZDZ VV010 04/M02 " + "A2900 RMK TORNADO B13 6 NE A02A PK WND 18056/28 WSHFT 30 FROPA " + "TWR VIS 1 1/2 VIS NE 2 1/2 VIS 2 1/2 RWY11 DVR/0600V1000FT " + "OCNL LTGICCG OVHD RAB15E25 TSB20 FCB1430 TS SE MOV NE GR 1 3/4 " + "VIRGA SW CIG 005V010 FG SCT000 BKN014 V OVC CB DSNT W " + "CIG 002 RWY11 PRESFR PRESRR " + "SLP701 ACFT MSHP NOSPECI SNINCR 2/10 FIRST " + "P0254 60217 70125 4/021 933036 8/903 98096 T00261015 " + "11021 21001 401120084 52032RVRNO PWINO PNO FZRANO TSNO " + "VISNO RWY06 CHINO RWY12 $", + + + +"KPHX 281156Z 12004KT 16KM CLR 15/05 A2996 RMK AOA SLP135 T01500050 " +"10250 20150 53006", +"KFCA 281156Z 30003KT 10SM CLR 06/02 A3009 RMRK AO TNO $ SLP191 " +"T00610023 10167 20056 53003", +"KAST 281156Z 00000KT 10SM BKN095 09/08 A2997 REMARK AOA SLP150 " +"T00940084 10161 20094 52005 ", +"KHVR 281156Z 03003KT 10SM OVC020 09/07 A3010 REMARKS AO TNO ZRNO " +"$ SLP194 T00940073 10156 20089 51005", + +"KGGW 281156Z 35006KT 5SM BR OVC010 10/09 A3003 RMK AOA $ SLP177 " +"70003 T01000095 10156 20110 53008", +"KELY 1153Z AUTO 14004KT 10SM SCT075 01/M01 A3011 RMK AOA TNO ZRNO " +"SLP171 70001 T00061011 10139 21006 51005", +"KFLG 281156Z 29006KT 10SM CLR 04/M01 A3012 RMK AO TNO SLP147 " +"T00391011 21006 51004", +"KGTF 281156Z 27005KT 7SM BKN080 04/04 A3010 RMK AOA SLP205 " +"T00440045 10117 20039 51006", +"KHLN 281156Z AUTO 27005KT 10SM OVC023 07/05 A3011 RMK AOA OVC V " +"BKN $ SLP202 60000 70001 T00670050 10122 20061 53003", + +"K13A 1918Z 20011KT 26/M06 RMK AO PKWND 020/RNO 644V264 T02611061", + + + "KGGW 1756Z 33018KT 10SM OVC015 M03/M06 A3041 RMK AOA SLP338 " + "4/007 60002 T10281055 11028 21072 51009", + "KPHX 1756Z 130004KT 10SM CLR 18/M03 A3001 RMK AOA SLP154 " + "T01781033 10178 20067 58007", + "KFCA 1756Z 29005KT 10SM CLR 05/M11 A3049 RMK AOA TNO SLP352 " + "T00501111 10050 21044 50004", + "KAST 1756Z 01006KT 10SM CLR 11/04 A3047 RMK AOA SLP316 " + "T01110045 10111 20000 50002", + "KHVR 1756Z 31007KT 5SM -SN SCT011 BKN024 OVC030 M05/M08 A3056 " + "RMK AOA 933004 " + "BKN V SCT TNO P0000 $ SLP389 4/015 60002 " + "T10501077 11050 21078 51010", + "KELY 1753Z 34010KT 10SM CLR 01/M07 A3022 RMK AOA TNO FZRNO " + "SLP240 T00111066 10011 21078 58007", + "KFLG 1756Z 07006KT 10SM CLR 06/M12 A3009 RMK AO TNO FZRNO " + "SLP178 T00561122 10061 21100 58005", + "KGTF 1756Z 35010KT 1/2SM -SN FG VV09 M06/M08 A3051 RMK AOA " + "933004 SFC VSBY 3/4 " + "P0009 SLP393 60010 T10611077 11044 21067 53013", + "KHLN 1756Z 35012KT 10SM SCT032 OVC060 M02/M09 A3048 RMK AOA " + "SLP369 60000 T10171094 11017 21061 53006", + "KAST 1756Z 01006KT 10SM CLR 11/04 A3047 RMK AOA SLP316 61104 " + "71235 T01110045 10111 20000 401720056 58002", + "METAR KLAX 04281156Z AUTO VRB100G135KT 130V210 3 1/2SM " + "R15L/0500FT R22L/2700FT R16/1200FT R34/1000V1600FT R01L/P6000FT FC " + "+TS BLPY FEW000 BKN050 SCT150 OVC250 3/M1 A2991 RMK " + "TORNADO B13 DSNT NE A02 PK WND 18515/45 " + "WSHFT 1350 FROPA TWR VIS 1 1/2 SFC VIS 1/4 VIS 1/4V1 1/4 " + "VIS 2 1/2 RWY11 OCNL LTG VCY STN " + "RAB1030E1145 FZDZE56 BLPYE57 CIG 1000V1500 CIG 020 RWY11 " + "PRESFR PRESRR SLP013 FG FEW000 VIS NW2 1/2 GR 3/4 " + "VIRGA SE -XRAFG3 CIGE005 BKN014 V OVC " + "FU BKN020 NOSPECI LAST 8/365 SNINCR 2/10 4/178 " + "933125 98096 P0125 60225 70565 " + "T00261015 10369 21026 " + "404800360 52101 PWINO FZRANO TSNO $", + + + + "METAR KGFI 041356Z AUTO 17012KT 130V210 3 1/2SM R15L/0500FT -RA " + "SCT050 OVC110 26/18 A2991 RMK FUNNEL CLOUDS A02 RAB30 " + "SLP 101 GR M1/4 VIRGA SCT V BKN P 0010 T02640178", + + "METAR KGFI 041356Z AUTO 05008KT 10SM R15L/P6000FT CLR A2991 " + "RMK WATERSPOUTS VCY STN NW A02 SLP 101 10288 20243 52021 $ ", + + "SPECI KGFI 041420Z AUTO 18030KT 3 1/2SM RVRNO TS -RA BKN008 OVC060 " + "26/22 A2991 RMK A02 RA15TSB20 PRESFR SLP 101 P 0000 T02640218", + + "KABE 281900Z NIL", + + "METAR KPIT NIL", + + "METAR KCLE 04281156Z 170100G135KT 110V180 M1/4SM " + "R01L/P6000FT +TSSHRA VCFG " + "BKN025 SCT100 OVC200 M26/ A2991 RMK PK WND 18515/45 A02 " + "WSHFT 1350 TWR VIS 1 1/2 SFC VIS 1/4 VIS 1/4V1 1/4 " + "CIG 1000V1500 PRESFR FRQ LTG CG NW " + "RAB1030E1145 FZDZE56 PRESRR SLP135 GS " + "T1263 " + "VIRGA NW 8/365 4/178 P 0125 60225 7//// 70565 10369 21026 " + "404800360 52101 PWINO FZRANO TSNO $", + + "METAR KPHL 040256Z AUTO 170100G135KT 130V210 1/2SM " + "R15L/0500FT R22L/2700FT R16/1200FT R34/1000V1600FT R01L/P6000FT " + "FC +TS BKN050 SCT150 OVC250 M26/ A2991 RMK A02 PK WND 185150/1345 " + "WSHFT 1350 TWR VIS 1 1/2 SFC VIS 1/4 VIS 1/4V1 1/4 LTG DSNT " + "RAB1030E1145 FZDZE56 CIG 1000V1500 PRESFR PRESRR SLP037 GR 2 3/4 " + "VIRGA E 8/365 4/178 P 0125 70565 21026 T0263 10369 60225 " + "404800360 52101 PWINO FZRANO TSNO $", + + "SPECI KGFI 041420Z AUTO 18030KT 2 1/2SM RVRNO TS -RA BKN008 " + "OVC060 25/22 A2991 RMK A02 LTG DSNT W " + "RAB15TSB20 PRESFR SLP101 P 0000 " + "254/218", + + "METAR KGFI 041356Z AUTO 170100G135KT 130V210 3 1/2SM " + "R15L/0500FT R22L/2700FT R16/1200FT R34/1000V1600FT R01L/P6000FT " + "FC +TS BKN050 SCT150 OVC250 M26/ A2991 RMK A02 PK WND 185150/1345 " + "WSHFT 1350 TWR VIS 1 1/2 SFC VIS 1/4 VIS 1/4V1 1/4 " + "RAB1030E1145 FZDZE56 CIG 1000V1500 PRESFR PRESRR SLP997 GR M1/4 " + "VIRGA SE 8/365 4/178 P 0125 6//// 60225 70565 T0263 10369 21026 " + "404800360 52101 PWINO FZRANO TSNO $", + + + + "METAR KGFI 041356Z AUTO 170100G135KT 130V210 3 1/2SM " + "R15L/0500FT R22L/2700FT R16/1200FT R34/1000V1600FT R01L/P6000FT " + "FC +TS BKN050 SCT150 OVC250 M26/ A2991 RMK A02 PK WND 185150/1345 " + "WSHFT 1350 TWR VIS 1 1/2 SFC VIS 1/4 VIS 1/4V1 1/4 " + "RAB1030E1145 FZDZE56 CIG 1000V1500 PRESFR PRESRR SLP997 GR 25 " + "VIRGA 35 8/365 4/178 P 0125 6//// 60225 70565 T0263 10369 21026 " + "VIRGA 35 8/365 4/178 P 0125 21026 70565 10369 60225 T0263 21026 " + "404800360 52101 PWINO FZRANO TSNO $", + + + "METAR KGFI 041356Z AUTO 170100G135KT 130V210 3 1/2SM " + "R15L/0500FT R22L/2700FT R16/1200FT R34/1000V1600FT R01L/P6000FT " + "FC +TS BKN050 SCT150 OVC250 3/M1 A2991 RMK A02 PK WND 18515/45 " + "WSHFT 1350 TWR VIS 1 1/2 SFC VIS 1/4 VIS 1/4V1 1/4 " + "RAB1030E1145 FZDZE56 CIG 1000V1500 PRESFR PRESRR SLP997 GR 25 " + "VIRGA 35 8/365 4/178 P 0125 60225 70565 T00261015 10369 21026 " + "404800360 52101 PWINO FZRANO TSNO $", + + "METAR KGFI 041356Z AUTO 170100G135KT 130V210 3 1/2SM " + "R15L/0500FT R22L/2700FT R16/1200FT R34/1000V1600FT R01L/P6000FT " + "FC +TS BKN050 SCT150 OVC250 3/M1 A2991 RMK A02 PK WND 185150/1345 " + "WSHFT 1350 TWR VIS 1 1/2 SFC VIS 1/4 VIS 1/4V1 1/4 " + "RAB1030E1145 FZDZE56 CIG 1000V1500 PRESFR PRESRR SLP997 GR 25 " + "VIRGA 35 8/365 4/178 P 0125 60225 70565 T00261015 10369 21026 " + "404800360 52101 PWINO FZRANO TSNO", + + + + "METAR KGFI 041356Z AUTO 05008KT 10SM R15L/P6000FT CLR A2991 RMK " + "A02 SLP 101 10288 20243 52021", + "SPECI DGFI 041430Z 18045G56KT M1/4SM R15/0200FT FC +TS VV010 20/18 " + "M20/M18 A2900 RMK A02A PK WND 18056/28 RAB15E25TSB20 FCB1430 PRESFR " + "SLP 701 P 0254 M199/M182", + + "SPECI DGFI 041430Z 18045G56KT M1/4SM R15/0200FT FC +TS VV010 20/18 " + "M20/M18 A2900 RMK A02A PK WND 18056/28 RAB15E25TSB20 FCB1430 PRESFR " + "SLP 701 P 0254 M199/182", + + "SPECI DGFI 041430Z 18045G56KT M1/4SM R15/0200FT FC +TS VV010 20/18 " + "M20/M18 A2900 RMK A02A PK WND 18056/28 RAB15E25TSB20 FCB1430 PRESFR " + "SLP 701 P 0254 199/M182", + + "METAR APIT 171755Z AUTO 22015G25KT 1 3/4SM R22L/2700FT R16/1200FT " + "R34/1000V1600FT R01L/P6000FT R04RR/900FT " + "TSRA -DZ FG +SNPE SCT005 BKN010 OVC250CB 18/16 A2992 " + "RMK 4/369 58033 6003/ TWELVE 70125 10039 20029 410840112 " + "PCPN 0009 8/563 WSHFT 1715 PK WND 2032/1725 " + "CIG 20V25 WND 12V25 TWR VIS 2 1/2 " + "SFC VIS 1 1/2 VIS 1 1/2V2 SLP875 SGB1213E1225", + + "NZWN 1700Z 35030G49KT 320V030 20KM 02 5SC021 7SC046 12/08 " + " Q0994.2 TEMPO 6000 RA 5ST012 2CB015 RMK SLP056 " + "RAE0123", + + "SPECI APIT 171755Z 22015G25KT 1 3/4SM R22L/2700FT R16/1200FT " + "R34/1000V1600FT R01L/P6000FT R04RR/900FT " + "TSRA -DZ FG +SNPE SCT005 BKN010 OVC250CB 18/16 A2992 " + "RMK 58033 6003/ TWELVE 70125 8/321 10039 20029 410840112 " + "PCPN 0009 WSHFT 1715 PK WND 2032/1725 " + "CIG 20V25 WND 12V25 TWR VIS 2 1/2 " + "SFC VIS 1 1/2 VIS 1 SLP875 FGB1713", + + "APIT 1755Z 22015G25KT 1 3/4SM R22L/2700FT R16/1200FT " + "R34/1000V1600FT R01L/P6000FT R04RR/900FT " + "TSRA -DZ FG +SNPE SCT005 BKN010 OVC250CB 18/16 A2992 " + "RMK 58033 6003/ TWELVE 70125 10039 20029 410840112 " + "PCPN 0009 WSHFT 1715 PK WND 2032/1725 " + "CIG 20V25 WND 12V25 TWR VIS 2 1/2 " + "SFC VIS 1 1/2 VIS 1V2 SLP875", + + + "APIT 1755Z 22015G25KT 1 3/4SM R22L/2700FT R16/1200FT " + "R34/1000V1600FT R01L/P6000FT R04RR/900FT " + "TSRA -DZ FG +SNPE SCT005 BKN010 OVC250CB 18/16 A2992 " + "RMK 58033 6003/ TWELVE 70125 10039 20029 410840112 " + "PCPN 0009 WSHFT 1715 PK WND 2032/1725 " + "CIG 20V25 WND 12V25 TWR VIS 2 1/2 " + "SFC VIS 1 1/2 VIS 1 1/2V2 1/2 SLP875", + + "APIT 1755Z 22015G25KT 1 3/4SM R22L/2700FT R16/1200FT " + "R34/1000V1600FT R01L/P6000FT R04RR/900FT " + "TSRA -DZ FG +SNPE SCT005 BKN010 OVC250CB 18/16 A2992 " + "RMK 58033 6003/ TWELVE 70125 10039 20029 410840112 " + "PCPN 0009 WSHFT 1715 PK WND 2032/1725 " + "CIG 20V25 WND 12V25 TWR VIS 2 1/2 " + "SFC VIS 1 1/2 VIS 1V2 1/2 SLP875", + + + "EGPF 1720Z 00000KT 9999 -SHRA STC014 SCT020CB BNK024 12/09 " + "Q1003 NOSIG", + + "NZAA 1700Z 03010KT 30KM 03 5CU022 7SC035 11/07 Q1006.5 NOSIG", + "NZWN 1700Z 35030G49KT 320V030 20KM 02 5SC021 7SC046 12/08 " + " Q0994.2 TEMPO 6000 RA 5ST012 2CB015 RMK KAUKAU 30050KT", + "DGAA 1800Z 22012KT 9999 SCT009 BKN120 25/21 Q1015", + "DAAT 1830Z 30010KT CAVOK 29/06 Q1019", + + "GQPP 1800Z 34023KT 3000 DRSA SKC 24/20 Q1011 NSG", + "DAAG 1830Z 06006KT 9999 SCT020 25/22 Q1015", + "DABB 1830Z 04010KT 9999 SCT030TCU SCT033CB 27/18 Q1017", + "DABC 1830Z 00000KT 9999 SCT026TCU SCT036CB 22/18 Q1020 RETS", + + "NZAA 1700Z 03010KT 30KM 03 5CU022 7SC035 11/07 Q1006.5 NOSIG", + "NZWN 1700Z 35030G49KT 320V030 20KM 02 5SC021 7SC046 12/08 " + " Q0994.2 TEMPO 6000 RA 5ST012 2CB015 RMK K", + "NZWN 1700Z 35030G49KT 320V030 20KM 02 5SC021 7SC046 12/08 " + " Q0994.2 TEMPO 6000 RA 5ST012 2CB015 RMK KAUKAU 30050KT", + "DGAA 1800Z 22012KT 9999 SCT009 BKN120 25/21 Q1015", + + "GFLL 1900Z NIL", + + "GOOY 1800Z 03006G17KT 340V080 6000 TSRA BKN016 BKN030CB " + "BKN133 26/23 Q1013 NOSIG", + + "GCXO 1930Z 32018KT 8000 SCT003 SCT007 18/16 Q1019", + + "APIT 1755Z 22015G25KT 1 3/4SM R22L/2700FT R16/1200FT " + "R34/1000V1600FT R01L/P6000FT R04RR/900FT " + "TSRA -DZ FG +SNPE SCT005 BKN010 OVC250CB 18/16 A2992 " + "RMK 58033 6003/ TWELVE 70125 10039 20029 410840112 " + "PCPN 0009 WSHFT 1715 PK WND 2032/1725 " + "CIG 20V25 WND 12V25 TWR VIS 2 1/2 " + "SFC VIS 1 1/2 VIS 1 1/2V2", + + "BPIT 1755Z 22015G25KT 1 3/4SM R22L/2700FT R16/1200FT " + "R34/1000V1600FT R01L/P6000FT R04RR/900FT " + "TSRA -DZ FG +SNPE SCT005 BKN010 OVC250CB 18/16 A2992 " + "RMK 58033 6003/ TWELVE 70125 10039 20029 410840112 " + "PCPN 0009 WSHFT 1715 PK WND 2032/1725 " + "CIG 20V25 WND 12V25 TWR VIS 2 1/2 " + "SFC VIS 1 1/2 VIS 1V2", + + "CPIT 1755Z 22015G25KT 1 3/4SM R22L/2700FT R16/1200FT " + "R34/1000V1600FT R01L/P6000FT R04RR/900FT " + "TSRA -DZ FG +SNPE SCT005 BKN010 OVC250CB 18/16 A2992 " + "RMK 58033 6003/ TWELVE 70125 10039 20029 410840112 " + "PCPN 0009 WSHFT 1715 PK WND 2032/1725 " + "CIG 20V25 WND 12V25 TWR VIS 2 1/2 " + "SFC VIS 1 1/2 VIS 1V2 1/2", + + "DPIT 1755Z 22015G25KT 1 3/4SM R22L/2700FT R16/1200FT " + "R34/1000V1600FT R01L/P6000FT R04RR/900FT " + "TSRA -DZ FG +SNPE SCT005 BKN010 OVC250CB 18/16 A2992 " + "RMK 58033 6003/ TWELVE 70125 10039 20029 410840112 " + "PCPN 0009 WSHFT 1715 PK WND 2032/1725 " + "CIG 20V25 WND 12V25 TWR VIS 2 1/2 " + "SFC VIS 1 1/2 VIS 1 1/2V2 1/2", + + "EPIT 1755Z 22015G25KT 1 3/4SM R22L/2700FT R16/1200FT " + "R34/1000V1600FT R01L/P6000FT R04RR/900FT " + "TSRA -DZ FG +SNPE SCT005 BKN010 OVC250CB 18/16 A2992 " + "RMK 58033 6003/ TWELVE 70125 10039 20029 410840112 " + "PCPN 0009 WSHFT 1715 PK WND 2032/1725 " + "CIG 20V25 WND 12V25 TWR VIS 2 1/2 " + "SFC VIS 1 1/2 VIS 1/2V3/4", + + "FPIT 1755Z 22015G25KT 1 3/4SM R22L/2700FT R16/1200FT " + "R34/1000V1600FT R01L/P6000FT R04RR/900FT " + "TSRA -DZ FG +SNPE SCT005 BKN010 OVC250CB 18/16 A2992 " + "RMK 58033 6003/ TWELVE 70125 10039 20029 410840112 " + "PCPN 0009 WSHFT 1715 PK WND 2032/1725 " + "CIG 20V25 WND 12V25 TWR VIS 2 1/2 " + "SFC VIS 1 1/2 VIS 3/4V2 1/2", + + "GPIT 1755Z 22015G25KT 1 3/4SM R22L/2700FT R16/1200FT " + "R34/1000V1600FT R01L/P6000FT R04RR/900FT " + "TSRA -DZ FG +SNPE SCT005 BKN010 OVC250CB 18/16 A2992 " + "RMK 58033 6003/ TWELVE 70125 10039 20029 410840112 " + "PCPN 0009 WSHFT 1715 PK WND 2032/1725 " + "CIG 20V25 WND 12V25 TWR VIS 2 1/2 " + "SFC VIS 1 1/2 VIS 3/4V3", + + "KPIT 1935Z 22015G25KT 1 3/4SM R22L/2700FT " + "TSRA -DZ FG +SNPE SCT005 BKN010 OVC250CB M18/M16 A2992", + + "KPIT 1935Z 22015G25KT 1 3/4SM R22L/2700FT " + "TSRA -DZ FG +SNPE SCT005 BKN010 OVC250CB M18/16 A2992", + + "KPIT 1935Z 22015G25KT 1 3/4SM R22L/2700FT " + "TSRA -DZ FG +SNPE SCT005 BKN010 OVC250CB 18/M16 A2992", + + "KPIT 1935Z 22015G25KT 1 3/4SM R22L/2700FT " + "TSRA -DZ FG +SNPE SCT005 BKN010 OVC250CB MM/M16 A2992", + + "KPIT 1935Z 22015G25KT 1 3/4SM R22L/2700FT " + "TSRA -DZ FG +SNPE SCT005 BKN010 OVC250CB MM/16 A2992", + + "KPIT 1935Z 22015G25KT 1 3/4SM R22L/2700FT " + "TSRA -DZ FG +SNPE SCT005 BKN010 OVC250CB M18/MM A2992", + + + NULL}; + + /***************************/ + /* DECLARE LOCAL VARIABLES */ + /***************************/ + + Decoded_METAR Metar; + Decoded_METAR *Mptr = &Metar; + int j, + ErReturn; + static char *synopRTRN = NULL; + char bltn_prefix[20]; + + /***************************************************/ + /* START BODY OF MAIN ROUTINE FOR CALLING DcdMETAR */ + /***************************************************/ + + j = 0; + + while( string[j] != NULL) + { + /*-- PRINT INPUT METAR REPORT ----------------------------*/ + printf("\n\nINPUT METAR REPORT:\n\n %s\n\n",string[j] ); + + /*-- DECODE INPUT REPORT ---------------------------------*/ + if ( (ErReturn = DcdMETAR( string[ j ], Mptr )) != 0 ) + printf("DcdMETAR: Error Return Number: %d\n",ErReturn); + + /*-- PRINT DECODED METAR REPORT ELEMENTS -----------------*/ + printf("\n\nFINAL DECODED PRODUCT...\n\n"); + prtDMETR( Mptr ); +#ifdef OLDSTUFF +/************************************************/ +/* Convert Decoded METAR into Synoptic format */ +/************************************************/ + + printf("Just after call to prtDMETR\n"); + + sprintf( bltn_prefix, "AAXX YYGGi##," ); + synopRTRN = BldSynop( Mptr, bltn_prefix ); + printf("After BldSynop, SynopRep =:\n%s\n",synopRTRN); + /**********************************************************/ + /*-- ENCODE SECTION 0 OF THE SYNTHETIC SYNOPTIC REPORT ---*/ + /**********************************************************/ + printf("Just before call to Sec0MeSM\n"); + + if( (synopRTRN = Sec0MeSm( Mptr )) == NULL ) + printf("Sec0MeSm returned a NULL pointer\n"); + else + printf("After Sec0MeSm: %s\n",synopRTRN); + + /**********************************************************/ + /*-- ENCODE SECTION 1 OF THE SYNTHETIC SYNOPTIC REPORT ---*/ + /**********************************************************/ + if( synopRTRN != NULL ) + synopRTRN = Sec1MeSm( Mptr,synopRTRN ); + printf("After Sec1MeSm: %s\n",synopRTRN); + + /**********************************************************/ + /*-- ENCODE SECTION 3 OF THE SYNTHETIC SYNOPTIC REPORT ---*/ + /**********************************************************/ + + if( synopRTRN != NULL ) + synopRTRN = Sec3MeSm( Mptr,synopRTRN ); + printf("After Sec3MeSm: %s\n",synopRTRN); + + /**********************************************************/ + /*-- ENCODE SECTION 5 OF THE SYNTHETIC SYNOPTIC REPORT ---*/ + /**********************************************************/ + + if( synopRTRN != NULL ) + synopRTRN = Sec5MeSm( Mptr,synopRTRN); + printf("After Sec5MeSm: %s\n",synopRTRN); + + /**********************************************************/ + /*-- PRINT THE ENCODED SYNTHETIC SYNOPTIC REPORT ---------*/ + /**********************************************************/ + + if( synopRTRN != NULL ) { + printf("\n\nOutput Synoptic Report: %s\n\n",synopRTRN); + free( synopRTRN); + } + +#endif + + j++; + + } + +} diff --git a/test/metar-to-text/mdsplib-code-1/src/fracpart.c b/test/metar-to-text/mdsplib-code-1/src/fracpart.c new file mode 100644 index 00000000..f338e4c2 --- /dev/null +++ b/test/metar-to-text/mdsplib-code-1/src/fracpart.c @@ -0,0 +1,84 @@ +/* +METAR Decoder Software Package Library: Parses Aviation Routine Weather Reports +Copyright (C) 2003 Eric McCarthy + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "metar_structs.h" + +#pragma subtitle(" ") +#pragma page(1) +#pragma subtitle("subtitle - description ") +/********************************************************************/ +/* */ +/* Title: fracPart */ +/* Organization: W/OSO242 - GRAPHICS AND DISPLAY SECTION */ +/* Date: 13 Jun 1995 */ +/* Programmer: CARL MCCALLA */ +/* Language: C/370 */ +/* */ +/* Abstract: Convert a character string fraction into a */ +/* decimal (floating point) number. */ +/* */ +/* External Functions Called: */ +/* None. */ +/* */ +/* Input: string - a pointer to a character string frac- */ +/* tion. */ +/* Output: A decimal (floating point) number. */ +/* */ +/* Modification History: */ +/* None. */ +/* */ +/********************************************************************/ +#pragma page(1) + +float fracPart( char *string ) +{ + + /***************************/ + /* DECLARE LOCAL VARIABLES */ + /***************************/ + + char buf[ 6 ], + *slash; + + float numerator, + denominator; + + /*************************/ + /* START BODY OF ROUTINE */ + /*************************/ + + slash = strchr(string, '/'); + + memset(buf , '\0', 6); + strncpy( buf, string, slash-string); + + numerator = (float) atoi(buf); + + memset(buf , '\0', 6); + strcpy( buf, slash+1); + + denominator = (float) atoi(buf); + + if( denominator == 0.0 ) + return ((float) MAXINT); + else + return (numerator/denominator); + +} + diff --git a/test/metar-to-text/mdsplib-code-1/src/local.h b/test/metar-to-text/mdsplib-code-1/src/local.h new file mode 100644 index 00000000..56d43dc6 --- /dev/null +++ b/test/metar-to-text/mdsplib-code-1/src/local.h @@ -0,0 +1,1257 @@ +/* +METAR Decoder Software Package Library: Parses Aviation Routine Weather Reports +Copyright (C) 2003 Eric McCarthy + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +/*********************************************************************/ +/* */ +/* Title: local h */ +/* Updated: 10 June 1996 */ +/* Organization: W/OSO242 - Graphics and Display Section */ +/* Language: C/370 */ +/* */ +/* Abstract: */ +/* This header file provides all function definitions necessary for */ +/* the OSO242 C function library. */ +/* */ +/*********************************************************************/ + +#ifndef locallib_defined +#define locallib_defined + + + +/*****************/ +/* Include Files */ +/*****************/ + +#include +#include +/* #include Includes IBM specific debugging libraries */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + + +/********************/ +/* Standard Defines */ +/********************/ + +#define FALSE 0 /* boolean value */ +#define MAXINT INT_MAX /* maximum integer */ +#define MININT INT_MIN /* minimum integer */ +#define MAXNEG INT_MIN /* minimum integer */ +#define NO FALSE /* boolean value */ +#define TRUE 1 /* boolean value */ +#define TRUNCATED -1 /* indicates truncation */ +#define YES TRUE /* boolean value */ + + +/*****************/ +/* Macro defines */ +/*****************/ + +#define ABS(x) (((x) < 0) ? -(x) : (x)) +#define clearscrn system("CLRSCRN") +#define assgndev(d, v) v = 0x##d +#define DIM(a) (sizeof(a) / sizeof(a[0])) +#define FOREVER for(;;) /* endless loop */ +#define getln(s, n) ((fgets(s, n, stdin)==NULL) ? EOF : strlen(s)) +#define IMOD(i, j) (((i) % (j)) < 0 ? ((i) % (j))+(j) : ((i) % (j))) +#define IN_RANGE(n, lo, hi) ((lo) <= (n) && (n) <= (hi)) +#define LOOPDN(r, n) for ((r) = (n)+1; --(r) > 0;) +#define MAX(x, y) (((x) < (y)) ? (y) : (x)) +#define max(x, y) (((x) < (y)) ? (y) : (x)) +#define MIN(x, y) (((x) < (y)) ? (x) : (y)) +#define min(x, y) (((x) < (y)) ? (x) : (y)) +#define STREQ(s, t) (strcmp(s, t) == 0) +#define STRGT(s, t) (strcmp(s, t) > 0) +#define STRLT(s, t) (strcmp(s, t) < 0) +#define STRNEQ(s, t, l) (strncmp(s, t, l) == 0) +#define STRNGT(s, t, l) (strncmp(s, t, l) > 0) +#define STRNLT(s, t, l) (strncmp(s, t, l) < 0) +#define SWAP(a,b,t) ((t) = (a), (a) = (b), (b) = (t)) + + +/*********************************************************************/ +/* */ +/* Memory allocation debugging routines */ +/* */ +/*********************************************************************/ + +#ifdef MEMDEBUG + +void *mallocx(size_t, char *, int); +void *callocx(size_t, size_t, char *, int); +void *reallocx(void *, size_t, char *, int); +void freex(void *, char *, int); + +#define malloct(x) mallocx((x), __FILE__, __LINE__) +#define calloct(x, y) callocx((x), (y), __FILE__, __LINE__) +#define realloct(x, y) reallocx((x), (y), __FILE__, __LINE__) +#define freet(x) freex((x), __FILE__, __LINE__) + +#define malloc malloct +#define calloc calloct +#define realloc realloct +#define free freet + +#endif + + + +/*********************************************************************/ +/* */ +/* General typedefs */ +/* */ +/*********************************************************************/ + +typedef unsigned char byte; + +typedef unsigned short int MDSP_BOOL; + +typedef unsigned short int Devaddr; + +typedef struct diskaddr { + int cylinder; + int track; + int record; +} Diskaddr; + + +typedef struct record_id { + + char id[8]; + time_t write_timestamp; + +} Record_ID; + + +typedef struct location { + + union { + unsigned bsn; + char cs[9]; + unsigned short msn; + } loc; + + unsigned location_is_bsn:1, + location_is_cs:1, + location_is_msn:1; + +} Location; + + + +/*********************************************************************/ +/*********************************************************************/ +/* */ +/* */ +/* Functions specific defines, typedefs, and structures */ +/* */ +/* */ +/*********************************************************************/ +/*********************************************************************/ + + + +/*********************************************************************/ +/* */ +/* Function prototype and structure(s) used in - */ +/* */ +/* bldstree - Build station information tree */ +/* delstree - Delete station information tree */ +/* getstinf - Get station information from tree */ +/* */ +/*********************************************************************/ + +typedef struct stn_info_node { + int key; + int block; + int station; + int latitude; + int longitude; + int elev; + struct stn_info_node * right; + struct stn_info_node * left; +} Stninfo; + +struct stn_info_node *bldstree(void); +void delstree(struct stn_info_node *); +struct stn_info_node *getstinf(struct stn_info_node *, + int, + int); + + + +/*********************************************************************/ +/* */ +/* Function prototype and structure(s) used in - */ +/* */ +/* capqread - Read bulletins from CAPQ chain */ +/* */ +/*********************************************************************/ + +typedef struct CAPQ_data { + char * bulletin; + int bulletin_length; + char * WMO_heading; + char * AFOS_pil; + char * current_CAPQ_end_address; + int start_offset; + int record_count; + int end_offset; + char * bulletin_address; + int input_line; + int receive_line; + int receive_hour; + int receive_minute; + int CAPQ_day; + int CAPQ_hour; + int CAPQ_minute; + int rc; + char flag1; + char flag2; +} CAPQdata; + +struct CAPQ_data * capqread (char *, ...); + + + +/*********************************************************************/ +/* */ +/* Function prototype and structure(s) used in - */ +/* */ +/* mdadread - Read bulletins from MDAD chain */ +/* */ +/*********************************************************************/ + +typedef struct MDAD_data { + char * bulletin; + int bulletin_length; + char * WMO_heading; + char * AFOS_pil; + char * current_MDAD_end_address; + int start_offset; + int record_count; + int end_offset; + char * bulletin_address; + int input_line; + int receive_line; + int receive_hour; + int receive_minute; + int MDAD_year; + int MDAD_month; + int MDAD_day; + int MDAD_hour; + int MDAD_minute; + int rc; + int part_number; + int number_of_parts; + char MDAD_flag; + char flag1; + char flag2; + char flag3; + char MDAD_flag2; +} MDADdata; + +MDADdata * mdadread (char *, ...); +MDADdata * mdupread (char *, ...); +MDADdata * mdadrd2 (char *, ...); + +MDADdata * mdadscan (char *, ...); +MDADdata * mdupscan (char *, ...); + +void mdadinpt ( MDADdata *, char, MDSP_BOOL ); + +char * mdadnxtr ( void ); +char * mdadnxtk ( void ); + + +#define MDAD_HISTORY_LIMIT 39 + +typedef +struct MDAD_history_entry +{ + unsigned short hour; + unsigned short minute; + Diskaddr MDAD_chain_addr; +} +MDAD_History_Entry; + +typedef +struct MDAD_history +{ + MDAD_History_Entry history_array[MDAD_HISTORY_LIMIT]; +} +MDAD_History; + +MDAD_History *mdadhist ( void ); + + +/*********************************************************************/ +/* */ +/* Function prototype and structure(s) used in - */ +/* */ +/* gethdgi - Get bulletin heading information */ +/* */ +/*********************************************************************/ + +typedef struct bltn_heading_info { + int bltn_day; + int bltn_hour; + int bltn_min; + int rtd_present; + int cor_present; + int amd_present; + char * first_report; + char TTAAii[7]; + char CCCC[5]; + char amd_seq; + char cor_seq; + char rtd_seq; +} Abbrevhdg; + +Abbrevhdg *gethdgi(char * ); + + + +/*********************************************************************/ +/* */ +/* Function prototype and structure(s) used in - */ +/* */ +/* getime - Get current system time */ +/* suspend - Delay execution until specified minute boundary */ +/* */ +/*********************************************************************/ + + +typedef struct tm_struct{ + int hour; + int min; +} Stime; + +Stime *gettime(void); +int suspend(Stime *, int); +int timediff(Stime *, Stime *); +#define timecmp timediff + + + +/*********************************************************************/ +/* */ +/* Function prototype and structure(s) used in - */ +/* */ +/* rdtaend - Specify rdtaread Ending Address */ +/* rdtaread - Read From RGTR Data Tank */ +/* rdtastrt - Specify rdtaread Starting Address */ +/* rdtatend - Specify rdtaread End Time */ +/* rdtatnke - Specify rdtaread Ending Address */ +/* rdtarstr - Specify rdtaread Start Time */ +/* */ +/*********************************************************************/ + +typedef struct rgtrdata { + Diskaddr forward_chain; + Diskaddr bulletin_addr; + int receive_line; + int receive_day; + Stime receive_time; + Stime RGTR_time; + int length; + char *bulletin; + char datatype; +} RGTRdata; + +int rdtaend(char, Diskaddr *); +int rdtaread(RGTRdata *); +int rdtastrt(char, Diskaddr *); +int rdtatend (char, Stime *); +int rdtatnke(char); +int rdtatstr(char, Stime *); +void rdtainit(void); + + + +/*********************************************************************/ +/* */ +/* Typedefs and function prototypes for bulletin and report parsing */ +/* functions. */ +/* */ +/*********************************************************************/ + + + +typedef struct rptNode { + char *rptPtr; + int rptLength; + struct rptNode* next; +} RptNode; + + +typedef struct synpBltn { + Abbrevhdg heading; + short int day; + short int hour; + int reportCount; + RptNode *rptList; + MDSP_BOOL valid; +} SynpBltn; + + +typedef struct shipBltn { + Abbrevhdg heading; + int reportCount; + RptNode *rptList; + MDSP_BOOL valid; +} ShipBltn; + + +typedef struct tepiBltn { + Abbrevhdg heading; + int reportCount; + RptNode *rptList; + MDSP_BOOL valid; +} TePiBltn; + + +typedef struct drftBltn { + Abbrevhdg heading; + int reportCount; + RptNode *rptList; + MDSP_BOOL valid; +} DrftBltn; + + +typedef struct airpBltn { + Abbrevhdg heading; + int reportCount; + RptNode *rptList; + MDSP_BOOL valid; +} AirpBltn; + + +typedef struct amdrBltn { + Abbrevhdg heading; + short int day; + short int hour; + int reportCount; + RptNode *rptList; + MDSP_BOOL valid; +} AmdrBltn; + + +typedef struct bthyBltn { + Abbrevhdg heading; + int reportCount; + RptNode *rptList; + MDSP_BOOL valid; +} BthyBltn; + + +typedef struct tescBltn { + Abbrevhdg heading; + int reportCount; + RptNode *rptList; + MDSP_BOOL valid; +} TescBltn; + + +typedef struct tracBltn { + Abbrevhdg heading; + int reportCount; + RptNode *rptList; + MDSP_BOOL valid; +} TracBltn; + + +typedef struct climBltn { + Abbrevhdg heading; + int reportCount; + int month; + int year; + RptNode *rptList; + MDSP_BOOL valid; +} ClimBltn; + + +typedef struct clmtBltn { + Abbrevhdg heading; + int reportCount; + int month; + int year; + RptNode *rptList; + MDSP_BOOL valid; +} ClmtBltn; + + +typedef struct metBltn { + Abbrevhdg heading; + int reportCount; + RptNode *rptList; + MDSP_BOOL valid; + short int day; /* -1 indicates missing/invalid */ + short int hour; /* -1 indicates missing/invalid */ + short int min; /* -1 indicates missing/invalid */ +} MetBltn; + + +typedef struct saoBltn { + Abbrevhdg heading; + int reportCount; + RptNode *rptList; + MDSP_BOOL valid; +} SAOBltn; + + +typedef struct prBltn { + Abbrevhdg heading; + int reportCount; + RptNode *rptList; + MDSP_BOOL valid; +} PRBltn; + + +typedef struct tafBltn { + Abbrevhdg heading; + int reportCount; + RptNode *rptList; + MDSP_BOOL valid; +} TafBltn; + + +typedef struct arepBltn{ + Abbrevhdg heading; + int reportCount; + RptNode *rptList; + MDSP_BOOL valid; +}ArepBltn; + +typedef struct metrRptP { + char locind[4]; + int groupCount; + short int day; /* -1 indicates missing or invalid */ + short int hour; /* -1 indicates missing or invalid */ + short int min; /* -1 indicates missing or invalid */ + MDSP_BOOL valid; +} MetrRptP; + + +typedef struct saoRptP { + char locind[4]; + int groupCount; + short int hour; /* -1 indicates missing or invalid */ + short int min; /* -1 indicates missing or invalid */ + MDSP_BOOL valid; +} SAORptP; + + +typedef struct prRptP { + char locind[4]; + int groupCount; + short int hour; /* -1 indicates missing or invalid */ + short int min; /* -1 indicates missing or invalid */ + MDSP_BOOL valid; +} PRRptP; + + +typedef struct tafRptP { + char locind[4]; + int groupCount; + short int YY; + short int GG; + short int validPeriod; + MDSP_BOOL ammendment; + MDSP_BOOL correction; + MDSP_BOOL valid; +} TafRptP; + + +typedef struct synpRptP { + short int II; + short int iii; + int groupCount; + MDSP_BOOL valid; +} SynpRptP; + + +typedef struct climRptP { + short int II; + short int iii; + int groupCount; + MDSP_BOOL valid; +} ClimRptP; + + +typedef struct clmtRptP { + short int II; + short int iii; + int groupCount; + MDSP_BOOL valid; +} ClmtRptP; + + +typedef struct tepiRptP { + short int II; + short int iii; + short int YY; + short int GG; + short int quad; + short int ulatitude; + short int ulongitude; + int msquare; + int latitude; + int longitude; + int groupCount; + char callsign[15]; + char type; + char part; + MDSP_BOOL valid; +} TePiRptP; + + +SynpBltn *pbsynp(char *); +ShipBltn *pbship(char *); +TePiBltn *pbtepi(char *); +DrftBltn *pbdrft(char *); +AirpBltn *pbairp(char *); +AmdrBltn *pbamdr(char *); +BthyBltn *pbbthy(char *); +TescBltn *pbtesc(char *); +TracBltn *pbtrac(char *); +ClimBltn *pbclim(char *); +ClmtBltn *pbclmt(char *); +MetBltn *pbmetr(char *); +MetBltn *pbspec(char *); +TafBltn *pbtaf(char *); +TafBltn *pbtaf2(char *); +TafBltn *pbtaf3(char *); +TafBltn *pbtaf4(char *); +SAOBltn *pbsao(char *); +PRBltn *pbpirep(char *); +ArepBltn *pbairep(char *); + +SynpRptP *prpsynp(char *, int); +TePiRptP *prptepi(char *, int); +ClimRptP *prpclim(char *, int); +ClmtRptP *prpclmt(char *, int); +MetrRptP *prpmetr(char *, int); +TafRptP *prptaf(char *, int); +TafRptP *prptaf2(char *, int); +TafRptP *prptaf3(char *, int); +TafRptP *prptaf4(char *, int); +SAORptP *prpsao(char *, int); +PRRptP *prppirep(char *, int); + + + + +/*********************************************************************/ +/* */ +/* Structures and Function Prototypes for RRN physical I/O */ +/* */ +/*********************************************************************/ + + +typedef struct RRN_device { + + char name[44], + ownerid[8]; + + unsigned short dev_addr, + base_cylinder, + base_track, + base_record, + max_cylinder, + max_track, + max_record, + records_per_track, + tracks_per_cylinder, + record_length; + +} RRN_Device; + + +MDSP_BOOL readrrn(char *device_name, + unsigned int rrn, + void *input_buffer, + unsigned int read_count); + +MDSP_BOOL writerrn(char *device_name, + unsigned int rrn, + void *output_buffer, + unsigned int write_count); + +RRN_Device *devinfo(char *device_name); + +MDSP_BOOL valid_dn(char *device_name); + + + +/*********************************************************************/ +/* */ +/* Function prototype for string value test functions. */ +/* */ +/*********************************************************************/ + + +int sisalnum(char *); +int sisalpha(char *); +int siscntrl(char *); +int sisdigit(char *); +int sisgraph(char *); +int sislower(char *); +int sisprint(char *); +int sispunct(char *); +int sisspace(char *); +int sisupper(char *); +int sisxdigi(char *); + +int nisalnum(char *, int); +int nisalpha(char *, int); +int niscntrl(char *, int); +int nisdigit(char *, int); +int nisgraph(char *, int); +int nislower(char *, int); +int nisprint(char *, int); +int nispunct(char *, int); +int nisspace(char *, int); +int nisupper(char *, int); +int nisxdigi(char *, int); + +char *nxtalnum(char *); +char *nxtalpha(char *); +char *nxtcntrl(char *); +char *nxtdigit(char *); +char *nxtgraph(char *); +char *nxtlower(char *); +char *nxtprint(char *); +char *nxtpunct(char *); +char *nxtspace(char *); +char *nxtupper(char *); +char *nxtxdigi(char *); + +char *lstalnum(char *, int); +char *lstalpha(char *, int); +char *lstcntrl(char *, int); +char *lstdigit(char *, int); +char *lstgraph(char *, int); +char *lstlower(char *, int); +char *lstprint(char *, int); +char *lstpunct(char *, int); +char *lstspace(char *, int); +char *lstupper(char *, int); +char *lstxdigi(char *, int); + + +/*********************************************************************/ +/* */ +/* Enumeration type and declaration for code form identification */ +/* function */ +/* */ +/*********************************************************************/ + + +typedef +enum codeform {AIREP, AMDAR, ARFOR, ARMET, BATHY, CLIMAT, CLIMAT_SHIP, + CLIMAT_TEMP, CLIMAT_TEMP_SHIP, CODAR, DRIFTER, FC, + HYFOR, IAC, IAC_FLEET, ICEAN, METAR, PILOT, PILOT_MOBILE, + PILOT_SHIP, RECCO, ROCOB, ROCOB_SHIP, ROFOR, SAO, PIREP, + SATEM, SATOB, SHIP, SPECI, SYNOP, TAF, TEMP, TEMP_DROP, + TEMP_MOBILE, TEMP_SHIP, TESAC, TRACKOB, WAVEOB, + UNKNOWN_FORM, TEMP_A, TEMP_B, TEMP_C, TEMP_D, + TEMP_DROP_A, TEMP_DROP_B, TEMP_DROP_C, TEMP_DROP_D, + TEMP_MOBILE_A, TEMP_MOBILE_B, TEMP_MOBILE_C, + TEMP_MOBILE_D, TEMP_SHIP_A, TEMP_SHIP_B, TEMP_SHIP_C, + TEMP_SHIP_D, PILOT_A, PILOT_B, PILOT_C, PILOT_D, + PILOT_MOBILE_A, PILOT_MOBILE_B, PILOT_MOBILE_C, + PILOT_MOBILE_D, PILOT_SHIP_A, PILOT_SHIP_B, + PILOT_SHIP_C, PILOT_SHIP_D } +CodeForm; + +CodeForm idcode(char *); + +char *codename(CodeForm); +CodeForm name2cf ( char * ); + + + +/********************************************************************/ +/* */ +/* Additional Bulletin and Report Parsing Structures and Routines */ +/* */ +/********************************************************************/ + +typedef struct mespBltn { + Abbrevhdg heading; + int reportCount; + RptNode *rptList; + CodeForm type; + MDSP_BOOL valid; + short int day; /* -1 indicates missing/invalid */ + short int hour; /* -1 indicates missing/invalid */ + short int min; /* -1 indicates missing/invalid */ +} MeSpBltn; + + +typedef struct mespRptP { + char locind[4]; + int groupCount; + CodeForm type; + char *rptStart; + int rptLen; + short int day; /* -1 indicates missing or invalid */ + short int hour; /* -1 indicates missing or invalid */ + short int min; /* -1 indicates missing or invalid */ + MDSP_BOOL valid; +} MeSpRptP; + + +MeSpBltn *pbmesp(char *); + +MeSpRptP *prpmesp(char *, int); + +MeSpBltn *tpbmesp(char *); + +MeSpRptP *tprpmesp(char *, int); + + + +/*********************************************************************/ +/* */ +/* String manipulation functions */ +/* */ +/*********************************************************************/ + + +char *strnlf(char *, size_t); +char *strnmid(char *, size_t, size_t); +char *strnrt(char *, size_t); +char *strrstr(char *, char *); +char *strcentr(char *, size_t); +char *strdel(char *, char *, size_t); +char *strins(char *, char *, char *); +char *strljust(char * , size_t); +char *strltrim(char *, char *); +char *strmrplc(char *, char *, char *); +char *strocat(char *, char *); +char *strrpt(char *, char *, size_t); +char *strrjust(char *, size_t); +char *strrplc(char * , char *, char *); +char *strrtrim(char * , char *); +char *strtrim(char *, char *); +char *strvcat(char *, char *, ...); + + + +/*********************************************************************/ +/* */ +/* Bulletin Generator declarations */ +/* */ +/*********************************************************************/ + +typedef MDSP_BOOL (*ParseBltnFnPtr) ( char *bltn, + char **rptPtr, + char *bbbTypePtr, + char **prefixPtr, + short *YYPtr, + short *GGPtr, + char *bltnTypePtr, + char **headingPtr ); + +void cbltngen ( ParseBltnFnPtr fnPtr, + char *filename, + Devaddr *historyDevice, + Diskaddr *historyAddr, + unsigned * bltnInCountPtr, + unsigned * bltnOutCountPtr, + unsigned * rptOutCountPtr ); + +void tbltngen ( ParseBltnFnPtr fnPtr, + char *filename, + Devaddr *historyDevice, + Diskaddr *historyAddr, + unsigned * bltnInCountPtr, + unsigned * bltnOutCountPtr, + unsigned * rptOutCountPtr ); + +typedef MDSP_BOOL (*ParseBltnFnPtrX) ( char *bltn, + char **rptPtr, + char *bbbTypePtr, + char **prefixPtr, + short *YYPtr, + short *GGPtr, + short *ggPtr, + char *bltnTypePtr, + char **headingPtr ); + +void xbltngen ( ParseBltnFnPtrX fnPtr, + char *filename, + Devaddr *historyDevice, + Diskaddr *historyAddr, + unsigned * bltnInCountPtr, + unsigned * bltnOutCountPtr, + unsigned * rptOutCountPtr ); + +void dbltngen ( ParseBltnFnPtrX fnPtr, + char *filename, + Devaddr *historyDevice, + Diskaddr *historyAddr, + unsigned * bltnInCountPtr, + unsigned * bltnOutCountPtr, + unsigned * rptOutCountPtr ); + +typedef MDSP_BOOL (*OParseBltnFnPtr) ( char *bltn, + char **rptPtr, + char *bbbTypePtr, + char **prefixPtr, + short *YYPtr, + short *GGPtr, + char *bltnTypePtr, + char **headingPtr, + char **ccccPtr ); + +void obltngen ( OParseBltnFnPtr fnPtr, + char *filename, + Devaddr *historyDevice, + Diskaddr *historyAddr, + unsigned * bltnInCountPtr, + unsigned * bltnOutCountPtr, + unsigned * rptOutCountPtr ); + + +void pbltngen ( OParseBltnFnPtr fnPtr, + char *filename, + Devaddr *historyDevice, + Diskaddr *historyAddr, + unsigned * bltnInCountPtr, + unsigned * bltnOutCountPtr, + unsigned * rptOutCountPtr ); + + +void sbltngen ( OParseBltnFnPtr fnPtr, + char *filename, + Devaddr *historyDevice, + Diskaddr *historyAddr, + unsigned * bltnInCountPtr, + unsigned * bltnOutCountPtr, + unsigned * rptOutCountPtr ); + + +void ebltngen ( ParseBltnFnPtr fnPtr, + char *filename, + Devaddr *historyDevice, + Diskaddr *historyAddr, + unsigned * bltnInCountPtr, + unsigned * bltnOutCountPtr, + unsigned * rptOutCountPtr ); + + +/*********************************************************************/ +/* */ +/* Typedefs and function prototypes for retrieving information from */ +/* switching directory. */ +/* */ +/*********************************************************************/ + +typedef struct sw_dir_info_rec { + char wmo_header[11]; + char AFOS_pil[10]; + char multiple_line; + short int line_num; + short int recvd_line; + char flag1; + char flag2; + char flag3; + char class; + short int domestic_cat_num; + char afos_tmp; + char ccb[2]; + char region_addr; + short int output_line_count; + unsigned short trans_line[128]; + char change_date[3]; + char dir_flags; + Diskaddr history_file_addr; + char birth_date[3]; +} SwDirInfo; + + +SwDirInfo *rtswdir(char *, int); +SwDirInfo *rtpswdir(void); +SwDirInfo *rtnswdir(void); + + + + + +/*********************************************************************/ +/* */ +/* General local functions */ +/* */ +/*********************************************************************/ + + +int itoc(int, char *, int); + +int antoi(char *, int); + +float antof(char *, int); + +void errmsg(char *, ...); + +void logmsg(char *, ...); + +void opermsg(char *, ...); + +int lmsg(const char *, const char *, ...); +int emsg(const char *, const char *, ...); +int omsg(const char *, const char *, ...); + +#pragma linkage(ASCTOEB, OS) +void ASCTOEB(char *, int); + +#pragma linkage(EAXLATE, OS) +void EAXLATE(char *, int); + +#pragma linkage(PASCTOEB, OS) +void PASCTOEB(char *, int); + +char **bldhdarr(char *); + +void dalchdar(char **); + +#pragma linkage(CCAPREAD, OS) +void *CCAPREAD(char *, int); + +#pragma linkage(CCAPWRIT, OS) +void CCAPWRIT(char *, char *, int); + +#pragma linkage(PPTOI, OS) +int PPTOI(char); + +char itopp(int); + +int diffmin(int, int, int, int, int, int); + +char incrseq(char); + +void nextdate(int *, int *, int *); + +void prevdate(int *, int *, int *); + +void rdstaddr(char *, char *); + +int wrenaddr(char *, char *); + +int vfydigit (char *, int); + +int readline(char * , int); + +int prevjul(int, int); + +int nextjul(int, int); + +int fcomppos(fpos_t *, fpos_t *); + +void lfprint(char *); + +void flfprint(FILE *, char *); + +void slfprint(char *, int, char *); + +void flfnprnt(FILE *, char *, int); + +void slfnprnt(char *, int, char *, int); + +int strhash(char *); + +void reverse(char *); + +MDSP_BOOL itoa(int, char *, int); + +int getsnn(char * , int); + +int fgetsnn(char *, int, FILE *); + +int getreply(char *, char *, int); + +MDSP_BOOL strfit(char *, char *, size_t); + +MDSP_BOOL addrfrm3(char *, Diskaddr *); + +MDSP_BOOL addrfrm5(char *, Diskaddr *); + +MDSP_BOOL addrto3(Diskaddr *, char *); + +MDSP_BOOL addrto5(Diskaddr *, char *); + +int addrcmp(Diskaddr *, Diskaddr *); + +void incraddr(Diskaddr *, Diskaddr *, Diskaddr *); +void decraddr(Diskaddr *, Diskaddr *, Diskaddr *); + +#pragma linkage(readrec, OS) +char *readrec(Diskaddr *, Devaddr *, int, void *); + +#pragma linkage(writerec, OS) +int writerec(Diskaddr*, Devaddr *, int, void *); + +char prhold(char *, ...); + +void dump(char *, int); + +void fdump(FILE *, char *, int); + +void fwdump(FILE *, char *, int); + +/* char toascii(char); */ + +char *strtoas(char *); + +char *strntoas(char *, int); + +char toebcdic(char); + +char *strtoeb(char *); + +char *strntoeb(char *, int); + +char *lfind(char *, char *, int, int, int(*)(char *, char *)); + +char *lsearch(char *, char *, int *, int, int(*)(char *, char *)); + +MDSP_BOOL strcmpw(char *, char *); + +int strccnt(char *, int); + +int strnccnt(char *, int, size_t); + +int pprt(FILE *, char *, char *, char *, char *, ...); + +MDSP_BOOL pprtbrk(FILE *, char *, char *, char *); + +MDSP_BOOL pprtend(FILE *, char *); + +MDSP_BOOL pprtinit(int, char, char *, char *, char *); + +char *monthnam(int, char); + +char *getrec(FILE *, int, char *); + +MDSP_BOOL jtog(int, int, int *, int *, int *); + +MDSP_BOOL gtoj(int, int, int, int *, int *); + +MDSP_BOOL ccap2std(char *, Devaddr *, Diskaddr *); + +MDSP_BOOL std2ccap(Devaddr *, Diskaddr *, char *); + +char *strupr(char *); +char *strlwr(char *); +/* char *strdup(char *); */ +/* char *strndup(char *, int); */ +int strcmpi(char *, char *); + +/* void *memccpy(void *, void *, int, unsigned); */ + +char *rptstrip(char *); +char *rptstrp2(char *); +char *rptfmt(char *); +char *rptfmt2(char *); +char *rptfmti(char *, unsigned short int); +char *rptfmtni(char *, unsigned short int); + +char *strnstr(char *, char *, size_t); + +int stregion(int); +int ccregion(char *); +char *rgnname(int); + +void *memrchr(const void *, int, size_t); + +MDSP_BOOL sysmonms(char *, char *, ...); +MDSP_BOOL sysmoncl(char *); + +short prevndx ( short max, short min, short current ); +short nextndx ( short max, short min, short current ); + +time_t extrym ( unsigned day, unsigned hour, unsigned minute ); +time_t extrymd ( unsigned hour, unsigned minute ); + +int cmptimet ( time_t t1, time_t t2 ); + +int tfprintf ( FILE *, const char *, ... ); + +MDSP_BOOL purgelog ( char *filename, unsigned short delete_age ); + +time_t odbtime ( void ); + +int bltnpcnt ( char *, int ); +void bltnpage ( char *, int, int ); + +void rot( char *, unsigned int ); +void unrot( char *, unsigned int ); + +void encrypt( char *, char * ); +void decrypt( char *, char * ); + +int HEXTOI( char *, int ); + +char **hdgxref( char * ); + +struct tm *zonetime( unsigned short, unsigned short, char ); + +int wordcnt( char * ); +int wordcntn( char *, unsigned int ); + +char *word( char *, unsigned int ); +char *wordn( char *, unsigned int, unsigned int ); + +char *crlfstrp( char * ); + +MDSP_BOOL charcmp( char *, char * ); + +int linecnt( char * ); +int linecntn( char *, unsigned int ); + +char *bltline( char *, unsigned int ); +char *bltlinen( char *, unsigned int, unsigned int ); + +char *pttoline( char *, unsigned int ); +char *pttoword( char *, unsigned int ); + +char *moblrgn(unsigned short, + unsigned short, + unsigned short ); + +char *nxtgroup( char * ); + +void prtmdade(struct MDAD_data *); + +#endif diff --git a/test/metar-to-text/mdsplib-code-1/src/metar_structs.h b/test/metar-to-text/mdsplib-code-1/src/metar_structs.h new file mode 100644 index 00000000..298d70b9 --- /dev/null +++ b/test/metar-to-text/mdsplib-code-1/src/metar_structs.h @@ -0,0 +1,283 @@ +/* +METAR Decoder Software Package Library: Parses Aviation Routine Weather Reports +Copyright (C) 2003 Eric McCarthy + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef METARX +#define METARX + +/********************************************************************/ +/* */ +/* Title: METAR H */ +/* Organization: W/OSO242 - GRAPHICS AND DISPLAY SECTION */ +/* Date: 19 Jan 1996 */ +/* Programmer: CARL MCCALLA */ +/* Language: C/370 */ +/* */ +/* Abstract: METAR Decoder Header File. */ +/* */ +/* Modification History: */ +/* None. */ +/* */ +/********************************************************************/ + + +#include "local.h" /* standard header file */ + + +/*********************************************/ +/* */ +/* RUNWAY VISUAL RANGE STRUCTURE DECLARATION */ +/* AND VARIABLE TYPE DEFINITION */ +/* */ +/*********************************************/ + +typedef struct runway_VisRange { + char runway_designator[6]; + MDSP_BOOL vrbl_visRange; + MDSP_BOOL below_min_RVR; + MDSP_BOOL above_max_RVR; + int visRange; + int Max_visRange; + int Min_visRange; +} Runway_VisRange; + +/***********************************************/ +/* */ +/* DISPATCH VISUAL RANGE STRUCTURE DECLARATION */ +/* AND VARIABLE TYPE DEFINITION */ +/* */ +/***********************************************/ + +typedef struct dispatch_VisRange { + MDSP_BOOL vrbl_visRange; + MDSP_BOOL below_min_DVR; + MDSP_BOOL above_max_DVR; + int visRange; + int Max_visRange; + int Min_visRange; +} Dispatch_VisRange; + +/*****************************************/ +/* */ +/* CLOUD CONDITION STRUCTURE DECLARATION */ +/* AND VARIABLE TYPE DEFINITION */ +/* */ +/*****************************************/ + +typedef struct cloud_Conditions { + char cloud_type[5]; + char cloud_hgt_char[4]; + char other_cld_phenom[4]; + int cloud_hgt_meters; +} Cloud_Conditions; + +/*****************************************/ +/* */ +/* WIND GROUP DATA STRUCTURE DECLARATION */ +/* AND VARIABLE TYPE DEFINITION */ +/* */ +/*****************************************/ + +typedef struct windstruct { + char windUnits[ 4 ]; + MDSP_BOOL windVRB; + int windDir; + int windSpeed; + int windGust; +} WindStruct; + +/*****************************************/ +/* */ +/* RECENT WX GROUP STRUCTURE DECLARATION */ +/* AND VARIABLE TYPE DEFINITION */ +/* */ +/*****************************************/ + +typedef struct recent_wx { + char Recent_weather[ 5 ]; + int Bhh; + int Bmm; + int Ehh; + int Emm; +} Recent_Wx; + +/***************************************/ +/* */ +/* DECODED METAR STRUCTURE DECLARATION */ +/* AND VARIABLE TYPE DEFINITION */ +/* */ +/***************************************/ + +typedef struct decoded_METAR { + char synoptic_cloud_type[ 6 ]; + char snow_depth_group[ 6 ]; + char codeName[ 6 ]; + char stnid[5]; + char horiz_vsby[5]; + char dir_min_horiz_vsby[3]; + char vsby_Dir[ 3 ]; + char WxObstruct[10][8]; + char autoIndicator[5]; + char VSBY_2ndSite_LOC[10]; + char SKY_2ndSite_LOC[10]; + char SKY_2ndSite[10]; + char SectorVsby_Dir[ 3 ]; + char ObscurAloft[ 12 ]; + char ObscurAloftSkyCond[ 12 ]; + char VrbSkyBelow[ 4 ]; + char VrbSkyAbove[ 4 ]; + char LTG_DIR[ 3 ]; + char CloudLow; + char CloudMedium; + char CloudHigh; + char CIG_2ndSite_LOC[10]; + char VIRGA_DIR[3]; + char TornadicType[15]; + char TornadicLOC[10]; + char TornadicDIR[4]; + char TornadicMovDir[3]; + char CHINO_LOC[6]; + char VISNO_LOC[6]; + char PartialObscurationAmt[2][7]; + char PartialObscurationPhenom[2][12]; + char SfcObscuration[6][10]; + char charPrevailVsby[12]; + char charVertVsby[10]; + char TS_LOC[3]; + char TS_MOVMNT[3]; + + MDSP_BOOL Indeterminant3_6HrPrecip; + MDSP_BOOL Indeterminant_24HrPrecip; + MDSP_BOOL CIGNO; + MDSP_BOOL SLPNO; + MDSP_BOOL ACFTMSHP; + MDSP_BOOL NOSPECI; + MDSP_BOOL FIRST; + MDSP_BOOL LAST; + MDSP_BOOL SunSensorOut; + MDSP_BOOL AUTO; + MDSP_BOOL COR; + MDSP_BOOL NIL_rpt; + MDSP_BOOL CAVOK; + MDSP_BOOL RVRNO; + MDSP_BOOL A_altstng; + MDSP_BOOL Q_altstng; + MDSP_BOOL VIRGA; + MDSP_BOOL VOLCASH; + MDSP_BOOL GR; + MDSP_BOOL CHINO; + MDSP_BOOL VISNO; + MDSP_BOOL PNO; + MDSP_BOOL PWINO; + MDSP_BOOL FZRANO; + MDSP_BOOL TSNO; + MDSP_BOOL DollarSign; + MDSP_BOOL PRESRR; + MDSP_BOOL PRESFR; + MDSP_BOOL Wshft_FROPA; + MDSP_BOOL OCNL_LTG; + MDSP_BOOL FRQ_LTG; + MDSP_BOOL CNS_LTG; + MDSP_BOOL CG_LTG; + MDSP_BOOL IC_LTG; + MDSP_BOOL CC_LTG; + MDSP_BOOL CA_LTG; + MDSP_BOOL DSNT_LTG; + MDSP_BOOL AP_LTG; + MDSP_BOOL VcyStn_LTG; + MDSP_BOOL OVHD_LTG; + MDSP_BOOL LightningVCTS; + MDSP_BOOL LightningTS; + + int TornadicDistance; + int ob_hour; + int ob_minute; + int ob_date; + int minWnDir; + int maxWnDir; + int VertVsby; + int temp; + int dew_pt_temp; + int QFE; + int hectoPasc_altstng; + int char_prestndcy; + int minCeiling; + int maxCeiling; + int WshfTime_hour; + int WshfTime_minute; + int min_vrbl_wind_dir; + int max_vrbl_wind_dir; + int PKWND_dir; + int PKWND_speed; + int PKWND_hour; + int PKWND_minute; + int SKY_2ndSite_Meters; + int Ceiling; + int Estimated_Ceiling; + int SNINCR; + int SNINCR_TotalDepth; + int SunshineDur; + int ObscurAloftHgt; + int VrbSkyLayerHgt; + int Num8thsSkyObscured; + int CIG_2ndSite_Meters; + int snow_depth; + int BTornadicHour; + int BTornadicMinute; + int ETornadicHour; + int ETornadicMinute; + + + float SectorVsby; + float WaterEquivSnow; + float VSBY_2ndSite; + float prevail_vsbySM; + float prevail_vsbyM; + float prevail_vsbyKM; + float prestndcy; + float precip_amt; + float precip_24_amt; + float maxtemp; + float mintemp; + float max24temp; + float min24temp; + float minVsby; + float maxVsby; + float hourlyPrecip; + float TWR_VSBY; + float SFC_VSBY; + float Temp_2_tenths; + float DP_Temp_2_tenths; + float SLP; + float GR_Size; + + double inches_altstng; + + Runway_VisRange RRVR[12]; + Dispatch_VisRange DVR; + Recent_Wx ReWx[3]; + WindStruct winData; + Cloud_Conditions cldTypHgt[6]; + +} Decoded_METAR; + +#define MAXWXSYMBOLS 10 /*-- NOT TO EXCEED 10 PRES. WX GRPS --*/ +#define MAXTOKENS 500 /*-- RPT NOT TO EXCEED 500 GRPS --*/ + + +#endif diff --git a/test/metar-to-text/mdsplib-code-1/src/prtdmetr.c b/test/metar-to-text/mdsplib-code-1/src/prtdmetr.c new file mode 100644 index 00000000..fe9491fd --- /dev/null +++ b/test/metar-to-text/mdsplib-code-1/src/prtdmetr.c @@ -0,0 +1,963 @@ +/* +METAR Decoder Software Package Library: Parses Aviation Routine Weather Reports +Copyright (C) 2003 Eric McCarthy + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "metar_structs.h" + + +void say_text(char *speech, char *text) { + int i; + char tmp[2]; + for (i = 0; i < strlen(text); i++) { + switch(text[i]) { + case '-': + strcat(speech, "minus"); + break; + case '0': + strcat(speech, "zero"); + break; + case '1': + strcat(speech, "one"); + break; + case '2': + strcat(speech, "two"); + break; + case '3': + strcat(speech, "three"); + break; + case '4': + strcat(speech, "four"); + break; + case '5': + strcat(speech, "five"); + break; + case '6': + strcat(speech, "six"); + break; + case '7': + strcat(speech, "seven"); + break; + case '8': + strcat(speech, "eight"); + break; + case '9': + strcat(speech, "niner"); + break; + case '@': + strcat(speech, "at"); + break; + default: + sprintf(tmp, "%c", text[i]); + strcat(speech, tmp); + break; + } + strcat(speech, " "); + } +} + + + +void sprint_metar (char * string, Decoded_METAR *Mptr) +{ + + /***************************/ + /* DECLARE LOCAL VARIABLES */ + /***************************/ + + int i; + char temp[100]; + + /*************************/ + /* START BODY OF ROUTINE */ + /*************************/ + + + strcat(string, "ME TAR. "); + + if ( Mptr->stnid[ 0 ] == '\0' ) { + strcat(string, "Error"); + return; + } + + for (i = 0; i < strlen(Mptr->stnid); i++) { + sprintf(temp, "%c ", Mptr->stnid[i]); + strcat(string, temp); + } + strcat(string, ". "); + + if (Mptr->ob_hour != MAXINT && Mptr->ob_minute != MAXINT) { + if (Mptr->AUTO) { + strcat(string, "Automated "); + } + if (Mptr->COR) { + strcat(string, "Corrected "); + } + strcat(string, "Observation "); + sprintf(temp, "%d%d", Mptr->ob_hour, Mptr->ob_minute); + say_text(string, temp); + strcat(string, "zulu. "); + } + + strcat(string, "Wind "); + if (Mptr->winData.windDir != MAXINT) { + sprintf(temp, "%03d", Mptr->winData.windDir); + say_text(string, temp); + } + + if ( Mptr->winData.windVRB ) { + strcat(string, "variable "); + } + + //FIXME. + if ( Mptr->minWnDir != MAXINT ) { + sprintf(temp, "%03d", Mptr->minWnDir); + say_text(string, temp); + } + + //FIXME. + if ( Mptr->maxWnDir != MAXINT ) { + sprintf(temp, "%03d", Mptr->maxWnDir); + say_text(string, temp); + } + + + if ( Mptr->winData.windSpeed != MAXINT ) { + sprintf(temp, "@%d", Mptr->winData.windSpeed); + say_text(string, temp); + } else { + strcat(string, "calm "); + } + + if ( Mptr->winData.windGust != MAXINT ) { + strcat(string, "gusting "); + sprintf(temp, "%d", Mptr->winData.windGust); + say_text(string, temp); + } + + strcat(string, ". "); + + if ( Mptr->prevail_vsbyM != (float) MAXINT ) { + strcat(string, "Visibility "); + sprintf(temp, "%d", (int)Mptr->prevail_vsbyM); + say_text(string, temp); + strcat(string, "miles. "); + } + + if ( Mptr->prevail_vsbySM != (float) MAXINT ) { + strcat(string, "Visibility "); + sprintf(temp, "%d", (int)Mptr->prevail_vsbySM); + say_text(string, temp); + strcat(string, "miles. "); + } + + +/* for ( i = 0; i < 12; i++ ) + { + if( Mptr->RRVR[i].runway_designator[0] != '\0' ) { + sprintf(temp, "RUNWAY DESIGNATOR : %s\n", + Mptr->RRVR[i].runway_designator); + strcat(string, temp); + } + + if( Mptr->RRVR[i].visRange != MAXINT ) { + sprintf(temp, "R_WAY VIS RANGE (FT): %d\n", + Mptr->RRVR[i].visRange); + strcat(string, temp); + } + + if ( Mptr->RRVR[i].vrbl_visRange ) { + sprintf(temp, "VRBL VISUAL RANGE : TRUE\n"); + strcat(string, temp); + } + + if ( Mptr->RRVR[i].below_min_RVR ) { + sprintf(temp, "BELOW MIN RVR : TRUE\n"); + strcat(string, temp); + } + + if ( Mptr->RRVR[i].above_max_RVR ) { + sprintf(temp, "ABOVE MAX RVR : TRUE\n"); + strcat(string, temp); + } + + if( Mptr->RRVR[i].Max_visRange != MAXINT ) { + sprintf(temp, "MX R_WAY VISRNG (FT): %d\n", + Mptr->RRVR[i].Max_visRange); + strcat(string, temp); + } + + if( Mptr->RRVR[i].Min_visRange != MAXINT ) { + sprintf(temp, "MN R_WAY VISRNG (FT): %d\n", + Mptr->RRVR[i].Min_visRange); + strcat(string, temp); + } + + } + + + if( Mptr->DVR.visRange != MAXINT ) { + sprintf(temp, "DISPATCH VIS RANGE : %d\n", + Mptr->DVR.visRange); + strcat(string, temp); + } + + if ( Mptr->DVR.vrbl_visRange ) { + sprintf(temp, "VRBL DISPATCH VISRNG: TRUE\n"); + strcat(string, temp); + } + + if ( Mptr->DVR.below_min_DVR ) { + sprintf(temp, "BELOW MIN DVR : TRUE\n"); + strcat(string, temp); + } + + if ( Mptr->DVR.above_max_DVR ) { + sprintf(temp, "ABOVE MAX DVR : TRUE\n"); + strcat(string, temp); + } + + if( Mptr->DVR.Max_visRange != MAXINT ) { + sprintf(temp, "MX DSPAT VISRNG (FT): %d\n", + Mptr->DVR.Max_visRange); + strcat(string, temp); + } + + if( Mptr->DVR.Min_visRange != MAXINT ) { + sprintf(temp, "MN DSPAT VISRNG (FT): %d\n", + Mptr->DVR.Min_visRange); + strcat(string, temp); + } + + + i = 0; + while ( Mptr->WxObstruct[i][0] != '\0' && i < MAXWXSYMBOLS ) + { + sprintf(temp, "WX/OBSTRUCT VISION : %s\n", + Mptr->WxObstruct[i] ); + strcat(string, temp); + i++; + } + + if ( Mptr->PartialObscurationAmt[0][0] != '\0' ) { + sprintf(temp, "OBSCURATION AMOUNT : %s\n", + &(Mptr->PartialObscurationAmt[0][0])); + strcat(string, temp); + } + + if ( Mptr->PartialObscurationPhenom[0][0] != '\0' ) { + sprintf(temp, "OBSCURATION PHENOM : %s\n", + &(Mptr->PartialObscurationPhenom[0][0])); + strcat(string, temp); + } + + + if ( Mptr->PartialObscurationAmt[1][0] != '\0' ) { + sprintf(temp, "OBSCURATION AMOUNT : %s\n", + &(Mptr->PartialObscurationAmt[1][0])); + strcat(string, temp); + } + + if ( Mptr->PartialObscurationPhenom[1][0] != '\0' ) { + sprintf(temp, "OBSCURATION PHENOM : %s\n", + &(Mptr->PartialObscurationPhenom[1][0])); + strcat(string, temp); + } + */ + + strcat(string, "Sky condition "); + + i = 0; + while ( Mptr->cldTypHgt[ i ].cloud_type[0] != '\0' && + i < 6 ) + { + if ( Mptr->cldTypHgt[ i ].cloud_type[0] != '\0' ) { + if (strcmp(Mptr->cldTypHgt[ i ].cloud_type, "CLR") == 0) { + strcat(string, "clear. "); + } else { + if (strcmp(Mptr->cldTypHgt[ i ].cloud_type, "FEW") == 0) { + strcat(string, "scattered "); + } + if (strcmp(Mptr->cldTypHgt[ i ].cloud_type, "SCT") == 0) { + strcat(string, "scattered "); + } + if (strcmp(Mptr->cldTypHgt[ i ].cloud_type, "BKN") == 0) { + strcat(string, "broken "); + } + if (strcmp(Mptr->cldTypHgt[ i ].cloud_type, "OVC") == 0) { + strcat(string, "overcast "); + } + //TODO: Others? + } + } + + if ( Mptr->cldTypHgt[ i ].cloud_hgt_char[0] != '\0' ) { + char thousands[4]; + char hundreds[4]; + int height = atoi(Mptr->cldTypHgt[i].cloud_hgt_char); + if ((height - (height%10)) > 0) { + sprintf(thousands, "%d", height/10); + say_text(string, thousands); + strcat(string, "thousand "); + } + if (height%10) { + sprintf(hundreds, "%d", height%10); + say_text(string, hundreds); + strcat(string, "hundred "); + } + } + + strcat(string, ". "); + /* + //TODO: TCU, Towering Cumulus, etc. + if ( Mptr->cldTypHgt[ i ].other_cld_phenom[0] != '\0' ) { + sprintf(temp, "OTHER CLOUD PHENOM : %s\n", + Mptr->cldTypHgt[ i ].other_cld_phenom); + strcat(string, temp); + } + */ + + i++; + + } + + if ( Mptr->temp != MAXINT ) { + strcat(string, "Temperature "); + sprintf(temp, "%d", Mptr->temp); + say_text(string, temp); + strcat(string, "celsius. "); + } + + if ( Mptr->dew_pt_temp != MAXINT ) { + strcat(string, "Dew point "); + sprintf(temp, "%d", Mptr->dew_pt_temp); + say_text(string, temp); + strcat(string, "celsius. "); + } + + if ( Mptr->A_altstng ) { + strcat(string, "Altimeter "); + sprintf(temp, "%d", (int)Mptr->inches_altstng); + say_text(string, temp); + strcat(string, ", "); + sprintf(temp, "%02d", (int)(100*Mptr->inches_altstng)%100); + say_text(string, temp); + strcat(string, ". "); + } + + /* + if ( Mptr->TornadicType[0] != '\0' ) { + sprintf(temp, "TORNADIC ACTVTY TYPE: %s\n", + Mptr->TornadicType ); + strcat(string, temp); + } + + if ( Mptr->BTornadicHour != MAXINT ) { + sprintf(temp, "TORN. ACTVTY BEGHOUR: %d\n", + Mptr->BTornadicHour ); + strcat(string, temp); + } + + if ( Mptr->BTornadicMinute != MAXINT ) { + sprintf(temp, "TORN. ACTVTY BEGMIN : %d\n", + Mptr->BTornadicMinute ); + strcat(string, temp); + } + + if ( Mptr->ETornadicHour != MAXINT ) { + sprintf(temp, "TORN. ACTVTY ENDHOUR: %d\n", + Mptr->ETornadicHour ); + strcat(string, temp); + } + + if ( Mptr->ETornadicMinute != MAXINT ) { + sprintf(temp, "TORN. ACTVTY ENDMIN : %d\n", + Mptr->ETornadicMinute ); + strcat(string, temp); + } + + if ( Mptr->TornadicDistance != MAXINT ) { + sprintf(temp, "TORN. DIST. FROM STN: %d\n", + Mptr->TornadicDistance ); + strcat(string, temp); + } + + if ( Mptr->TornadicLOC[0] != '\0' ) { + sprintf(temp, "TORNADIC LOCATION : %s\n", + Mptr->TornadicLOC ); + strcat(string, temp); + } + + if ( Mptr->TornadicDIR[0] != '\0' ) { + sprintf(temp, "TORNAD. DIR FROM STN: %s\n", + Mptr->TornadicDIR ); + strcat(string, temp); + } + + if ( Mptr->TornadicMovDir[0] != '\0' ) { + sprintf(temp, "TORNADO DIR OF MOVM.: %s\n", + Mptr->TornadicMovDir ); + strcat(string, temp); + } + + + if ( Mptr->autoIndicator[0] != '\0' ) { + sprintf(temp, "AUTO INDICATOR : %s\n", + Mptr->autoIndicator); + strcat(string, temp); + } + + if ( Mptr->PKWND_dir != MAXINT ) { + sprintf(temp, "PEAK WIND DIRECTION : %d\n",Mptr->PKWND_dir); + strcat(string, temp); + } + if ( Mptr->PKWND_speed != MAXINT ) { + sprintf(temp, "PEAK WIND SPEED : %d\n",Mptr->PKWND_speed); + strcat(string, temp); + } + if ( Mptr->PKWND_hour != MAXINT ) { + sprintf(temp, "PEAK WIND HOUR : %d\n",Mptr->PKWND_hour); + strcat(string, temp); + } + if ( Mptr->PKWND_minute != MAXINT ) { + sprintf(temp, "PEAK WIND MINUTE : %d\n",Mptr->PKWND_minute); + strcat(string, temp); + } + + if ( Mptr->WshfTime_hour != MAXINT ) { + sprintf(temp, "HOUR OF WIND SHIFT : %d\n",Mptr->WshfTime_hour); + strcat(string, temp); + } + if ( Mptr->WshfTime_minute != MAXINT ) { + sprintf(temp, "MINUTE OF WIND SHIFT: %d\n",Mptr->WshfTime_minute); + strcat(string, temp); + } + if ( Mptr->Wshft_FROPA != FALSE ) { + sprintf(temp, "FROPA ASSOC. W/WSHFT: TRUE\n"); + strcat(string, temp); + } + + if ( Mptr->TWR_VSBY != (float) MAXINT ) { + sprintf(temp, "TOWER VISIBILITY : %.2f\n",Mptr->TWR_VSBY); + strcat(string, temp); + } + if ( Mptr->SFC_VSBY != (float) MAXINT ) { + sprintf(temp, "SURFACE VISIBILITY : %.2f\n",Mptr->SFC_VSBY); + strcat(string, temp); + } + + if ( Mptr->minVsby != (float) MAXINT ) { + sprintf(temp, "MIN VRBL_VIS (SM) : %.4f\n",Mptr->minVsby); + strcat(string, temp); + } + if ( Mptr->maxVsby != (float) MAXINT ) { + sprintf(temp, "MAX VRBL_VIS (SM) : %.4f\n",Mptr->maxVsby); + strcat(string, temp); + } + + if( Mptr->VSBY_2ndSite != (float) MAXINT ) { + sprintf(temp, "VSBY_2ndSite (SM) : %.4f\n",Mptr->VSBY_2ndSite); + strcat(string, temp); + } + + if( Mptr->VSBY_2ndSite_LOC[0] != '\0' ) { + sprintf(temp, "VSBY_2ndSite LOC. : %s\n", + Mptr->VSBY_2ndSite_LOC); + strcat(string, temp); + } + + if ( Mptr->OCNL_LTG ) { + sprintf(temp, "OCCASSIONAL LTG : TRUE\n"); + strcat(string, temp); + } + + if ( Mptr->FRQ_LTG ) { + sprintf(temp, "FREQUENT LIGHTNING : TRUE\n"); + strcat(string, temp); + } + + if ( Mptr->CNS_LTG ) { + sprintf(temp, "CONTINUOUS LTG : TRUE\n"); + strcat(string, temp); + } + + if ( Mptr->CG_LTG ) { + sprintf(temp, "CLOUD-GROUND LTG : TRUE\n"); + strcat(string, temp); + } + + if ( Mptr->IC_LTG ) { + sprintf(temp, "IN-CLOUD LIGHTNING : TRUE\n"); + strcat(string, temp); + } + + if ( Mptr->CC_LTG ) { + sprintf(temp, "CLD-CLD LIGHTNING : TRUE\n"); + strcat(string, temp); + } + + if ( Mptr->CA_LTG ) { + sprintf(temp, "CLOUD-AIR LIGHTNING : TRUE\n"); + strcat(string, temp); + } + + if ( Mptr->AP_LTG ) { + sprintf(temp, "LIGHTNING AT AIRPORT: TRUE\n"); + strcat(string, temp); + } + + if ( Mptr->OVHD_LTG ) { + sprintf(temp, "LIGHTNING OVERHEAD : TRUE\n"); + strcat(string, temp); + } + + if ( Mptr->DSNT_LTG ) { + sprintf(temp, "DISTANT LIGHTNING : TRUE\n"); + strcat(string, temp); + } + + if ( Mptr->LightningVCTS ) { + sprintf(temp, "L'NING W/I 5-10(ALP): TRUE\n"); + strcat(string, temp); + } + + if ( Mptr->LightningTS ) { + sprintf(temp, "L'NING W/I 5 (ALP) : TRUE\n"); + strcat(string, temp); + } + + if ( Mptr->VcyStn_LTG ) { + sprintf(temp, "VCY STN LIGHTNING : TRUE\n"); + strcat(string, temp); + } + + if ( Mptr->LTG_DIR[0] != '\0' ) { + sprintf(temp, "DIREC. OF LIGHTNING : %s\n", Mptr->LTG_DIR); + strcat(string, temp); + } + + + + i = 0; + while( i < 3 && Mptr->ReWx[ i ].Recent_weather[0] != '\0' ) + { + sprintf(temp, "RECENT WEATHER : %s", + Mptr->ReWx[i].Recent_weather); + strcat(string, temp); + + if ( Mptr->ReWx[i].Bhh != MAXINT ) { + sprintf(temp, " BEG_hh = %d",Mptr->ReWx[i].Bhh); + strcat(string, temp); + } + if ( Mptr->ReWx[i].Bmm != MAXINT ) { + sprintf(temp, " BEG_mm = %d",Mptr->ReWx[i].Bmm); + strcat(string, temp); + } + + if ( Mptr->ReWx[i].Ehh != MAXINT ) { + sprintf(temp, " END_hh = %d",Mptr->ReWx[i].Ehh); + strcat(string, temp); + } + if ( Mptr->ReWx[i].Emm != MAXINT ) { + sprintf(temp, " END_mm = %d",Mptr->ReWx[i].Emm); + strcat(string, temp); + } + + strcat(string, "\n"); + + i++; + } + + if ( Mptr->minCeiling != MAXINT ) { + sprintf(temp, "MIN VRBL_CIG (FT) : %d\n",Mptr->minCeiling); + strcat(string, temp); + } + if ( Mptr->maxCeiling != MAXINT ) { + sprintf(temp, "MAX VRBL_CIG (FT)) : %d\n",Mptr->maxCeiling); + strcat(string, temp); + } + + if ( Mptr->CIG_2ndSite_Meters != MAXINT ) { + sprintf(temp, "CIG2ndSite (FT) : %d\n",Mptr->CIG_2ndSite_Meters); + strcat(string, temp); + } + if ( Mptr->CIG_2ndSite_LOC[0] != '\0' ) { + sprintf(temp, "CIG @ 2nd Site LOC. : %s\n",Mptr->CIG_2ndSite_LOC); + strcat(string, temp); + } + + if ( Mptr->PRESFR ) { + sprintf(temp, "PRESFR : TRUE\n"); + strcat(string, temp); + } + if ( Mptr->PRESRR ) { + sprintf(temp, "PRESRR : TRUE\n"); + strcat(string, temp); + } + + if ( Mptr->SLPNO ) { + sprintf(temp, "SLPNO : TRUE\n"); + strcat(string, temp); + } + + if ( Mptr->SLP != (float) MAXINT ) { + sprintf(temp, "SLP (hPa) : %.1f\n", Mptr->SLP); + strcat(string, temp); + } + + if ( Mptr->SectorVsby != (float) MAXINT ) { + sprintf(temp, "SECTOR VSBY (MILES) : %.2f\n", Mptr->SectorVsby ); + strcat(string, temp); + } + + if ( Mptr->SectorVsby_Dir[ 0 ] != '\0' ) { + sprintf(temp, "SECTOR VSBY OCTANT : %s\n", Mptr->SectorVsby_Dir ); + strcat(string, temp); + } + + if ( Mptr->TS_LOC[ 0 ] != '\0' ) { + sprintf(temp, "THUNDERSTORM LOCAT. : %s\n", Mptr->TS_LOC ); + strcat(string, temp); + } + + if ( Mptr->TS_MOVMNT[ 0 ] != '\0' ) { + sprintf(temp, "THUNDERSTORM MOVMNT.: %s\n", Mptr->TS_MOVMNT); + strcat(string, temp); + } + + if ( Mptr->GR ) { + sprintf(temp, "GR (HAILSTONES) : TRUE\n"); + strcat(string, temp); + } + + if ( Mptr->GR_Size != (float) MAXINT ) { + sprintf(temp, "HLSTO SIZE (INCHES) : %.3f\n",Mptr->GR_Size); + strcat(string, temp); + } + + if ( Mptr->VIRGA ) { + sprintf(temp, "VIRGA : TRUE\n"); + strcat(string, temp); + } + + if ( Mptr->VIRGA_DIR[0] != '\0' ) { + sprintf(temp, "DIR OF VIRGA FRM STN: %s\n", Mptr->VIRGA_DIR); + strcat(string, temp); + } + + for( i = 0; i < 6; i++ ) { + if( Mptr->SfcObscuration[i][0] != '\0' ) { + sprintf(temp, "SfcObscuration : %s\n", + &(Mptr->SfcObscuration[i][0]) ); + strcat(string, temp); + } + } + + if ( Mptr->Num8thsSkyObscured != MAXINT ) { + sprintf(temp, "8ths of SkyObscured : %d\n",Mptr->Num8thsSkyObscured); + strcat(string, temp); + } + + if ( Mptr->CIGNO ) { + sprintf(temp, "CIGNO : TRUE\n"); + strcat(string, temp); + } + + if ( Mptr->Ceiling != MAXINT ) { + sprintf(temp, "Ceiling (ft) : %d\n",Mptr->Ceiling); + strcat(string, temp); + } + + if ( Mptr->Estimated_Ceiling != MAXINT ) { + sprintf(temp, "Estimated CIG (ft) : %d\n",Mptr->Estimated_Ceiling); + strcat(string, temp); + } + + if ( Mptr->VrbSkyBelow[0] != '\0' ) { + sprintf(temp, "VRB SKY COND BELOW : %s\n",Mptr->VrbSkyBelow); + strcat(string, temp); + } + + if ( Mptr->VrbSkyAbove[0] != '\0' ) { + sprintf(temp, "VRB SKY COND ABOVE : %s\n",Mptr->VrbSkyAbove); + strcat(string, temp); + } + + if ( Mptr->VrbSkyLayerHgt != MAXINT ) { + sprintf(temp, "VRBSKY COND HGT (FT): %d\n",Mptr->VrbSkyLayerHgt); + strcat(string, temp); + } + + if ( Mptr->ObscurAloftHgt != MAXINT ) { + sprintf(temp, "Hgt Obscur Aloft(ft): %d\n",Mptr->ObscurAloftHgt); + strcat(string, temp); + } + + if ( Mptr->ObscurAloft[0] != '\0' ) { + sprintf(temp, "Obscur Phenom Aloft : %s\n",Mptr->ObscurAloft); + strcat(string, temp); + } + + if ( Mptr->ObscurAloftSkyCond[0] != '\0' ) { + sprintf(temp, "Obscur ALOFT SKYCOND: %s\n",Mptr->ObscurAloftSkyCond); + strcat(string, temp); + } + + + if ( Mptr->NOSPECI ) { + sprintf(temp, "NOSPECI : TRUE\n"); + strcat(string, temp); + } + + if ( Mptr->LAST ) { + sprintf(temp, "LAST : TRUE\n"); + strcat(string, temp); + } + + if ( Mptr->synoptic_cloud_type[ 0 ] != '\0' ) { + sprintf(temp, "SYNOPTIC CLOUD GROUP: %s\n",Mptr->synoptic_cloud_type); + strcat(string, temp); + } + + if ( Mptr->CloudLow != '\0' ) { + sprintf(temp, "LOW CLOUD CODE : %c\n",Mptr->CloudLow); + strcat(string, temp); + } + + if ( Mptr->CloudMedium != '\0' ) { + sprintf(temp, "MEDIUM CLOUD CODE : %c\n",Mptr->CloudMedium); + strcat(string, temp); + } + + if ( Mptr->CloudHigh != '\0' ) { + sprintf(temp, "HIGH CLOUD CODE : %c\n",Mptr->CloudHigh); + strcat(string, temp); + } + + if ( Mptr->SNINCR != MAXINT ) { + sprintf(temp, "SNINCR (INCHES) : %d\n",Mptr->SNINCR); + strcat(string, temp); + } + + if ( Mptr->SNINCR_TotalDepth != MAXINT ) { + sprintf(temp, "SNINCR(TOT. INCHES) : %d\n",Mptr->SNINCR_TotalDepth); + strcat(string, temp); + } + + if ( Mptr->snow_depth_group[ 0 ] != '\0' ) { + sprintf(temp, "SNOW DEPTH GROUP : %s\n",Mptr->snow_depth_group); + strcat(string, temp); + } + + if ( Mptr->snow_depth != MAXINT ) { + sprintf(temp, "SNOW DEPTH (INCHES) : %d\n",Mptr->snow_depth); + strcat(string, temp); + } + + if ( Mptr->WaterEquivSnow != (float) MAXINT ) { + sprintf(temp, "H2O EquivSno(inches): %.2f\n",Mptr->WaterEquivSnow); + strcat(string, temp); + } + + if ( Mptr->SunshineDur != MAXINT ) { + sprintf(temp, "SUNSHINE (MINUTES) : %d\n",Mptr->SunshineDur); + strcat(string, temp); + } + + if ( Mptr->SunSensorOut ) { + sprintf(temp, "SUN SENSOR OUT : TRUE\n"); + strcat(string, temp); + } + + if ( Mptr->hourlyPrecip != (float) MAXINT ) { + sprintf(temp, "HRLY PRECIP (INCHES): %.2f\n",Mptr->hourlyPrecip); + strcat(string, temp); + } + + if( Mptr->precip_amt != (float) MAXINT) { + sprintf(temp, "3/6HR PRCIP (INCHES): %.2f\n", + Mptr->precip_amt); + strcat(string, temp); + } + + if( Mptr->Indeterminant3_6HrPrecip ) { + sprintf(temp, "INDTRMN 3/6HR PRECIP: TRUE\n"); + strcat(string, temp); + } + + if( Mptr->precip_24_amt != (float) MAXINT) { + sprintf(temp, "24HR PRECIP (INCHES): %.2f\n", + Mptr->precip_24_amt); + strcat(string, temp); + } + + if ( Mptr->Indeterminant_24HrPrecip ) { + sprintf(temp, "INDTRMN 24 HR PRECIP: TRUE\n"); + strcat(string, temp); + } + + if ( Mptr->Temp_2_tenths != (float) MAXINT ) { + sprintf(temp, "TMP2TENTHS (CELSIUS): %.1f\n",Mptr->Temp_2_tenths); + strcat(string, temp); + } + + if ( Mptr->DP_Temp_2_tenths != (float) MAXINT ) { + sprintf(temp, "DPT2TENTHS (CELSIUS): %.1f\n",Mptr->DP_Temp_2_tenths); + strcat(string, temp); + } + + if ( Mptr->maxtemp != (float) MAXINT) { + sprintf(temp, "MAX TEMP (CELSIUS) : %.1f\n", + Mptr->maxtemp); + strcat(string, temp); + } + + if ( Mptr->mintemp != (float) MAXINT) { + sprintf(temp, "MIN TEMP (CELSIUS) : %.1f\n", + Mptr->mintemp); + strcat(string, temp); + } + + if ( Mptr->max24temp != (float) MAXINT) { + sprintf(temp, "24HrMAXTMP (CELSIUS): %.1f\n", + Mptr->max24temp); + strcat(string, temp); + } + + if ( Mptr->min24temp != (float) MAXINT) { + sprintf(temp, "24HrMINTMP (CELSIUS): %.1f\n", + Mptr->min24temp); + strcat(string, temp); + } + + if ( Mptr->char_prestndcy != MAXINT) { + sprintf(temp, "CHAR PRESS TENDENCY : %d\n", + Mptr->char_prestndcy ); + strcat(string, temp); + } + + if ( Mptr->prestndcy != (float) MAXINT) { + sprintf(temp, "PRES. TENDENCY (hPa): %.1f\n", + Mptr->prestndcy ); + strcat(string, temp); + } + + if ( Mptr->PWINO ) { + sprintf(temp, "PWINO : TRUE\n"); + strcat(string, temp); + } + + if ( Mptr->PNO ) { + sprintf(temp, "PNO : TRUE\n"); + strcat(string, temp); + } + + if ( Mptr->CHINO ) { + sprintf(temp, "CHINO : TRUE\n"); + strcat(string, temp); + } + + if ( Mptr->CHINO_LOC[0] != '\0' ) { + sprintf(temp, "CHINO_LOC : %s\n",Mptr->CHINO_LOC); + strcat(string, temp); + } + + if ( Mptr->VISNO ) { + sprintf(temp, "VISNO : TRUE\n"); + strcat(string, temp); + } + + if ( Mptr->VISNO_LOC[0] != '\0' ) { + sprintf(temp, "VISNO_LOC : %s\n",Mptr->VISNO_LOC); + strcat(string, temp); + } + + if ( Mptr->FZRANO ) { + sprintf(temp, "FZRANO : TRUE\n"); + strcat(string, temp); + } + + if ( Mptr->TSNO ) { + sprintf(temp, "TSNO : TRUE\n"); + strcat(string, temp); + } + + if ( Mptr->DollarSign) { + sprintf(temp, "DOLLAR $IGN INDCATR : TRUE\n"); + strcat(string, temp); + } + + if ( Mptr->horiz_vsby[ 0 ] != '\0' ) { + sprintf(temp, "HORIZ VISIBILITY : %s\n",Mptr->horiz_vsby); + strcat(string, temp); + } + + if ( Mptr->dir_min_horiz_vsby[ 0 ] != '\0' ) { + sprintf(temp, "DIR MIN HORIZ VSBY : %s\n",Mptr->dir_min_horiz_vsby); + strcat(string, temp); + } + + if ( Mptr->CAVOK ) { + sprintf(temp, "CAVOK : TRUE\n"); + strcat(string, temp); + } + + + if( Mptr->VertVsby != MAXINT ) { + sprintf(temp, "Vert. Vsby (meters) : %d\n", + Mptr->VertVsby ); + strcat(string, temp); + } + */ + + /* + if( Mptr->charVertVsby[0] != '\0' ) + sprintf(temp, "Vert. Vsby (CHAR) : %s\n", + Mptr->charVertVsby ); + */ + /* + if ( Mptr->QFE != MAXINT ) { + sprintf(temp, "QFE : %d\n", Mptr->QFE); + strcat(string, temp); + } + + if ( Mptr->VOLCASH ) { + sprintf(temp, "VOLCANIC ASH : TRUE\n"); + strcat(string, temp); + } + + if ( Mptr->min_vrbl_wind_dir != MAXINT ) { + sprintf(temp, "MIN VRBL WIND DIR : %d\n",Mptr->min_vrbl_wind_dir); + strcat(string, temp); + } + if ( Mptr->max_vrbl_wind_dir != MAXINT ) { + sprintf(temp, "MAX VRBL WIND DIR : %d\n",Mptr->max_vrbl_wind_dir); + strcat(string, temp); + } + */ + + strcat(string, "\n\n\n"); +} + + +void prtDMETR (Decoded_METAR *Mptr) +{ + char string[5000]; + + sprint_metar(string, Mptr); + printf(string); +} diff --git a/test/metar-to-text/mdsplib-code-1/src/stspack2.c b/test/metar-to-text/mdsplib-code-1/src/stspack2.c new file mode 100644 index 00000000..158289a3 --- /dev/null +++ b/test/metar-to-text/mdsplib-code-1/src/stspack2.c @@ -0,0 +1,230 @@ +/* +METAR Decoder Software Package Library: Parses Aviation Routine Weather Reports +Copyright (C) 2003 Eric McCarthy + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#pragma comment (compiler) +#pragma comment (date) +#pragma comment (timestamp) +#pragma pagesize(80) + +#include "local.h" /* standard header file */ + +#pragma page(1) +#pragma subtitle(" ") +#pragma subtitle("stspack2 - Local string test functions ") +/********************************************************************/ +/* */ +/* Title: stspack2 */ +/* Organization: W/OSO242 - GRAPHICS AND DISPLAY SECTION */ +/* Date: 05 Oct 1992 */ +/* Programmer: ALLAN DARLING */ +/* Language: C/2 */ +/* */ +/* Abstract: The stspack2 package contains functions to */ +/* perform the isalnum through isxdigit functions */ +/* on strings. The functions come in four forms: */ +/* those that test NULL delimited strings and are */ +/* named in the form sxxxxxxx, those that test at */ +/* most n characters and are named in the form */ +/* nxxxxxxx, those that search forward in a string */ +/* and are named in the form nxtyyyyy, and those */ +/* that search backward in a string and are named */ +/* in the form lstyyyyy. */ +/* */ +/* The xxxxxxx is the name of the test applied to */ +/* each character in the string, such as isalpha, */ +/* thus a function to test a NULL delimited string */ +/* an return a nonzero value if all characters in */ +/* the string are digits is named sisdigit. */ +/* */ +/* The yyyyy is the name of the test applied to */ +/* characters in a string, minus the 'is' prefix. */ +/* Thus a function to find the next digit in a NULL */ +/* delimited string and return a pointer to it is */ +/* named nxtdigit. */ +/* */ +/* The only exception to the naming rule is for the */ +/* functions that test for hexadecimal digits. */ +/* These are named sisxdigi, nisxdigi, nxtxdigi, */ +/* and lstxdigi because of the eight character */ +/* function name limitation. */ +/* */ +/* The nxxxxxxx class of functions will test up to */ +/* n characters or the first NULL character */ +/* encountered, whichever comes first. For all */ +/* classes of functions, the string sentinal is */ +/* not included in the test. */ +/* */ +/* External Functions Called: */ +/* isalnum, isalpha, iscntrl, isdigit, isgraph, */ +/* islower, isprint, ispunct, isspace, isupper, */ +/* isxdigit. */ +/* */ +/* Input: For sxxxxxxx class functions, a pointer to a */ +/* NULL delimited character string. */ +/* */ +/* For nxtyyyyy class functions, a pointer to a */ +/* NULL delimited character string. */ +/* */ +/* for nxxxxxxx class functions, a pointer to a */ +/* character array, and a positive, nonzero integer.*/ +/* */ +/* for lstyyyyy class functions, a pointer to a */ +/* character array, and a positive, nonzero integer.*/ +/* */ +/* Output: A nonzero value if the test is true for all */ +/* characters in the string, a zero value otherwise.*/ +/* */ +/* Modification History: */ +/* None. */ +/* */ +/********************************************************************/ +#pragma page(1) + +int nisalnum(char *s, int n) { + + for (; *s && n; s++, n--) + + if (!isalnum(*s)) + return (0); + + return (1); + +} /* end nisalnum */ + + +int nisalpha(char *s, int n) { + + for (; *s && n; s++, n--) + + if (!isalpha(*s)) + return (0); + + return (1); + +} /* end nisalpha */ + + +int niscntrl(char *s, int n) { + + for (; *s && n; s++, n--) + + if (!iscntrl(*s)) + return (0); + + return (1); + +} /* end niscntrl */ + + +int nisdigit(char *s, int n) { + + for (; *s && n; s++, n--) + + if (!isdigit(*s)) + return (0); + + return (1); + +} /* end nisdigit */ + + +int nisgraph(char *s, int n) { + + for (; *s && n; s++, n--) + + if (!isgraph(*s)) + return (0); + + return (1); + +} /* end nisgraph */ + + +int nislower(char *s, int n) { + + for (; *s && n; s++, n--) + + if (!islower(*s)) + return (0); + + return (1); + +} /* end nislower */ + + +int nisprint(char *s, int n) { + + for (; *s && n; s++, n--) + + if (!isprint(*s)) + return (0); + + return (1); + +} /* end nisprint */ + + +int nispunct(char *s, int n) { + + for (; *s && n; s++, n--) + + if (!ispunct(*s)) + return (0); + + return (1); + +} /* end nispunct */ + + +int nisspace(char *s, int n) { + + for (; *s && n; s++, n--) + + if (!isspace(*s)) + return (0); + + return (1); + +} /* end nisspace */ + + +int nisupper(char *s, int n) { + + for (; *s && n; s++, n--) + + if (!isupper(*s)) + return (0); + + return (1); + +} /* end nisupper */ + + +int nisxdigi(char *s, int n) { + + for (; *s && n; s++, n--) + + if (!isxdigit(*s)) + return (0); + + return (1); + +} /* end nisxdigi */ + +#pragma page(1) diff --git a/test/metar-to-text/mdsplib-code-1/src/stspack3.c b/test/metar-to-text/mdsplib-code-1/src/stspack3.c new file mode 100644 index 00000000..0247c86e --- /dev/null +++ b/test/metar-to-text/mdsplib-code-1/src/stspack3.c @@ -0,0 +1,230 @@ +/* +METAR Decoder Software Package Library: Parses Aviation Routine Weather Reports +Copyright (C) 2003 Eric McCarthy + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#pragma comment (compiler) +#pragma comment (date) +#pragma comment (timestamp) +#pragma pagesize(80) + +#include "local.h" /* standard header file */ + +#pragma page(1) +#pragma subtitle(" ") +#pragma subtitle("stspack3 - Local string test functions ") +/********************************************************************/ +/* */ +/* Title: stspack3 */ +/* Organization: W/OSO242 - GRAPHICS AND DISPLAY SECTION */ +/* Date: 05 Oct 1992 */ +/* Programmer: ALLAN DARLING */ +/* Language: C/2 */ +/* */ +/* Abstract: The stspack3 package contains functions to */ +/* perform the isalnum through isxdigit functions */ +/* on strings. The functions come in four forms: */ +/* those that test NULL delimited strings and are */ +/* named in the form sxxxxxxx, those that test at */ +/* most n characters and are named in the form */ +/* nxxxxxxx, those that search forward in a string */ +/* and are named in the form nxtyyyyy, and those */ +/* that search backward in a string and are named */ +/* in the form lstyyyyy. */ +/* */ +/* The xxxxxxx is the name of the test applied to */ +/* each character in the string, such as isalpha, */ +/* thus a function to test a NULL delimited string */ +/* an return a nonzero value if all characters in */ +/* the string are digits is named sisdigit. */ +/* */ +/* The yyyyy is the name of the test applied to */ +/* characters in a string, minus the 'is' prefix. */ +/* Thus a function to find the next digit in a NULL */ +/* delimited string and return a pointer to it is */ +/* named nxtdigit. */ +/* */ +/* The only exception to the naming rule is for the */ +/* functions that test for hexadecimal digits. */ +/* These are named sisxdigi, nisxdigi, nxtxdigi, */ +/* and lstxdigi because of the eight character */ +/* function name limitation. */ +/* */ +/* The nxxxxxxx class of functions will test up to */ +/* n characters or the first NULL character */ +/* encountered, whichever comes first. For all */ +/* classes of functions, the string sentinal is */ +/* not included in the test. */ +/* */ +/* External Functions Called: */ +/* isalnum, isalpha, iscntrl, isdigit, isgraph, */ +/* islower, isprint, ispunct, isspace, isupper, */ +/* isxdigit. */ +/* */ +/* Input: For sxxxxxxx class functions, a pointer to a */ +/* NULL delimited character string. */ +/* */ +/* For nxtyyyyy class functions, a pointer to a */ +/* NULL delimited character string. */ +/* */ +/* for nxxxxxxx class functions, a pointer to a */ +/* character array, and a positive, nonzero integer.*/ +/* */ +/* for lstyyyyy class functions, a pointer to a */ +/* character array, and a positive, nonzero integer.*/ +/* */ +/* Output: A nonzero value if the test is true for all */ +/* characters in the string, a zero value otherwise.*/ +/* */ +/* Modification History: */ +/* None. */ +/* */ +/********************************************************************/ +#pragma page(1) +char *nxtalnum(char *s) { + + for (; !isalnum(*s) && *s; s++) ; + + if (*s) + return (s); + else + return (NULL); + +} /* end nxtalnum */ + + +char *nxtalpha(char *s) { + + for (; !isalpha(*s) && *s; s++) ; + + if (*s) + return (s); + else + return (NULL); + +} /* end nxtalpha */ + + +char *nxtcntrl(char *s) { + + for (; !iscntrl(*s) && *s; s++) ; + + if (*s) + return (s); + else + return (NULL); + +} /* end nxtcntrl */ + + +char *nxtdigit(char *s) { + + for (; !isdigit(*s) && *s; s++) ; + + if (*s) + return (s); + else + return (NULL); + +} /* end nxtdigit */ + + +char *nxtgraph(char *s) { + + for (; !isgraph(*s) && *s; s++) ; + + if (*s) + return (s); + else + return (NULL); + +} /* end nxtgraph */ + + +char *nxtlower(char *s) { + + for (; !islower(*s) && *s; s++) ; + + if (*s) + return (s); + else + return (NULL); + +} /* end nxtlower */ + + +char *nxtprint(char *s) { + + for (; !isprint(*s) && *s; s++) ; + + if (*s) + return (s); + else + return (NULL); + +} /* end nxtprint */ + + +char *nxtpunct(char *s) { + + for (; !ispunct(*s) && *s; s++) ; + + if (*s) + return (s); + else + return (NULL); + +} /* end nxtpunct */ + + +char *nxtspace(char *s) { + + for (; !isspace(*s) && *s; s++) ; + + if (*s) + return (s); + else + return (NULL); + +} /* end nxtspace */ + + +char *nxtupper(char *s) { + + for (; !isupper(*s) && *s; s++) ; + + if (*s) + return (s); + else + return (NULL); + +} /* end nxtupper */ + + +char *nxtxdigi(char *s) { + + for (; !isxdigit(*s) && *s; s++) ; + + if (*s) + return (s); + else + return (NULL); + +} /* end nxtxdigi */ + + +#pragma page(1) diff --git a/test/mtk_config.sh b/test/mtk_config.sh new file mode 100755 index 00000000..e16afd0d --- /dev/null +++ b/test/mtk_config.sh @@ -0,0 +1,73 @@ +#!/bin/bash +# +# mtk_config.sh: Script to set up MTK3339 receiver for Stratux. +# Resets receiver to 9600 and 1 Hz GPRMC messaging, then enables +# WAAS, 5 Hz position reporting, the NMEA messages needed by +# Stratux, and 38400 bps serial output. + +printf "About to configure MTK3339 receiver on /dev/ttyAMA0.\n" +printf "Press ctrl-C to abort or any other key to continue.\n" +read + +# Iterate through common bitrates and send commands to reduce output to 1 Hz / 9600 bps. +printf "Setting MTK and RPi baud rate of /dev/ttyAMA0 to 9600. Iterating through common rates.\n" +printf "Current /dev/ttyAMA0 baudrate.\n" +printf "\$PMTK220,1000*1F\r\n" > /dev/ttyAMA0 +printf "\$PMTK251,9600*17\r\n" > /dev/ttyAMA0 +sleep 0.2 +printf "38400 bps.\n" +stty -F /dev/ttyAMA0 38400 +printf "\$PMTK220,1000*1F\r\n" > /dev/ttyAMA0 +printf "\$PMTK251,9600*17\r\n" > /dev/ttyAMA0 +sleep 0.2 +printf "115200 bps.\n" +printf "\$PMTK220,1000*1F\r\n" > /dev/ttyAMA0 +stty -F /dev/ttyAMA0 115200 +printf "\$PMTK220,1000*1F\r\n" > /dev/ttyAMA0 +printf "\$PMTK251,9600*17\r\n" > /dev/ttyAMA0 +sleep 0.2 +printf "57600 bps.\n" +stty -F /dev/ttyAMA0 57600 +printf "\$PMTK220,1000*1F\r\n" > /dev/ttyAMA0 +printf "\$PMTK251,9600*17\r\n" > /dev/ttyAMA0 +sleep 0.2 +printf "19200 bps.\n" +stty -F /dev/ttyAMA0 19200 +printf "\$PMTK220,1000*1F\r\n" > /dev/ttyAMA0 +printf "\$PMTK251,9600*17\r\n" > /dev/ttyAMA0 +sleep 0.2 +printf "4800 bps.\n" +stty -F /dev/ttyAMA0 4800 +printf "\$PMTK220,1000*1F\r\n" > /dev/ttyAMA0 +printf "\$PMTK251,9600*17\r\n" > /dev/ttyAMA0 +sleep 0.2 + + +stty -F /dev/ttyAMA0 9600 +printf "\$PMTK314,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*29\r\n" > /dev/ttyAMA0 + +printf "MTK has been set to 9600 baud with RMC messages at 1 Hz.\n" +printf "Press ctrl-C to abort, or any other key enter to continue with setup.\n" +read + +# Now start the Stratux setup. +printf "Sending MTK command to set GPS baud rate to 38400\n" +printf "\$PMTK251,38400*27\r\n" > /dev/ttyAMA0 + +printf "Setting RPi baud rate of /dev/ttyAMA0 to 38400\n" +stty -F /dev/ttyAMA0 38400 +sleep 0.2 +printf "Sending MTK command to configure NMEA message output\n" +printf "\$PMTK314,0,1,1,1,5,5,0,0,0,0,0,0,0,0,0,0,0,0,0*29\r\n" > /dev/ttyAMA0 +sleep 0.2 +printf "Sending MTK commands to enable WAAS\n" +printf "\$PMTK301,2*2E\r\n" > /dev/ttyAMA0 +sleep 0.2 +printf "\$PMTK513,1*28\r\n" > /dev/ttyAMA0 +sleep 0.2 +printf "Sending MTK commands to enable 5 Hz position reporting\n" +printf "\$PMTK220,200*2C\r\n" > /dev/ttyAMA0 + +# Finally, test the connection. +printf "Opening /dev/ttyAMA0 to listen to GPS. Press ctrl-C to cancel.\n" +cat /dev/ttyAMA0 \ No newline at end of file diff --git a/test/packetrate b/test/packetrate new file mode 100755 index 00000000..30236f4e Binary files /dev/null and b/test/packetrate differ diff --git a/test/sirf_config.sh b/test/sirf_config.sh new file mode 100755 index 00000000..e944d617 --- /dev/null +++ b/test/sirf_config.sh @@ -0,0 +1,112 @@ +#!/bin/bash +# +# sirf_config.sh: Script to set up BU-353-S4 receiver for Stratux. +# Resets receiver to 4800 baud and 1 Hz GPRMC messaging, then enables +# WAAS, 5 Hz position reporting, all NMEA messages needed by +# Stratux, and 38400 bps serial output. + +printf "About to configure SIRF receiver on /dev/ttyUSB0.\n" +printf "Press ctrl-C to abort or any other key to continue.\n" +read + +# Iterate through common bitrates and send commands to reduce output to 1 Hz / 4800 bps. +printf "Setting SIRF and RPi baud rate of /dev/ttyUSB0 to 4800. Iterating through common rates.\n" +printf "Current /dev/ttyUSB0 baudrate.\n" +printf "\$PSRF103,00,7,00,0*22\r\n" > /dev/ttyUSB0 +printf "\$PSRF100,1,4800,8,1,0*0E\r\n" > /dev/ttyUSB0 +sleep 0.2 +printf "38400 bps.\n" +stty -F /dev/ttyUSB0 38400 +printf "\$PSRF103,00,7,00,0*22\r\n" > /dev/ttyUSB0 +printf "\$PSRF100,1,4800,8,1,0*0E\r\n" > /dev/ttyUSB0 +sleep 0.2 +printf "115200 bps.\n" +printf "\$PSRF103,00,7,00,0*22\r\n" > /dev/ttyUSB0 +stty -F /dev/ttyUSB0 115200 +printf "\$PSRF103,00,7,00,0*22\r\n" > /dev/ttyUSB0 +printf "\$PSRF100,1,4800,8,1,0*0E\r\n" > /dev/ttyUSB0 +sleep 0.2 +printf "57600 bps.\n" +stty -F /dev/ttyUSB0 57600 +printf "\$PSRF103,00,7,00,0*22\r\n" > /dev/ttyUSB0 +printf "\$PSRF100,1,4800,8,1,0*0E\r\n" > /dev/ttyUSB0 +sleep 0.2 +printf "19200 bps.\n" +stty -F /dev/ttyUSB0 19200 +printf "\$PSRF103,00,7,00,0*22\r\n" > /dev/ttyUSB0 +printf "\$PSRF100,1,4800,8,1,0*0E\r\n" > /dev/ttyUSB0 +sleep 0.2 +printf "9600 bps.\n" +stty -F /dev/ttyUSB0 9600 +printf "\$PSRF103,00,7,00,0*22\r\n" > /dev/ttyUSB0 +printf "\$PMTK251,4800*17\r\n" > /dev/ttyUSB0 +sleep 0.2 + + +stty -F /dev/ttyUSB0 4800 +# GGA off: +printf "\$PSRF103,00,00,00,01*24\r\n" > /dev/ttyUSB0 + +# GLL off: +printf "\$PSRF103,01,00,00,01*27\r\n" > /dev/ttyUSB0 + +# GSA off: +printf "\$PSRF103,02,00,00,01*26\r\n" > /dev/ttyUSB0 + +# GSV off: +printf "\$PSRF103,03,00,00,01*27\r\n" > /dev/ttyUSB0 + +# RMC on: +printf "\$PSRF103,04,00,01,01*21\r\n" > /dev/ttyUSB0 + +# VTG off: +printf "\$PSRF103,05,00,00,01*21\r\n" > /dev/ttyUSB0 + +printf "SIRF device has been set to 4800 baud with RMC messages at 1 Hz.\n" +printf "Press ctrl-C to abort, or any other key enter to continue with setup.\n" +read + +# Now start the Stratux setup. +printf "Sending Sirf PSRF100 command to set GPS baud rate to 38400\n" +printf "\$PSRF100,1,38400,8,1,0*3D\r\n" > /dev/ttyUSB0 + +printf "Resetting RPi baud rate of /dev/ttyUSB0 to 38400\n" +stty -F /dev/ttyUSB0 38400 +sleep 0.2 +printf "Sending SIRF PSRF103 commands to configure NMEA message output\n" + +#GGA: +printf "\$PSRF103,00,00,01,01*25\r\n" > /dev/ttyUSB0 + +# Uncomment next two commands set GSA/GSV on each position message. +# Stratux doesn't need this much info - but keep for developer debug +# GSA (every position message): +#printf "\$PSRF103,02,00,01,01*27\r\n" > /dev/ttyUSB0 +# GSV (every position message): +#printf "\$PSRF103,03,00,01,01*26\r\n" > /dev/ttyUSB0 + +# Next two commands set GSA/GSV on every 5th position message. +# Comment out (and uncomment above two commands) to report on +# every position. +# GSA (every 5 position messages): +printf "\$PSRF103,02,00,05,01*23\r\n" > /dev/ttyUSB0 +# GSV (every 5 position messages): +printf "\$PSRF103,03,00,05,01*22\r\n" > /dev/ttyUSB0 + + +# RMC: +printf "\$PSRF103,04,00,01,01*21\r\n" > /dev/ttyUSB0 +# VTG: +printf "\$PSRF103,05,00,01,01*20\r\n" > /dev/ttyUSB0 +sleep 0.2 + +printf "Sending SIRF PSRF151 command to enable WAAS\n" +printf "\$PSRF151,01*3F\r\n" > /dev/ttyUSB0 +sleep 0.2 + +printf "Sending SIRF PSRF103 command to enable 5 Hz position reporting\n" +printf "\$PSRF103,00,6,00,0*23\r\n" > /dev/ttyUSB0 + +# Finally, test the connection. +printf "Opening /dev/ttyUSB0 to listen to GPS. Press ctrl-C to cancel.\n" +cat /dev/ttyUSB0 diff --git a/uatparse/uatparse.go b/uatparse/uatparse.go index 8bea5a8a..c5c11e87 100644 --- a/uatparse/uatparse.go +++ b/uatparse/uatparse.go @@ -404,55 +404,63 @@ func (f *UATFrame) decodeAirmet() { f.Points = points } case 9: // Extended Range 3D Point (AGL). p.47. - lng_raw := (int32(record_data[0]) << 11) | (int32(record_data[1]) << 3) | (int32(record_data[2]) & 0xE0 >> 5) - lat_raw := ((int32(record_data[2]) & 0x1F) << 14) | (int32(record_data[3]) << 6) | ((int32(record_data[4]) & 0xFC) >> 2) - alt_raw := ((int32(record_data[4]) & 0x03) << 8) | int32(record_data[5]) - - fmt.Fprintf(ioutil.Discard, "lat_raw=%d, lng_raw=%d, alt_raw=%d\n", lat_raw, lng_raw, alt_raw) - lat, lng := airmetLatLng(lat_raw, lng_raw, false) - - alt := alt_raw * 100 - fmt.Fprintf(ioutil.Discard, "lat=%f,lng=%f,alt=%d\n", lat, lng, alt) - fmt.Fprintf(ioutil.Discard, "coord:%f,%f\n", lat, lng) - var point GeoPoint - point.Lat = lat - point.Lon = lng - point.Alt = alt - f.Points = []GeoPoint{point} - case 7, 8: // Extended Range Circular Prism (MSL). (8 = AGL) - lng_bot_raw := (int32(record_data[0]) << 10) | (int32(record_data[1]) << 2) | (int32(record_data[2]) & 0xC0 >> 6) - lat_bot_raw := ((int32(record_data[2]) & 0x3F) << 12) | (int32(record_data[3]) << 4) | ((int32(record_data[4]) & 0xF0) >> 4) - lng_top_raw := ((int32(record_data[4]) & 0x0F) << 14) | (int32(record_data[5]) << 6) | ((int32(record_data[6]) & 0xFC) >> 2) - lat_top_raw := ((int32(record_data[6]) & 0x03) << 16) | (int32(record_data[7]) << 8) | int32(record_data[8]) - - alt_bot_raw := (int32(record_data[9]) & 0xFE) >> 1 - alt_top_raw := ((int32(record_data[9]) & 0x01) << 6) | ((int32(record_data[10]) & 0xFC) >> 2) - - r_lng_raw := ((int32(record_data[10]) & 0x03) << 7) | ((int32(record_data[11]) & 0xFE) >> 1) - r_lat_raw := ((int32(record_data[11]) & 0x01) << 8) | int32(record_data[12]) - alpha := int32(record_data[13]) - - lat_bot, lng_bot := airmetLatLng(lat_bot_raw, lng_bot_raw, true) - lat_top, lng_top := airmetLatLng(lat_top_raw, lng_top_raw, true) - - alt_bot := alt_bot_raw * 5 - alt_top := alt_top_raw * 500 - - r_lng := float64(r_lng_raw) * float64(0.2) - r_lat := float64(r_lat_raw) * float64(0.2) - - fmt.Fprintf(ioutil.Discard, "lat_bot, lng_bot = %f, %f\n", lat_bot, lng_bot) - fmt.Fprintf(ioutil.Discard, "lat_top, lng_top = %f, %f\n", lat_top, lng_top) - - if geometry_overlay_options == 8 { - fmt.Fprintf(ioutil.Discard, "alt_bot, alt_top = %d AGL, %d AGL\n", alt_bot, alt_top) + if len(record_data) < 6 { + fmt.Fprintf(ioutil.Discard, "invalid data: Extended Range 3D Point. Should be 6 bytes; % seen.\n", len(record_data)) } else { - fmt.Fprintf(ioutil.Discard, "alt_bot, alt_top = %d MSL, %d MSL\n", alt_bot, alt_top) + lng_raw := (int32(record_data[0]) << 11) | (int32(record_data[1]) << 3) | (int32(record_data[2]) & 0xE0 >> 5) + lat_raw := ((int32(record_data[2]) & 0x1F) << 14) | (int32(record_data[3]) << 6) | ((int32(record_data[4]) & 0xFC) >> 2) + alt_raw := ((int32(record_data[4]) & 0x03) << 8) | int32(record_data[5]) + + fmt.Fprintf(ioutil.Discard, "lat_raw=%d, lng_raw=%d, alt_raw=%d\n", lat_raw, lng_raw, alt_raw) + lat, lng := airmetLatLng(lat_raw, lng_raw, false) + + alt := alt_raw * 100 + fmt.Fprintf(ioutil.Discard, "lat=%f,lng=%f,alt=%d\n", lat, lng, alt) + fmt.Fprintf(ioutil.Discard, "coord:%f,%f\n", lat, lng) + var point GeoPoint + point.Lat = lat + point.Lon = lng + point.Alt = alt + f.Points = []GeoPoint{point} } - fmt.Fprintf(ioutil.Discard, "r_lng, r_lat = %f, %f\n", r_lng, r_lat) + case 7, 8: // Extended Range Circular Prism (7 = MSL, 8 = AGL) + if len(record_data) < 14 { + fmt.Fprintf(ioutil.Discard, "invalid data: Extended Range Circular Prism. Should be 14 bytes; % seen.\n", len(record_data)) + } else { - fmt.Fprintf(ioutil.Discard, "alpha=%d\n", alpha) + lng_bot_raw := (int32(record_data[0]) << 10) | (int32(record_data[1]) << 2) | (int32(record_data[2]) & 0xC0 >> 6) + lat_bot_raw := ((int32(record_data[2]) & 0x3F) << 12) | (int32(record_data[3]) << 4) | ((int32(record_data[4]) & 0xF0) >> 4) + lng_top_raw := ((int32(record_data[4]) & 0x0F) << 14) | (int32(record_data[5]) << 6) | ((int32(record_data[6]) & 0xFC) >> 2) + lat_top_raw := ((int32(record_data[6]) & 0x03) << 16) | (int32(record_data[7]) << 8) | int32(record_data[8]) + alt_bot_raw := (int32(record_data[9]) & 0xFE) >> 1 + alt_top_raw := ((int32(record_data[9]) & 0x01) << 6) | ((int32(record_data[10]) & 0xFC) >> 2) + + r_lng_raw := ((int32(record_data[10]) & 0x03) << 7) | ((int32(record_data[11]) & 0xFE) >> 1) + r_lat_raw := ((int32(record_data[11]) & 0x01) << 8) | int32(record_data[12]) + alpha := int32(record_data[13]) + + lat_bot, lng_bot := airmetLatLng(lat_bot_raw, lng_bot_raw, true) + lat_top, lng_top := airmetLatLng(lat_top_raw, lng_top_raw, true) + + alt_bot := alt_bot_raw * 5 + alt_top := alt_top_raw * 500 + + r_lng := float64(r_lng_raw) * float64(0.2) + r_lat := float64(r_lat_raw) * float64(0.2) + + fmt.Fprintf(ioutil.Discard, "lat_bot, lng_bot = %f, %f\n", lat_bot, lng_bot) + fmt.Fprintf(ioutil.Discard, "lat_top, lng_top = %f, %f\n", lat_top, lng_top) + + if geometry_overlay_options == 8 { + fmt.Fprintf(ioutil.Discard, "alt_bot, alt_top = %d AGL, %d AGL\n", alt_bot, alt_top) + } else { + fmt.Fprintf(ioutil.Discard, "alt_bot, alt_top = %d MSL, %d MSL\n", alt_bot, alt_top) + } + fmt.Fprintf(ioutil.Discard, "r_lng, r_lat = %f, %f\n", r_lng, r_lat) + + fmt.Fprintf(ioutil.Discard, "alpha=%d\n", alpha) + } default: fmt.Fprintf(ioutil.Discard, "unknown geometry: %d\n", geometry_overlay_options) } @@ -476,8 +484,10 @@ func (f *UATFrame) decodeInfoFrame() { switch f.Product_id { case 413: f.decodeTextFrame() - case 8, 11, 13: - f.decodeAirmet() + /* + case 8, 11, 13: + f.decodeAirmet() + */ default: fmt.Fprintf(ioutil.Discard, "don't know what to do with product id: %d\n", f.Product_id) } @@ -489,6 +499,10 @@ func (u *UATMsg) DecodeUplink() error { // position_valid := (uint32(frame[5]) & 0x01) != 0 frame := u.msg + if len(frame) < UPLINK_FRAME_DATA_BYTES { + return errors.New(fmt.Sprintf("DecodeUplink: short read (%d).", len(frame))) + } + raw_lat := (uint32(frame[0]) << 15) | (uint32(frame[1]) << 7) | (uint32(frame[2]) >> 1) raw_lon := ((uint32(frame[2]) & 0x01) << 23) | (uint32(frame[3]) << 15) | (uint32(frame[4]) << 7) | (uint32(frame[5]) >> 1) diff --git a/web/js/main.js b/web/js/main.js index 884a9c4f..a92eae92 100755 --- a/web/js/main.js +++ b/web/js/main.js @@ -5,10 +5,13 @@ var URL_SETTINGS_SET = "http://" + URL_HOST_BASE + "/setSettings"; var URL_GPS_GET = "http://" + URL_HOST_BASE + "/getSituation"; var URL_TOWERS_GET = "http://" + URL_HOST_BASE + "/getTowers" var URL_STATUS_GET = "http://" + URL_HOST_BASE + "/getStatus" +var URL_SATELLITES_GET = "http://" + URL_HOST_BASE + "/getSatellites" var URL_STATUS_WS = "ws://" + URL_HOST_BASE + "/status" var URL_TRAFFIC_WS = "ws://" + URL_HOST_BASE + "/traffic"; var URL_WEATHER_WS = "ws://" + URL_HOST_BASE + "/weather"; var URL_UPDATE_UPLOAD = "http://" + URL_HOST_BASE + "/updateUpload"; +var URL_REBOOT = "http://" + URL_HOST_BASE + "/reboot"; +var URL_SHUTDOWN = "http://" + URL_HOST_BASE + "/shutdown"; // define the module with dependency on mobile-angular-ui //var app = angular.module('stratux', ['ngRoute', 'mobile-angular-ui', 'mobile-angular-ui.gestures', 'appControllers']); diff --git a/web/plates/gps-help.html b/web/plates/gps-help.html index 110071ae..df1942c8 100644 --- a/web/plates/gps-help.html +++ b/web/plates/gps-help.html @@ -1,7 +1,8 @@

The GPS / AHRS page provides a view on the current status of GPS data and AHRS orientation. The Satellite count is located on the Status page.

-

The GPS reports position with estimated accuracy, ground track, ground speed, and GPS derived altitude are displayed along with the location depicted on a world map.

-

The AHRS reports magnetic heading, pressure altitude, pitch and roll are displayed along with a graphical representation of movement.

-

The AHR graphical depiction is a 3-dimentional paper airplane which rotates about a heading with 000° being off in the distance and 180° being down in front. The airplane will pitch up/down and left/right based on the data from the AHRS. To aid with recognizing orientation, the left wing is blue and the right wing is tan.

+

GPS shows position with estimated accuracy, ground track, ground speed, and geometric altitude. Location is displayed on a world map.

+

Satellites shows the status of GNSS constellations, and lists all satellites that your receiver is tracking. Stratux uses Satellite Based Augmentation System (SBAS) and multi-GNSS solutions on supported receivers. GPS satellites are prefixed with "G", SBAS satellites such as WAAS or EGNOS are prefixed with "S", and Russian GLONASS satellites are prefixed with "R". A checkmark shows if each satellite is used in the current position solution. For each satellite, the elevation, azimuth, and signal strength are provided. A summary of total satellites is presented at the bottom of the table.

+

AHRS reports heading, pressure altitude, pitch and roll, along with a graphical representation of movement. As of version v0.8, heading is derived from GPS track, and is provided in degrees true.

+

The AHRS graphical depiction is a 3-dimensional paper airplane. Heading of 000° is depicted with the nose of the airplane into the page; 180° is depicted with the nose pointing out of the page.The airplane will pitch up/down and left/right based on the data from the AHRS. To aid with recognizing orientation, the left wing is blue and the right wing is tan.

NOTE: This page is for reference only and must not be used for flight operations.

\ No newline at end of file diff --git a/web/plates/gps.html b/web/plates/gps.html index 92c76865..d1408f7a 100755 --- a/web/plates/gps.html +++ b/web/plates/gps.html @@ -18,7 +18,7 @@
Location: Track: -
+
{{gps_lat}}, {{gps_lon}} ± {{gps_accuracy}} m
{{gps_alt}} ± {{gps_vert_accuracy}} ft @ {{gps_vert_speed}} ft/min
{{gps_track}}° @ {{gps_speed}} KTS @@ -54,6 +54,46 @@
+
+
+
+
+ Satellites +
+
+
+ Satellite + + Elevation + Azimuth + Signal +
+ +
+
+ {{satellite.SatelliteID}} ✅ + + {{satellite.Elevation < -5 ? "---" : satellite.Elevation}}° + {{satellite.Azimuth < 0 ? "---" : satellite.Azimuth}}° + {{satellite.Signal < 1 ? "---" : satellite.Signal}} dB-Hz +
+
+ +
+ +
+
+
+ - {{aircraft.age}}s + {{aircraft.speed}}KTS + 00{{aircraft.heading}}° + {{aircraft.signal.toFixed(2)}}dB + {{aircraft.age.toFixed(1)}}s - + + +
+
+ Basic Mode S and No-Position Messages +
+ +
+
+
+ Callsign + Code + Squawk +
+ +
+ Altitude  + Speed + Course + Power + Age +
+
+ +
+
+
+ + {{aircraft.addr_symb}} {{aircraft.tail}} + {{aircraft.addr_symb}} [--N/A--] + + {{aircraft.icao}}{{aircraft.addr_type == 3 ? " (TFID)" : ""}} + 000{{aircraft.squawk}} +
+ +
+ {{aircraft.alt}} + + {{aircraft.vspeed}} + {{0-aircraft.vspeed}} + + {{aircraft.speed}}KTS + 00{{aircraft.heading}}° + {{aircraft.signal.toFixed(2)}}dB + {{aircraft.ageLastAlt.toFixed(1)}}s +
+
+
+ +