From b77996ac1b8ea956c6c3ae6c7992d9bd87447e82 Mon Sep 17 00:00:00 2001 From: teelworks Date: Thu, 8 May 2014 13:53:23 -0500 Subject: [PATCH] Add 5.21.5 source as initial github master branch. --- AUTHORS | 50 + COPYING | 340 + ChangeLog | 3491 ++++++++ INSTALL | 370 + LJ-Article.txt | 90 + MAINTAIN | 21 + Makefile.am | 215 + NEWS | 0 README | 8 + TODO | 8 + UPGRADE | 377 + afedit/COPYING | 340 + afedit/Doxyfile | 283 + afedit/afedit.kdevelop | 245 + afedit/afedit.kdevelop.pcs | Bin 0 -> 29156 bytes afedit/afedit.kdevses | 49 + afedit/afedit.pro | 6 + afedit/afedit.spec | 46 + afedit/bin/afedit | Bin 0 -> 115184 bytes afedit/images/editcopy | Bin 0 -> 248 bytes afedit/images/editcut | Bin 0 -> 187 bytes afedit/images/editpaste | Bin 0 -> 270 bytes afedit/images/filenew | Bin 0 -> 173 bytes afedit/images/fileopen | Bin 0 -> 210 bytes afedit/images/filesave | Bin 0 -> 217 bytes afedit/images/print | Bin 0 -> 725 bytes afedit/images/redo | Bin 0 -> 173 bytes afedit/images/searchfind | Bin 0 -> 662 bytes afedit/images/undo | Bin 0 -> 172 bytes afedit/src/.moc/moc_aboutafedit.cpp | 101 + afedit/src/.moc/moc_mainform.cpp | 129 + afedit/src/.ui/aboutafedit.cpp | 92 + afedit/src/.ui/aboutafedit.h | 44 + afedit/src/.ui/mainform.cpp | 146 + afedit/src/.ui/mainform.h | 69 + afedit/src/.ui/qmake_image_collection.cpp | 295 + afedit/src/Makefile | 206 + afedit/src/aboutafedit.ui | 152 + afedit/src/afedit.h | 40 + afedit/src/archiveFile.cpp | 540 ++ afedit/src/archiveFile.h | 226 + afedit/src/fileopen.xpm | 22 + afedit/src/fileprint.xpm | 24 + afedit/src/filesave.xpm | 22 + afedit/src/formutils.cpp | 314 + afedit/src/formutils.h | 32 + afedit/src/main.cpp | 11 + afedit/src/mainform.ui | 278 + afedit/src/mainform.ui.h | 361 + afedit/src/src.pro | 37 + afedit/templates/cpp | 19 + afedit/templates/h | 19 + alarms/Makefile.am | 44 + alarms/alarms.c | 1650 ++++ alarms/alarms.h | 181 + alarms/sample-datafeed-client/Makefile | 80 + .../sample-datafeed-client/datafeedClient.c | 203 + bin/alarms/placeholder.txt | 1 + bin/archive/readme.txt | 2 + bin/archive/wview-archive.sql | 56 + bin/img/Archive/placeholder.txt | 1 + bin/img/Clouds.jpg | Bin 0 -> 2494 bytes bin/img/NOAA/placeholder.txt | 1 + bin/img/above-clouds.jpg | Bin 0 -> 3862 bytes bin/noaa/placeholder.txt | 1 + common/beaufort.h | 45 + common/datadefs.h | 370 + common/datafeed.c | 428 + common/datafeed.h | 121 + common/dbdatabase.c | 935 ++ common/dbdatabase.h | 103 + common/dbsqlite.c | 1210 +++ common/dbsqlite.h | 287 + common/dbsqliteHiLow.c | 1662 ++++ common/dbsqliteHistory.c | 494 ++ common/dbsqliteNOAA.c | 1158 +++ common/emailAlerts.c | 87 + common/emailAlerts.h | 79 + common/lunarCycle.c | 494 ++ common/sensor.c | 621 ++ common/sensor.h | 158 + common/services.h | 207 + common/status.c | 181 + common/status.h | 87 + common/sunTimes.c | 551 ++ common/sysdefs.h | 432 + common/windAverage.c | 116 + common/windAverage.h | 64 + common/wvconfig.c | 237 + common/wvconfig.h | 298 + common/wvutils.c | 1406 +++ configure.in | 128 + cross-compile/config-libgd-arm-linux | 39 + cross-compile/config-libpng-arm-linux | 31 + cross-compile/config-libz-arm-linux | 31 + cross-compile/config-radlib-arm-linux | 32 + cross-compile/config-wview-arm-linux | 33 + cwop/Makefile.am | 40 + cwop/cwop.c | 952 +++ cwop/cwop.h | 114 + dbexport/Makefile.am | 40 + dbexport/wview-mysql-create.sh | 422 + dbexport/wview-mysql-export.sh | 377 + dbexport/wview-pgsql-create.sh | 429 + dbexport/wview-pgsql-export.sh | 333 + debian/99-wview.conf | 6 + debian/arc_be2le.1 | 43 + debian/arc_le2be.1 | 43 + debian/changelog | 234 + debian/compat | 1 + debian/conffiles | 0 debian/control | 19 + debian/copyright | 18 + debian/dirs | 6 + debian/docs | 8 + debian/hilowcreate.1 | 39 + debian/htmlgend.1 | 42 + debian/init.d | 181 + debian/install | 0 debian/postinst | 180 + debian/postrm | 81 + debian/preinst | 35 + debian/rules | 143 + debian/sqlite2wlk.1 | 43 + debian/vpconfig.1 | 78 + debian/vpinstall.1 | 43 + debian/wlk2sqlite.1 | 44 + debian/wvalarmd.1 | 43 + debian/wvcwopd.1 | 43 + debian/wvhttpd.1 | 43 + debian/wview-logrotate | 9 + debian/wview-mysql-create.1 | 48 + debian/wview-mysql-export.1 | 44 + debian/wview-pgsql-create.1 | 44 + debian/wview-pgsql-export.1 | 44 + debian/wviewcleardata.1 | 43 + debian/wviewconfig.1 | 43 + debian/wviewd_sim.1 | 42 + debian/wviewd_vpro.1 | 43 + debian/wviewd_wmr918.1 | 43 + debian/wviewd_ws2300.1 | 43 + debian/wviewd_wxt510.1 | 43 + debian/wviewftpd.1 | 43 + debian/wviewhtmlconfig.1 | 43 + debian/wviewsshd.1 | 43 + debian/wvpmond.1 | 44 + debian/wxt510config.1 | 44 + depcomp | 791 ++ examples/Archlinux/Makefile.am | 19 + examples/Archlinux/example.sh | 40 + examples/Archlinux/wview.sh | 187 + examples/Debian/Makefile.am | 19 + examples/Debian/wview.sh | 174 + examples/FedoraCore/Makefile.am | 19 + examples/FedoraCore/wview.sh | 198 + examples/FreeBSD/Makefile.am | 19 + examples/FreeBSD/wview.sh | 214 + examples/MacOSX/wview/Makefile.am | 19 + examples/MacOSX/wview/StartupParameters.plist | 7 + examples/MacOSX/wview/wview.sh | 149 + examples/Makefile.am | 3 + examples/NSLU2/Makefile.am | 19 + examples/NSLU2/wview.sh | 201 + examples/SuSE/Makefile.am | 19 + examples/SuSE/wview.sh | 242 + examples/alarms/outtempMax.sh | 5 + examples/alarms/outtempMaxEmail.sh | 8 + examples/alarms/outtempMin.sh | 5 + examples/conf/arcrec-header.conf | 2 + examples/conf/forecast.conf-no-forecast | 285 + examples/conf/wview-conf-update.sql | 44 + examples/conf/wview-conf.sql | 207 + examples/conf/wview-conf.sql-deb-pkg | 202 + examples/html/Template-Skins-HOWTO.txt | 73 + .../html/chrome/plus/Current_Conditions.htx | 60 + examples/html/chrome/plus/Current_Plus.htx | 249 + examples/html/chrome/plus/Daily_Plus.htx | 247 + examples/html/chrome/plus/Monthly_Plus.htx | 229 + examples/html/chrome/plus/Weekly_Plus.htx | 229 + examples/html/chrome/plus/Yearly_Plus.htx | 180 + examples/html/chrome/plus/almanac_Plus.htx | 1747 ++++ .../html/chrome/plus/awekas_wl.htx-metric | 32 + examples/html/chrome/plus/awekas_wl.htx-us | 32 + examples/html/chrome/plus/graphics.conf | 165 + examples/html/chrome/plus/header-big.incx | 43 + examples/html/chrome/plus/header.incx | 40 + examples/html/chrome/plus/html-templates.conf | 76 + examples/html/chrome/plus/images-metric.conf | 231 + examples/html/chrome/plus/images-user.conf | 20 + examples/html/chrome/plus/images.conf | 225 + examples/html/chrome/plus/index-day.htx | 377 + examples/html/chrome/plus/index-night.htx | 378 + examples/html/chrome/plus/index.htx | 377 + .../html/chrome/plus/nav-buttons_Plus.incx | 14 + examples/html/chrome/plus/post-generate.sh | 8 + examples/html/chrome/plus/pre-generate.sh | 8 + examples/html/chrome/plus/readings-plus.incx | 422 + examples/html/chrome/plus/wxrss.xtx | 39 + examples/html/chrome/standard/Current.htx | 237 + .../chrome/standard/Current_Conditions.htx | 60 + examples/html/chrome/standard/Daily.htx | 231 + examples/html/chrome/standard/Monthly.htx | 214 + examples/html/chrome/standard/Weekly.htx | 214 + examples/html/chrome/standard/Yearly.htx | 165 + examples/html/chrome/standard/almanac.htx | 1748 ++++ .../html/chrome/standard/awekas_wl.htx-metric | 32 + .../html/chrome/standard/awekas_wl.htx-us | 32 + examples/html/chrome/standard/data.htx | 7 + examples/html/chrome/standard/graphics.conf | 165 + examples/html/chrome/standard/header-big.incx | 43 + examples/html/chrome/standard/header.incx | 40 + .../html/chrome/standard/html-templates.conf | 76 + .../html/chrome/standard/images-metric.conf | 231 + .../html/chrome/standard/images-user.conf | 20 + examples/html/chrome/standard/images.conf | 224 + examples/html/chrome/standard/index-day.htx | 378 + examples/html/chrome/standard/index-night.htx | 378 + examples/html/chrome/standard/index.htx | 400 + .../html/chrome/standard/nav-buttons.incx | 14 + .../html/chrome/standard/post-generate.sh | 8 + examples/html/chrome/standard/pre-generate.sh | 8 + examples/html/chrome/standard/readings.incx | 386 + examples/html/chrome/standard/wxrss.xtx | 39 + examples/html/chrome/static/Clouds-blue.jpg | Bin 0 -> 6068 bytes examples/html/chrome/static/Clouds.jpg | Bin 0 -> 2494 bytes examples/html/chrome/static/above-clouds.jpg | Bin 0 -> 3862 bytes examples/html/chrome/static/bucket_bg.png | Bin 0 -> 13032 bytes examples/html/chrome/static/chart_bg.png | Bin 0 -> 12827 bytes examples/html/chrome/static/dial_bg.png | Bin 0 -> 10142 bytes examples/html/chrome/static/django_bg.jpg | Bin 0 -> 2254 bytes examples/html/chrome/static/dot_bg.png | Bin 0 -> 2930 bytes .../html/chrome/static/fc-icon-cloudy.gif | Bin 0 -> 1825 bytes .../chrome/static/fc-icon-partlycloudy.gif | Bin 0 -> 2086 bytes .../static/fc-icon-partlycloudyandrain.gif | Bin 0 -> 2255 bytes .../static/fc-icon-partlycloudyandsnow.gif | Bin 0 -> 2299 bytes examples/html/chrome/static/fc-icon-rain.gif | Bin 0 -> 2127 bytes examples/html/chrome/static/fc-icon-snow.gif | Bin 0 -> 2289 bytes .../chrome/static/fc-icon-snowandrain.gif | Bin 0 -> 2583 bytes examples/html/chrome/static/fc-icon-sunny.gif | Bin 0 -> 1555 bytes .../html/classic/plus/Current_Conditions.htx | 60 + examples/html/classic/plus/Current_Plus.htx | 223 + examples/html/classic/plus/Daily_Plus.htx | 233 + examples/html/classic/plus/Monthly_Plus.htx | 215 + examples/html/classic/plus/Weekly_Plus.htx | 215 + examples/html/classic/plus/Yearly_Plus.htx | 161 + examples/html/classic/plus/almanac_Plus.htx | 1748 ++++ .../html/classic/plus/awekas_wl.htx-metric | 32 + examples/html/classic/plus/awekas_wl.htx-us | 32 + examples/html/classic/plus/graphics.conf | 165 + examples/html/classic/plus/header-big.incx | 43 + examples/html/classic/plus/header.incx | 40 + .../html/classic/plus/html-templates.conf | 76 + examples/html/classic/plus/images-metric.conf | 231 + examples/html/classic/plus/images-user.conf | 20 + examples/html/classic/plus/images.conf | 225 + examples/html/classic/plus/index-day.htx | 374 + examples/html/classic/plus/index-night.htx | 374 + examples/html/classic/plus/index.htx | 374 + .../html/classic/plus/nav-buttons_Plus.incx | 14 + examples/html/classic/plus/post-generate.sh | 8 + examples/html/classic/plus/pre-generate.sh | 8 + examples/html/classic/plus/readings-plus.incx | 422 + examples/html/classic/plus/wxrss.xtx | 39 + examples/html/classic/standard/Current.htx | 216 + .../classic/standard/Current_Conditions.htx | 60 + examples/html/classic/standard/Daily.htx | 221 + examples/html/classic/standard/Monthly.htx | 204 + examples/html/classic/standard/Weekly.htx | 204 + examples/html/classic/standard/Yearly.htx | 150 + examples/html/classic/standard/almanac.htx | 1748 ++++ .../classic/standard/awekas_wl.htx-metric | 32 + .../html/classic/standard/awekas_wl.htx-us | 32 + examples/html/classic/standard/data.htx | 7 + examples/html/classic/standard/graphics.conf | 165 + .../html/classic/standard/header-big.incx | 43 + examples/html/classic/standard/header.incx | 40 + .../html/classic/standard/html-templates.conf | 76 + .../html/classic/standard/images-metric.conf | 231 + .../html/classic/standard/images-user.conf | 20 + examples/html/classic/standard/images.conf | 225 + examples/html/classic/standard/index-day.htx | 374 + .../html/classic/standard/index-night.htx | 374 + examples/html/classic/standard/index.htx | 374 + .../html/classic/standard/nav-buttons.incx | 14 + .../html/classic/standard/post-generate.sh | 8 + .../html/classic/standard/pre-generate.sh | 8 + examples/html/classic/standard/readings.incx | 386 + examples/html/classic/standard/wxrss.xtx | 39 + examples/html/classic/static/Clouds-blue.jpg | Bin 0 -> 6068 bytes examples/html/classic/static/Clouds.jpg | Bin 0 -> 2494 bytes examples/html/classic/static/above-clouds.jpg | Bin 0 -> 3862 bytes examples/html/classic/static/bucket_bg.png | Bin 0 -> 13032 bytes examples/html/classic/static/chart_bg.png | Bin 0 -> 12827 bytes examples/html/classic/static/dial_bg.png | Bin 0 -> 10142 bytes examples/html/classic/static/django_bg.jpg | Bin 0 -> 2254 bytes examples/html/classic/static/dot_bg.png | Bin 0 -> 2930 bytes .../html/classic/static/fc-icon-cloudy.gif | Bin 0 -> 1825 bytes .../classic/static/fc-icon-partlycloudy.gif | Bin 0 -> 2086 bytes .../static/fc-icon-partlycloudyandrain.gif | Bin 0 -> 2255 bytes .../static/fc-icon-partlycloudyandsnow.gif | Bin 0 -> 2299 bytes examples/html/classic/static/fc-icon-rain.gif | Bin 0 -> 2127 bytes examples/html/classic/static/fc-icon-snow.gif | Bin 0 -> 2289 bytes .../classic/static/fc-icon-snowandrain.gif | Bin 0 -> 2583 bytes .../html/classic/static/fc-icon-sunny.gif | Bin 0 -> 1555 bytes examples/html/exfoliation/plus/Current.htx | 76 + .../exfoliation/plus/Current_Conditions.htx | 80 + examples/html/exfoliation/plus/Daily.htx | 72 + examples/html/exfoliation/plus/Monthly.htx | 72 + examples/html/exfoliation/plus/Weekly.htx | 72 + examples/html/exfoliation/plus/Yearly.htx | 76 + examples/html/exfoliation/plus/almanac.htx | 736 ++ .../exfoliation/plus/awekas_wl.htx-metric | 32 + .../html/exfoliation/plus/awekas_wl.htx-us | 32 + .../html/exfoliation/plus/curreadings.incx | 121 + examples/html/exfoliation/plus/footer.incx | 5 + examples/html/exfoliation/plus/graphics.conf | 157 + .../html/exfoliation/plus/hiloreadings.incx | 138 + .../html/exfoliation/plus/html-templates.conf | 32 + .../html/exfoliation/plus/images-metric.conf | 233 + .../html/exfoliation/plus/images-user.conf | 4 + examples/html/exfoliation/plus/images.conf | 200 + examples/html/exfoliation/plus/index-day.htx | 15 + .../html/exfoliation/plus/index-night.htx | 15 + examples/html/exfoliation/plus/index.htx | 15 + examples/html/exfoliation/plus/index.incx | 94 + examples/html/exfoliation/plus/location.incx | 8 + examples/html/exfoliation/plus/navctls.incx | 12 + examples/html/exfoliation/plus/phone.htx | 131 + .../html/exfoliation/plus/post-generate.sh | 19 + examples/html/exfoliation/plus/readings.incx | 150 + examples/html/exfoliation/plus/wxrss.xtx | 39 + examples/html/exfoliation/readme.txt | 80 + .../html/exfoliation/standard/Current.htx | 68 + .../standard/Current_Conditions.htx | 80 + examples/html/exfoliation/standard/Daily.htx | 64 + .../html/exfoliation/standard/Monthly.htx | 64 + examples/html/exfoliation/standard/Weekly.htx | 64 + examples/html/exfoliation/standard/Yearly.htx | 68 + .../html/exfoliation/standard/almanac.htx | 736 ++ .../exfoliation/standard/awekas_wl.htx-metric | 32 + .../exfoliation/standard/awekas_wl.htx-us | 32 + .../exfoliation/standard/curreadings.incx | 121 + .../html/exfoliation/standard/footer.incx | 5 + .../html/exfoliation/standard/graphics.conf | 157 + .../exfoliation/standard/hiloreadings.incx | 138 + .../exfoliation/standard/html-templates.conf | 32 + .../exfoliation/standard/images-metric.conf | 233 + .../exfoliation/standard/images-user.conf | 4 + .../html/exfoliation/standard/images.conf | 200 + .../html/exfoliation/standard/index-day.htx | 15 + .../html/exfoliation/standard/index-night.htx | 15 + examples/html/exfoliation/standard/index.htx | 15 + examples/html/exfoliation/standard/index.incx | 94 + .../html/exfoliation/standard/location.incx | 8 + .../html/exfoliation/standard/navctls.incx | 12 + examples/html/exfoliation/standard/phone.htx | 131 + .../exfoliation/standard/post-generate.sh | 19 + .../html/exfoliation/standard/readings.incx | 138 + examples/html/exfoliation/standard/wxrss.xtx | 39 + .../html/exfoliation/static/exfoliation.css | 114 + .../html/exfoliation/static/exfoliation.js | 107 + examples/html/parameterlist.htx | 411 + examples/html/parameterlist.txt | 416 + examples/rsyslog/99-wview.conf | 6 + examples/scripts/rainInLastWeek.sh | 18 + examples/scripts/sqlite2mysql-HOWTO.txt | 16 + examples/scripts/sqlite2mysql.sh | 10 + examples/scripts/wview-ftp.sh | 74 + ftp/Makefile.am | 41 + ftp/ftp.c | 423 + ftp/ftp.h | 86 + ftp/ftpUtils.c | 509 ++ ftp/ftpUtils.h | 124 + ftp/wviewftp.debug.sh | 68 + htmlgenerator/Makefile.am | 74 + htmlgenerator/arcrecGenerate.c | 317 + htmlgenerator/arcrecGenerate.h | 93 + htmlgenerator/glbucket.c | 679 ++ htmlgenerator/glbucket.h | 105 + htmlgenerator/glchart.c | 874 ++ htmlgenerator/glchart.h | 129 + htmlgenerator/glmultichart.c | 823 ++ htmlgenerator/glmultichart.h | 107 + htmlgenerator/html.c | 931 ++ htmlgenerator/html.h | 150 + htmlgenerator/htmlGenerate.c | 6221 ++++++++++++++ htmlgenerator/htmlGenerate.h | 390 + htmlgenerator/htmlMgr.c | 1210 +++ htmlgenerator/htmlMgr.h | 240 + htmlgenerator/htmlStates.c | 823 ++ htmlgenerator/htmlUtils.c | 145 + htmlgenerator/images-user.c | 97 + htmlgenerator/images-user.h | 68 + htmlgenerator/images.c | 7573 +++++++++++++++++ htmlgenerator/images.h | 72 + htmlgenerator/noaaGenerate.c | 1025 +++ htmlgenerator/noaaGenerate.h | 99 + http/Makefile.am | 46 + http/http.c | 831 ++ http/http.h | 104 + install-sh | 527 ++ missing | 215 + pbuilder/armel/pbuilderrc | 91 + pbuilder/armel/wview-build-all | 83 + pbuilder/armhf/pbuilderrc | 47 + pbuilder/armhf/wview-build-all | 83 + pbuilder/pbuilderrc | 54 + pbuilder/powerpc/pbuilderrc | 93 + pbuilder/powerpc/wview-build-all | 83 + pbuilder/wview-build-all | 199 + procmon/Makefile.am | 41 + procmon/procmon.c | 765 ++ procmon/procmon.h | 134 + procmon/procmonStates.c | 194 + reconf | 19 + scripts/wview-install-debian | 198 + scripts/wview-install-macosx | 37 + scripts/wview-update | 100 + scripts/wview-update-macosx | 37 + ssh/Makefile.am | 41 + ssh/ssh.c | 428 + ssh/ssh.h | 85 + ssh/sshUtils.c | 350 + ssh/sshUtils.h | 106 + stations/Makefile.am | 16 + stations/Simulator/Makefile.am | 60 + stations/Simulator/simulator.c | 409 + stations/Simulator/simulator.h | 73 + stations/TE923/Makefile.am | 66 + stations/TE923/te923Interface.c | 320 + stations/TE923/te923Interface.h | 60 + stations/TE923/te923Protocol.c | 649 ++ stations/TE923/te923Protocol.h | 115 + stations/TWI/Makefile.am | 65 + stations/TWI/twiConfig.c | 159 + stations/TWI/twiInterface.c | 625 ++ stations/TWI/twiInterface.h | 89 + stations/TWI/twiProtocol.c | 376 + stations/TWI/twiProtocol.h | 130 + stations/VantagePro/Ccitt.h | 58 + stations/VantagePro/Makefile.am | 64 + stations/VantagePro/dbfiles.c | 2504 ++++++ stations/VantagePro/dbfiles.h | 491 ++ .../Vantage Serial Protocol Docs v2.1.0.pdf | Bin 0 -> 259515 bytes .../VantagePro/doc/binary_file_format-5.3.txt | 340 + .../VantagePro/doc/binary_file_format-5.4.txt | 347 + stations/VantagePro/vpconfig/Makefile.am | 57 + stations/VantagePro/vpconfig/vpconfig.c | 836 ++ stations/VantagePro/vpconfig/vpinstall.sh | 252 + stations/VantagePro/vproInterface.c | 2938 +++++++ stations/VantagePro/vproInterface.h | 379 + stations/VantagePro/vproStates.c | 749 ++ stations/Virtual/Makefile.am | 66 + stations/Virtual/virtualInterface.c | 453 + stations/Virtual/virtualInterface.h | 77 + stations/Virtual/virtualProtocol.c | 282 + stations/Virtual/virtualProtocol.h | 92 + stations/WH1080/Makefile.am | 66 + stations/WH1080/wh1080Interface.c | 313 + stations/WH1080/wh1080Interface.h | 60 + stations/WH1080/wh1080Protocol.c | 722 ++ stations/WH1080/wh1080Protocol.h | 181 + stations/WMR918/Makefile.am | 64 + stations/WMR918/doc/WMR9x8-Protocol.txt | 208 + stations/WMR918/wmr918Interface.c | 414 + stations/WMR918/wmr918Interface.h | 64 + stations/WMR918/wmr918protocol.c | 663 ++ stations/WMR918/wmr918protocol.h | 153 + stations/WMRUSB/Makefile.am | 65 + stations/WMRUSB/wmrusbinterface.c | 332 + stations/WMRUSB/wmrusbinterface.h | 60 + stations/WMRUSB/wmrusbprotocol.c | 1114 +++ stations/WMRUSB/wmrusbprotocol.h | 213 + stations/WS-2300/Makefile.am | 64 + stations/WS-2300/ws2300Interface.c | 494 ++ stations/WS-2300/ws2300Interface.h | 64 + stations/WS-2300/ws2300protocol.c | 961 +++ stations/WS-2300/ws2300protocol.h | 167 + stations/WXT510/Makefile.am | 67 + stations/WXT510/nmea0183.c | 813 ++ stations/WXT510/nmea0183.h | 153 + stations/WXT510/wxt510Interface.c | 598 ++ stations/WXT510/wxt510Interface.h | 80 + stations/WXT510/wxt510config/Makefile.am | 46 + stations/WXT510/wxt510config/wxt510config.c | 335 + stations/common/computedData.c | 1207 +++ stations/common/computedData.h | 84 + stations/common/daemon.c | 1489 ++++ stations/common/daemon.h | 218 + stations/common/ethernet.c | 390 + stations/common/ethernet.h | 68 + stations/common/hidapi-linux.c | 788 ++ stations/common/hidapi-osx.c | 664 ++ stations/common/hidapi.h | 259 + stations/common/parser.c | 152 + stations/common/parser.h | 80 + stations/common/serial.c | 255 + stations/common/serial.h | 63 + stations/common/serialReopen.c | 259 + stations/common/serialReopen.h | 63 + stations/common/station.c | 552 ++ stations/common/station.h | 250 + stations/common/stormRain.c | 185 + stations/common/stormRain.h | 78 + stations/common/usbcompat.c | 938 ++ stations/common/usbcompat.h | 400 + stations/common/usbhid.c | 306 + stations/common/usbhid.h | 71 + stations/common/usbif.c | 276 + stations/common/usbif.h | 69 + utilities/Makefile.am | 11 + utilities/archive-be2le/Makefile.am | 39 + utilities/archive-be2le/arc_be2le.c | 78 + utilities/archive-le2be/Makefile.am | 39 + utilities/archive-le2be/arc_le2be.c | 78 + utilities/hilowcreate/Makefile.am | 36 + utilities/hilowcreate/hilowcreate.c | 128 + utilities/sqlite2wlk/Makefile.am | 37 + utilities/sqlite2wlk/sqlite2wlk.c | 223 + utilities/wlk2sqlite/Makefile.am | 37 + utilities/wlk2sqlite/wlk2sqlite.c | 335 + utilities/wvutilities.c | 509 ++ utilities/wvutilities.h | 55 + wview-Debian-Quick-Start.html | 38 + wview-Old-User-Manual.html | 3603 ++++++++ wview-Quick-Start-FreeBSD.html | 127 + wview-Quick-Start-MacOSX.html | 170 + wview-Quick-Start.html | 137 + wview-User-Manual.html | 5182 +++++++++++ wviewconfig/Makefile.am | 34 + wviewconfig/wviewcleardata.sh | 122 + wviewconfig/wviewconfig.sh | 309 + wviewconfig/wviewhtmlconfig.sh | 241 + wviewmgmt/Makefile.am | 89 + wviewmgmt/alarms.php | 1798 ++++ wviewmgmt/calibration.php | 536 ++ wviewmgmt/cwop.php | 440 + wviewmgmt/file_generation.php | 650 ++ wviewmgmt/ftp.php | 740 ++ wviewmgmt/functions.php.sh | 563 ++ wviewmgmt/http_services.php | 309 + wviewmgmt/imgs/black.png | Bin 0 -> 3012 bytes wviewmgmt/imgs/blue.png | Bin 0 -> 3088 bytes wviewmgmt/imgs/blueline.gif | Bin 0 -> 786 bytes wviewmgmt/imgs/calendar.png | Bin 0 -> 844 bytes wviewmgmt/imgs/email.png | Bin 0 -> 641 bytes wviewmgmt/imgs/gray.png | Bin 0 -> 3084 bytes wviewmgmt/imgs/green.png | Bin 0 -> 3094 bytes wviewmgmt/imgs/header-norm.gif | Bin 0 -> 113 bytes wviewmgmt/imgs/phone.png | Bin 0 -> 488 bytes wviewmgmt/imgs/red.png | Bin 0 -> 3093 bytes wviewmgmt/imgs/tip_small.png | Bin 0 -> 3459 bytes wviewmgmt/imgs/yellow.png | Bin 0 -> 3083 bytes wviewmgmt/login.php | 10 + wviewmgmt/logout.php | 29 + wviewmgmt/network_update.php | 49 + wviewmgmt/password_protect.php | 148 + wviewmgmt/password_update.php | 30 + wviewmgmt/preload_alarms.php | 117 + wviewmgmt/preload_calibration.php | 110 + wviewmgmt/preload_cwop.php | 73 + wviewmgmt/preload_file_generation.php | 83 + wviewmgmt/preload_ftp.php | 80 + wviewmgmt/preload_http_services.php | 68 + wviewmgmt/preload_services.php | 118 + wviewmgmt/preload_sql_export.php | 67 + wviewmgmt/preload_ssh.php | 98 + wviewmgmt/preload_station.php | 83 + wviewmgmt/preload_system_status.php | 106 + wviewmgmt/process_alarms.php | 121 + wviewmgmt/process_calibration.php | 83 + wviewmgmt/process_cwop.php | 37 + wviewmgmt/process_file_generation.php | 57 + wviewmgmt/process_ftp.php | 41 + wviewmgmt/process_http_services.php | 26 + wviewmgmt/process_services.php | 94 + wviewmgmt/process_sql_export.php | 25 + wviewmgmt/process_ssh.php | 55 + wviewmgmt/process_station.php | 121 + wviewmgmt/services.php | 669 ++ wviewmgmt/sql_export.php | 294 + wviewmgmt/ssh.php | 997 +++ wviewmgmt/station.php | 1234 +++ wviewmgmt/style.css | 224 + .../system_status-with-network-setup.php | 779 ++ wviewmgmt/system_status.php | 604 ++ wviewmgmt/wview-100x100.png | Bin 0 -> 16062 bytes wviewmgmt/wview-40x40.png | Bin 0 -> 3486 bytes wviewmgmt/wview.ico | Bin 0 -> 5430 bytes wviewmgmt/wview_control.php | 28 + wviewmgmt/wview_upgrade.php | 36 + 591 files changed, 142164 insertions(+) create mode 100755 AUTHORS create mode 100644 COPYING create mode 100755 ChangeLog create mode 100644 INSTALL create mode 100755 LJ-Article.txt create mode 100644 MAINTAIN create mode 100755 Makefile.am create mode 100644 NEWS create mode 100755 README create mode 100755 TODO create mode 100755 UPGRADE create mode 100644 afedit/COPYING create mode 100644 afedit/Doxyfile create mode 100644 afedit/afedit.kdevelop create mode 100644 afedit/afedit.kdevelop.pcs create mode 100644 afedit/afedit.kdevses create mode 100644 afedit/afedit.pro create mode 100644 afedit/afedit.spec create mode 100755 afedit/bin/afedit create mode 100644 afedit/images/editcopy create mode 100644 afedit/images/editcut create mode 100644 afedit/images/editpaste create mode 100644 afedit/images/filenew create mode 100644 afedit/images/fileopen create mode 100644 afedit/images/filesave create mode 100644 afedit/images/print create mode 100644 afedit/images/redo create mode 100644 afedit/images/searchfind create mode 100644 afedit/images/undo create mode 100644 afedit/src/.moc/moc_aboutafedit.cpp create mode 100644 afedit/src/.moc/moc_mainform.cpp create mode 100644 afedit/src/.ui/aboutafedit.cpp create mode 100644 afedit/src/.ui/aboutafedit.h create mode 100644 afedit/src/.ui/mainform.cpp create mode 100644 afedit/src/.ui/mainform.h create mode 100644 afedit/src/.ui/qmake_image_collection.cpp create mode 100644 afedit/src/Makefile create mode 100644 afedit/src/aboutafedit.ui create mode 100644 afedit/src/afedit.h create mode 100644 afedit/src/archiveFile.cpp create mode 100644 afedit/src/archiveFile.h create mode 100644 afedit/src/fileopen.xpm create mode 100644 afedit/src/fileprint.xpm create mode 100644 afedit/src/filesave.xpm create mode 100644 afedit/src/formutils.cpp create mode 100644 afedit/src/formutils.h create mode 100644 afedit/src/main.cpp create mode 100644 afedit/src/mainform.ui create mode 100644 afedit/src/mainform.ui.h create mode 100644 afedit/src/src.pro create mode 100644 afedit/templates/cpp create mode 100644 afedit/templates/h create mode 100755 alarms/Makefile.am create mode 100644 alarms/alarms.c create mode 100644 alarms/alarms.h create mode 100755 alarms/sample-datafeed-client/Makefile create mode 100755 alarms/sample-datafeed-client/datafeedClient.c create mode 100644 bin/alarms/placeholder.txt create mode 100644 bin/archive/readme.txt create mode 100644 bin/archive/wview-archive.sql create mode 100644 bin/img/Archive/placeholder.txt create mode 100755 bin/img/Clouds.jpg create mode 100644 bin/img/NOAA/placeholder.txt create mode 100755 bin/img/above-clouds.jpg create mode 100644 bin/noaa/placeholder.txt create mode 100755 common/beaufort.h create mode 100755 common/datadefs.h create mode 100755 common/datafeed.c create mode 100755 common/datafeed.h create mode 100755 common/dbdatabase.c create mode 100755 common/dbdatabase.h create mode 100755 common/dbsqlite.c create mode 100755 common/dbsqlite.h create mode 100755 common/dbsqliteHiLow.c create mode 100755 common/dbsqliteHistory.c create mode 100755 common/dbsqliteNOAA.c create mode 100755 common/emailAlerts.c create mode 100644 common/emailAlerts.h create mode 100755 common/lunarCycle.c create mode 100755 common/sensor.c create mode 100755 common/sensor.h create mode 100755 common/services.h create mode 100755 common/status.c create mode 100644 common/status.h create mode 100644 common/sunTimes.c create mode 100755 common/sysdefs.h create mode 100755 common/windAverage.c create mode 100644 common/windAverage.h create mode 100644 common/wvconfig.c create mode 100644 common/wvconfig.h create mode 100755 common/wvutils.c create mode 100755 configure.in create mode 100755 cross-compile/config-libgd-arm-linux create mode 100755 cross-compile/config-libpng-arm-linux create mode 100755 cross-compile/config-libz-arm-linux create mode 100755 cross-compile/config-radlib-arm-linux create mode 100755 cross-compile/config-wview-arm-linux create mode 100755 cwop/Makefile.am create mode 100755 cwop/cwop.c create mode 100644 cwop/cwop.h create mode 100755 dbexport/Makefile.am create mode 100644 dbexport/wview-mysql-create.sh create mode 100644 dbexport/wview-mysql-export.sh create mode 100644 dbexport/wview-pgsql-create.sh create mode 100644 dbexport/wview-pgsql-export.sh create mode 100644 debian/99-wview.conf create mode 100644 debian/arc_be2le.1 create mode 100644 debian/arc_le2be.1 create mode 100644 debian/changelog create mode 100644 debian/compat create mode 100644 debian/conffiles create mode 100644 debian/control create mode 100644 debian/copyright create mode 100644 debian/dirs create mode 100644 debian/docs create mode 100644 debian/hilowcreate.1 create mode 100644 debian/htmlgend.1 create mode 100644 debian/init.d create mode 100644 debian/install create mode 100644 debian/postinst create mode 100644 debian/postrm create mode 100644 debian/preinst create mode 100755 debian/rules create mode 100644 debian/sqlite2wlk.1 create mode 100644 debian/vpconfig.1 create mode 100644 debian/vpinstall.1 create mode 100644 debian/wlk2sqlite.1 create mode 100644 debian/wvalarmd.1 create mode 100644 debian/wvcwopd.1 create mode 100644 debian/wvhttpd.1 create mode 100644 debian/wview-logrotate create mode 100644 debian/wview-mysql-create.1 create mode 100644 debian/wview-mysql-export.1 create mode 100644 debian/wview-pgsql-create.1 create mode 100644 debian/wview-pgsql-export.1 create mode 100644 debian/wviewcleardata.1 create mode 100644 debian/wviewconfig.1 create mode 100644 debian/wviewd_sim.1 create mode 100644 debian/wviewd_vpro.1 create mode 100644 debian/wviewd_wmr918.1 create mode 100644 debian/wviewd_ws2300.1 create mode 100644 debian/wviewd_wxt510.1 create mode 100644 debian/wviewftpd.1 create mode 100644 debian/wviewhtmlconfig.1 create mode 100644 debian/wviewsshd.1 create mode 100644 debian/wvpmond.1 create mode 100644 debian/wxt510config.1 create mode 100755 depcomp create mode 100755 examples/Archlinux/Makefile.am create mode 100644 examples/Archlinux/example.sh create mode 100755 examples/Archlinux/wview.sh create mode 100755 examples/Debian/Makefile.am create mode 100644 examples/Debian/wview.sh create mode 100755 examples/FedoraCore/Makefile.am create mode 100755 examples/FedoraCore/wview.sh create mode 100755 examples/FreeBSD/Makefile.am create mode 100755 examples/FreeBSD/wview.sh create mode 100755 examples/MacOSX/wview/Makefile.am create mode 100644 examples/MacOSX/wview/StartupParameters.plist create mode 100755 examples/MacOSX/wview/wview.sh create mode 100755 examples/Makefile.am create mode 100755 examples/NSLU2/Makefile.am create mode 100755 examples/NSLU2/wview.sh create mode 100755 examples/SuSE/Makefile.am create mode 100755 examples/SuSE/wview.sh create mode 100755 examples/alarms/outtempMax.sh create mode 100644 examples/alarms/outtempMaxEmail.sh create mode 100755 examples/alarms/outtempMin.sh create mode 100644 examples/conf/arcrec-header.conf create mode 100644 examples/conf/forecast.conf-no-forecast create mode 100644 examples/conf/wview-conf-update.sql create mode 100644 examples/conf/wview-conf.sql create mode 100644 examples/conf/wview-conf.sql-deb-pkg create mode 100644 examples/html/Template-Skins-HOWTO.txt create mode 100644 examples/html/chrome/plus/Current_Conditions.htx create mode 100644 examples/html/chrome/plus/Current_Plus.htx create mode 100644 examples/html/chrome/plus/Daily_Plus.htx create mode 100644 examples/html/chrome/plus/Monthly_Plus.htx create mode 100644 examples/html/chrome/plus/Weekly_Plus.htx create mode 100644 examples/html/chrome/plus/Yearly_Plus.htx create mode 100644 examples/html/chrome/plus/almanac_Plus.htx create mode 100644 examples/html/chrome/plus/awekas_wl.htx-metric create mode 100644 examples/html/chrome/plus/awekas_wl.htx-us create mode 100644 examples/html/chrome/plus/graphics.conf create mode 100644 examples/html/chrome/plus/header-big.incx create mode 100644 examples/html/chrome/plus/header.incx create mode 100644 examples/html/chrome/plus/html-templates.conf create mode 100755 examples/html/chrome/plus/images-metric.conf create mode 100644 examples/html/chrome/plus/images-user.conf create mode 100755 examples/html/chrome/plus/images.conf create mode 100644 examples/html/chrome/plus/index-day.htx create mode 100644 examples/html/chrome/plus/index-night.htx create mode 100644 examples/html/chrome/plus/index.htx create mode 100644 examples/html/chrome/plus/nav-buttons_Plus.incx create mode 100644 examples/html/chrome/plus/post-generate.sh create mode 100644 examples/html/chrome/plus/pre-generate.sh create mode 100644 examples/html/chrome/plus/readings-plus.incx create mode 100644 examples/html/chrome/plus/wxrss.xtx create mode 100644 examples/html/chrome/standard/Current.htx create mode 100644 examples/html/chrome/standard/Current_Conditions.htx create mode 100644 examples/html/chrome/standard/Daily.htx create mode 100644 examples/html/chrome/standard/Monthly.htx create mode 100644 examples/html/chrome/standard/Weekly.htx create mode 100644 examples/html/chrome/standard/Yearly.htx create mode 100644 examples/html/chrome/standard/almanac.htx create mode 100644 examples/html/chrome/standard/awekas_wl.htx-metric create mode 100644 examples/html/chrome/standard/awekas_wl.htx-us create mode 100644 examples/html/chrome/standard/data.htx create mode 100644 examples/html/chrome/standard/graphics.conf create mode 100644 examples/html/chrome/standard/header-big.incx create mode 100644 examples/html/chrome/standard/header.incx create mode 100644 examples/html/chrome/standard/html-templates.conf create mode 100755 examples/html/chrome/standard/images-metric.conf create mode 100644 examples/html/chrome/standard/images-user.conf create mode 100755 examples/html/chrome/standard/images.conf create mode 100644 examples/html/chrome/standard/index-day.htx create mode 100644 examples/html/chrome/standard/index-night.htx create mode 100644 examples/html/chrome/standard/index.htx create mode 100644 examples/html/chrome/standard/nav-buttons.incx create mode 100644 examples/html/chrome/standard/post-generate.sh create mode 100644 examples/html/chrome/standard/pre-generate.sh create mode 100644 examples/html/chrome/standard/readings.incx create mode 100644 examples/html/chrome/standard/wxrss.xtx create mode 100755 examples/html/chrome/static/Clouds-blue.jpg create mode 100755 examples/html/chrome/static/Clouds.jpg create mode 100755 examples/html/chrome/static/above-clouds.jpg create mode 100644 examples/html/chrome/static/bucket_bg.png create mode 100644 examples/html/chrome/static/chart_bg.png create mode 100644 examples/html/chrome/static/dial_bg.png create mode 100644 examples/html/chrome/static/django_bg.jpg create mode 100644 examples/html/chrome/static/dot_bg.png create mode 100755 examples/html/chrome/static/fc-icon-cloudy.gif create mode 100755 examples/html/chrome/static/fc-icon-partlycloudy.gif create mode 100755 examples/html/chrome/static/fc-icon-partlycloudyandrain.gif create mode 100755 examples/html/chrome/static/fc-icon-partlycloudyandsnow.gif create mode 100755 examples/html/chrome/static/fc-icon-rain.gif create mode 100755 examples/html/chrome/static/fc-icon-snow.gif create mode 100755 examples/html/chrome/static/fc-icon-snowandrain.gif create mode 100755 examples/html/chrome/static/fc-icon-sunny.gif create mode 100644 examples/html/classic/plus/Current_Conditions.htx create mode 100644 examples/html/classic/plus/Current_Plus.htx create mode 100644 examples/html/classic/plus/Daily_Plus.htx create mode 100644 examples/html/classic/plus/Monthly_Plus.htx create mode 100644 examples/html/classic/plus/Weekly_Plus.htx create mode 100644 examples/html/classic/plus/Yearly_Plus.htx create mode 100644 examples/html/classic/plus/almanac_Plus.htx create mode 100644 examples/html/classic/plus/awekas_wl.htx-metric create mode 100644 examples/html/classic/plus/awekas_wl.htx-us create mode 100644 examples/html/classic/plus/graphics.conf create mode 100644 examples/html/classic/plus/header-big.incx create mode 100644 examples/html/classic/plus/header.incx create mode 100644 examples/html/classic/plus/html-templates.conf create mode 100755 examples/html/classic/plus/images-metric.conf create mode 100644 examples/html/classic/plus/images-user.conf create mode 100755 examples/html/classic/plus/images.conf create mode 100644 examples/html/classic/plus/index-day.htx create mode 100644 examples/html/classic/plus/index-night.htx create mode 100644 examples/html/classic/plus/index.htx create mode 100644 examples/html/classic/plus/nav-buttons_Plus.incx create mode 100644 examples/html/classic/plus/post-generate.sh create mode 100644 examples/html/classic/plus/pre-generate.sh create mode 100644 examples/html/classic/plus/readings-plus.incx create mode 100644 examples/html/classic/plus/wxrss.xtx create mode 100644 examples/html/classic/standard/Current.htx create mode 100644 examples/html/classic/standard/Current_Conditions.htx create mode 100644 examples/html/classic/standard/Daily.htx create mode 100644 examples/html/classic/standard/Monthly.htx create mode 100644 examples/html/classic/standard/Weekly.htx create mode 100644 examples/html/classic/standard/Yearly.htx create mode 100644 examples/html/classic/standard/almanac.htx create mode 100644 examples/html/classic/standard/awekas_wl.htx-metric create mode 100644 examples/html/classic/standard/awekas_wl.htx-us create mode 100644 examples/html/classic/standard/data.htx create mode 100644 examples/html/classic/standard/graphics.conf create mode 100644 examples/html/classic/standard/header-big.incx create mode 100644 examples/html/classic/standard/header.incx create mode 100644 examples/html/classic/standard/html-templates.conf create mode 100755 examples/html/classic/standard/images-metric.conf create mode 100644 examples/html/classic/standard/images-user.conf create mode 100755 examples/html/classic/standard/images.conf create mode 100644 examples/html/classic/standard/index-day.htx create mode 100644 examples/html/classic/standard/index-night.htx create mode 100644 examples/html/classic/standard/index.htx create mode 100644 examples/html/classic/standard/nav-buttons.incx create mode 100644 examples/html/classic/standard/post-generate.sh create mode 100644 examples/html/classic/standard/pre-generate.sh create mode 100644 examples/html/classic/standard/readings.incx create mode 100644 examples/html/classic/standard/wxrss.xtx create mode 100755 examples/html/classic/static/Clouds-blue.jpg create mode 100755 examples/html/classic/static/Clouds.jpg create mode 100755 examples/html/classic/static/above-clouds.jpg create mode 100644 examples/html/classic/static/bucket_bg.png create mode 100644 examples/html/classic/static/chart_bg.png create mode 100644 examples/html/classic/static/dial_bg.png create mode 100644 examples/html/classic/static/django_bg.jpg create mode 100644 examples/html/classic/static/dot_bg.png create mode 100755 examples/html/classic/static/fc-icon-cloudy.gif create mode 100755 examples/html/classic/static/fc-icon-partlycloudy.gif create mode 100755 examples/html/classic/static/fc-icon-partlycloudyandrain.gif create mode 100755 examples/html/classic/static/fc-icon-partlycloudyandsnow.gif create mode 100755 examples/html/classic/static/fc-icon-rain.gif create mode 100755 examples/html/classic/static/fc-icon-snow.gif create mode 100755 examples/html/classic/static/fc-icon-snowandrain.gif create mode 100755 examples/html/classic/static/fc-icon-sunny.gif create mode 100644 examples/html/exfoliation/plus/Current.htx create mode 100644 examples/html/exfoliation/plus/Current_Conditions.htx create mode 100644 examples/html/exfoliation/plus/Daily.htx create mode 100644 examples/html/exfoliation/plus/Monthly.htx create mode 100644 examples/html/exfoliation/plus/Weekly.htx create mode 100644 examples/html/exfoliation/plus/Yearly.htx create mode 100644 examples/html/exfoliation/plus/almanac.htx create mode 100644 examples/html/exfoliation/plus/awekas_wl.htx-metric create mode 100644 examples/html/exfoliation/plus/awekas_wl.htx-us create mode 100644 examples/html/exfoliation/plus/curreadings.incx create mode 100644 examples/html/exfoliation/plus/footer.incx create mode 100644 examples/html/exfoliation/plus/graphics.conf create mode 100644 examples/html/exfoliation/plus/hiloreadings.incx create mode 100644 examples/html/exfoliation/plus/html-templates.conf create mode 100755 examples/html/exfoliation/plus/images-metric.conf create mode 100644 examples/html/exfoliation/plus/images-user.conf create mode 100755 examples/html/exfoliation/plus/images.conf create mode 100644 examples/html/exfoliation/plus/index-day.htx create mode 100644 examples/html/exfoliation/plus/index-night.htx create mode 100644 examples/html/exfoliation/plus/index.htx create mode 100644 examples/html/exfoliation/plus/index.incx create mode 100644 examples/html/exfoliation/plus/location.incx create mode 100644 examples/html/exfoliation/plus/navctls.incx create mode 100644 examples/html/exfoliation/plus/phone.htx create mode 100755 examples/html/exfoliation/plus/post-generate.sh create mode 100644 examples/html/exfoliation/plus/readings.incx create mode 100644 examples/html/exfoliation/plus/wxrss.xtx create mode 100644 examples/html/exfoliation/readme.txt create mode 100644 examples/html/exfoliation/standard/Current.htx create mode 100644 examples/html/exfoliation/standard/Current_Conditions.htx create mode 100644 examples/html/exfoliation/standard/Daily.htx create mode 100644 examples/html/exfoliation/standard/Monthly.htx create mode 100644 examples/html/exfoliation/standard/Weekly.htx create mode 100644 examples/html/exfoliation/standard/Yearly.htx create mode 100644 examples/html/exfoliation/standard/almanac.htx create mode 100644 examples/html/exfoliation/standard/awekas_wl.htx-metric create mode 100644 examples/html/exfoliation/standard/awekas_wl.htx-us create mode 100644 examples/html/exfoliation/standard/curreadings.incx create mode 100644 examples/html/exfoliation/standard/footer.incx create mode 100644 examples/html/exfoliation/standard/graphics.conf create mode 100644 examples/html/exfoliation/standard/hiloreadings.incx create mode 100644 examples/html/exfoliation/standard/html-templates.conf create mode 100755 examples/html/exfoliation/standard/images-metric.conf create mode 100644 examples/html/exfoliation/standard/images-user.conf create mode 100755 examples/html/exfoliation/standard/images.conf create mode 100644 examples/html/exfoliation/standard/index-day.htx create mode 100644 examples/html/exfoliation/standard/index-night.htx create mode 100644 examples/html/exfoliation/standard/index.htx create mode 100644 examples/html/exfoliation/standard/index.incx create mode 100644 examples/html/exfoliation/standard/location.incx create mode 100644 examples/html/exfoliation/standard/navctls.incx create mode 100644 examples/html/exfoliation/standard/phone.htx create mode 100755 examples/html/exfoliation/standard/post-generate.sh create mode 100644 examples/html/exfoliation/standard/readings.incx create mode 100644 examples/html/exfoliation/standard/wxrss.xtx create mode 100644 examples/html/exfoliation/static/exfoliation.css create mode 100644 examples/html/exfoliation/static/exfoliation.js create mode 100644 examples/html/parameterlist.htx create mode 100755 examples/html/parameterlist.txt create mode 100644 examples/rsyslog/99-wview.conf create mode 100644 examples/scripts/rainInLastWeek.sh create mode 100644 examples/scripts/sqlite2mysql-HOWTO.txt create mode 100755 examples/scripts/sqlite2mysql.sh create mode 100644 examples/scripts/wview-ftp.sh create mode 100755 ftp/Makefile.am create mode 100755 ftp/ftp.c create mode 100644 ftp/ftp.h create mode 100644 ftp/ftpUtils.c create mode 100755 ftp/ftpUtils.h create mode 100644 ftp/wviewftp.debug.sh create mode 100755 htmlgenerator/Makefile.am create mode 100644 htmlgenerator/arcrecGenerate.c create mode 100644 htmlgenerator/arcrecGenerate.h create mode 100644 htmlgenerator/glbucket.c create mode 100644 htmlgenerator/glbucket.h create mode 100755 htmlgenerator/glchart.c create mode 100644 htmlgenerator/glchart.h create mode 100644 htmlgenerator/glmultichart.c create mode 100644 htmlgenerator/glmultichart.h create mode 100755 htmlgenerator/html.c create mode 100755 htmlgenerator/html.h create mode 100755 htmlgenerator/htmlGenerate.c create mode 100755 htmlgenerator/htmlGenerate.h create mode 100755 htmlgenerator/htmlMgr.c create mode 100755 htmlgenerator/htmlMgr.h create mode 100755 htmlgenerator/htmlStates.c create mode 100644 htmlgenerator/htmlUtils.c create mode 100755 htmlgenerator/images-user.c create mode 100644 htmlgenerator/images-user.h create mode 100755 htmlgenerator/images.c create mode 100644 htmlgenerator/images.h create mode 100644 htmlgenerator/noaaGenerate.c create mode 100644 htmlgenerator/noaaGenerate.h create mode 100755 http/Makefile.am create mode 100755 http/http.c create mode 100644 http/http.h create mode 100755 install-sh create mode 100755 missing create mode 100644 pbuilder/armel/pbuilderrc create mode 100755 pbuilder/armel/wview-build-all create mode 100644 pbuilder/armhf/pbuilderrc create mode 100755 pbuilder/armhf/wview-build-all create mode 100644 pbuilder/pbuilderrc create mode 100644 pbuilder/powerpc/pbuilderrc create mode 100755 pbuilder/powerpc/wview-build-all create mode 100755 pbuilder/wview-build-all create mode 100755 procmon/Makefile.am create mode 100755 procmon/procmon.c create mode 100644 procmon/procmon.h create mode 100755 procmon/procmonStates.c create mode 100755 reconf create mode 100644 scripts/wview-install-debian create mode 100755 scripts/wview-install-macosx create mode 100755 scripts/wview-update create mode 100755 scripts/wview-update-macosx create mode 100755 ssh/Makefile.am create mode 100755 ssh/ssh.c create mode 100644 ssh/ssh.h create mode 100644 ssh/sshUtils.c create mode 100644 ssh/sshUtils.h create mode 100755 stations/Makefile.am create mode 100755 stations/Simulator/Makefile.am create mode 100755 stations/Simulator/simulator.c create mode 100755 stations/Simulator/simulator.h create mode 100755 stations/TE923/Makefile.am create mode 100755 stations/TE923/te923Interface.c create mode 100755 stations/TE923/te923Interface.h create mode 100755 stations/TE923/te923Protocol.c create mode 100755 stations/TE923/te923Protocol.h create mode 100755 stations/TWI/Makefile.am create mode 100755 stations/TWI/twiConfig.c create mode 100755 stations/TWI/twiInterface.c create mode 100755 stations/TWI/twiInterface.h create mode 100755 stations/TWI/twiProtocol.c create mode 100755 stations/TWI/twiProtocol.h create mode 100644 stations/VantagePro/Ccitt.h create mode 100755 stations/VantagePro/Makefile.am create mode 100755 stations/VantagePro/dbfiles.c create mode 100755 stations/VantagePro/dbfiles.h create mode 100755 stations/VantagePro/doc/Vantage Serial Protocol Docs v2.1.0.pdf create mode 100644 stations/VantagePro/doc/binary_file_format-5.3.txt create mode 100644 stations/VantagePro/doc/binary_file_format-5.4.txt create mode 100755 stations/VantagePro/vpconfig/Makefile.am create mode 100755 stations/VantagePro/vpconfig/vpconfig.c create mode 100755 stations/VantagePro/vpconfig/vpinstall.sh create mode 100755 stations/VantagePro/vproInterface.c create mode 100644 stations/VantagePro/vproInterface.h create mode 100755 stations/VantagePro/vproStates.c create mode 100755 stations/Virtual/Makefile.am create mode 100755 stations/Virtual/virtualInterface.c create mode 100755 stations/Virtual/virtualInterface.h create mode 100755 stations/Virtual/virtualProtocol.c create mode 100755 stations/Virtual/virtualProtocol.h create mode 100755 stations/WH1080/Makefile.am create mode 100755 stations/WH1080/wh1080Interface.c create mode 100755 stations/WH1080/wh1080Interface.h create mode 100755 stations/WH1080/wh1080Protocol.c create mode 100755 stations/WH1080/wh1080Protocol.h create mode 100755 stations/WMR918/Makefile.am create mode 100644 stations/WMR918/doc/WMR9x8-Protocol.txt create mode 100755 stations/WMR918/wmr918Interface.c create mode 100755 stations/WMR918/wmr918Interface.h create mode 100755 stations/WMR918/wmr918protocol.c create mode 100755 stations/WMR918/wmr918protocol.h create mode 100755 stations/WMRUSB/Makefile.am create mode 100755 stations/WMRUSB/wmrusbinterface.c create mode 100755 stations/WMRUSB/wmrusbinterface.h create mode 100755 stations/WMRUSB/wmrusbprotocol.c create mode 100755 stations/WMRUSB/wmrusbprotocol.h create mode 100755 stations/WS-2300/Makefile.am create mode 100755 stations/WS-2300/ws2300Interface.c create mode 100755 stations/WS-2300/ws2300Interface.h create mode 100755 stations/WS-2300/ws2300protocol.c create mode 100755 stations/WS-2300/ws2300protocol.h create mode 100755 stations/WXT510/Makefile.am create mode 100755 stations/WXT510/nmea0183.c create mode 100755 stations/WXT510/nmea0183.h create mode 100755 stations/WXT510/wxt510Interface.c create mode 100755 stations/WXT510/wxt510Interface.h create mode 100755 stations/WXT510/wxt510config/Makefile.am create mode 100755 stations/WXT510/wxt510config/wxt510config.c create mode 100755 stations/common/computedData.c create mode 100644 stations/common/computedData.h create mode 100755 stations/common/daemon.c create mode 100755 stations/common/daemon.h create mode 100755 stations/common/ethernet.c create mode 100755 stations/common/ethernet.h create mode 100755 stations/common/hidapi-linux.c create mode 100755 stations/common/hidapi-osx.c create mode 100755 stations/common/hidapi.h create mode 100755 stations/common/parser.c create mode 100644 stations/common/parser.h create mode 100755 stations/common/serial.c create mode 100644 stations/common/serial.h create mode 100755 stations/common/serialReopen.c create mode 100644 stations/common/serialReopen.h create mode 100755 stations/common/station.c create mode 100755 stations/common/station.h create mode 100755 stations/common/stormRain.c create mode 100644 stations/common/stormRain.h create mode 100644 stations/common/usbcompat.c create mode 100644 stations/common/usbcompat.h create mode 100755 stations/common/usbhid.c create mode 100644 stations/common/usbhid.h create mode 100755 stations/common/usbif.c create mode 100644 stations/common/usbif.h create mode 100755 utilities/Makefile.am create mode 100755 utilities/archive-be2le/Makefile.am create mode 100755 utilities/archive-be2le/arc_be2le.c create mode 100755 utilities/archive-le2be/Makefile.am create mode 100755 utilities/archive-le2be/arc_le2be.c create mode 100755 utilities/hilowcreate/Makefile.am create mode 100755 utilities/hilowcreate/hilowcreate.c create mode 100755 utilities/sqlite2wlk/Makefile.am create mode 100755 utilities/sqlite2wlk/sqlite2wlk.c create mode 100755 utilities/wlk2sqlite/Makefile.am create mode 100755 utilities/wlk2sqlite/wlk2sqlite.c create mode 100755 utilities/wvutilities.c create mode 100644 utilities/wvutilities.h create mode 100755 wview-Debian-Quick-Start.html create mode 100755 wview-Old-User-Manual.html create mode 100755 wview-Quick-Start-FreeBSD.html create mode 100755 wview-Quick-Start-MacOSX.html create mode 100755 wview-Quick-Start.html create mode 100755 wview-User-Manual.html create mode 100755 wviewconfig/Makefile.am create mode 100644 wviewconfig/wviewcleardata.sh create mode 100644 wviewconfig/wviewconfig.sh create mode 100644 wviewconfig/wviewhtmlconfig.sh create mode 100755 wviewmgmt/Makefile.am create mode 100644 wviewmgmt/alarms.php create mode 100644 wviewmgmt/calibration.php create mode 100644 wviewmgmt/cwop.php create mode 100644 wviewmgmt/file_generation.php create mode 100644 wviewmgmt/ftp.php create mode 100644 wviewmgmt/functions.php.sh create mode 100644 wviewmgmt/http_services.php create mode 100755 wviewmgmt/imgs/black.png create mode 100755 wviewmgmt/imgs/blue.png create mode 100644 wviewmgmt/imgs/blueline.gif create mode 100755 wviewmgmt/imgs/calendar.png create mode 100755 wviewmgmt/imgs/email.png create mode 100755 wviewmgmt/imgs/gray.png create mode 100755 wviewmgmt/imgs/green.png create mode 100644 wviewmgmt/imgs/header-norm.gif create mode 100755 wviewmgmt/imgs/phone.png create mode 100755 wviewmgmt/imgs/red.png create mode 100755 wviewmgmt/imgs/tip_small.png create mode 100755 wviewmgmt/imgs/yellow.png create mode 100755 wviewmgmt/login.php create mode 100755 wviewmgmt/logout.php create mode 100755 wviewmgmt/network_update.php create mode 100755 wviewmgmt/password_protect.php create mode 100755 wviewmgmt/password_update.php create mode 100755 wviewmgmt/preload_alarms.php create mode 100755 wviewmgmt/preload_calibration.php create mode 100755 wviewmgmt/preload_cwop.php create mode 100755 wviewmgmt/preload_file_generation.php create mode 100755 wviewmgmt/preload_ftp.php create mode 100755 wviewmgmt/preload_http_services.php create mode 100755 wviewmgmt/preload_services.php create mode 100755 wviewmgmt/preload_sql_export.php create mode 100755 wviewmgmt/preload_ssh.php create mode 100755 wviewmgmt/preload_station.php create mode 100755 wviewmgmt/preload_system_status.php create mode 100755 wviewmgmt/process_alarms.php create mode 100755 wviewmgmt/process_calibration.php create mode 100755 wviewmgmt/process_cwop.php create mode 100755 wviewmgmt/process_file_generation.php create mode 100755 wviewmgmt/process_ftp.php create mode 100755 wviewmgmt/process_http_services.php create mode 100755 wviewmgmt/process_services.php create mode 100755 wviewmgmt/process_sql_export.php create mode 100755 wviewmgmt/process_ssh.php create mode 100755 wviewmgmt/process_station.php create mode 100644 wviewmgmt/services.php create mode 100644 wviewmgmt/sql_export.php create mode 100644 wviewmgmt/ssh.php create mode 100644 wviewmgmt/station.php create mode 100755 wviewmgmt/style.css create mode 100644 wviewmgmt/system_status-with-network-setup.php create mode 100644 wviewmgmt/system_status.php create mode 100755 wviewmgmt/wview-100x100.png create mode 100755 wviewmgmt/wview-40x40.png create mode 100755 wviewmgmt/wview.ico create mode 100755 wviewmgmt/wview_control.php create mode 100755 wviewmgmt/wview_upgrade.php diff --git a/AUTHORS b/AUTHORS new file mode 100755 index 0000000..32cd892 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,50 @@ +Mark Teel + radlib and wview design and development + +Steve Mellon + initial help with html templates, glchart enhancements, friendly suggestions + +Ken McGuire + help with archive interval abstraction; motivation for ARM cross-compilation + support; changes to better support embedded devices + +Juan Luis Perez + motivation and suggestions for internationalization, MySQL database + archive support and extended sensor support + +Clarence Whetten + motivation and testing of extended sensor support and alarms; beta testing + +Wyatt Miler + motivation and testing of MacOSX support and Weather Underground + +Jon Barber + motivation, major contributions and testing of the NSLU2 ipkg installation; + beta testing; too much to list here + +Tom Hogland + cold weather testing; beta testing; general feedback + +Brooks Clark + added the ability to configure colors and sizes of graphics + +Randy Miller + added US/Metric combined bucket support + +Michael Puckett/Jerry Fiddler + debug of endian conversion utilities and suggested solutions + +W. Krenn + added more robust support for the VP rain tipper types and html tags + +M. Nausch + contributed a chrome skin + +Deborah Pickett + Enhanced the vpconfig utility; added wind direction scatter mode for charts + +Kevin Cloy + Significant WMRXXX sensor enhancements and configurability + +Jerry Fiddler/Graham Eddy + Prototype for wind rose graphic diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..c3c7a9e --- /dev/null +++ b/COPYING @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) 19yy + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) 19yy name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/ChangeLog b/ChangeLog new file mode 100755 index 0000000..1432e86 --- /dev/null +++ b/ChangeLog @@ -0,0 +1,3491 @@ +RELEASE VERSION HISTORY (newest to oldest) +------------------------------------------ + + +5.21.5 04-06-2014 +-------------------- + +radlib version required: 2.12.0 or newer + +1) Remove debug message. + + + + +5.21.4 04-06-2014 +-------------------- + +radlib version required: 2.12.0 or newer + +1) Added stale pid file cleanup to the APT package start script. + +2) WMRUSB HID interface changes. Changed reader thread to blocking IO. + Mitigated the console occasionally blasting out bogus data. Now zero + packet loss. Fixed issue with pressure sanity check. Added sanity + check for bogus rain accumulator values from the WMR console. + + + + +5.21.3 03-31-2014 +-------------------- + +radlib version required: 2.12.0 or newer + +1) Further WMRUSB interface improvements. Better packet framing. + + + + +5.21.2 03-30-2014 +-------------------- + +radlib version required: 2.12.0 or newer + +1) Updated debian APT postinst script to include copying the exfoliation skin + for new installs. + +2) Enabled checksum processing for WMR stations. + +3) Changed LOOP windspeed items to floats for better precision. + NOTE: If you use custom datafeed clients the LOOP data structure has changed. + +4) Added temp only packet support for WMRUSB stations. This adds support for + add-on sensors such as the pool sensor. + +5) Fixed APT installs changing the wview.log file ownership incorrectly. Also + restarts rsyslog automatically if a new install. + + + + +5.21.1 03-13-2014 +-------------------- + +radlib version required: 2.12.0 or newer + +1) Modified HILOW database storage so higher precision is used for cumulative + data such as rain and ET. + +2) Fixed issue with yearly historical charts when generated at wview start + (as opposed to at midnight). The current day was erroneously being included in + the history data. This was most apparent with the UV and Solar Radiation + charts. + +3) Fixed bug introduced in 5.21.0 release causing NULL wind direction to have + 360 added to it (to normalize calibration result). A value of -99640 was + being stored/displayed. + + + + +5.21.0 03-05-2014 +-------------------- + +radlib version required: 2.12.0 or newer + +1) WMRUSB improvements. Further reverse engineering of the undocumented protocol. Reset and + heartbeat messages now working. Packet reception more stable. Initial packet acquisition + faster resulting in faster startup. + +2) Modify CWOP packet header for new stricter compliance requirements of CWOP servers. + +3) Fix log message time_t bug for 64-bit platforms. + +4) Add timeout parameter for ssh rules. Added rows to config database. + +5) Updated data feed conversion logic to handle ARCHIVE_VALUE_NULL in loop data. + +6) Added flat line sensor value detection for outside temp, wind direction and wind speed. + If there are no changes over a 4 hour period an email alert will be sent. + +7) Added Station Name, City and State to the wviewmgmt System Status page header. + +8) Added logic to SSH timer to fixup next timer interval if system time has skewed or + if the ssh transfer took longer than 2 minutes. + +9) Fixed TE923 rain calculation to assume each tip is 1/36" instead of 1mm. + +10) Fixed a bug for Vantage Pro stations which could cause the last archive interval of + the previous day to be included in the current day HILOW data. This occurred if the + midnight archive record was provided late by the console. + +11) Added wet bulb temperature calculation and html tag --WetBulbTemp--. Added to all + "readings.incx" templates. + +12) Added email alert notification if no loop data has been received for a given archive + interval. This applies to all stations except the Davis stations (alerts already + exist for them). + +13) Updated start scripts to detect and delete stale pid files. + +14) Modified WH1080 data parsing to assume a humidity of 9% if the value from the + station is undefined (0xff). + + + + +5.20.2 03-21-2012 +-------------------- + +radlib version required: 2.12.0 or newer + +1) Added back "dailyrainin" for http reporting. They do no computation based on hourly rain values. + +2) Fixed a typo in the wviewmgmt station.php file. + + + + +5.20.1 03-20-2012 +-------------------- + +radlib version required: 2.12.0 or newer + +1) Modified http rain submission (Wunderground and Weatherforyou) so only the + "rainin" parameter is sent so daily rain will be correct. + + + + +5.20.0 03-17-2012 +-------------------- + +radlib version required: 2.12.0 or newer + +1) Added "wait_for_time_set" function to most wview start script examples so + wview daemons are not started until the system time is set. This applies to + platforms without a real time clock which must reset their time during + startup. + +2) Updated the User Manual section on skin selection to better describe enabling + extended data generation (UV, ET, radiation). + +3) Updated the debian post install script to try harder to find the default http + document root location for creation of symbolic links to wviewmgmt and the + weather home page. + +4) Added new configuration option to optionally disable using the Vantage Pro + console archive records and instead autogenerate them from the LOOP data. + This is how all other stations work. It also supports non-standard console + interfaces (those not using the datalogger on the console). + +5) Updated the Weatherforyou URL for data submissions. + +6) Fixed bug with DataFeed clients that caused an infinite loop if you + restarted wview while there was a datafeed client connection. + +7) Fixed bug with Alarm Type configuration introduced in 5.19.0. + +8) Changed TWI station to compute rain fall rate as it is reported to not + be provided by the station. + +9) Added socket retry logic to the station ethernet interface (used by the + Vantage Pro IP datalogger and any serial-to-ethernet device). + +10) Modified CWOP host name to IP address resolution so that 'getaddrinfo' is + used instead of 'gethostbyname' and all results are tried in the order they + are returned. This requires radlib 2.12.0 or newer. + +11) Modified the WH1080 station interface to relax packet header requirements + so it is compliant with more stations of this type. Also hardened the read + process to require two consecutive matching reads of any record block. + Added log text to indicate that bad magic numbers can be resolved by clearing + the station memory. + +12) Added bounds checking for calibrated humidity values so they don't fall + outside [0, 100]. + +13) Fixed a bug with datafeed client utilites which incorrectly converted + ARCHIVE_VALUE_NULL (-100000) when preparing a packet for network transmission. + +14) Fixed a bug with wind average computation that overflowed the accumulator + for very large sample size. + +15) Added a default logrotate configuration for APT installs. + +16) Fixed a bug introduced with 5.19.0 which caused the wind and windgust values + to always be the same. + +17) Removed requirement for a rain sensor for the WMRUSB stations. Stopped sanity + check of dewpoint values from WMR console which can vary wildly when the + temperature is low. + +18) Fixed a data size mismatch when the virtual server is used and one end is + 64-bit and the other end is 32-bit. Modified LOOP_PKT and ARCHIVE_PKT to + use sized integers (int32_t) defined in stdint.h. + +19) Incorporated Debbie P's changes for the debian wview start script so it uses + start-stop-daemon and supports running wview processes as a non-root user + specified in a new wview-user config file. + +20) Modified wviewmgmt Station page to hide location entry if the station type + is Davis Vantage Pro. ELEV/LAT/LON are defined in the VP console and not by + wview. + +21) Added configuration item "Default Wind Units" to the wviewmgmt generation + page. Added support for all tags and graphics to use the selected wind + units. You can now select one of mph, m/s, knots or km/h for the global + default wind units in wview. + +22) Fixed UV processing for FFFF protocol WMRUSB stations. + +23) Added support for last 60 minutes rain ("r"), last 24 hours rain ("p") and + solar radiation ("L") to CWOP packet submission. The use of "P" (rain since + local midnight) has been removed in favor of the Madis-preferred rain + reporting data. + +24) Added support for solar radiation and UV to http clients (Wunderground + and WeatherForYou). If these sensor values are populated they will now + be transmitted. + +25) Added exfoliation skin designed by Matthew Wall. + +26) Changed the minimum CWOP reporting interval from 10 to 5 minutes. + +27) Added n1otx's example script for fast SQLite3 queries: + examples/scripts/rainInLastWeek.sh. + +28) Added socket recovery logic to the ethernet station medium. Added restart + logic to the serial medium. Added restart function to the medium abstraction. + +29) Changed debian package definition to "Recommends" apache and php-sqlite as + opposed to "Requires". + + + + +5.19.0 05-22-2011 +-------------------- + +radlib version required: 2.11.2 or newer + +1) Added "-f" argument to all processes so they can be run in the foreground + (i.e., not as daemons). This will facilitate tracing and debugging wview + processes from the command line. + +2) Added "start-trace" command to the wview start script examples that don't + use some distro-specific magic to start/stop processes so all processes can + be started with strace and run in the foreground. Added section in the User + Manual to describe the use of strace and the "-f" argument for debugging. + +3) Added an rsyslog config file to the debian APT installation such that wview + logs are placed in the file /var/log/wview.log. Also added "99-wview.conf" + to the examples/rsyslog directory and instructions for use in the source + build section of the User Manual. + +4) Added alarm type definitions to the Alarm Type mouse over tool tip so they + can be viewed directly from the wviewmgmt configuration page. + +5) Added release of SIGABRT signal for all process initializations. This allows + a clean abort for the operating system. + +6) Update WH1080 station interface to better handle potential extraneous data + on the USB bus. + +7) Moved all calculated values (wind chill, heat index, dew point) to the + generic station code so they are not computed until after calibrations. + +8) Added new configuration parameter to control if station interface details + (such as host/port or serial device) are displayed with the station type + when the tag "!--stationType--" is expanded. The default is "yes". Can be + configured on the File Generation page of wviewmgmt. + + + + +5.18.6 04-01-2011 +-------------------- + +radlib version required: 2.11.0 or newer + +1) Added more valid magic numbers for WH1080 stations. + + + + +5.18.5 03-31-2011 +-------------------- + +radlib version required: 2.11.0 or newer + +1) Fixed NOAA yearly reports missing rain on day that DST starts. + +2) Added read thread to WMRUSB using new radthread facility. + +3) Added email alert when VP archive record has to be retried. + + + +5.18.4 03-26-2011 +-------------------- + +radlib version required: 2.10.3 or newer + +1) Added better WH1080 sensor decode. + +2) Added console reset logic for WMRUSB stations when sensor data stops being + sent. + + + +5.18.3 03-20-2011 +-------------------- + +radlib version required: 2.10.3 or newer + +1) Fixed TE923 rain decode logic. + +2) Changed WMRUSB device open paradigm to harden the interface for marginal + USB environments. + + + +5.18.2 03-15-2011 +-------------------- + +radlib version required: 2.10.3 or newer + +1) Fixed WMRUSB UV value decode logic. + +2) Fixed hidapi compile error for OSX. + + + +5.18.1 03-14-2011 +-------------------- + +radlib version required: 2.10.3 or newer + +1) Fixed the hidapi utility to work on armel and powerpc platforms. + + + +5.18.0 03-13-2011 +-------------------- + +radlib version required: 2.10.1 or newer + +1) Added wind rose graphic initially contributed by J. Fiddler and G. Eddy. + Integrated with html generation. Added to default templates. + +2) Added rsync option "-L" (--copy-links) to command used for ssh file + transfers. + +3) Added the ability to test alarm scripts by enabling the test and alarm + number via wviewmgmt on the Alarms configuration page. + +4) Added a configuration parameter to the Station page to allow + enable/disable of DTR toggling for certain station interfaces. Currently + only the TWI and Vaisala stations use DTR toggling. + +5) Fixed issue with datafeed packets and negative values based on research + done by P. Sanchez. + +6) Fixed computation of rain/ET cumulative values when preset values are + provided. + +7) Added new wind speed composite graph contributed by G. Eddy. Added to all + Day, Week and Month templates. + +8) Added workaround for TWI single digit negative temperature reporting bug + contributed by pannetron. + +9) Fixed issue causing stations without ET having the rain dials always + render at the highest resolution (contributed by P. Sanchez). + +10) Fixed all templates' "images-metric.conf" file so the decimal places + field for the barometer bucket is "1" instead of "2". "2" was causing + truncation of the labels. + +11) Changed Virtual station connection recovery behavior to never stop trying + to reconnect with the remote station when the connection is lost. + +12) Added support for the Weatherlink IP datalogger. Updated the station + configuration page of wviewmgmt to support Weatherlink IP and more + coherent display of optional config items based on station type. Cleaned + up some miscellaneous commands. + +13) Added support for the WMRUSB station based on M. Maehre contribution. This + includes the WMR88, WMR100, WMR100N, WMR200 and WMRS200 stations. + Added a compatibility layer so libusb-1.0 is used. Added wviewmgmt and + wviewconfig support. Added new medium abstraction support for USB. + +14) Added a quick start guide for FreeBSD "wview-Quick-Start-FreeBSD.html". + +15) Added a separate medium abstraction for the USB hidapi. This allows support + for USB devices in Mac OSX and can be used for linux and FreeBSD as well. + MEDIUM_USBHID should be used for USB stations whenever possible so the + station is accessible for all supported operating systems. + +16) Added support for the Fine Offset WH1080 station and all of its variants. + These include: Fine Offset: WH1080, WH1081; Watson: W8681, WX2008; + National Geographic: 265 NE; Elecsa: 6975, 6976; Ambient Weather: WS-1080, + WS-1090, WS-2080; Tycon: TP1080WC; Jaycar XC0348. Used the new USBHID + medium for the interface so it will work on Mac OSX systems. + +17) Fixed a bug in the "wvutilsConvertMPSToMPH" utility. + +18) Fixed a race condition in the process monitor daemon causing possible + start up hangs. + +19) Added support for the Oregon Scientific WMR88A (not the same protocol as the + WMR88/WMR100). Uses the USB HID medium. Added wviewmgmt and wviewconfig + support. + +20) Added support for the Honeywell TE923W station and all of its variants. + These include: TE923, TE821W, WXR810, DV928 (manufacturers include Hideki, + Nexus, Mebus, Irox, Honeywell, Cresta). Uses the USB HID interface so it is + supported for Mac OSX systems. + +21) Added generic extra sensor support to various stations and the html generator. + See the file "parameterlist.txt" or the generated file "parameterlist.htm" + for details. This changed the LOOP packet structure thus requiring an update + to both remote and local wview servers for users of the "virtual" station type. + +22) Removed use of the "HOST_IS_BIGENDIAN" macro and replaced with "WORDS_BIGENDIAN" + to support OSX universal binaries (thanks B. Northcott). + + + + +5.17.3 05-16-2010 +-------------------- + +radlib version required: 2.10.1 or newer + +1) Changed packet receive timeout action from error to warning. Increased + wviewd packet response timeout from 30 to 60 seconds. + +2) Updated User Manual source so it is easier to print. Contributed by + D. Merritt. + + + + +5.17.2 04-18-2010 +-------------------- + +radlib version required: 2.9.0 or newer + +1) Fixed "stormStart" display. + +2) Increased wviewd packet response timeout from 10 to 30 seconds. + + + + +5.17.1 04-06-2010 +-------------------- + +radlib version required: 2.9.0 or newer + +1) Increased wviewd response timeout from 2 to 10 seconds. + + + + +5.17.0 04-04-2010 +-------------------- + +radlib version required: 2.9.0 or newer + +1) Added more verbose logging of WMR918 sensor initialization. Fixed timeout + value for select call while waiting for first data packets. + +2) Added htmlgend timer to limit the wait time for wviewd data packets once + requested. If packets are not received the timeout will produce log output + to indicate a problem. + +3) Add back in the display of time for storm start. + +4) Added user contributed PHP code to do path and file sanity check for certain + configuration items in wviewmgmt screens. Will indicate problems in red. + +5) Added "From" email address configuration and use for email alerts. + + + + +5.16.0 03-28-2010 +-------------------- + +radlib version required: 2.9.0 or newer + +1) Added contributed java script code to wviewmgmt so the focus defaults to the + login page's password entry. + +2) Added contributed moon rise and set utilites and html tags. If you are + upgrading and you want to add them to your Almanac page, you will have to + do so manually. + +3) Enhanced alarms datafeed archive sync so new archive records will not be + sent if archive sync is in progress. + +4) Changed storm start display to use the date format specified during + configuration. + + + +5.15.0 03-21-2010 +-------------------- + +radlib version required: 2.9.0 or newer + +1) Added daily rain (since midnight) to the wunderground and weatherforyou + data submissions. + +2) Added station time sync after DST change so stations like the Vantage Pro + which time stamp archive records will heve the new DST time update in a + *timely* manner. + +3) Added a 2 minute timeout argument to rsync for ssh file transfers. + + + + +5.14.3 03-02-2010 +-------------------- + +radlib version required: 2.9.0 or newer + +1) Modified NOAA update to use a 30 second offset from midnight to avoid + DB resource conflicts at midnight. Simplified update logic and fixed + bug introduced in 5.14.2 for NOAA causing alternating update and no + update at midnight. + + + + +5.14.2 02-25-2010 +-------------------- + +radlib version required: 2.9.0 or newer + +1) Remove legacy FTP interval definition per rule and replace with one global + interval value to allow control of the frequency of possible FTP transfers. + Files are still only transmitted if new. Added more verbose logs describing + per file status/action. + +2) Modified NOAA database update logic based on time stamps. + +3) Fixed bug in process monitor preventing restart of the station daemon if the + wview-binary file included CR or LF. + + + + +5.14.1 02-21-2010 +-------------------- + +radlib version required: 2.9.0 or newer + +1) Enhance serial read retry logic for the WS-2300 so the station daemon will + try harder when serial errors occur. + + + + +5.14.0 02-19-2010 +-------------------- + +radlib version required: 2.9.0 or newer + +1) Add user-contributed WS-2300 sensor data validation via multiple sample + collection. Three "similar" readings are required before each sensor data + is stored. + +2) Modified NOAA database behavior to allow replacement of existing NOAA + records with newer data. + + + + +5.12.6 01-31-2010 +-------------------- + +radlib version required: 2.9.0 or newer + +1) Fixed ssh rule counter initialization. + +2) Restart apache2 for APT installs (not upgrades) so the php5-sqlite module is + activated. + + + + +5.12.5 01-28-2010 +-------------------- + +radlib version required: 2.9.0 or newer + +1) Modified history rollup utility to better handle database and wview NULL + values in station data. + +2) Removed obsolete station type selection during ./configure. + +3) Added final prompt for confirmation before wviewcleardata purges all data. + + + + +5.12.4 01-24-2010 +-------------------- + +radlib version required: 2.9.0 or newer + +1) Fixed wvhttpd and wvcwopd not setting exit status properly. + +2) Cleaned up some previously contributed generator code. + +3) Added new wviewcleardata utility. This script will purge all archive records + and delete the dependent databases to allow a clean start. Can be used to + purge simulator data when ready to "go live" with your weather station. + + + + +5.12.3 01-16-2010 +-------------------- + +radlib version required: 2.9.0 or newer + +1) Fixed bug in ssh rule processing introduced with popen change. + + + + +5.12.2 01-16-2010 +-------------------- + +radlib version required: 2.9.0 or newer + +1) Changed libcurl connection options including not stomping on SIG_ALARM for + timeouts (which can skew/disable radlib timers). This was causing the FTP + transfer timer to fail when a transfer timeout occurred. Also enabled DNS + cacheing for libcurl which significantly speeds up file transfers. + + + + +5.12.1 01-14-2010 +-------------------- + +radlib version required: 2.9.0 or newer + +1) Added passive mode configuration to the FTP daemon. + + + + +5.12.0 01-13-2010 +-------------------- + +radlib version required: 2.9.0 or newer + +1) Change FTP daemon to use libcurl transactions directly, not tnftp. This should + alleviate any "hangs" waiting on the remote server. tnftp dependency removed. + + + + +5.11.0 01-09-2010 +-------------------- + +radlib version required: 2.9.0 or newer + +1) Fix log buffer overflow for FTP verbosity. + +2) Modify FTP to execute multiple smaller ftp commands to avoid large command + lines which may timeout. + +3) Added an FTP marker file indicating the last update time for FTP. This allows + only new files to be transferred, saving time and bandwidth. + +4) Added new SSH configuration parameters for remote port number and remote + ssh login username. + +5) Added ability to automatically update the configuration database during update + of existing installations when new configuration parameters are added in + future releases. + + + + +5.10.3 01-05-2010 +-------------------- + +radlib version required: 2.9.0 or newer + +1) Fix interrupted system call handling for FTP and SSH popen sessions. + + + + +5.10.2 01-03-2010 +-------------------- + +radlib version required: 2.9.0 or newer + +1) Modified build environment to better support cross-development. + +2) Enhanced the FTP daemon to do a better job of logging the command output + if verbose logging is enabled and the tnftp arguments are changed from "-iV" + to "-iv". All tnftp stdout/stderr output will now be logged for transfer + debugging purposes. The User Manual describes how to do this. + +3) Enhanced the SSH daemon to do a better job of logging the command output + if verbose logging is enabled. All rsync stdout/stderr output will now be + logged for transfer debugging purposes. + +4) Removed conditional build of the HTTP service. It will always be built and + can be enabled/disabled like any other service. + + + +5.10.1 12-29-2009 +-------------------- + +radlib version required: 2.9.0 or newer + +1) Added PHP logic to the system status page so the refresh is 3 seconds while + starting and 60 seconds otherwise. + +2) Fixed a few html errors in the system_status.php file. + +3) Modified the software identifier string tacked on to the end of CWOP packets. + + + +5.10.0 12-28-2009 +-------------------- + +radlib version required: 2.9.0 or newer + +1) Added new status facility so wview processes can report their status and + meaningful statistics. The status data will be stored in text files in the + /var/run/wview directory in an "INI" file format. /var/run is transient and + usually not mounted on any physical media (including flash or SD drives). + +2) Added status information by wview service to the wviewmgmt System Status + page. Status, last message and up to 4 service-specific statistics are + presented. Modified the page to auto-refresh every 10 seconds. + + + +5.9.5 12-26-2009 +-------------------- + +radlib version required: 2.9.0 or newer + +1) Updated day history table insertion to replace existing records. + +2) Fixed LSB header for debian init.d start script. + + + +5.9.4 12-25-2009 +-------------------- + +radlib version required: 2.9.0 or newer + +1) Added "IsBlocking" check for ethernet medium reads. + + + +5.9.3 12-25-2009 +-------------------- + +radlib version required: 2.9.0 or newer + +1) Added LSB header to the debian start script. + +2) Fixed debian postinst script to always overwrite the wview-version file. + +3) Added support for wviewd shutdown notification to other daemons if it fails + initialization and shuts down. "/etc/init.d/wview stop" should still be + issued to ensure all daemons shutdown (ftp and ssh daemons do not receive + internal messages and radmrouted must be stopped externally). + + + +5.9.2 12-24-2009 +-------------------- + +radlib version required: 2.9.0 or newer + +1) Fixed errant "functions.php" inclusion in the tarball. + +2) Added gawk dependency for the debian package. + +3) Fixed a link in the User Manual. + + + +5.9.1 12-23-2009 +-------------------- + +radlib version required: 2.9.0 or newer + +1) Fixed sqlite3 dependency in debian control file. Fixed install "error" when + apache2 is not installed. + +2) Updated alarm daemon handling of VP consoleBatteryStatus. + + + +5.9.0 12-22-2009 +-------------------- + +radlib version required: 2.9.0 or newer + +1) Added station support for most Texas Weather Instruments stations. Thanks to + David Finster for development and debug support. + +2) Changed alarm datafeed socket to publish LOOP data and archive records in + support of the new wview virtual station. LOOP and archive packets will now + be sent in network byte order. Added new utilities to provide host-to-network + and network-to-host conversion of LOOP and archive packets in datafeed.[ch]. + The start frame for loop data has been changed so the start frame can be used + as a packet type indicator as both LOOP and archive packets will now be pushed. + +3) Added new station type "Virtual". This allows remote forwarding of one wview + installation to another. A wview server connected to a real station can run + just the wviewd daemon and alarm daemon which will push archive and LOOP data + to a remote wview server configured as "Virtual" which will receive the archive + and LOOP data and use it to generate files and html as a normal wview server + would. + +4) Fixed a bug in htmlHistoryInit which caused the wview-history.sdb database to + be filled with NULL records if there was no archive history available. Simply + delete the $prefix/var/wview/archive/wview-history.sdb file and restart wview. + +5) Cleaned up the debian package generation control files including making + "radlib-dev" a prerequisite for wview. APT upgrades of the 5.8.0 package + should be seamless. + +6) Source code: GLC_VALUE_NONE is dead, replaced by ARCHIVE_VALUE_NULL. + + + +5.8.0 12-12-2009 +-------------------- + +radlib version required: 2.8.5 or newer + +1) Updated Awekas US templates to send proper rain unit. + +2) Added new data tag "--stationName--" (brackets removed) to display the + station name as configured in wviewmgmt. + +3) Updated build environment to support debian packaging. Added wview debian + package support. See the new User Manual for details. + +4) Removed use of the HTTP_DOC_ROOT environment variable. Now the install + location for wviewmgmt will be $(localstatedir)/wviewmgmt. Soft links can + be used to make this visible in the http server document root as desired. + $(localstatedir) is /usr/local/var by default. + +5) Added new configuration database creation script for debian package + installations. It is used by the debian package post-installation script. + +6) Changed the default station type configured from Vantage Pro to Simulator. + +7) Added targets for default generation configuration of "chrome", "standard", + "US units" to the build. It will not overwrite existing html configuration + (based on existence of the wview-conf.sdb file). wviewhtmlconfig will no + longer be required outside of the new installation unless a different skin + or different units are selected. + +8) Reformatted the User Manual wview-User-Manual.html. Removed old references + and added new content for the debian and MacPorts package installs. + + + +5.7.1 11-14-2009 +-------------------- + +radlib version required: 2.8.5 or newer + +1) Fixed legacy HILOW retrieval logic so the first hour of rain/ET in a day is + not skipped when computing daily rain. + + + +5.7.0 11-12-2009 +-------------------- + +radlib version required: 2.8.4 or newer + +1) Added "install-exec-hook" to Makefile.am and removed "install-env" target. + Now the old "install-env" tasks will be done automatically after the + installation tasks and all can be invoked with "make install". Added + "uninstall-hook" target to clean up all "install-exec-hook" data. + The "uninstall" target should never be used unless all data (including + archive) is to be removed. + +2) Changed library search order in the LDFLAGS variable for all processes. This + ensures that libraries installed by package management systems are used + before older system-installed libraries. + + + +5.6.4 11-05-2009 +-------------------- + +radlib version required: 2.8.4 or newer + +1) Further clean up of chart labels to support DST strangeness. + + + +5.6.3 11-03-2009 +-------------------- + +radlib version required: 2.8.4 or newer + +1) Changed computed rain rate period to 5 minutes for stations for which it is + computed. + +2) Fixed example alarm scripts so the path is correct. + +3) Fixed issue in Vantage Pro archive record time stamping when DST "falls back". + Now all records will be stored in the archive database for Vantage Pro, + including the "2nd" 1 AM to 2 AM period. All non-archive-generating stations + were already doing this. + +4) Modified day history chart time labeling to be smarter about DST changes. + + + +5.6.2 10-21-2009 +-------------------- + +radlib version required: 2.8.4 or newer + +1) Fixed bug in Makefile.am which allowed the station type to be overwritten + during "make install-env". + + + + +5.6.1 10-21-2009 +-------------------- + +radlib version required: 2.8.4 or newer + +1) Updated the User Manual and quick start guides. + + + + +5.6.0 10-11-2009 +-------------------- + +radlib version required: 2.8.4 or newer + +1) Added a "scripts" directory to the distribution. Added "wview-install-debian" + and "wview-upgrade" scripts to the distro. + +2) Incorporated a few more defensive measures and fixed a few return values and + one transposed "break" statement based on user comments. Thanks David Barto. + +3) Added the HTML page "wview-Debian-Quick-Start.html" to the distribution. It + describes a very simple method for installation on debian and derivative + systems. + +4) Fixed a remaining bug with the day history table population when no data + exists for one or more archive periods. Consult the User Manual for how to + regenerate the day history table. + +5) Added better resolution to email alerts; added support for all stations. + + + + +5.5.6 09-24-2009 +-------------------- + +radlib version required: 2.8.4 or newer + +1) Fixed a bug for utilities accessing old WLK database files introduced in + 5.5.4. + + + + +5.5.5 09-22-2009 +-------------------- + +radlib version required: 2.8.4 or newer + +1) Fixed a bug for forecast icons and rules introduced in 5.5.4. + +2) Updated selected log messages to be more user friendly. + + + + +5.5.4 09-13-2009 +-------------------- + +radlib version required: 2.8.4 or newer + +1) Fixed a loop boundary bug in htmlgend initialization. + +2) Replaced many non-static source string strcpy invocations to provide better + defensive coverage of excessively long configuration strings. + +3) Fixed a bug when the proper images.conf format is compromised by user + configuration error. + + + + +5.5.3 08-15-2009 +-------------------- + +radlib version required: 2.8.4 or newer + +1) Restored WS-2300 serial operation from change made in 5.5.2. + + + + +5.5.2 07-21-2009 +-------------------- + +radlib version required: 2.8.3 or newer + +1) Changed Vantage Pro serial initialization to remove hangup and HW flow + control. + +2) Changed internal data structures to not be packed. + +3) Changed SIGCHLD processing to be more robust. + + + + +5.5.1 06-14-2009 +-------------------- + +radlib version required: 2.8.2 or newer + +1) Changed UV to float in LOOP structure so decimal precision is not lost. + +2) Add more paranoid sanity checks for NULL archive data. + +3) Remove references to "NOAA" for NOAA-style reports in HTML templates + (because it apparently isn't clear wview generated the data?!?). + +4) Updated the User Manual. + +5) Added more sanity checks for data converters. + + + +5.5.0 05-14-2009 +-------------------- + +radlib version required: 2.8.2 or newer + +1) Modified WMR9XX station init so it will exit more gracefully. + +2) Removed the wviewsqld process. Exports to external database servers will now + be done through the $distro/dbexports scripts. + +3) Improved NOAA initialization process so when there are no records, it doesn't + try so hard. + +4) Integrated WMR9XX changes to support configurable outside channel (thanks + Kevin Cloy). + +5) Split out some NULL value checks for Vantage Pro archive record reception. + +6) Added more boundary tests for image generation so when the user tries to + generate non-existent sensors, htmlgend will not crash. + +7) Changed wind direction graphs from line graphs to scatter graphs (thanks + Debbie Pickett). + +8) Fixed errant UV conversions in the generator. + +9) Changed wind direction processing so NULL values are not graphed. Modified + wind direction archive record creation for non-archive generating stations so + it is consistent with VP record creation. + +10) Fixed some bucket issues from prior dual units contributions. + +11) Updated the wview management site (wviewmgmt) to support network configuration + on Debian-based distributions. Added automatic upgrade support for wviewPlug + systems. + +12) Enhanced the install-env make target further including setup for Debian-based + network config. + + + +5.4.0 04-18-2009 +-------------------- + +radlib version required: 2.8.2 or newer + +1) Added new SQLite3-MySQL export scripts. Added $distro/dbexport directory + to contain the script sources. Version 5.4.x will be the last release containing + the wviewsqld daemon as it is now deprecated in favor of the wview-mysql-export + script. Added wview-mysql-create script to allow easy setup of the user, database + and tables on the MySQL server in preparation for export. Archive, HILOW and + NOAA tables are exported to the same MySQL database. Updated User Manual + section on MySQL exports. + +2) Added missing documentation of the default wviewmgmt password in the User + Manual. + +3) Added marker file support for the archive, hilow and noaa databases so the + synchronization externally via scripts is facilitated. + +4) Removed sample databases for config and archive and changed the "install-env" + make target so it generates them. This avoids platform issues of sqlite3 databases. + +5) Modified wvpmond so it reads the wview-binary marker file to know which binary + name to use for wviewd. wviewd binary names are now appended with the station type. + +6) Added new SQLite3-PostgreSQL export scripts. Added $distro/dbexport directory + to contain the script sources. Version 5.4.x will be the last release containing + the wviewsqld daemon as it is now deprecated in favor of the wview-pgsql-export + script. Added wview-pgsql-create script to allow easy setup of the database + and tables on the PostgreSQL server in preparation for export. Archive, HILOW and + NOAA tables are exported to the same PostgreSQL database. Updated User Manual + section on PostgreSQL exports. + +7) Added vpconfig contributions from Deborah Pickett so the ISS channels can be + configured. Also adds support for setting rain collector size and wind direction + calibration. + +8) Commented out SQLite3 pragma attempts to change the journaling mode. Support + for it is not consistent across SQLite3 versions. + + + +5.3.2 03-27-2009 +-------------------- + +radlib version required: 2.8.2 or newer + +1) Changed behavior of the "install-env" make target so it only copies config + and run environment examples from the distro if the directories do not exist. + This means future upgrades will require manual copy or manual rename of the + config and run directories prior to building the "install-env" target (if + and when new features are desired). + + + +5.3.1 03-26-2009 +-------------------- + +radlib version required: 2.8.2 or newer + +1) Added SQLite3 version check before applying "journal_mode" pragmas. + + + +5.3.0 03-25-2009 +-------------------- + +radlib version required: 2.8.2 or newer + +1) Created new wviewmgmt site for distribution. Better site navigation, database + updates by page, a new system status page and password protection for site + pages are a few of the enhancements. + +2) Added support for a new configure environment variable, HTTP_DOC_ROOT. When + defined prior to invocation of the configure script, the install-env target + will install wviewmgmt to the http document root specified. + +3) Updated the install-env target for wview build so the $prefix/var/wview/archive + directory is NOT replaced, but rather updated. This means your existing archive + database should be preserved and will not require a manual copy from the save + directory after the installation. + +4) Modified HILOW database population so that archive records are now always added + as a sample (except for cumulative sensors rain, ET and hail). This avoids the + missing of the high wind value in the archive record and LOOP data during the + interval not catching the high wind value. This only applies to Vantage Pro + stations. + +5) Changed initial start of sync timer so it cannot inadvertently track with the + archive interval timer. + + + +5.2.0 03-12-2009 +-------------------- + +radlib version required: 2.8.2 or newer + +1) Changed station build procedure so all station binaries are built with unique + names such as wviewd_vpro, wviewd_wxt510, etc. Modified the wviewmgmt and + wviewconfig configuration utilities to populate the file + $prefix/etc/wview/wview-binary with the appropriate wviewd binary name; + changed all start scripts to use the contents of this file when starting. + +2) Moved the ConfigForms directory out of $distro/examples to $distro/wviewmgmt + and added automake logic so it is aware of the install prefix and opens the + wview-conf.sdb file directly (no soft link required). Added the ability + (with sudo membership for the http user) to start and stop wview from the + Management web interface. + +3) Moved NOAA data from the ASCII file noaa.dat to an SQLite3 database, + wview-noaa.sdb. Modified NOAA generation to use the new API. See the User + Manual for a schema description. + +4) Added email alert notifications. Added configuration support in wviewmgmt + and added new config parameters to the example wview-conf.sdb database. See + the UPGRADE file for instructions. Sendmail and sendEmail packages are required. + +5) Added All Time html tags and sensor support. Added new tags to the almanac + template examples. + +6) Fixed a bug in VP LOOP data reception logic which caused UV samples to be + a factor of 10 too large. + +7) Fixed a bug in html generation caused by old DST code that pre-dated the new + SQLite archive database and the use of unix time for the records. + + + +5.1.3 03-03-2009 +-------------------- + +radlib version required: 2.8.2 or newer + +1) Added hilowcreate to the distribution - it was erroneously left out of the + prior two builds. + + + +5.1.2 03-03-2009 +-------------------- + +radlib version required: 2.8.2 or newer + +1) Fixed a bug for Vantage Pro stations which only occured if there were no + records in the wview-archive.sdb database (new install) which caused the + wviewd daemon to never retrieve archive records because of a bad + LastArchiveTime value (retrying archive record...). + + + +5.1.1 03-01-2009 +-------------------- + +radlib version required: 2.8.2 or newer + +1) Added an out-of-band utility to create the HILOW database so that limited + power platforms like the NSLU2 can create the new database while the older + version continues to archive data and generate files from templates. See + the UPGRADE file for full details on the process. + + + +5.1.0 03-01-2009 +-------------------- + +radlib version required: 2.8.2 or newer + +1) Added new HTML/Javascript/PHP forms for wview configuration. They are found + at $distro/examples/ConfigForms. That entire directory should be copied + to your web server document root. The entry point is + /ConfigForms/wview-form.php. Hooks were included for parameter value checking + but for now whatever you enter is what gets put in the wview-conf.sdb database. + A link to $prefix/etc/wview/wview-conf.sdb must be placed in the /ConfigForms + directory with proper rw permissions. + +2) Added new database to store HILOW data, wview-hilow.sdb. Hourly highs and lows + are stored along with timestamps for the events, an accumulator and the number + of samples included (average = accumulator/samples). There is a separate table + for each sensor type and one record per hour is maintained from the beginning + of archive records to the current time. sensorstore.bin is no longer required. + This will also allow for implementation of all-time highs, lows and averages + in the near future. Note that the initial creation of this database will occur + the first time wview runs and will take some time according to how far back + your archive records go. + +3) Fixed bug in computed data utility for archive records which was still using + the old WLK 0-15 wind direction scheme for a validation check. + +4) Fixed a 1 second delay in file generation introduced when the pre/post generation + zombie bug was fixed. + +5) Added more checks to the station time sync routine so time syncs are never + done at the top of the hour. + + + +5.0.1 02-07-2009 +-------------------- + +radlib version required: 2.8.2 or newer + +1) Fixed bug in alarms configuration processing introduced with the sqlite + configuration database. + + + +5.0.0 02-05-2009 +-------------------- + +radlib version required: 2.8.2 or newer + +1) Replaced use of multiple text files for wview configuration with one SQLite3 + database. There are multiple ways to configure wview now including some new + graphical choices. See the User Manual section on configuration for full + details. + +2) Replaced the use of the "WLK" database files with the use of one SQLite3 + database. Added conversion utilities wlk2sqlite and sqlite2wlk to allow the + import of old WLK records into the SQLite3 archive database. Added the + dbsqlite.[ch] utility in common to provide the archive database access. + +3) Updated the Awekas template files per changes by Othmar Gattringer. + +4) Segregated rain tipper support to the VP station code. Cleaned up history + data storage and references. Cleaned up internal archive data processing and + made it more generic (less VP-specific). Moved WLK utility dbfiles.[ch] under + the VP-specific station code to retain for the conversion utilities. + +5) Updated sensor utility and added magic number to the sensorstore.bin file + so when the magic number is changed between versions, wview will automatically + regenerate the sensor store. + +6) Removed configurability of the archive path. It will now always be at + $prefix/var/wview/archive. + +7) Autogenerate CWOP passcode for non-CW/DW stations (Gerry Creager). + +8) Updated index page templates to support year selections through 2050. + +9) Updated computed data utility to use new dbsqlite API. + +10) Modified wviewconfig to update the SQLite3 configuration database directly + through the use of the sqlite3 utility, other unix tools and some fancy + scripting. + +11) Added WMR9x8 support for extra sensors and the undocumented pool sensor + (thanks to Kevin Cloy). + +12) Added support for new radlib red-black text search utility for data tag + matching. + +13) Added 12-hour temperature accumulator so the 12 hour average temperature can + be used for pressure conversions. + +14) Added support for a dayHistory table in the archive database so once daily + averages have been computed for historical purposes, they can be stored in + the database and retrieved to save repeated calculation during startup. + +15) Fortified SIGCHILD handling in all signal handlers to mitigate race condition + with the subsequent signal assignment (thanks Graham Eddy). + +16) Changed how station pressure values are calibrated. Instead of calibrating + each of BP, SP and ALT independently, only the pressure type produced by your + station needs to be calibrated and the other two are computed based on the + calibrated sensor value. To date, all stations except the VP produce SP; the + VP produces BP. Thus only SP (or BP for VP) need to be calibrated. + + + +4.0.1 07-03-2008 +-------------------- + +radlib version required: 2.7.5 or newer + +1) Fixed nuisance bug in FTP status logging - did not effect performance. + +2) Fixed "Rain Units are mm" message appearing during boot when imperial units + are selected. + + + +4.0.0 06-28-2008 +-------------------- + +radlib version required: 2.7.5 or newer + +1) Adjusted rain rate accumulator window for the WS-2300. + +2) Add rain rate accumulator to the WMR918 station as the reported rain rate + from the station is inferior. + +3) Changed default source location for ftp transfers from $prefix/var/wview + to $prefix/var/wview/img so by default no "img" directory will be tacked on + to the destination directory. This will require all users of ftp to modify + their wviewftp.conf file to remove all leading "img" references. + +4) Added tipper size support for Vantage Pro stations. The calibration solution + for VP stations with non-0.1" tippers did not work because of the VP protocol + inconsistencies. Now no calibration is required to support any VP tipper + size. + +5) Enhanced dual unit support in charts and buckets (thanks W. Krenn). Unit + names must be those indicated in the example images.conf and + images-metric.conf files. English is the only language supported for dual + units at this time. + +6) Added new html tags for various wind units (thanks W. Krenn). See + parameterlist.txt for details. + +7) Added a new html template "parameterlist.htx" which includes every html tag + displayed as "=TAG_VALUE". Can be accessed at: + http:///parameterlist.htm. + +8) Modified conversion of Sea Level Pressure (SLP) to Station Pressure (SP) in + wvutils. This should correct deviations at higher elevations. + +9) Fixed homepage templates for all skins (plus) so the !--localRadarURL-- and + !--localForecastURL-- tags are used. + +10) Added the new chrome skin based on work by M. Nausch. + +11) Fixed a bug with rain and ET aggregate values for charts so that values across + midnight are not displayed incorrectly. + + + +3.9.0 04-20-2008 +-------------------- + +radlib version required: 2.7.5 or newer + +1) Modify for new unsigned long long radTimeGetMSSinceEpoch return value. You + must update your version of radlib to 2.7.5 BEFORE upgrading to this version + of wview. + +2) Added new station support for the Oregon Scientific WMR918/968 stations. The + WMR918/968 is selected during the "configure" step by specifying + "--enable-station-wmr918". Added battery status HTML tags for the four + WMR918 transmitters. + +3) Added example alarm script "outtempMaxEmail.sh" to illustrate how to use + alarm scripts to send notification emails. + +4) Added checks for unpopulated wind gust speed. + +5) Fixed bug where wviewsqld expected SQLDB_EXTENDED_INFO to be defined in all + cases when in fact it is only valid for VP with extra sensors. + + + +3.8.0 04-01-2008 +-------------------- + +radlib version required: 2.7.4 or newer + +1) Added basic sensor calibration support such that a multiplier and constant + are defined for each of the sensors which can be calibrated. Added a new + config file "calibrate.conf" to allow configuration of the calibration. Now + (for example) calibration of the rain collector type is possible: 0.01", + 0.2mm, 0.1mm and others can be supported. Station pressure deviations can + be calibrated. Allows ratio and/or constant calibrations. + +2) Fix input bug in vpinstall script so configuration is only done if 'y' is + entered. + +3) Fix strcmp bug in alarms configuration. + +4) Remove barometer trend indicator from automatic inclusion with barometer + value during generation. + +5) Fixed a bug in dbfStoreArchiveRecord which allowed the "dashed" value of + 255 through as a valid wind reading for VP stations. + +6) Fixed a bug in the station simulator so no serial fd is added to the wviewd + file descriptor list. + +7) Added additional console wakeup try for VP archive retrieval. + +8) Changed station time sync interval from every 1 hour to every 4 hours. + + + +3.7.3 03-22-2008 +-------------------- + +radlib version required: 2.7.4 or newer + +1) Fixed "exec-prefix" bug in wview start script generation. + +2) Added support for generation of NOAA reports in metric units. + +3) Added support for a new "pre-generate.sh" script which, if it exists in + $prefix/etc/wview, will be executed after image generation but before + template generation. Added to the classic/standard and classic/plus skins. + + + +3.7.2 03-18-2008 +-------------------- + +radlib version required: 2.7.4 or newer + +1) Fixed MaxWindSpeed bug in the WS-2300 interface. + +2) Fixed wviewconfig bug introduced with stationType addition in 3.7.1 which + omits position configuration for non-VP stations. + +3) Fixed small bug in alarms error logging if the script execution fails. + + + +3.7.1 03-17-2008 +-------------------- + +radlib version required: 2.7.4 or newer + +1) Enabled NOAA generation for metric stations. + +2) Fixed bug in wviewconfig which prevented non-standard database names for SQL + setup. + +3) Fixed start script generation to use $exec-prefix instead of $prefix for + "WVIEW_INSTALL_DIR". + +4) Clarified text for "STATION_RAIN_ET_YTD_YEAR" config value to indicate the + year is the year of the rain season start. + +5) Added new tag !--stationType-- so the weather station type can be produced + for html pages, etc. Modified wviewconfig to allow for specification of the + station type. Removed "Generic" type and added "Simulator", "WXT510" and + "WS-2300". Added to classic skin homepages. + + + +3.7.0 03-09-2008 +-------------------- + +radlib version required: 2.7.3 or newer + +1) Fixed missing config file blocks in htmlGenerateInit. + +2) Added transparency level values to graphics.conf and support for all + graphic types (thanks to Jon Barber). + +3) Fixed bugs in endian conversion utilities (thanks to Michael Puckett and + Jerry Fiddler). + +4) Added dual unit buckets (thanks to Randy Miller). This is presently only + available on non-metric systems and is selected during configuration. + +5) Changed CWOP submission logic to only send a packet if a new archive record + has been received. Changed the report interval from a hard-coded 10 minutes + to be the station archive interval, with a minimum duration of 10 minutes. + +6) Added new wview.conf parameters to configure preset values for rain and ET. + This allows the display of current conditions to include rain/ET accumulated + outside of the data in archive records for the specified year. Added support + in wviewconfig. + +7) Added support for a "static" directory to be included in site skins. The + contents of this directory will be copied to the HTML destination once when + wviewhtmlconfig is run. + +8) Changed location of wview configuration and data trees from /etc/wview and + /var/wview to $prefix/etc/wview and $prefix/var/wview respectively. This + allows wview to be installed at any prefix specified during the configure + process (--prefix=/opt/local for example). Binary files were already being + installed to $prefix. This allows wview to be installed/run in a chroot jail + if so desired (or required by package management systems). + +9) Enhanced example start scripts so they are populated with the proper paths + for binaries and the data directory based on the configure $prefix during + the build process. + +10) Added new station support for the La Crosse WS-2300 series stations. The + WS-2300 is selected during the "configure" step by specifying + "--enable-station-ws2300". Added rain rate computation for the WS-2300 as it + does not provide it. + +11) Update DST change handling to recompute the sun rise/set etc. data when a + daylight savings time event occurs. + + + +3.6.0 02-23-2008 +-------------------- + +radlib version required: 2.7.2 or newer + +1) Added new config file "graphics.conf" to define alternate graphics colors + and sizes. Added support in htmlGenerate.c for these definitions (thanks to + Brooks Clark). Made "graphics.conf" part of skin definitions so graphics + can be customized by skin designers. + +2) Added "post-generate.sh" and "images-user.conf" to skin definitions so post- + generation and image customization can be defined by skin designers. + +3) Updated "examples/html/Template-Skins-HOWTO.txt" to reflect changes and better + describe the skin creation process. + +4) Moved the destination directory for NOAA and ARC pages to ".../NOAA" and + ".../Archive" respectively. + +5) Fixed bug introduced in delayed CWOP submission logic of 3.5.0 release. + +6) Added support for image background transparency (configured in graphics.conf). + +7) Fixed missing change for the "htmlmgrReReadImageFiles" method so it no longer + looks for "images-metric.conf" if a metric station (images-metric.conf is + now copied to images.conf by wviewhtmlconfig based on your wviewconfig + choices). + + + +3.5.0 02-12-2008 +-------------------- + +radlib version required: 2.7.2 or newer + +1) Changed CWOP generation logic so that packets are sent every 10 minutes and + the transmission offset is based on the last digit/character of the APRS + call sign. Call signs with a character in the last position will have a + transmission offset based on the ASCII value of the character, i.e. a final + character of "A" would have a transmission offset of: + 'A' = 65 => 65 % 10 = 5, thus the offset is 5. No effort was made to + support the 9 or 11 minute interval for transmission offsets of 0 as this + algorithm will evenly distribute CWOP transmissions over any 10 minute + interval so long as the callsigns are distributed uniformly. Also changed the + default servers to all be "cwop.aprs.net" per recent requirements. + +2) Added a new HTML configuration script "wviewhtmlconfig" to simplify template + setup for US/metric and standard/extended data settings. It must be run + immediately after "wviewconfig". Moved old example templates to + examples/html/classic/standard and /examples/html/classic/plus and added + a HOWTO in examples/html for contribution of new template skins. + +3) Changed the format of the "wviewftp.conf" file so that the FTP binary and + arguments can be configured there rather than hard-coded. + +4) Added config value SQLDB_FULL_SYNC to allow choice of either full sync of + all archive data or partial sync from last database entry. The partial + sync option should allow those with lots of data or slow links to be up + and running more quickly. + + + +3.4.1 12-22-2007 +-------------------- + +radlib version required: 2.7.1 or newer + +1) Fixed make target "install-env" so the html template directory is properly + created. + +2) Updated new include templates to support "plus" pages more completely. + +3) Modify WXT510 station interface so it does not over-write the heating + configuration. Heating enable/disable must be done outside of wview. + + + +3.4.0 12-16-2007 +-------------------- + +radlib version required: 2.7.1 or newer + +1) Added new meta-tag for file inclusion: !--include filename.xxx--. If this + tag is found in any template file, then htmlgend will attempt to open the + file at /var/wview/img/filename.xxx and place its contents in the file + being generated (if your default output location is different from the + default location of /var/wview/img, htmlgend will look in your destination + folder). Included files can themselves be generated from templates, just + place them before the template file which includes them in + html-templates.conf (htmlgend generates them in the order found in this conf + file). + +2) Added new wview process, wvpmond, to monitor other wview processes and + kill/restart ones which have become non-responsive (such as wvhttpd). + Added new config file (processes.conf) so the user can set the timeout + values for each process or to disable monitoring for that process. Created + new wview start scripts to include the wvpmond process. + +3) Fixed bug in htmlgend introduced with the post-generate enhancement which + caused messages sent to htmlgend to not be received until the next + generation timer expiry. + +4) Moved default html templates from "examples/html" to "examples/html/classic". + This will allow the future addition of other "skins" to the distribution as + users contribute them. Modified html templates to utilize newly added + include files for navigation buttons, headers and current reading tables. + Removed redundant old template files which only differed by the background + image. Modified the example html-templates.conf to include (and thus + generate) these new template include files. Changed the default homepage + behavior to use the "day/night" paradigm. + +5) Added a timeout value to the configuration of libcurl transactions for + wunderground data submission. This may prevent transactions from "hanging" + when the wunderground servers are non-responsive. + +6) Renamed the wunderground process to "wvhttpd" and added support for the + weatherforyou system. wunderground and/or weatherforyou are enabled during + configuration by specifying "--enable-http". They may be disabled in the + new http.conf file by setting their station IDs to 0. + +7) Modified the former "wunderground.conf" (now "http.conf") to include + weatherforyou settings. Modified wviewconfig to support configuration of + weatherforyou. + +8) Modified the Vantage Pro station state machine to only log being in the + error state one time to avoid continuous system logging. + + + +3.3.0 05-28-2007 +-------------------- + +radlib version required: 2.7.0 or newer + +1) Fixed wunderground verbose log output (no change to wunderground data sent). + +2) Changed rain dials to only use ET if the station is configured for extended + data samples. + +3) Fixed rain and ET amounts being rounded up when interval amount is added - + sprintf was doing the rounding up. Was only an issue on WXT-510 stations. + +4) Added "withmysql" make target to the sample-datafeed-client Makefile so the + example could be built with MySQL support. + +5) Added DATE_FORMAT config value in htmlgen.conf to allow user-defined date + format in images and tags. Formats allowed are those defined by strftime(3). + If the value is not present in the config file, mm/dd/yyyy will be used for + non-metric systems, or yyyymmdd for metric systems. + +6) Added DIAL_TEMP_FEELSLIKE_DISABLE macro to htmlGenerate.[ch] so that if the + caller passes this value for the "feelsLike" argument, no apparent + temperature will be drawn for the htmlGenPngDialTemperature method. + +7) Changed default CWOP servers and port numbers to match new APRS servers + specified by Dave Helms. + +8) Verified 64-bit operation with radlib versions 2.7.0 and up. + +9) Added new html tags for !--windGustSpeed-- and !--windGustDirectionDegrees-- + which allow access to the "current" (this term is of course relative) wind + gust speed and direction. + +10) Added new html tags for !--windBeaufortScale-- (which represents the current + windspeed) and !--intervalAvgBeaufortScale-- (which represents the last + archive interval windspeed). Added wvutils routine to convert windspeed in + mph to a Beaufort string. See parameterlist.txt for the exact tag + expressions to use in your templates. Added "Recent Avg Wind" and "Recent + Beaufort Scale" rows to all the "index" templates. + +11) Added a post-generation script for the htmlgend daemon. It will be run + (if found at /etc/wview/post-generate.sh) after all template files have + been generated, each time they are generated. Added an empty script file + to the distribution (examples/conf/post-generate.sh) and support for it in + the "install-env" make target. + + + +3.2.1 03-16-2006 +-------------------- + +radlib version required: 2.6.2 or newer + +1) Modified example images-metric(-mm).conf files to use zero decimal places + for the weekly barometer chart. + +2) Modified VP state machine to better detect and handle missed archive records. + +3) Added support for the WXT-510 extra sensors to the Alarms processing. + +4) Added two new HTML tags: --localRadarURL-- and --localForecastURL--. Added + support for configuring them in wviewconfig and producing them in htmlgend. + Added references to them in all of the homepage (index*.htx) templates. See + examples/html/index.htx for example usage. + +5) Fixed a bug in the common file for the endian conversion utilities. + + + + +3.2.0 03-05-2006 +-------------------- + +radlib version required: 2.6.2 or newer + +1) Added more logic to handle system time changes with respect to archive + record generation. Also enhanced html generator to better handle missing + archive records. + +2) Added endian conversion utilities for WLK and sensor archive files: + arc_be2le and arc_le2be. These utilities will convert all WLK and sensor + store files in a given source directory then store the results in a given + destination directory. The conversions are Big Endian to Little Endian and + Little Endian to Big Endian respectively and can be run from either a big or + little endian system. + +3) Added weekly historical chart support. Added image definitions in the three + images config files. Added new HTML templates for Weekly and Weekly_Plus. + Added references to the new weekly pages in the other template navigation + bars. Added reference to the weekly page in the home page templates. You + will need a full template and config file upgrade to properly deploy weekly + charts (make install-env). + +4) Fixed SuSE wview start script radmrouted invocation. + +5) Added generic ".[your_ext]x" => ".[your_ext]" template file generation + support. Any template file with extension ending in 'x' will be generated + without the trailing 'x'. Thus, example.phpx is generated as example.php. + Note: wview will search for tags in these templates and replace them without + any regard for a particular file/scripting syntax. + +6) Added archive retrieval retry logic to the VP station interface to address + VP consoles which do not have a record ready when it should be. + + + + +3.1.4 02-11-2006 +-------------------- + +radlib version required: 2.6.2 or newer + +1) Added station altimeter computation support. Changed CWOP submission (again) + to provide Altimeter value instead of Station Pressure. Added new HTML tag + --altimeter-- and associated support. + +2) Removed "set -e" from NSLU2 wview start script so it behaves properly. + +3) Changed logic for parsing the *directory* entry of the wviewftp.conf file. + It appears that "isdigit" was not functioning as expected on the NSLU2. + + + + +3.1.3 02-05-2006 +-------------------- + +radlib version required: 2.6.2 or newer + +1) Fixed rain dial scale calculation for metric stations. + +2) Changed images-metric-mm.conf to use 0 decimal places for rain charts. + + + + +3.1.2 02-03-2006 +-------------------- + +radlib version required: 2.6.2 or newer + +1) Fixed several hour/day/week "change" values for temp, dewpoint and barometer + introduced with the LOOP data change to float. + +2) Changed rain dials to be zero-based if there is no ET measurement. + +3) Added mm support to the rain dials, buckets, and historical charts. + +4) Fixed rain accumulator bug for WXT-510 archive records. + + + + +3.1.1 01-30-2006 +-------------------- + +radlib version required: 2.6.2 or newer + +1) Fixed a rain HTML tag value reporting bug in the WXT-510 station support. + +2) Fixed a couple of "Plus" references missing in some of the Plus templates. + +3) Changed the HUP signal behavior for log verbosity to just toggle logging + on/off instead of rereading the wview.conf file. Thus the logging can be + turned on/off individually for each daemon run time without touching the + default in wview.conf. + +4) Changed significant LOOP structure data to type float to get away from the + VP "shifted" shorts. + +5) Changed use of the VERBOSE_MSGS config value in wview.conf to be a bitmask + representing each of the wview daemons. This allows controlling the log + verbosity individually for each wview daemon. The config Value should now be + 8 characters of zeros or ones but if the value is less than 8 characters, + the old global setting will be used to maintain backward compatibility. See + the distro example wview.conf file for a description of the bitmask. + +6) Fixed big endian bug (Mac) in new VP loop processing code. + + + + +3.1.0 01-28-2006 +-------------------- + +radlib version required: 2.6.1 or newer + +1) Added station support for the Vaisala WXT-510 in stations/WXT510. Added + content to the User Manual for enabling the WXT510 during configuration + and setting it up. Added the wxt510config utility to set up the serial + parameters for the WXT-510. + +2) Fixed a bug with the moon phase calculator. + +3) Fixed a potential race condition at startup which could allow html + generation before the first LOOP cycle had been completed by wviewd. + +4) Added station support for the Simulator station in stations/Simulator. + Added content to the User Manual for enabling the Simulated Station during + configuration and setting it up. Now wview can be demonstrated/tested + without connectivity to a physical station. + +5) Added SIGHUP support to wviewd, htmlgend, wviewftpd, wviewsshd and wvalarmd + so this signal can be sent to cause the log verbosity setting to be reread. + The configuration value VERBOSE_MSGS in wview.conf should be changed prior + to sending the HUP signal(s). + For htmlgend, the image and html config files are also reloaded - this + allows the user to change images.conf, images-user.conf, images-metric.conf, + html-templates.conf and (if supported) forecast.conf without having to + restart wview. + It can be invoked individually for each process with these commands: + kill -s HUP `cat /var/wview/wviewd.pid` + kill -s HUP `cat /var/wview/htmlgend.pid` + kill -s HUP `cat /var/wview/wviewftpd.pid` + kill -s HUP `cat /var/wview/wviewsshd.pid` + kill -s HUP `cat /var/wview/wvalarmd.pid` + +6) Added FTP configuration ability to specify a relative path from the FTP + login directory to which the wview-generated files will be transferred. + Added commented out example in distro wviewftp.conf-no-ftp file. If the new + config parameter is not found, all FTP transfers will be placed in the FTP + login directory (the old behavior). + +7) Modified the elevation HTML tag to be populated with meters instead of feet + if a metric station. + +8) Added contributed code to allow selection of cm or mm for rain display if + the station is metric. Added new htmlgen.conf parameter "METRIC_USE_RAIN_MM" + to control this option. Added support in wviewconfig. + +9) Fixed CWOP/Wunderground rain reporting bug introduced by the station + abstraction changes in 3.0.0. + +10) Added wvutilsCalculateSLP to calculate sea level pressure (barometric + pressure) from station pressure. + +11) Added new wview.conf config parameters "STATION_RAIN_STORM_TRIGGER_START" + and "STATION_RAIN_STORM_IDLE_STOP" and support for them in the wviewconfig + script. Changed the rainStorm utility to use the user-configurable values. + +12) Changed CWOP submission so it uses Station Pressure instead of sea level or + Barometric Pressure. This is what CWOP really wants. For the case of the + Vantage Pro, the same algorithm used to create Barometric Pressure from + Station Pressure on stations like the WXT-510 is used with negative + elevation to generate the station pressure value to send to CWOP. This + corrects a long-standing problem with Vantage Pro CWOP participants + reporting sea level pressure instead of station pressure. Added an HTML tag + for Station Pressure (!--stationPressure--). This does not modify what is + displayed for the !--barom-- HTML tag (sea level pressure). + + + + +3.0.0 01-15-2006 +-------------------- + +radlib version required: 2.6.1 or newer + +1) Added support for new HTML tags --stationDateMetric--, --dailyRainMM-- and + --baromTrend--. See parameterlist.txt for the exact tag expressions to use + in your templates. + +2) Added AWEKAS support. Added html template files awekas_wl.htx-us and + awekas_wl.htx-metric to the examples/html directory in the distro and added + a new User Manual section for setting up AWEKAS with wview. + +3) Totally reorganized and rewrote the station interface code to abstract the + station interface. This is done in preparation for Vaisala WXT510 support in + wview. The daemon directory is gone, replaced with a stations directory + which contains a common directory for all station-generic code and a + VantagePro directory for the Vantage Pro-specific interface code. Care was + taken to minimize the code required for station-specific interfaces to ease + the implementation of new stations in the future. + +4) Added a new station interface API in stations.h. Clearly defines the + functions which must be implemented for specific station implementations. + +5) Added a new storm rain API (stormRain.[ch]). wview now computes rain storm + events based on a 0.05"/hour rain rate start trigger and an idle (no rain) + condition of 12 hours to determine when the storm ends. Vantage Pro storm + values are no longer used so this is available for all station types. Added + a new HTML tag --stormStart-- which retrieves the storm start date/time. + +6) Added a new sensor API (sensor.[ch]). This provides a robust API for storing + and retrieving sensor highs, lows and averages for any time frame (hour, + day, week, month, year). + +7) Significantly enhanced the computed data API to use the sensor API for HILOW + data storage. HILOW computation and storage are now done generically by + wview - the Vantage Pro HILOW packet is no longer used. Sensor HILOW values + are exported to a binary file in the archive directory so they can be + restored when wview is restarted. + +8) Added a new sun/civil/astronomical rise and set API. This includes a method + for "midday" and "day length". New HTML tags were added to access these new + values. The Vantage Pro sunrise and sunset values are no longer used so that + these values are available for all station types. + +9) Renamed all HTML templates and references so that they do not contain the + words "Vantage Pro". + +10) Enhanced the wviewconfig script so that it queries for station type and + does station-aware configuration. + +11) Added new HTML tag --forecastIconFile-- which just inserts the name of the + icon file without any HTML img helper text. + +12) Added native wview support for Rain Season Start Month. Added new parameter + to wview.conf and support in the wviewconfig script so it will create the + entry in wview.conf if it is not there. This only effects Yearly rain, ET + and rain rate calculation. Added a new HTML tag --rainSeasonStart-- so the + start month can be used in templates. Added it to all example home page + templates and the alamanac template. + +13) Added sanity check logic and requirement to the station initialization so + that the station/user config setting for archive interval matches the + interval found in the archive files. Startup will not continue if they do + not match. This prevents accidental corruption of the archive files. + +14) Added new HTML tags --intervalAvgWindSpeed-- and --intervalAvgWindChill-- + along with the code to support them. These provide the most recent archive + interval average values for wind speed and wind chill. + +15) Reorganized the User Manual and added content to the Building and + Troubleshooting sections. Added new content to the UPGRADE file. + +16) Enhanced almanac, Current, Daily, Monthly and Yearly HTML templates to + include java buttons at the top of the page for site navigation. Created + alamanac_Plus.htx so it may be migrated in the same way the other files are + for extended sensor support. Updated the example html-templates.conf file. + Removed the davisticker applet. + + + + +2.0.0 12-28-2005 +-------------------- + +radlib version required: 2.6.1 or newer + +1) Added support for HTML tags soilMoist1, soilMoist2, leafWet1 and leafWet2. + See parameterlist.txt for the exact tag expressions to use in your templates. + +2) Added support for computation of the daily high wind direction and a + corresponding new HTML tag dayhighwinddir. Also added displaying a high wind + direction "tick" on the wind dial. + +3) Improved the resolution of the HTML tag windDirection from 8 possible values + to 16. + +4) Added new SQL background process which synchronizes the SQL table (if + SQLDB_ENABLE is enabled in wview.conf) to the entire WLK binary database + even if the records were stored before SQL was enabled. By running the SQL + interface in the background as a separate process, it eliminates possible + problems with wviewd VP console timing that could have been introduced by + inserting the SQL records inline from the wviewd process. Uses existing + SQL configuration values in wview.conf. You must install the appropriate new + wview start script for your platform so the new wviewsqld process is started + when SQL archiving is enabled. + +5) Added configurability of the Moon Phase text. New parameters were added to + htmlgen.conf and to the wviewconfig script to support using different words + or languages for "Waxing", "Waning" and "Full". Should be configured with + the wviewconfig script. + +6) Optimized dial graphics generation by creating a gd dial palette file during + initialization and using that run-time to avoid re-drawing the dial circle + for every image. 25% or better improvement in overall generation times. + +7) Addressed a rare scenario which could cause wviewd to not send an archive + notification to wviewd, wvcwopd and wvhttpd when a new archive record is + stored. The VP console was indicating an empty archive page. This would + cause chart data to "fall behind" by an archive interval and CWOP/ + Wunderground packets to not be sent every time the scenario occurred. + Archive record storage was not affected. + +8) Enhanced the "Precip" dials so that gross and net precipitation are depicted. + Gross is measured rainfall and net is rainfall minus ET. This should clear + up any confusion concerning "rain vs. precip". + + + + +1.9.1 12-18-2005 +-------------------- + +radlib version required: 2.6.0 or newer + +1) Added support for hosting wview on the Linksys NSLU2. Created an ipkg + repository on the wviewweather.com site so wview and prerequisites (including + an assortment of USB drivers) can be easily installed from the NSLU2 ipkg + utility. See the new User Manual section "Using a Linksys NSLU2 as the wview + Host" for full details. + +2) Fixed a memory leak in dbdatabase.c for SQL record insertions which caused + the memory used by wviewd to gradually grow in size. This only affects those + who have "SQLDB_ENABLE" enabled in wview.conf. + +3) Changed archive receive processing so if the VP console fails to send the + next archive page wviewd will cancel the download and proceed with the + HILOW request. + + + + +1.9.0 12-09-2005 +-------------------- + +radlib version required: 2.6.0 or newer + +1) Changed all wview interprocess messaging to use the new radlib message + router utility. Cleaned up message type definition and broke out request + types to a separate enumeration. You MUST upgrade your radlib installation + to 2.6.0 or higher before building/installing this version of wview. You + MUST also copy the appropriate wview run script from the 1.9.0 distribution + for your platform (examples/SuSE, examples/FedoraCore, examples/FreeBSD, + examples/MacOSX/wview, etc.). The new wview run script starts the radlib + message router first which is now required by the wview processes. See the + User Manual section "4.6 Configure to run at system boot" for details. + +2) Fixed a bug in computed data processing when wview is run with a new station + that only has archive records for the current day. + + + +1.8.6 12-03-2005 +-------------------- + +radlib version required: 2.5.0 or newer + +1) Added a new configuration parameter "DO_RXCHECK" to wview.conf which + enables/disables the generation of receive check stats for the rxcheck.png + chart (which indicates wireless receive quality). If the parameter is not + found at startup, wviewd will assume it is enabled (the old behavior). + Added support for this parameter to the wviewconfig script and added it to + the example wview.conf file in the distro. + +2) Changed CWOP temperature reporting so it rounds up (or down for negative + temps) instead of truncating fractional values. Also changed so it will + report negative temperatures. + +3) Modified the wakeup console routines to be more robust in certain odd USB + scenarios. + + + + +1.8.5 11-27-2005 +-------------------- + +radlib version required: 2.5.0 or newer + +1) Fixed a bug in the computation of temperature and dewpoint change values + for hour, day and week when the wview system is metric. It was computing + the absolute celsius value for 1 degree F change instead of the celsius + equivalent for one degree F (5/9 of a degree C). + +2) Added composite inside temperature and humidity chart support for the last + day and month. You will need to update your /etc/wview/images.conf (or + images-metric.conf) file with the new (commented out) entries found in the + example config file in the distribution (examples/conf/images.conf). New + chart images (intempdaycomp.png and intempmonthcomp.png) were not added to + any example HTML templates - you will need to do that manually if desired. + Examples can be seen at: http://www.weather.teel.ws/intempdaycomp.png and + http://www.weather.teel.ws/intempmonthcomp.png. + +3) Added XML template and generation support. wview now recognizes XML + templates (*.xtx) and generates files with extension "xml" from them. Added + a default RSS feed for current conditions to all home page templates. Added + a new XML template file "wxrss.xtx" to the examples/html directory of the + distro and added an entry for it to examples/conf/html-templates.conf. The + template file can be copied to your /etc/wview/html directory and edited for + your site and preferences. Be sure to add an entry like the one now in + examples/conf/html-templates.conf for "wxrss.xtx" in your + /etc/wview/html-templates.conf file so the XML file will be generated. + + + + +1.8.4 11-13-2005 +-------------------- + +radlib version required: 2.5.0 or newer + +1) Changed all build targets to used shared libraries instead of static if + they are available. This fixes build problems on Mac OSX. + +2) Added new wviewd configuration value "SQLDB_DB_NAME" to allow configuration + of the database name used for MySQL/PostGreSQL archive record storage. + Added support for this new parameter in the wviewconfig script and to the + example wview.conf file in ./examples. If the new parameter is not found in + wview.conf, it will default to the old hard-coded value of "wviewDB". + +3) Fixed a bug in images.c for month rain and ET charts that could occur at + midnight. + + + + +1.8.3 11-05-2005 +-------------------- + +radlib version required: 2.5.0 or newer + +1) Changed all references to LIST and LIST_ID to RADLIST and RADLIST_ID + respectively to avoid namespace conflicts with newer versions of MySQL, + which only recently started using the same name. You must upgrade to + radlib 2.5.0 or higher before installing this version. + +2) Improved DST change processing by adding a timer to allow the kernel GMT + offset to be reset properly before re-initializing the history data and + resetting the VP console time and GMT offset. Fixed a minor bug that caused + history data to be added in cases where the VP console reports new archive + records but they are actually not new. + +3) Modified some build settings to work properly with the new radlib (libtool) + build method. Modified all start scripts to set the LD_LIBRARY_PATH + environment variable. + + + + +1.8.2 10-22-2005 +-------------------- + +radlib version required: 2.3.0 + +1) Changed archive record storage so that the bucket type indicator for 0.01 + inches is stored in the rain field (0x1000). This is moot for wview, but + is important for weatherlink. + +2) Changed HTML file generation so it will continue generating files even if + a missing template file is encountered. It will now just log a warning + message. + +3) Added GMT offset configuration of the VP console, done each time the + station time is synchronized. The VP console GMT offset is set to the same + value as the wview host system and DST is turned off on the VP console as + the GMT offset already reflects the state of Daylight Savings Time. This + corrects many problems folks have had getting their Sunrise and Sunset + values to be reported correctly by the VP console (assuming the wview host + is configured properly for time zone). + + + + +1.8.1 10-01-2005 +-------------------- + +radlib version required: 2.2.5 + +1) Fixed potential divide-by-zero bug in vpifGetRXCheck function. + +2) Added better data integrity checking to the computed data utilities. + +3) Added Debian start script. + +4) Added generation of a simple current conditions text file to be used for + mesonet submission. The file generated is mesonet.txt. + + + + +1.8.0 09-24-2005 +-------------------- + +radlib version required: 2.2.5 + +1) Increased buffer sizes for FTP username and password. + +2) Changed FTP to use malloc heap instead of radlib buffers for static rules + and work area. + +3) Cleaned up format and comments for the example html-templates.conf config + file. + + + + +1.7.8 09-13-2005 +-------------------- + +radlib version required: 2.2.5 + +1) Changed database timestamp format to something pgresql 8+ likes. + + + + +1.7.7 09-02-2005 +-------------------- + +radlib version required: 2.2.4 + +1) Fixed wview start scripts so they don't fail for those who don't build the + Weather Underground daemon. + +2) More tweaking of the NOAA high and low determination. + +3) Fixed a compiler warning introduced with the rxcheck image addition. + + + + +1.7.6 09-01-2005 +-------------------- + +radlib version required: 2.2.4 + +1) Fixed bug introduced with NOAA changes for 1.7.5. + + + + +1.7.5 08-31-2005 +-------------------- + +radlib version required: 2.2.4 + +1) Changed NOAA file generation to use low and high outside temps from archive + records instead of the outsideTemp value for lows and highs. + +2) Added newer example message log output for the User Manual Troubleshooting + section. + +3) Cleaned up "departure from normal" values being displayed as "-0.0" when + there are no previous year's months to use. + + + + +1.7.4 08-22-2005 +-------------------- + +radlib version required: 2.2.4 + +1) Changed yearly number of records counter for yearly average computation + from a USHORT to a UINT (it was rolling over today 8/22). Changed month + and day counter sizes just to be on the safe side. + + + + +1.7.3 08-21-2005 +-------------------- + +radlib version required: 2.2.4 + +1) Fixed air density calculation for older compilers. + + + + +1.7.2 08-20-2005 +-------------------- + +radlib version required: 2.2.4 + +1) Added new HTML tags for day/month/year high THSW/Radiation/UV plus time of + day for the daily highs. See examples/html/parameterlist.txt for details. + +2) Updated build environment for MacOSX PostgreSQL builds. Updated the + wview-Quick-Start-MacOSX.html file to include a hint for system shared + memory configuration. + +3) Added new HTML tag for moon phase and corresponding new + source file to calculate it: lunarCycle.c. Embedded users may want to avoid + this tag as it uses significant floating-point and trigonometric + calculations. + +4) Added new HTML tags and and and respectively. + Added a new configuration file, forecast.conf, which allows the user to + define the icon files and text associated with the forecast rules. See the + example forecast.conf file for details. The icon tag will be replaced by + an image html construct pointing at the appropriate icon image file. + +2) Changed Heat Index temperature threshold so it is calculated for + temperatures >= 75 degrees F - the old threshold was 80 degrees F. I can + find no definitive constraints for the input values of the heat index + formula. 75 F seems to be a reasonable starting point. + +3) Added support for different homepage templates based on day or night. + If the files "index-day.htx" and "index-night.htx" are listed in + /etc/wview/html-templates.conf INSTEAD of "index.htx", then wview will + generate the file "index.html" based on the values of sunrise, sunset and + the current wview server time. See the example html-templates.conf file + in the distro for more details. Also changed index.htx and the two new + index-*.htx files just described so that their extensions will be "html" + instead of "htm" when generated, as it has always been. This only applies + to the homepage file generated. If you have a soft link in your webserver + document root named "index.html" that points to "index.htm" (as I did), + you will want to delete that so that htmlgend can generate the "index.html" + file directly now. + +4) Added ethernet TCP socket support for VP consoles connected to terminal + servers or ethernet-to-serial convertors. Added the parameter + "STATION_INTERFACE" to wview.conf so that one of serial or ethernet can + be selected. Added "STATION_HOST" and "STATION_PORT" to wview.conf as well + so the hostname/IP and port number of the terminal server can be specified. + Confirmed proper operation on an Atop GW21E converter and a Lantronix + MSS1-T. Setup of terminal server for proper operation will vary by type + and capabilities. Proper serial configuration, transparent data flow (no + control character processing), and no packet delimiter are required at a + minimum for the terminal server setup. See the User Manual section + "Prerequisites" for general requirements. + +5) Confirmed USB operation for 2.4 linux kernels using the flagrantly non-GPL + GPL driver from Silicon Labs for the CP2101 device used by Davis for the + USB data logger. The driver presents the VP as "/dev/ttyUSB0", thus the + serial option should be selected for "STATION_INTERFACE". + +6) Added "wviewconfig" script to distro. This is an interactive script which + will ask the user questions and write the wview.conf and htmlgen.conf + configuration files based on the answers. It can be run for fresh installs + or for upgrades. See the README file for details. + +7) Moved all README content to the new wview-User-Manual.html file. + +8) Integrated a bug fix by Steve Pacenka concerning the new temperature dial + when the station is metric. + +9) Fixed a minor annoyance with rain and ET charts that caused the y-axis + maximum to be one more step than it should have been when rendered. + +10) Improved vpinstall/vpconfig reliability for use with serial, ethernet or + USB interfaces. Also added year-to-date rain and ET configuration to + vpconfig and the vpinstall script. + +11) Added new html tags for station elevation, latitude and longitude. Added + the tags to the home page templates as part of the heading. The new tags + are: , and + respectively. + + + + + +1.3.0 06-15-2005 +-------------------- + +radlib-gpl version required: 2.2.0 + +1) "YourCity,YourState" has been replaced in the html template files by + "," - html tags which wview will + replace with the values you specify for "STATION_CITY" and "STATION_STATE" + in /etc/wview/htmlgen.conf. So you no longer need to edit every template + for your location. + +2) Added PostgreSQL archive support (via radlib-gpl-2.2.0). Note that the + configure script option to enable MySQL or now PostgreSQL support has been + changed from "--enable-database" to either "--enable-mysql" or + "--enable-pgresql" for obvious reasons. There is a full section in the + README file that addresses either database setup and building wview for + relational database archiving. + + + + + +1.2.4 06-12-2005 +-------------------- + +radlib-gpl version required: 2.1.0 + +1) Fixed several exit scenarios when things don't start properly. + +2) Relaxed the timing constraints on the initialization commands to the VP. + +3) Added new graphics - temperature, wind, and humidity "dials". See the + new example images.conf for the entry specifics. Each dial adds some + time to generation - embedded users will want to keep this in mind and + can of course just not generate them if there is a time issue. + +4) Modified the example HTML templates - removed some odd characters and + added the new dial images to the home page templates (index-*). + +5) Added a troubleshooting section to the README file. + + + + + +1.2.3 06-09-2005 +-------------------- + +radlib-gpl version required: 2.1.0 + +1) Fixed wvalarmd child signal omission when changing signal handling for + 1.2.2. + + + + + +1.2.2 06-09-2005 +-------------------- + +radlib-gpl version required: 2.1.0 + +1) Changed wind meter image size to ~75% of the previous size. Changed colors + and moved direction indicators inside the dial. Made the image background + transparent. Generation time improvement of ~ 8%. + +2) Decoupled serial specific logic from the wviewd state machine and + initialization. Added "WVIEW_MEDIUM" abstraction with well-defined + demarcation points for implementation of non-serial VP interfaces such as + USB or ethernet. See serial.c and vpinterface.[ch] for details. + +3) Added result checking for png image file generation to detect file save + failure (normally a bad IMAGE_PATH setting in htmlgen.conf). Now logs + warnings concerning the failure. + +4) Changed all process signal handling to do a more "friendly" job of cleaning + up when unrecoverable exceptions occur. + +5) Added SIGPIPE handling to wvalarmd so far-end disconnects can be handled + gracefully. + + + + + +1.2.1 06-06-2005 +-------------------- + +radlib-gpl version required: 2.1.0 + +1) Changed default home page template files index.htx and index_Plus.htx. + The "classic" home page templates have been renamed to index-classic.htx + and index-classic_Plus.htx. If one prefers the classic look, these files + can be renamed appropriately when placed in /etc/wview/html. There are + new index-*.htx templates for several different backgrounds and layouts. + The new templates are in examples/html and the new background images are + in bin/img. See the "Configuration" section of the README file for a + discussion of the templates and a way to rotate your index.htx template. + The default index.htx and index_Plus.htx templates are equivalent to the + index-clouds.htx style. + +2) Added a TCP/stream socket server interface to wvalarmd to support raw + data feed applications. An example data feed client is included which can + be built and run to illustrate how the socket interface works. The README + file is chock full of details for this new capability in the section + "Alarms and wview As a Data Feed Engine". + +3) Fixed a bug with generation of newly added extended sensor data historical + charts in glchart.c and glmultichart.c. + + + + +1.2.0 05-30-2005 +-------------------- + +1) Added html tags for all extended sensor values supported by the VP + console for current conditions. These are: + + Solar Radiation + Extra Temp 1 + Extra Temp 2 + Extra Temp 3 + Soil Temp 1 + Soil Temp 2 + Soil Temp 3 + Soil Temp 4 + Leaf Temp 1 + Leaf Temp 2 + Extra Humidity 1 + Extra Humidity 2 + + See .../wview-x.y.z/bin/html/parameterlist.txt for the new HTML tags that + may be placed in your HTML template files. + +2) Changed the bucket and chart title background so that multichart titles + can be more easily seen. + +3) Fixed vpconfig build problem on Mac OSX. + +4) Added Current Condition Alarms and user-definable alarm actions (scripts). + Added a new process (wvalarmd) and a corresponding new config file + wvalarm.conf to support MIN and MAX alarm thresholds for current condition + values provided by the VP console. This facility may be used to control + irrigation systems, etc. based on weather data values. It can also be used + to configure wview as an archive and data feed engine only, for those + people who want to do something else with their VP instead of/in addition + to generation of a weather web site. The alarm types are: + + Barometer + InsideTemp + InsideHumidity + OutsideTemp + WindSpeed + TenMinuteAvgWindSpeed + WindDirection + OutsideHumidity + RainRate + StormRain + DayRain + MonthRain + YearRain + TxBatteryStatus + ConsoleBatteryVoltage + DewPoint + WindChill + HeatIndex + Radiation + UV + ET + ExtraTemp1 + ExtraTemp2 + ExtraTemp3 + SoilTemp1 + SoilTemp2 + SoilTemp3 + SoilTemp4 + LeafTemp1 + LeafTemp2 + ExtraHumid1 + ExtraHumid2 + + As always, the README file contains a full description of the alarms + implementation and configuration. + +5) Added a new rsync/ssh daemon (wviewsshd) for secure file posting capability + to one or more remote servers. Utilizing rsync's ability to only transfer + changes to files, this is significantly faster and more secure than using + ftp. You must be able to setup the ssh shared key on the remote server - + see the README file for full setup details. + +6) Finally, added log message verbosity control. The "VERBOSE_MSGS" parameter + of the wview.conf configure script now controls all process status log + production. Set it to "0", and no log output will be generated for normal + operations by any wview daemon. Error conditions, if they occur, will + continue to be logged. + + + + +1.1.0 05-14-2005 +-------------------- + +1) Added Vantage Pro Plus extended data support for historical charts + (day, month and year): + + Solar Radiation + UV + ET + LeafTemp1 + LeafTemp2 + LeafWetness1 + LeafWetness2 + SoilTemp1 + SoilTemp2 + SoilTemp3 + SoilTemp4 + ExtraHumidity1 + ExtraHumidity2 + ExtraTemp1 + ExtraTemp2 + ExtraTemp3 + SoilMoisture1 + SoilMoisture2 + SoilMoisture3 + SoilMoisture4 + + Extended data chart generation can be enabled/disabled in + /etc/wview/images.conf (or images-metric.conf if a metric station). HTML + templates have been added (named *_Plus.htx) which include all of the + extended data charts for the VP Plus. These can be edited and included in + html-templates.conf so they are generated. Commented out entries for each + of the new extended data images have been added to the example images.conf + and images-metric.conf files which can be enabled by removing all leading + '#' characters for a given image definition. + See the README file for full details on configuring extended data support. + +2) Simplified htmlgend generator timer initial start offset computation. This + should tighten up the html generation to always occur on the 10th second. + +3) Cleaned up and improved the HTML templates. Added java function to support + the 'Weather Summary' URL button on the history pages. Changed history page + href's to java buttons in index.htx. + +4) Added support for older VP 'A' archive records (firmware dated before + April 24, 2002). This is only an issue for VP Plus extended data - 'A' + and 'B' records are identical up to 'ET'. + +5) Fixed floating point wierdness causing Heat Index and Wind Chill to + sometimes have values 0.1 degrees less than the actual temperature for + cases when they should be equal. + + + + +1.0.1 05-01-2005 +-------------------- + +1) Fixed bug with SQL archive record insertion at midnight caused by how + WLK records want the packed time formatted at midnight (24:00 vs. 00:00). + +2) Fixed bug with bad archive record handling of archive interval increment + to determine the timestamp for the next archive record from the console. + Replaced a legacy hard-coded reference to 5 minute archive intervals with + the working value determined from the VP console interface. + + + + +1.0.0 04-25-2005 +-------------------- + +1) Removed CONS_ARCHIVE_INTERVAL configuration option. wview now dynamically + determines the archive interval the station is using during initialization + and uses that to prevent the VP station and wview from being out of sync. + This greatly simplifies installation setup as well. Although the Vantage + Pro supports archive intervals up to 2 hours, wview supports the + following (in minutes): 1, 5, 10, 15, 30, 60. + +2) Added an optional MySQL database interface for archive records. Requires + radlib-gpl-2.0.0 or greater which has been built with database support. + Configured via new entries in /etc/wview/wview.conf - can store in metric + units, can enable or disable extended values such as UV index and solar + radiation which are available with the Vantage Pro(2) Plus, and can + specify the username, password, and table name used for archive storage. + See the README file for full details. + +3) Added a new Vantage Pro console setup utility: "vpconfig". The primary + purpose of vpconfig is to be used by the new install script "vpinstall" + to setup new VP consoles prior to beginning use of wview. See the README + file for full details on new setups using "vpinstall". + +4) Disabled NOAA report generation when wview is configured for metric + values and units (if METRIC_UNITS[1]=1 in /etc/wview/htmlgen.conf). + +5) Fixed a minor bug that caused chart y scale computation to allow + fractional values to cause a point to be plotted below the y axis minimum. + + + + +0.9.3 04-09-2005 +-------------------- + +1) Added metric units and value conversions. This makes wview compatible for + non-US installations. A new configuration parameter in htmlgen.conf + named "METRIC_UNITS" has been added that if set to "1" will cause wview + to output all images (buckets and charts) as well as all values for HTML + templates in the metric equivalents. + The conversions are: + + Temperature ........... Celsius + Barometer ............. Millibars + Wind Speed ............ Kilometers per hour + Rain .................. Centimeters + Date Format ........... YYYYMMDD + + A new image config file named "images-metric.conf" has been added which + is used instead of "images.conf" if "METRIC_UNITS" is set. This file can + be edited to translate the English labels, titles and units to any + language. By editing this file and the HTML template files, any language + can be supported by wview. For existing installations, if the + "METRIC_UNITS" parameter is not found in htmlgen.conf, wview will assume + US units and no conversions will be done. Thus existing US users will not + be affected by this new feature. In fact one can easily switch back and + forth between US and metric units by toggling this configuration value. + All archive data in the WLK files is still stored in US format - the + conversions are only done for real time image and HTML file generation. + +2) Added a new feature to detect Daylight Savings Time changes. If a DST + change occurs (based on the tm struct component "tm_isdst"), the real + time data arrays will be automatically updated so that chart data does + not appear to be 1 hour behind (when DST begins) or one hour ahead + (when DST ends). If the host system is not configured to automatically + make DST time changes, this feature will not be activated and will have + no effect. + + + + +0.9.2 02-13-2005 +-------------------- + +1) Implemented a simple barometric pressure trend algorithm for the last 4 + hours of observed values. Barometer value display will now give an + indication of the BP trend via the characters "+", "-", "~" immediately + following the value, i.e. "30.027 + in", "29.921 - in", "30.511 ~ in" + indicating "rising", "falling", "steady" respectively. + Indicator may fluctuate in the first few hours after a restart until + some history has been built up. + +2) Added multi-plot charts. Now charts can display several (up to 4) data + graphs which improves comprehension in select cases such as Temperature + and Dewpoint. New images.conf entries can be found in + examples/conf/images.conf for Temp/Dew Point and Heat Index/Wind Chill + multi-plot charts. Current_Vantage_Pro.htx, Daily_Vantage_Pro.htx and + Monthly_Vantage_Pro.htx in the bin/html directory have been changed to use + these four new charts. + Upgraders will want to consider using these files as examples for how to + modify their existing configuration and template files (see the file + "UPGRADE" for more details). + +3) Added logic so that for daily and monthly temp-based charts, the min and + max chart values are normalized. + + + + +0.9.1 01-07-2005 +-------------------- + +1) Removed the history timer. Implemented an event-driven approach so that + the wviewd process sends a notification message to the htmlgend process + to indicate that a new archive record has been received. This greatly + simplifies the history data processing and addresses slow start up + scenarios on low power devices. + +2) Added verbage in the README file to discuss HTML generation configuration. + +3) Modified archive retrieval interval timer start mechanism so that it + uniformly lands 4 seconds into the next archive interval, as an even + divisor of 60 seconds. Thus archive retrieval intervals should be between + 15 and 60 seconds (inclusive) and divide 60 evenly. + +4) Fixed minor bug with chart boundary calculations. + + + + +0.9.0 01-04-2005 +-------------------- + +1) Modified chart generation to add an argument for number of x-axis "hashes" + so that chart structure and labeling can be more closely controlled. Also + added one data point to chart value arrays and processing to allow for + the "rightmost" label - thus simplifying other aspects of the old chart + generation algorithm. + +2) Fixed a bug when wview is started between an archive minute and the + subsequent history timer expiry. + +3) Fixed a bug in line chart rendering when a data point has an undefined + value. This was causing GLC_VALUE_NONE (-10000.0) to be graphed. + +4) Modified initial station time synchronization so that it is not + synchronized until the wviewd state machine has cycled through one + Archive/HiLow/Loop request cycle. Resetting the station time when it is + in or near the archive minute could have potentially caused record loss, + although that has yet to be reported. Now the archive record will be + retrieved prior to the time sync. + +5) Cleaned up legacy archive request handling in the wviewd state machine. + This code was now being executed because of the time synchronization move + (#4). Now the proper handler function is called in both the RUN state and + other states. + +6) Optimized chart creation memory allocation. + +7) Enhanced chart min and max boundary calculation so that they fall on + nice numbers. + +8) Improved some of the titles for charts in the example images.conf file. + + + + +0.8.6 12-31-2004 +-------------------- + +1) Fixed bug with computation of dayStart value for html generation causing + systems with a 30 minute archive interval to not be computed properly, + thus all 24-hour charts were 30 minutes "behind". + +2) Changed windchill calculation to require ">" 3.0 mph for wind speed and + "<" 50 degrees F. + + + + +0.8.5 12-29-2004 +-------------------- + +1) Further optimized image generation by creating finer pitch flags for + new archive records and what images they impact. Daily charts are updated + every archive interval, 28 day charts are updated every hour and 365 day + charts are updated every day at 12:01 AM. + +2) Changed html generator startup so that it does an initial generation at + initialization so that images will exist prior to the first generator + timer expiry (after the next archive record is received). + +3) Updated README file to better reflect recent changes and to describe the + historical chart generation process. + +4) Fixed run paths in example start scripts. + + + + +0.8.4 12-24-2004 +-------------------- + +1) Fixed minor bug in the display of negative values for temp, dewpoint, + windchill and heatindex. + +2) Optimized image generation so that historical chart images are only + updated immediately after a new archive record has been received. This + saves about 75% of image generation time when there is no new archive + record. + +3) Removed adjustAccuracy function from noaaGenerate.c. + + + + +0.8.3 12-15-2004 +-------------------- + +1) Changed location of run files to /var/wview (/var/run/* can be purged by + the system). + +2) Changed log output to use CONS_ARCHIVE_INTERVAL for sample message. + +3) Fixed a minor bug with systems brought up for the first time with no + archive files at all. Also optimized data averaging. + +4) Added compiler optimization value "O2" to default builds. + + + + +0.8.2 12-14-2004 +-------------------- + +1) Added Ken McGuire's configurable archive interval changes into the + main source branch. Now the archive interval of the weather console + can be configured at build time by specifying "CONS_ARCHIVE_INTERVAL=X" + to the configure script (X = 5 or X = 30). See the README file for more + details. + +2) Changed (per Ken) the use of "#pragma(pack,1)" to + "__attribute__ ((packed))" on several data structures for more portable + code. + +3) Abolished the /usr/local/wview directory - now configuration files + (*.conf, html templates) are located in /etc/wview and run files + (*.pid, archive, FIFO devices, images, NOAA) are located in + /var/wview. This allows the dynamic files under /var to be mounted + on a ramdisk for embedded applications. + +4) Changed the install-env build target to use the new locations. See the + README file for more details. + +5) Added the directory "cross-compile" which includes example configure + scripts for configuring and building all the libraries necessary for + wview and a script for wview itself. See the README file for more + cross compiling details. + +6) Added SuSE, FreeBSD, RedHat, and FedoraCore directories under "examples" + which contain distro/OS specific run control scripts. + + + + +0.8.1 11-30-2004 +-------------------- + +1) Converted wview to the GNU autoconf/automake paradigm. + + Now the package is built as follows: + #./configure + #make + #make install + + The ftpd daemon executable has been renamed "wviewftpd". + + All three daemons (wviewd, htmlgend and wviewftpd) are installed + in /usr/local/bin by default, although the prefix (=/usr/local) + can be changed when "configure" is invoked. + + make.config, rules.make, and Makefiles have been removed from + the distro. The "configure" script generates the build files + based on the host platform. + +2) Fixed an annoying text alignment issue for month labels in + noaaGenerate.c. All labels are now 3 chars long. + + + + +0.8.0 11-21-2004 +-------------------- + +1) Added the file "make.config" and changed the build environment to + utilize it. Linux or BSD can now be specified as well as the + install location of the wview daemons. + +2) Added the "pkg" make target and the file "excludefiles" so distro + tarballs can be easily built. + +3) Fixed several trivial problems with building on a BSD platform. + +4) Fixed a bug with archive records from the console which contained + bogus data - such as when the console loses communication with the + wireless data collector. Now if a bogus record is received from the + Vantage Pro Console, data from the last good record is used. + + + + +0.7.5 05-02-2004 +-------------------- + +1) Added a new tag for system up time called + which will insert the length of time the wview system has + been running. This release requires radlib-gpl version 1.3.1. + + + + +0.7.4 02-22-2004 +-------------------- + +1) Found a minor problem with the wind direction calculation + which would skew the result slightly. The fix requires + the file $WVIEW_DIRECTORY/noaa.dat to be deleted so that + it can be correctly regenerated after wview has been + upgraded and built, but before it is restarted. This MUST + be done to gain the improvements of the bug fix. +2) Changed ftpd to use /usr/bin/ftp - a child process is forked + which calls execv for the ftp binary. This requires a format + change for the ftp.conf file (which now allows wildcards). + See the example in .../examples/conf/ftp.conf for the new + file format. It is possible for multiple instances of the + ftp binary to run concurrently if the transmission time for + a file set overlaps the next transmission interval. This was + not observed to be a problem when tested but care should be + observed in the rule definitions in ftp.conf. This behavior + is identical to cron jobs which overlap invocations. + + + + +0.7.3 02-18-2004 +-------------------- + +1) Fixed bug #32 - chart data for time frames before archive + data exists should not be "drawn". +2) Fixed label problem with rain chart for last 24 hours. +3) Enhanced sample processing so that the last bar for the + last 28 days rain chart and the last 365 days rain chart + which represent the last current day and current week + respectively, are update with each new sample. This allows + all of the rain charts to be accurate to the last sample. + + + + +0.7.2 02-15-2004 +-------------------- + +1) Changed rain charts to be bar charts. Rain is expressed as: + inches/hour for the last 24 hours, inches/day for the last + 28 days, and inches/week for the last 365 days. Chart labels + found in images.conf should be changed (the new images.conf + under .../examples/conf has these changes). +2) Modified wviewd loop packet processing to ignore values of + "0xFF" or "0xFFFF" in received loop packets from the Vantage + Pro. These apparently indicate "no reading detected" or + an RF error. + + + + +0.7.1 02-09-2004 +-------------------- + +1) Ported wview to radlib-gpl version 1.3.0. radlib-gpl now has + its own homepage, www.radlib.teel.ws, for reference documents + and other support. Download radlib-gpl-1.3.0 from the homepage + and build it per the instructions in the distribution README + for radlib-gpl before installing and building this version of + wview. The wview homepage will now specify the version of + radlib-gpl required for the current version of wview. + + + + +0.7.0 02-04-2004 +-------------------- + +1) Fixed bug #26 - Monthly "Low Humidity" was always zero. +2) Renamed all radlib references and build against new GPL version + of radlib. radlib must now be built separately (and before) + wview. radlib can now be located anywhere and the env variable + "RADLIB_SRC_BASE" is no longer required at all. Headers and + the library file are stored under "/usr/local/include" and + "/usr/local/lib" respectively. The new radlib version is + 1.2 and the distro tree is rooted at "radlib-gpl". The README + file has been updated (and simplified) accordingly. +3) Added GPL headers and the "COPYING" file which contains the + GPL version 2 license. wview is now officially GPL software. + + + + +0.6.3 02-01-2004 +-------------------- + +1) Improved wind direction averaging by using a consensus averaging + algorithm. This corrects the problem where 1 degree and 359 degrees + would normally average to 180 (south), when in fact we want them + to average 0 (north). This is deployed for all instances of wind + direction averaging: 24 hour, 28 day, and 365 day charts, and + NOAA reports. +2) Cleaned up some NOAA report formatting. + + + + +0.6.2 01-29-2004 +-------------------- + +1) Fixed a few annoyances in the generated NOAA reports such as + "DEP FROM NORM" values. +2) Improved normal values calculation to handle case of no historical + data. +3) Added html tag so that the version of wview + is available for html templates to use. This requires the + "globalWviewVersionStr" macro to be changed for each new release + in ../common/sysdefs.h. +4) Added a new html template: .../bin/html/index.htx. This is a + good starting point for a weather home page. It includes the + new tag so the wview version can be displayed. + It also includes some nifty javascript code for picking NOAA + reports from drop down lists (thanks SSM!). It references other + generated html files such as Current_Vantage_Pro.htm. + + + +0.6.1 01-28-2004 +-------------------- + +1) Added enhancement #22 - NOAA monthly and yearly historical reports + are now generated and placed where the other image and html files + are located. The files are named "NOAA-.text" and + "NOAA--.txt". This capability is implemented in + noaaGenerate.[ch] in the htmlgenerator directory. +2) Added a recovery state for serial read errors. + + + +0.6.0 01-21-2004 +-------------------- + +1) Added enhancement #19 - html generator images and htmls can now be + configured by the user. The source files "images-user.[ch]" can be + modified and saved outside the source tree during upgrades (then + copied back in the source tree) so that users can easily save their + customized image creation source and config files. This requires + new config files "images.conf" (for built-in generators), + "images-user.conf" (for user defined generators), and "html-templates.conf" + (to define which html templates to generate). See README for complete + details. + + + +0.5.4 01-17-2004 +-------------------- + +1) Actually fixed Bug #8! + + + +0.5.3 01-17-2004 +-------------------- + +1) Fixed bug in ftp process where FtpClose was being called when a file + failed to transfer, instead of continuing to the next file. +2) Modified ftplib.c to not malloc memory for each file, but rather use + a static buffer. +3) Tested more thoroughly against remote ftp server. +4) Changed ftplib to use system buffers and not malloc all over creation. +5) Fixed Bug #17 - partial write bug in ftplib so it continues to transfer + when the far end abates (stalls). +6) Fixed memory leak in ftplib. +7) Fixed Bug #8 - shift forward day chart samples by one sample. + + + +0.5.2 01-12-2004 +-------------------- + +1) Changed FTP process to always start in the same minute as history timer. + This guarantees that ftp.conf intervals coincide with % 5 = 1. +2) Changed ftp.conf file format to specify host, user, and pass once at + the top of the file. +3) Changed ftpUtils.c to only connect and login once per transmit interval + (minute). + + + +0.5.1 01-11-2004 +-------------------- + +1) Improved archive path request processing so that the wviewd daemon does + not respond until it has stored all archive records during initial start-up +2) Added ftp process to allow moving images to remote server (see README) +3) Refined generator data and history timers to more accurately sync to a + millisecond offset + + + +0.4.3 01-06-2004 +-------------------- + +1) Removed Davis SDK directory +2) added vantageSDK directory +3) created "install-env" target for first time installation + + + +0.4.2 01-06-2004 +-------------------- + +1) Fixed chart spikes +2) added time synchronization for the console clock +3) added top level makefile +4) fixed dbfGetAverages "wrap-around" file bug +5) fixed add history sample bugs + + + +0.4.1 12-31-2003 +-------------------- + +1) Fixed shutdown nuisance - wview script now starts and stops cleanly +2) added SSM chart label changes (moved startDay up 5 mins, startMonth + up 1 hour, and startYear up 1 day so the last label is correct) + + + +0.4.0 12-31-2003 +-------------------- + +1) Removed pnggen directory from project - link against system libraries, + build against /usr/local/include + + + +0.3.1 12-31-2003 +-------------------- + +1) Fixed midnight wakeup failure +2) added wait in html initialization for the daemon process + + + +0.2.1 12-15-2003 +-------------------- + +1) Cleaned up the pnggen build environment + + + +0.1.0 12-12-2003 +-------------------- + +1) Initial version diff --git a/INSTALL b/INSTALL new file mode 100644 index 0000000..007e939 --- /dev/null +++ b/INSTALL @@ -0,0 +1,370 @@ +Installation Instructions +************************* + +Copyright (C) 1994-1996, 1999-2002, 2004-2013 Free Software Foundation, +Inc. + + Copying and distribution of this file, with or without modification, +are permitted in any medium without royalty provided the copyright +notice and this notice are preserved. This file is offered as-is, +without warranty of any kind. + +Basic Installation +================== + + Briefly, the shell commands `./configure; make; make install' should +configure, build, and install this package. The following +more-detailed instructions are generic; see the `README' file for +instructions specific to this package. Some packages provide this +`INSTALL' file but do not implement all of the features documented +below. The lack of an optional feature in a given package is not +necessarily a bug. More recommendations for GNU packages can be found +in *note Makefile Conventions: (standards)Makefile Conventions. + + The `configure' shell script attempts to guess correct values for +various system-dependent variables used during compilation. It uses +those values to create a `Makefile' in each directory of the package. +It may also create one or more `.h' files containing system-dependent +definitions. Finally, it creates a shell script `config.status' that +you can run in the future to recreate the current configuration, and a +file `config.log' containing compiler output (useful mainly for +debugging `configure'). + + It can also use an optional file (typically called `config.cache' +and enabled with `--cache-file=config.cache' or simply `-C') that saves +the results of its tests to speed up reconfiguring. Caching is +disabled by default to prevent problems with accidental use of stale +cache files. + + If you need to do unusual things to compile the package, please try +to figure out how `configure' could check whether to do them, and mail +diffs or instructions to the address given in the `README' so they can +be considered for the next release. If you are using the cache, and at +some point `config.cache' contains results you don't want to keep, you +may remove or edit it. + + The file `configure.ac' (or `configure.in') is used to create +`configure' by a program called `autoconf'. You need `configure.ac' if +you want to change it or regenerate `configure' using a newer version +of `autoconf'. + + The simplest way to compile this package is: + + 1. `cd' to the directory containing the package's source code and type + `./configure' to configure the package for your system. + + Running `configure' might take a while. While running, it prints + some messages telling which features it is checking for. + + 2. Type `make' to compile the package. + + 3. Optionally, type `make check' to run any self-tests that come with + the package, generally using the just-built uninstalled binaries. + + 4. Type `make install' to install the programs and any data files and + documentation. When installing into a prefix owned by root, it is + recommended that the package be configured and built as a regular + user, and only the `make install' phase executed with root + privileges. + + 5. Optionally, type `make installcheck' to repeat any self-tests, but + this time using the binaries in their final installed location. + This target does not install anything. Running this target as a + regular user, particularly if the prior `make install' required + root privileges, verifies that the installation completed + correctly. + + 6. You can remove the program binaries and object files from the + source code directory by typing `make clean'. To also remove the + files that `configure' created (so you can compile the package for + a different kind of computer), type `make distclean'. There is + also a `make maintainer-clean' target, but that is intended mainly + for the package's developers. If you use it, you may have to get + all sorts of other programs in order to regenerate files that came + with the distribution. + + 7. Often, you can also type `make uninstall' to remove the installed + files again. In practice, not all packages have tested that + uninstallation works correctly, even though it is required by the + GNU Coding Standards. + + 8. Some packages, particularly those that use Automake, provide `make + distcheck', which can by used by developers to test that all other + targets like `make install' and `make uninstall' work correctly. + This target is generally not run by end users. + +Compilers and Options +===================== + + Some systems require unusual options for compilation or linking that +the `configure' script does not know about. Run `./configure --help' +for details on some of the pertinent environment variables. + + You can give `configure' initial values for configuration parameters +by setting variables in the command line or in the environment. Here +is an example: + + ./configure CC=c99 CFLAGS=-g LIBS=-lposix + + *Note Defining Variables::, for more details. + +Compiling For Multiple Architectures +==================================== + + You can compile the package for more than one kind of computer at the +same time, by placing the object files for each architecture in their +own directory. To do this, you can use GNU `make'. `cd' to the +directory where you want the object files and executables to go and run +the `configure' script. `configure' automatically checks for the +source code in the directory that `configure' is in and in `..'. This +is known as a "VPATH" build. + + With a non-GNU `make', it is safer to compile the package for one +architecture at a time in the source code directory. After you have +installed the package for one architecture, use `make distclean' before +reconfiguring for another architecture. + + On MacOS X 10.5 and later systems, you can create libraries and +executables that work on multiple system types--known as "fat" or +"universal" binaries--by specifying multiple `-arch' options to the +compiler but only a single `-arch' option to the preprocessor. Like +this: + + ./configure CC="gcc -arch i386 -arch x86_64 -arch ppc -arch ppc64" \ + CXX="g++ -arch i386 -arch x86_64 -arch ppc -arch ppc64" \ + CPP="gcc -E" CXXCPP="g++ -E" + + This is not guaranteed to produce working output in all cases, you +may have to build one architecture at a time and combine the results +using the `lipo' tool if you have problems. + +Installation Names +================== + + By default, `make install' installs the package's commands under +`/usr/local/bin', include files under `/usr/local/include', etc. You +can specify an installation prefix other than `/usr/local' by giving +`configure' the option `--prefix=PREFIX', where PREFIX must be an +absolute file name. + + You can specify separate installation prefixes for +architecture-specific files and architecture-independent files. If you +pass the option `--exec-prefix=PREFIX' to `configure', the package uses +PREFIX as the prefix for installing programs and libraries. +Documentation and other data files still use the regular prefix. + + In addition, if you use an unusual directory layout you can give +options like `--bindir=DIR' to specify different values for particular +kinds of files. Run `configure --help' for a list of the directories +you can set and what kinds of files go in them. In general, the +default for these options is expressed in terms of `${prefix}', so that +specifying just `--prefix' will affect all of the other directory +specifications that were not explicitly provided. + + The most portable way to affect installation locations is to pass the +correct locations to `configure'; however, many packages provide one or +both of the following shortcuts of passing variable assignments to the +`make install' command line to change installation locations without +having to reconfigure or recompile. + + The first method involves providing an override variable for each +affected directory. For example, `make install +prefix=/alternate/directory' will choose an alternate location for all +directory configuration variables that were expressed in terms of +`${prefix}'. Any directories that were specified during `configure', +but not in terms of `${prefix}', must each be overridden at install +time for the entire installation to be relocated. The approach of +makefile variable overrides for each directory variable is required by +the GNU Coding Standards, and ideally causes no recompilation. +However, some platforms have known limitations with the semantics of +shared libraries that end up requiring recompilation when using this +method, particularly noticeable in packages that use GNU Libtool. + + The second method involves providing the `DESTDIR' variable. For +example, `make install DESTDIR=/alternate/directory' will prepend +`/alternate/directory' before all installation names. The approach of +`DESTDIR' overrides is not required by the GNU Coding Standards, and +does not work on platforms that have drive letters. On the other hand, +it does better at avoiding recompilation issues, and works well even +when some directory options were not specified in terms of `${prefix}' +at `configure' time. + +Optional Features +================= + + If the package supports it, you can cause programs to be installed +with an extra prefix or suffix on their names by giving `configure' the +option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'. + + Some packages pay attention to `--enable-FEATURE' options to +`configure', where FEATURE indicates an optional part of the package. +They may also pay attention to `--with-PACKAGE' options, where PACKAGE +is something like `gnu-as' or `x' (for the X Window System). The +`README' should mention any `--enable-' and `--with-' options that the +package recognizes. + + For packages that use the X Window System, `configure' can usually +find the X include and library files automatically, but if it doesn't, +you can use the `configure' options `--x-includes=DIR' and +`--x-libraries=DIR' to specify their locations. + + Some packages offer the ability to configure how verbose the +execution of `make' will be. For these packages, running `./configure +--enable-silent-rules' sets the default to minimal output, which can be +overridden with `make V=1'; while running `./configure +--disable-silent-rules' sets the default to verbose, which can be +overridden with `make V=0'. + +Particular systems +================== + + On HP-UX, the default C compiler is not ANSI C compatible. If GNU +CC is not installed, it is recommended to use the following options in +order to use an ANSI C compiler: + + ./configure CC="cc -Ae -D_XOPEN_SOURCE=500" + +and if that doesn't work, install pre-built binaries of GCC for HP-UX. + + HP-UX `make' updates targets which have the same time stamps as +their prerequisites, which makes it generally unusable when shipped +generated files such as `configure' are involved. Use GNU `make' +instead. + + On OSF/1 a.k.a. Tru64, some versions of the default C compiler cannot +parse its `' header file. The option `-nodtk' can be used as +a workaround. If GNU CC is not installed, it is therefore recommended +to try + + ./configure CC="cc" + +and if that doesn't work, try + + ./configure CC="cc -nodtk" + + On Solaris, don't put `/usr/ucb' early in your `PATH'. This +directory contains several dysfunctional programs; working variants of +these programs are available in `/usr/bin'. So, if you need `/usr/ucb' +in your `PATH', put it _after_ `/usr/bin'. + + On Haiku, software installed for all users goes in `/boot/common', +not `/usr/local'. It is recommended to use the following options: + + ./configure --prefix=/boot/common + +Specifying the System Type +========================== + + There may be some features `configure' cannot figure out +automatically, but needs to determine by the type of machine the package +will run on. Usually, assuming the package is built to be run on the +_same_ architectures, `configure' can figure that out, but if it prints +a message saying it cannot guess the machine type, give it the +`--build=TYPE' option. TYPE can either be a short name for the system +type, such as `sun4', or a canonical name which has the form: + + CPU-COMPANY-SYSTEM + +where SYSTEM can have one of these forms: + + OS + KERNEL-OS + + See the file `config.sub' for the possible values of each field. If +`config.sub' isn't included in this package, then this package doesn't +need to know the machine type. + + If you are _building_ compiler tools for cross-compiling, you should +use the option `--target=TYPE' to select the type of system they will +produce code for. + + If you want to _use_ a cross compiler, that generates code for a +platform different from the build platform, you should specify the +"host" platform (i.e., that on which the generated programs will +eventually be run) with `--host=TYPE'. + +Sharing Defaults +================ + + If you want to set default values for `configure' scripts to share, +you can create a site shell script called `config.site' that gives +default values for variables like `CC', `cache_file', and `prefix'. +`configure' looks for `PREFIX/share/config.site' if it exists, then +`PREFIX/etc/config.site' if it exists. Or, you can set the +`CONFIG_SITE' environment variable to the location of the site script. +A warning: not all `configure' scripts look for a site script. + +Defining Variables +================== + + Variables not defined in a site shell script can be set in the +environment passed to `configure'. However, some packages may run +configure again during the build, and the customized values of these +variables may be lost. In order to avoid this problem, you should set +them in the `configure' command line, using `VAR=value'. For example: + + ./configure CC=/usr/local2/bin/gcc + +causes the specified `gcc' to be used as the C compiler (unless it is +overridden in the site shell script). + +Unfortunately, this technique does not work for `CONFIG_SHELL' due to +an Autoconf limitation. Until the limitation is lifted, you can use +this workaround: + + CONFIG_SHELL=/bin/bash ./configure CONFIG_SHELL=/bin/bash + +`configure' Invocation +====================== + + `configure' recognizes the following options to control how it +operates. + +`--help' +`-h' + Print a summary of all of the options to `configure', and exit. + +`--help=short' +`--help=recursive' + Print a summary of the options unique to this package's + `configure', and exit. The `short' variant lists options used + only in the top level, while the `recursive' variant lists options + also present in any nested packages. + +`--version' +`-V' + Print the version of Autoconf used to generate the `configure' + script, and exit. + +`--cache-file=FILE' + Enable the cache: use and save the results of the tests in FILE, + traditionally `config.cache'. FILE defaults to `/dev/null' to + disable caching. + +`--config-cache' +`-C' + Alias for `--cache-file=config.cache'. + +`--quiet' +`--silent' +`-q' + Do not print messages saying which checks are being made. To + suppress all normal output, redirect it to `/dev/null' (any error + messages will still be shown). + +`--srcdir=DIR' + Look for the package's source code in directory DIR. Usually + `configure' can determine that directory automatically. + +`--prefix=DIR' + Use DIR as the installation prefix. *note Installation Names:: + for more details, including other options available for fine-tuning + the installation locations. + +`--no-create' +`-n' + Run the configure checks, but stop before creating any output + files. + +`configure' also accepts some other, not widely useful, options. Run +`configure --help' for more details. diff --git a/LJ-Article.txt b/LJ-Article.txt new file mode 100755 index 0000000..4b88d6f --- /dev/null +++ b/LJ-Article.txt @@ -0,0 +1,90 @@ +Using wview +----------- + +Do you have a fascination with weather? Do you often find yourself checking local weather conditions? Is the weather your favorite part of the news broadcast? If so, you may be a weather geek and wview may be the application for you. + +wview is a weather application which retrieves sensor readings from a weather station. The sensor data is stored in sqlite3 databases. Aggregate data such as minimums, maximums and averages are computed and stored in the database backend. Optional uses of the stored data include: weather web site generation; generic file generation for external applications; data submission to third-party organizations including Citizens Weather Observer Program (CWOP) and Weather Underground; store-forward to remote data collection centers. A user-friendly html interface is provided for configuration of your weather station as well as optional features. + +To set up your weather station and publish your data with wview you will need a weather station. Supported stations include: Davis Vantage Pro/Pro2 or Vantage Vue, Texas Weather Instruments, Vaisala WXT510/520, Oregon Scientific WMR9X8 and La Crosse WS-23XX. Next you will need a platform to host the wview application. Desktop computers of any vintage work well but it is often desirable to host wview on a low-power, unattended system. The now discontinued Linksys NSLU2 has been a popular choice. The new SheevaPlug is quickly gaining popularity as a wview host. Industrious users have even used a Western Digital Worldbook NAS as their wview host. Because wview is modular and designed for embedded applications it can be hosted on low horsepower systems. Next you will need to install a linux distribution of your choice. The debian and derivatives wview packages provide the most idiot-proof installation path but source installs are also straightforward for any linux distribution. Finally you will need an interface cable. This may be a simple 9-pin serial cable or perhaps a USB-serial adapter if your host has no serial ports. + + +Configuration +------------- +To configure wview, we open our favorite browser and point it to the wview management web site, typically http://[your_wview_server]/wviewmgmt/login.php. An HTTP server is required on the wview host (and automatically installed for APT package installs). Use the default administration password "wview" (you can change this later). After successful login, the System Status page is displayed. The System Status Page displays the current state of all wview services as well as other status information. + +[Insert System Status page image here] + +Configuration is broken up into logical sections with context sensitive help available by mousing over the configuration items. Click the "Station" tab to configure the station parameters. + +[Insert Station page image here] + +The critical parameters here are the station type and the interface characteristics. Select "Save Changes" when you are done. Next, click the "Services" tab. + +[Insert Services page image here] + +This page provides the configuration of wview services, log verbosity for the services and email alerts. Services available are File Generation, Alarms, CWOP, HTTP (Weather Underground and Weatherforyou), File Export (SSH or FTP) and Process Monitoring. For now we will not enable any additional services until we have confirmed our station interface. + + +Station Confirmation +-------------------- +We now proceed to the station interface verification. Open a shell so we can follow updates to the system log. +At the prompt enter the following: +> sudo tail -f /var/log/syslog +This will display new system log messages as they are generated. Here we will monitor wview startup and status messages. +Open another shell and execute the following: +> sudo /etc/init.d/wview stop +> sudo /etc/init.d/wview start +You will see a flurry of activity in the system log from the wview processes as they start up. It is a good idea to familiarize yourself with these wview log messages as there is a wealth of detail included which can be very helpful. + +Some of the more important messages are: + + +Return to the System Status page and observe the status of the station interface and the file generation. If both are not status "green", "Running" then further investigation in the system log file will be required to find any configuration or station interface issues. + + +Default Website +--------------- +If all is well we can now view the default wview weather site. This is typically found at http://[your_server_url]/weather/index.html. + +[Insert sample weather site here] + +Current conditions are given in the table on the left by the dials in the center. These values are updated every time station data is polled (default is 30 seconds). The weather site pages are regenerated every 60 seconds (configurable). Observing changes in the current conditions is an easy way to confirm proper station interface operation. + +Historical data for the last 24 hours are presented as graphs. Graphs of the last 24 hours, the last 7 days, the last 28 days and the last 365 days are available on other site pages. + + +Citizens Weather Observer Program (CWOP) +---------------------------------------- +CWOP (Citizen Weather Observer Program, http://www.wxqa.com/) is a system by which individuals with weather stations and the proper software can submit their weather data to an APRS-based data storage system so that anyone, including NOAA, can use the data however they see fit. There are some really neat station display web sites including some java apps to look up station data, position, maps, etc. + +As an example, see http://www.findu.com/cgi-bin/wxpage.cgi?call=CW4097 for an example weather station. + +CWOP participation requires registering for an APRS "Call Sign". Once you have configured wview for CWOP properly and confirmed your data online, you must contact the maintainers via email to confirm your registration. Then your data will be available for anyone to see and possibly be used in NOAA forecast models, etc. + +When CWOP support is enabled and configured properly, wview will transmit a new WX packet to the APRS server every 10 minutes based on the last digit of your callsign. + +wview supports the APRS-IS Rollover functionality by enforcing the definition of 3 APRS-IS server:port pairs - the goal is to avoid data loss to the CWOP system caused by connection errors. Select 3 different servers from the list at http://www.wxqa.com/activecwd.html. + +Click the Services tab and enable CWOP submission and CWOP verbose logging. Click "Save Changes". Click the CWOP tab and enter your call sign, Latitude and Longitude (see the mouse-over help for format details) and the CWOP servers (three should be entered) and port numbers. Go ahead and enable "Log CWOP Packet?", you can disable it after submission is confirmed. Click "Save Changes". + +Now restart wview: +> sudo /etc/init.d/wview restart + +You can monitor CWOP packet submission in the system log (and on the CWOP status pages). + + +Weather Underground Submission +------------------------------ +The Weather Underground (www.wunderground.com/) (Wunderground) is a privately held organization which provides many weather services - some free and some not. Among the free services is the ability to register your weather station and submit your data to them so that you can access your data and some nice graphs from their site. Weatherforyou.com is also a privately held outfit with similar capabilities to wunderground. + +Register for a Weather Underground Station ID (unless you already have one): +http://www.wunderground.com/weatherstation/usersignup.asp. Determine your accurate latitude and longitude: http://www.topozone.com/viewmaps.asp. + +Click the "Services" tab and enable the HTTP service. Click "HTTP Services" and configure the Weather Underground settings. Click "Save Changes". + +Look in the system log for something similar to: +"WUNDERGROUND: configured to submit station KTXCOLLI1 data to wunderground.com" + +Confirm your data at the Wunderground server: http://www.wunderground.com/weatherstation/WXDailyHistory.asp?ID=XXXXXXX where XXXXXXX is your Wunderground Station ID. This should start displaying your weather data graphically and as a packet list. + + diff --git a/MAINTAIN b/MAINTAIN new file mode 100644 index 0000000..dd22f3f --- /dev/null +++ b/MAINTAIN @@ -0,0 +1,21 @@ +############################################################################## +MAINTAIN - radlib-gpl maintenance notes +############################################################################## + + +To regenerate the distribution from CVS: + + #./reconf + +To generate Makefile and config.h: + + #./configure + +make targets: + +make +make distcheck +make dist +make clean +make distclean +make install diff --git a/Makefile.am b/Makefile.am new file mode 100755 index 0000000..fda7e8b --- /dev/null +++ b/Makefile.am @@ -0,0 +1,215 @@ +# Makefile - top level + +# files to include in the distro but not build +EXTRA_DIST = reconf \ + $(top_srcdir)/stations/VantagePro/doc \ + $(top_srcdir)/bin \ + $(top_srcdir)/examples/alarms \ + $(top_srcdir)/examples/conf \ + $(top_srcdir)/examples/html \ + $(top_srcdir)/examples/scripts \ + $(top_srcdir)/examples/rsyslog \ + $(top_srcdir)/debian \ + $(top_srcdir)/scripts \ + $(top_srcdir)/cross-compile \ + $(top_srcdir)/alarms/sample-datafeed-client \ + $(top_srcdir)/ftp/wviewftp.debug.sh \ + $(top_srcdir)/UPGRADE \ + $(top_srcdir)/MAINTAIN \ + $(top_srcdir)/wview-User-Manual.html \ + $(top_srcdir)/wview-Quick-Start.html \ + $(top_srcdir)/wview-Debian-Quick-Start.html \ + $(top_srcdir)/wview-Quick-Start-MacOSX.html \ + $(top_srcdir)/wview-Quick-Start-FreeBSD.html + +SUBDIRS = stations htmlgenerator alarms cwop http ftp ssh procmon wviewconfig wviewmgmt utilities dbexport examples + +STATION_T = wviewd_sim + +WV_CONFIG_DIR = $(sysconfdir)/wview +WV_RUN_DIR = $(localstatedir)/wview +WV_HTML_DIR = $(localstatedir) +WV_BIN_DIR = $(exec_prefix)/bin +WV_SHARE_DIR = /usr/share/wview + + +# this target removes svn directories from the tarball +dist-hook: + rm -rf `find $(distdir) -name .svn` + + +# Keep this for backwards compatibility: +install-env: install + + +# Do some post-install tasks: +install-data-hook: +if INSTALL_DPKG + mkdir -p $(DESTDIR)$(WV_SHARE_DIR) + mkdir -p $(DESTDIR)$(WV_SHARE_DIR)/var + mkdir -p $(DESTDIR)$(WV_SHARE_DIR)/var/wview + mkdir -p $(DESTDIR)$(WV_SHARE_DIR)/var/wview/noaa + mkdir -p $(DESTDIR)$(WV_SHARE_DIR)/var/wview/alarms + mkdir -p $(DESTDIR)$(WV_SHARE_DIR)/var/wview/img + mkdir -p $(DESTDIR)$(WV_SHARE_DIR)/var/wview/img/Archive + mkdir -p $(DESTDIR)$(WV_SHARE_DIR)/var/wview/img/NOAA + mkdir -p $(DESTDIR)$(WV_SHARE_DIR)/var/wview/archive + cp -fp $(top_srcdir)/bin/img/*.* $(DESTDIR)$(WV_SHARE_DIR)/var/wview/img + cp -fp $(top_srcdir)/examples/html/chrome/static/*.* $(DESTDIR)$(WV_SHARE_DIR)/var/wview/img + cp -fp $(top_srcdir)/bin/archive/* $(DESTDIR)$(WV_SHARE_DIR)/var/wview/archive + mkdir -p $(DESTDIR)$(WV_SHARE_DIR)/etc + mkdir -p $(DESTDIR)$(WV_SHARE_DIR)/etc/wview + mkdir -p $(DESTDIR)$(WV_SHARE_DIR)/etc/wview/export + chmod 777 $(DESTDIR)$(WV_SHARE_DIR)/etc/wview/export + mkdir -p $(DESTDIR)$(WV_SHARE_DIR)/etc/wview/html + mkdir -p $(DESTDIR)$(WV_SHARE_DIR)/etc/wview/html/chrome + mkdir -p $(DESTDIR)$(WV_SHARE_DIR)/etc/wview/html/chrome/plus + mkdir -p $(DESTDIR)$(WV_SHARE_DIR)/etc/wview/html/chrome/static + mkdir -p $(DESTDIR)$(WV_SHARE_DIR)/etc/wview/html/chrome/standard + mkdir -p $(DESTDIR)$(WV_SHARE_DIR)/etc/wview/html/classic + mkdir -p $(DESTDIR)$(WV_SHARE_DIR)/etc/wview/html/classic/plus + mkdir -p $(DESTDIR)$(WV_SHARE_DIR)/etc/wview/html/classic/static + mkdir -p $(DESTDIR)$(WV_SHARE_DIR)/etc/wview/html/classic/standard + mkdir -p $(DESTDIR)$(WV_SHARE_DIR)/etc/wview/html/exfoliation + mkdir -p $(DESTDIR)$(WV_SHARE_DIR)/etc/wview/html/exfoliation/plus + mkdir -p $(DESTDIR)$(WV_SHARE_DIR)/etc/wview/html/exfoliation/static + mkdir -p $(DESTDIR)$(WV_SHARE_DIR)/etc/wview/html/exfoliation/standard + mkdir -p $(DESTDIR)$(WV_SHARE_DIR)/etc/wview/alarms + mkdir -p $(DESTDIR)$(WV_SHARE_DIR)/etc/rsyslog.d + mkdir -p $(DESTDIR)$(WV_SHARE_DIR)/etc/logrotate.d + cp -fp $(top_srcdir)/debian/99-wview.conf $(DESTDIR)$(WV_SHARE_DIR)/etc/rsyslog.d + cp -fp $(top_srcdir)/debian/wview-logrotate $(DESTDIR)$(WV_SHARE_DIR)/etc/logrotate.d + @echo "$(PACKAGE_VERSION)" > $(DESTDIR)$(WV_SHARE_DIR)/etc/wview/wview-version + cp -fp $(top_srcdir)/examples/html/*.* $(DESTDIR)$(WV_SHARE_DIR)/etc/wview/html + cp -fp $(top_srcdir)/examples/html/chrome/plus/*.* $(DESTDIR)$(WV_SHARE_DIR)/etc/wview/html/chrome/plus + cp -fp $(top_srcdir)/examples/html/chrome/static/*.* $(DESTDIR)$(WV_SHARE_DIR)/etc/wview/html/chrome/static + cp -fp $(top_srcdir)/examples/html/chrome/standard/*.* $(DESTDIR)$(WV_SHARE_DIR)/etc/wview/html/chrome/standard + cp -fp $(top_srcdir)/examples/html/classic/plus/*.* $(DESTDIR)$(WV_SHARE_DIR)/etc/wview/html/classic/plus + cp -fp $(top_srcdir)/examples/html/classic/static/*.* $(DESTDIR)$(WV_SHARE_DIR)/etc/wview/html/classic/static + cp -fp $(top_srcdir)/examples/html/classic/standard/*.* $(DESTDIR)$(WV_SHARE_DIR)/etc/wview/html/classic/standard + cp -fp $(top_srcdir)/examples/html/exfoliation/plus/*.* $(DESTDIR)$(WV_SHARE_DIR)/etc/wview/html/exfoliation/plus + cp -fp $(top_srcdir)/examples/html/exfoliation/static/*.* $(DESTDIR)$(WV_SHARE_DIR)/etc/wview/html/exfoliation/static + cp -fp $(top_srcdir)/examples/html/exfoliation/standard/*.* $(DESTDIR)$(WV_SHARE_DIR)/etc/wview/html/exfoliation/standard + cp -fp $(top_srcdir)/examples/alarms/*.* $(DESTDIR)$(WV_SHARE_DIR)/etc/wview/alarms + cp -fp $(top_srcdir)/examples/conf/*.* $(DESTDIR)$(WV_SHARE_DIR)/etc/wview + cp -fp $(top_srcdir)/examples/html/chrome/standard/graphics.conf $(DESTDIR)$(WV_SHARE_DIR)/etc/wview + cp -fp $(top_srcdir)/examples/html/chrome/standard/html-templates.conf $(DESTDIR)$(WV_SHARE_DIR)/etc/wview + cp -fp $(top_srcdir)/examples/html/chrome/standard/pre-generate.sh $(DESTDIR)$(WV_SHARE_DIR)/etc/wview + cp -fp $(top_srcdir)/examples/html/chrome/standard/post-generate.sh $(DESTDIR)$(WV_SHARE_DIR)/etc/wview + cp -fp $(top_srcdir)/examples/html/chrome/standard/images-user.conf $(DESTDIR)$(WV_SHARE_DIR)/etc/wview + cp -fp $(top_srcdir)/examples/html/chrome/standard/images.conf $(DESTDIR)$(WV_SHARE_DIR)/etc/wview + cp -fp $(top_srcdir)/examples/html/chrome/standard/*.* $(DESTDIR)$(WV_SHARE_DIR)/etc/wview/html + cp -fp $(top_srcdir)/examples/html/chrome/standard/awekas_wl.htx-us $(DESTDIR)$(WV_SHARE_DIR)/etc/wview/html/awekas_wl.htx + cp -fp $(top_srcdir)/examples/html/chrome/static/*.* $(DESTDIR)$(WV_SHARE_DIR)/etc/wview/html +else + @if [ ! -d $(DESTDIR)$(WV_RUN_DIR) ]; then \ + mkdir -p $(DESTDIR)$(WV_RUN_DIR); \ + mkdir -p $(DESTDIR)$(WV_RUN_DIR)/noaa; \ + mkdir -p $(DESTDIR)$(WV_RUN_DIR)/alarms; \ + mkdir -p $(DESTDIR)$(WV_RUN_DIR)/img; \ + mkdir -p $(DESTDIR)$(WV_RUN_DIR)/img/Archive; \ + mkdir -p $(DESTDIR)$(WV_RUN_DIR)/img/NOAA; \ + mkdir -p $(DESTDIR)$(WV_RUN_DIR)/archive; \ + cp -fp $(top_srcdir)/bin/img/*.* $(DESTDIR)$(WV_RUN_DIR)/img; \ + cp -fp $(top_srcdir)/examples/html/chrome/static/*.* $(DESTDIR)$(WV_RUN_DIR)/img; \ + cp -fp $(top_srcdir)/bin/archive/* $(DESTDIR)$(WV_RUN_DIR)/archive; \ + sqlite3 $(DESTDIR)$(WV_RUN_DIR)/archive/wview-archive.sdb '.read $(DESTDIR)$(WV_RUN_DIR)/archive/wview-archive.sql'; \ + echo "==> $(DESTDIR)$(WV_RUN_DIR) has been created with distro examples"; \ + else \ + echo "==> $(DESTDIR)$(WV_RUN_DIR) already exists - skipping default data installation"; \ + fi + @if [ ! -e $(DESTDIR)$(WV_CONFIG_DIR)/wview-conf.sdb ]; then \ + mkdir -p $(DESTDIR)$(WV_CONFIG_DIR); \ + mkdir -p $(DESTDIR)$(WV_CONFIG_DIR)/export; \ + chmod 777 $(DESTDIR)$(WV_CONFIG_DIR)/export; \ + mkdir -p $(DESTDIR)$(WV_CONFIG_DIR)/html; \ + mkdir -p $(DESTDIR)$(WV_CONFIG_DIR)/html/chrome; \ + mkdir -p $(DESTDIR)$(WV_CONFIG_DIR)/html/chrome/plus; \ + mkdir -p $(DESTDIR)$(WV_CONFIG_DIR)/html/chrome/static; \ + mkdir -p $(DESTDIR)$(WV_CONFIG_DIR)/html/chrome/standard; \ + mkdir -p $(DESTDIR)$(WV_CONFIG_DIR)/html/classic; \ + mkdir -p $(DESTDIR)$(WV_CONFIG_DIR)/html/classic/plus; \ + mkdir -p $(DESTDIR)$(WV_CONFIG_DIR)/html/classic/static; \ + mkdir -p $(DESTDIR)$(WV_CONFIG_DIR)/html/classic/standard; \ + mkdir -p $(DESTDIR)$(WV_CONFIG_DIR)/html/exfoliation; \ + mkdir -p $(DESTDIR)$(WV_CONFIG_DIR)/html/exfoliation/plus; \ + mkdir -p $(DESTDIR)$(WV_CONFIG_DIR)/html/exfoliation/static; \ + mkdir -p $(DESTDIR)$(WV_CONFIG_DIR)/html/exfoliation/standard; \ + mkdir -p $(DESTDIR)$(WV_CONFIG_DIR)/alarms; \ + cp -fp $(top_srcdir)/examples/html/*.* $(DESTDIR)$(WV_CONFIG_DIR)/html; \ + cp -fp $(top_srcdir)/examples/html/chrome/plus/*.* $(DESTDIR)$(WV_CONFIG_DIR)/html/chrome/plus; \ + cp -fp $(top_srcdir)/examples/html/chrome/static/*.* $(DESTDIR)$(WV_CONFIG_DIR)/html/chrome/static; \ + cp -fp $(top_srcdir)/examples/html/chrome/standard/*.* $(DESTDIR)$(WV_CONFIG_DIR)/html/chrome/standard; \ + cp -fp $(top_srcdir)/examples/html/classic/plus/*.* $(DESTDIR)$(WV_CONFIG_DIR)/html/classic/plus; \ + cp -fp $(top_srcdir)/examples/html/classic/static/*.* $(DESTDIR)$(WV_CONFIG_DIR)/html/classic/static; \ + cp -fp $(top_srcdir)/examples/html/classic/standard/*.* $(DESTDIR)$(WV_CONFIG_DIR)/html/classic/standard; \ + cp -fp $(top_srcdir)/examples/html/exfoliation/plus/*.* $(DESTDIR)$(WV_CONFIG_DIR)/html/exfoliation/plus; \ + cp -fp $(top_srcdir)/examples/html/exfoliation/static/*.* $(DESTDIR)$(WV_CONFIG_DIR)/html/exfoliation/static; \ + cp -fp $(top_srcdir)/examples/html/exfoliation/standard/*.* $(DESTDIR)$(WV_CONFIG_DIR)/html/exfoliation/standard; \ + cp -fp $(top_srcdir)/examples/alarms/*.* $(DESTDIR)$(WV_CONFIG_DIR)/alarms; \ + cp -fp $(top_srcdir)/examples/conf/*.* $(DESTDIR)$(WV_CONFIG_DIR); \ + cp -fp $(top_srcdir)/examples/html/chrome/standard/graphics.conf $(DESTDIR)$(WV_CONFIG_DIR); \ + cp -fp $(top_srcdir)/examples/html/chrome/standard/html-templates.conf $(DESTDIR)$(WV_CONFIG_DIR); \ + cp -fp $(top_srcdir)/examples/html/chrome/standard/pre-generate.sh $(DESTDIR)$(WV_CONFIG_DIR); \ + chmod +x $(DESTDIR)$(WV_CONFIG_DIR)/pre-generate.sh; \ + cp -fp $(top_srcdir)/examples/html/chrome/standard/post-generate.sh $(DESTDIR)$(WV_CONFIG_DIR); \ + chmod +x $(DESTDIR)$(WV_CONFIG_DIR)/post-generate.sh; \ + cp -fp $(top_srcdir)/examples/html/chrome/standard/images-user.conf $(DESTDIR)$(WV_CONFIG_DIR); \ + cp -fp $(top_srcdir)/examples/html/chrome/standard/images.conf $(DESTDIR)$(WV_CONFIG_DIR); \ + cp -fp $(top_srcdir)/examples/html/chrome/standard/*.* $(DESTDIR)$(WV_CONFIG_DIR)/html; \ + cp -fp $(top_srcdir)/examples/html/chrome/standard/awekas_wl.htx-us $(DESTDIR)$(WV_CONFIG_DIR)/html/awekas_wl.htx; \ + cp -fp $(top_srcdir)/examples/html/chrome/static/*.* $(DESTDIR)$(WV_CONFIG_DIR)/html; \ + sqlite3 $(DESTDIR)$(WV_CONFIG_DIR)/wview-conf.sdb '.read $(DESTDIR)$(WV_CONFIG_DIR)/wview-conf.sql'; \ + echo "==> $(DESTDIR)$(WV_CONFIG_DIR) has been created with distro examples"; \ + echo ""; \ + echo "This is a first time install so the station type will be set to \"simulator\"."; \ + echo "To change the station type, run \"wviewconfig\" or use the wviewmgmt web interface to change it."; \ + echo "You will need to stop wview and clear the databases of simulator data after you have changed the station type:"; \ + echo "#> wviewcleardata"; \ + echo ""; \ + echo "This is a first time install so the generation templates will be set to \"chrome-standard-US units\"."; \ + echo "To change this run \"wviewhtmlconfig\" to configure the template directory"; \ + echo "after you have configured wview for metric units and/or extended data."; \ + echo ""; \ + echo "If you want start/stop control of wview from the Management Web Site"; \ + echo "(and you are comfortable giving the http server user account sudo privileges):"; \ + echo "Add the http user to the sudo group:"; \ + echo "#> sudo adduser www-data sudo"; \ + echo "Set the sudo group to no password required privileges in /etc/sudoers:"; \ + echo "#> sudo visudo"; \ + echo "(Make sure the line [%sudo ALL=NOPASSWD: ALL] is at the bottom of the /etc/sudoers file)"; \ + echo ""; \ + else \ + echo "==> $(DESTDIR)$(WV_CONFIG_DIR)/wview-conf.sdb already exists - skipping default configuration installation"; \ + cp -fp $(top_srcdir)/examples/conf/wview-conf-update.sql $(DESTDIR)$(WV_CONFIG_DIR); \ + sqlite3 $(DESTDIR)$(WV_CONFIG_DIR)/wview-conf.sdb '.read $(DESTDIR)$(WV_CONFIG_DIR)/wview-conf-update.sql'; \ + fi + @echo "To start/stop wview:" + @echo "#> sudo /etc/init.d/wview start|stop" + @echo "$(PACKAGE_VERSION)" > $(DESTDIR)$(WV_CONFIG_DIR)/wview-version +endif + @mkdir -p $(DESTDIR)$(WV_CONFIG_DIR) + @chmod 777 $(DESTDIR)$(WV_CONFIG_DIR) + @if [ ! -e $(DESTDIR)$(WV_CONFIG_DIR)/wview-binary ]; then \ + echo "$(STATION_T)" > $(DESTDIR)$(WV_CONFIG_DIR)/wview-binary; \ + fi + @chmod 666 $(DESTDIR)$(WV_CONFIG_DIR)/wview-binary + @if [ -e $(DESTDIR)$(WV_CONFIG_DIR)/wview-conf.sdb ]; then \ + chmod 666 $(DESTDIR)$(WV_CONFIG_DIR)/wview-conf.sdb; \ + fi + @mkdir -p $(DESTDIR)$(WV_HTML_DIR)/wviewmgmt + @mkdir -p $(DESTDIR)$(WV_HTML_DIR)/wviewmgmt/imgs + @cp -fp $(top_srcdir)/wviewmgmt/*.* $(DESTDIR)$(WV_HTML_DIR)/wviewmgmt + @cp -fp $(top_srcdir)/wviewmgmt/imgs/*.* $(DESTDIR)$(WV_HTML_DIR)/wviewmgmt/imgs + @if [ ! -e $(DESTDIR)$(WV_HTML_DIR)/wviewmgmt/functions.php ]; then \ + $(LN_S) $(DESTDIR)$(WV_BIN_DIR)/functions.php $(DESTDIR)$(WV_HTML_DIR)/wviewmgmt/functions.php; \ + fi + +uninstall-hook: + rm -rf $(DESTDIR)$(WV_RUN_DIR) + rm -rf $(DESTDIR)$(WV_HTML_DIR)/wviewmgmt + rm -rf $(DESTDIR)$(WV_CONFIG_DIR) + @if [ -e $(DESTDIR)$(sysconfdir)/init.d/wview ]; then \ + rm -f $(DESTDIR)$(sysconfdir)/init.d/wview; \ + fi + diff --git a/NEWS b/NEWS new file mode 100644 index 0000000..e69de29 diff --git a/README b/README new file mode 100755 index 0000000..17145ce --- /dev/null +++ b/README @@ -0,0 +1,8 @@ +############################################################################## +README - wview Build/Configuration Instructions and User Manual +############################################################################## + + +This file is no longer maintained. + +See wview-User-Manual.html or wview-Quick-Start.html for a better experience... diff --git a/TODO b/TODO new file mode 100755 index 0000000..5d2370e --- /dev/null +++ b/TODO @@ -0,0 +1,8 @@ +TODO List +--------- + +- Add libusb check to configure + +- Awekas protocol? + +- add WeatherBug Backyard Network support diff --git a/UPGRADE b/UPGRADE new file mode 100755 index 0000000..9b44671 --- /dev/null +++ b/UPGRADE @@ -0,0 +1,377 @@ +wview Upgrade Directions +------------------------ + + +1) Extract new wview source tree to standard location (the name includes + the version number for uniqueness - your old source tree will not be + copied over or disturbed) + +2) Read ChangeLog for any new configuration or environment requirements + +3) Stop any running wview daemons (/etc/init.d/wview stop) + +4) Copy any user-specific source (such as .../htmlgenerator/images-user.c) + into the new source tree + +5) cd to the wview-X.Y.Z root source directory + +6) ./configure (see the User Manual for configuration options) + +7) make && make install (install requires root priveleges) + +8) Find the version you are upgrading from in the list below and proceed from + there through all versions to the end + +9) Execute wviewconfig then wviewhtmlconfig - should always be run after upgrades + +10) Start wview daemons (/etc/init.d/wview start) + +11) Check /var/log/messages (tail -n 100 -f /var/log/messages) for proper + operation (htmls generated, wview daemon downloading archive pages, + history timer adding samples every archive interval minutes, etc.) + +12) If all is good, store the distro tarball in a safe place, delete the + old source tree, you're done! + + +Version Specific Directions +--------------------------- + +0.8.1 or Older: (backup your /usr/local/wview directory first): +--------------- + + (Mandatory) + + a) mv /usr/local/wview/html /etc/wview/html + b) mv /usr/local/wview/*.conf /etc/wview + c) mv /usr/local/wview/archive /var/wview/archive + d) mv /usr/local/wview/img /var/wview/img + e) mv /usr/local/wview/noaa.dat /var/wview/noaa/noaa.dat + f) Edit your start script (/etc/init.d/wview or /etc/rc.d/wview) + and change the path to the binaries from /usr/local/wview to + /usr/local/bin, change run path from /usr/local/wview to /var/wview + then change the executable name for ftpd to wviewftpd (see the + new wview start scripts in examples/* for examples of these + changes). + +0.9.1 or Older: +--------------- + + (Optional) - If you want to utilize the new combo-charts for + temp/dewpoint and heat index/wind chill, do the following: + + a) Using examples/conf/images.conf as a guide, edit your site-specific + /etc/wview/images.conf to add Temp/Dew Point and Heat Index/Wind Chill + multi-plot charts. Don't forget to comment out the old single chart + entries for TempDay, DewDay, HeatDay, ChillDay, TempMonth, DewMonth, + HeatMonth and ChillMonth (see examples/conf/images.conf). + b) Using the files Current_Vantage_Pro.htx, Daily_Vantage_Pro.htx and + Monthly_Vantage_Pro.htx in the bin/html directory as a guide, modify + your site-specific versions of these files in /etc/wview/html. Look + for references to the filenames you commented out in images.conf - + a bit of rearranging is necessary but is illustrated in the distro + examples in bin/html. According to your site, you may be able to just + copy and paste the image blocks from the examples. + +2.0.0 or Older: +--------------- + + (Optional) - From version 3.0.0 forward, the HTML template files with + references to "Vantage Pro" have changed as have the + references to them in the home page templates. New navigation + buttons were also added at the top of each template. If you + want to stay up to date on template files, you should upgrade + your template files based on the distro examples then upgrade + your html-templates.conf to the newest format using the new + file names and update your home page template(s) to refer to + these new file names. + + Current_Vantage_Pro.htx => Current.htx + Current_Vantage_Pro_Plus.htx => Current_Plus.htx + Daily_Vantage_Pro.htx => Daily.htx + Daily_Vantage_Pro_Plus.htx => Daily_Plus.htx + Monthly_Vantage_Pro.htx => Monthly.htx + Monthly_Vantage_Pro_Plus.htx => Monthly_Plus.htx + Yearly_Vantage_Pro.htx => Yearly.htx + Yearly_Vantage_Pro_Plus.htx => Yearly_Plus.htx + + (Optional) - Udgrade your almanac.htx file from the distro examples so + you will have the new solar rise/set times, the new Storm + Start Date entry, etc. If you have extended sensors, add the + new almanac_Plus.htx to your template directory. + +3.1.4 or Older: +--------------- + + (Optional) - Udgrade all of your *.htx template files from the distro + examples so you will have the new Weekly page and references + to it on the other pages. Upgrade your images(-metric(-mm)).conf + file to generate the new weekly chart images. Upgrade your + html-templates.conf to include the new Weekly.htx page. As + always, refer to the examples in the distro for changes/usage. + +3.2.0 or Older: +--------------- + + (Optional) - Udgrade your index.htx template file (or index-day.htx and + index-night.htx if doing day/night themes) from the distro + examples so you may take advantage of the new radar and + forecast HTML tags. Be sure to always run wviewconfig after + an upgrade. As always, refer to the examples in the distro + for changes/usage. + +3.2.1 or Older: +--------------- +If you have user-defined images in ../htmlgenerator/images-user.c which include a +date and time stamp, (i.e. all non-dial images), add an extra parameter, + img->mgrWork->dateFormat +as the final parameter to the function call which produces the image. See the +version of images-user.c from the distro for an example. + +3.3.0 or Older: +--------------- + (Mandatory) - (re)copy the wview start script for your OS distribution from + the examples directory of the distribution so the process + monitor will be started. + + (Mandatory) - copy the new wvpmond config file: + > sudo cp examples/conf/processes.conf /etc/wview + It shouldn't require editing. + + (Mandatory) - if you were generating data to wunderground, the new configure + option to enable it is "--enable-http" - the renamed http + daemon can submit data to wunderground and/or weatherforyou. + The config file for the http daemon is http.conf and replaces + wvwunderd.conf. + (wviewconfig will create the new http.conf config file for + you) + + (Optional) - copy the new html templates from examples/html/classic to + your /etc/wview/html directory to take advantage of the new + template macro inclusion capability. Customize the include + templates and html templates for your site. + + (Optional) - Submit your site template to the wview google group so it can + be included in future wview releases as + "examples/html/". + +3.4.1 or Older: +--------------- + (Mandatory) - If you use wviewftpd to transfer files to a remote web server + you will need to copy the new example wviewftp.conf-no-ftp to + /etc/wview/wviewftp.conf. If you need to modify the FTP + binary used or the arguments passed to it you can now do so + in the new config file. + + (Mandatory) - You should now start using the new html template configuration + script "wviewhtmlconfig" to setup your HTML templates. Run it + immediately after "make install-env"/"wviewconfig" then + customize as you wish. + +3.5.0 or Older: +--------------- + (Mandatory) - The location of generated NOAA and ARC files has been changed + to "img/NOAA" and "img/Archive" respectively. The new "install-env" + make target creates these directories but if you have existing + NOAA and ARC files in the img directory (both on your wview + server and your web site), you will want to create these new + subdirectories and move the files. The new index*.htx files + have been updated to support these locations (but your old ones + will not). + +3.6.0 or Older: +--------------- + (Mandatory) - The location of the wview configuration and data trees has + moved as of version 3.7.0. To allow installation and + execution from a chroot jail and/or allow package management + systems to specify an install/run root prefix, the configure + script "--prefix" argument now controls not only where binaries + are installed but also where "/etc/wview" and "/var/wview" are + located. The default for configure if no "--prefix" argument + is given is "--prefix=/usr/local". So the new default locations + are: "/usr/local/etc/wview" and "/usr/local/var/wview". + Executing "sudo make install-env" for 3.7.0 will create these + directories for you, then you will need to move anything + pertinent from the old "/etc/wview" and "/var/wview" locations + to the new "prefix" locations. Obvious choices are: + "/var/wview/archive" + "/var/wview/alarms" - if applicable + "/etc/wview/alarms" - if applicable + "/etc/wview/*.conf " - not advisable to move directly + "/etc/wview/html customizations" + + Alternatively, if you just refuse to be compliant ;) and *must* + keep your config and data trees on the root prefix, you can + specify "--prefix=/ --exec-prefix=/usr/local". But this means + you must remember this every time you upgrade wview and I am + not going to keep reminding you... + + The really good news in all this is that the example start + scripts are now configured for paths based on the "--prefix" + argument too (during the build step), so there should be much + less customization of those required in the future. + +3.7.3 or Older: +--------------- + (Optional) - Support for a new config file "calibrate.conf" has been added. + If you want to calibrate basic sensor readings, copy the + example version .../wview-x.y.z/examples/conf/calibrate.conf + to $prefix/etc/wview and edit for your calibration requirements. + See the example config file or the User Manual for calibration + details. + +3.9.0 or Older: +--------------- + (Mandatory) - If you use wviewftpd to transfer files to your web/ISP server, + you will need to modify your wviewftp.conf file to remove the + preceding "img" from all file transfer rules. Also, destination + paths will no longer have a trailing "img" directory tacked on + so if you want to retain this in your destination path, you + will need to add "img" to the end of your "directory" parameter + in wviewftp.conf. + +5.0.0 or Older: +--------------- + (Mandatory) - Prior to building and installing wview 5.0.0, you need to do + the following: + 1) Install gawk, libreadline5-dev + 2) Install sqlite3 and libsqlite3-dev (if not already installed). + 3) Install radlib 2.8.2 or newer. + + Given the changes to the configuration location and html template + changes, it is imperative you use the "install-env" make target + when upgrading to 5.0.0: + >./configure [your configure options] + >sudo make install-env + >sudo wviewconfig + >sudo wviewhtmlconfig + + The vast majority of wview configuration has been moved from + individual config files to an SQLite3 database: + $prefix/etc/wview/wview-conf.sdb. The wviewconfig script has + been updated to support the sqlite3 database. There are also + several GUI and web tools for configuring wview including a + Web Forms Inteface: $distro/ConfigForms. See the User + Manual for complete details. At a minimum, running wviewconfig + after upgrading will suffice. + + wview archive data has been moved to an SQLite3 database from + the rusty old WLK files we have formerly been shackled with. + A new conversion utility "wlk2sqlite" has been added to the + wview distribution to import existing WLK archive data to the + new SQLite3 database. A default empty archive database file + will be installed when you use "make install-env" and is + located in a familiar place: $prefix/var/wview/archive. The + database filename is "wview-archive.sdb". Entering "sudo wlk2sqlite" + with no parameters at a shell prompt will display usage details. + wlk2sqlite may require adding "/usr/local/lib" to the + "LD_LIBRARY_PATH" environment variable: + >export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib + The User Manual has more details on this process. + +5.1.1 or Older: +--------------- + (Mandatory) - 5.1.0 introduces a new HILOW database which will be populated + with your archive data for historical purposes and with LOOP + data for better resolution once 5.1.0+ is running. It is necessary + to create and populate the wview-hilow.sdb database prior + to running 5.1.0+ or else wview will do this automatically the + first time it is run. The down side to this automatic creation + is that it can be very slow on some platforms such as the NSLU2. + Stations other than the Vantage Pro which do not archive data + in console memory will NOT have archive records generated during + this automatic conversion. The alternative for these stations is + the hilowcreate utility, included with versions 5.1.1+. + The procedure is: + 1) Leave your old version running, so long as it is 5.0.0+ + (i.e., creating archives in wview-archive.sdb). + 2) Build and install wview 5.1.1+. + 3) Copy your $prefix/var/wview/archive/wview-archive.sdb database + to a working directory. + 4) Execute the utility: + > hilowcreate [working_directory] + 5) When it completes, copy the resulting wview-hilow.sdb database + to $prefix/var/wview/archive. + 6) Stop old wview. + 7) Start new wview, it will pick up archive records for HILOW + that were generated during the execution of hilowcreate. + + +5.2.0 or Older: +--------------- + (Mandatory) - 5.2.0 introduces a new method for starting the wviewd daemon + which relies on the newly generated file $prefix/etc/wview/wview-binary. + The make install-env process will populate this file based on + your station choice during the configure step of wview install. + All station binaries are now built and if the station type is + changed when running wviewconfig or using the new wviewmgmt + web interface, the $prefix/etc/wview/wview-binary file will + be updated. To support this you must (in addition to normal steps): + 1) Execute the "install-env" target when building: + > make install-env + 2) Copy the new wview start script for your platform from the distro: + > sudo cp $distro/examples/[your_platform]/wview /etc/init.d + (or similar, use the appropriate path for your distro) + + (Mandatory) - 5.2.0 introduces email alerts. This required several new config + parameters so you must upgrade your wview-conf.sdb file. + + The simple way (but it requires you to reconfigure everything) + is to copy the new distro database: + > sudo cp $distro/examples/conf/wview-conf.sdb $prefix/etc/wview + > sudo wviewconfig (or use the web interface) + > sudo /etc/init.d/wview restart + + The simpler way is to add the three new records to your existing + wview-conf.sdb: + > sudo sqlite3 $prefix/etc/wview/wview-conf.sdb + sqlite> INSERT INTO "config" VALUES('EMAIL_ADDRESS','address@server.com','Destination for email alerts:',NULL); + sqlite> INSERT INTO "config" VALUES('ENABLE_EMAIL_ALERTS','no','Send system alert emails?',NULL); + sqlite> INSERT INTO "config" VALUES('SEND_TEST_EMAIL','no','Send a test email?',NULL); + sqlite> .quit + + (Optional) - If you want to be able to Start/Stop wview remotely via the new + wviewmgmt web interface, you will need to do the following: + + 1) The $distro/wviewmgmt directory should be copied to the HTTP + server's document root: + > sudo cp -R $distro/wviewmgmt $documentRoot + + 2) Make $prefix/etc/wview (the directory) and $prefix/etc/wview/wview-conf.sdb + read-write for the http server account (the new install-env build target + does this for you): + > sudo chmod 777 $prefix/etc/wview + > sudo chmod 777 $prefix/etc/wview/wview-conf.sdb + (This is brute force, you can play with group memberships for the http server + account to avoid 777) + + 3) Be sure PHP support is included with the http server: + For apache2, I have /etc/apache2/mods-enabled/php5.conf and + /etc/apache2/mods-enabled/php5.load. + + 4) Be sure the SQLite3 module for php is installed (php5-sqlite or similar). + + 5) Add the http user to the sudo group: + > sudo adduser www-data sudo + + 6) Make sure the sudo group has no password required privileges in /etc/sudoers: + > sudo visudo + (Make sure the line "%sudo ALL=NOPASSWD: ALL" is at the bottom of the + /etc/sudoers file) + + 7) Point your browser to: + http://[wview_URL_or_IP_address]/wviewmgmt/wview-form.php + +5.4.0 or Older: +--------------- + (Mandatory) - 5.5.0 removes the wviewsqld daemon. If you were exporting to an SQL + database you will need to consult the User Manual on how to use the + new SQL export scripts. + + +5.7.1 or Older: +--------------- + (Optional) - 5.8.0 adds debian package installation capability. See the User Manual + for step-by-step instructions to transition from source installs to + debian APT install. + diff --git a/afedit/COPYING b/afedit/COPYING new file mode 100644 index 0000000..5b6e7c6 --- /dev/null +++ b/afedit/COPYING @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/afedit/Doxyfile b/afedit/Doxyfile new file mode 100644 index 0000000..3514bea --- /dev/null +++ b/afedit/Doxyfile @@ -0,0 +1,283 @@ +# Doxyfile 1.5.1-KDevelop + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- +PROJECT_NAME = afedit +PROJECT_NUMBER = 0.1 +OUTPUT_DIRECTORY = +CREATE_SUBDIRS = NO +OUTPUT_LANGUAGE = English +USE_WINDOWS_ENCODING = NO +BRIEF_MEMBER_DESC = YES +REPEAT_BRIEF = YES +ABBREVIATE_BRIEF = "The $name class" \ + "The $name widget" \ + "The $name file" \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the +ALWAYS_DETAILED_SEC = NO +INLINE_INHERITED_MEMB = NO +FULL_PATH_NAMES = YES +STRIP_FROM_PATH = /home/mteel/ +STRIP_FROM_INC_PATH = +SHORT_NAMES = NO +JAVADOC_AUTOBRIEF = NO +MULTILINE_CPP_IS_BRIEF = NO +DETAILS_AT_TOP = NO +INHERIT_DOCS = YES +SEPARATE_MEMBER_PAGES = NO +TAB_SIZE = 8 +ALIASES = +OPTIMIZE_OUTPUT_FOR_C = NO +OPTIMIZE_OUTPUT_JAVA = NO +BUILTIN_STL_SUPPORT = NO +DISTRIBUTE_GROUP_DOC = NO +SUBGROUPING = YES +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- +EXTRACT_ALL = NO +EXTRACT_PRIVATE = NO +EXTRACT_STATIC = NO +EXTRACT_LOCAL_CLASSES = YES +EXTRACT_LOCAL_METHODS = NO +HIDE_UNDOC_MEMBERS = NO +HIDE_UNDOC_CLASSES = NO +HIDE_FRIEND_COMPOUNDS = NO +HIDE_IN_BODY_DOCS = NO +INTERNAL_DOCS = NO +CASE_SENSE_NAMES = YES +HIDE_SCOPE_NAMES = NO +SHOW_INCLUDE_FILES = YES +INLINE_INFO = YES +SORT_MEMBER_DOCS = YES +SORT_BRIEF_DOCS = NO +SORT_BY_SCOPE_NAME = NO +GENERATE_TODOLIST = YES +GENERATE_TESTLIST = YES +GENERATE_BUGLIST = YES +GENERATE_DEPRECATEDLIST= YES +ENABLED_SECTIONS = +MAX_INITIALIZER_LINES = 30 +SHOW_USED_FILES = YES +SHOW_DIRECTORIES = NO +FILE_VERSION_FILTER = +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- +QUIET = NO +WARNINGS = YES +WARN_IF_UNDOCUMENTED = YES +WARN_IF_DOC_ERROR = YES +WARN_NO_PARAMDOC = NO +WARN_FORMAT = "$file:$line: $text" +WARN_LOGFILE = +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- +INPUT = /home/mteel/dev/wview/trunk/wview/afedit +FILE_PATTERNS = *.c \ + *.cc \ + *.cxx \ + *.cpp \ + *.c++ \ + *.d \ + *.java \ + *.ii \ + *.ixx \ + *.ipp \ + *.i++ \ + *.inl \ + *.h \ + *.hh \ + *.hxx \ + *.hpp \ + *.h++ \ + *.idl \ + *.odl \ + *.cs \ + *.php \ + *.php3 \ + *.inc \ + *.m \ + *.mm \ + *.dox \ + *.py \ + *.C \ + *.CC \ + *.C++ \ + *.II \ + *.I++ \ + *.H \ + *.HH \ + *.H++ \ + *.CS \ + *.PHP \ + *.PHP3 \ + *.M \ + *.MM \ + *.PY \ + *.C \ + *.H \ + *.tlh \ + *.diff \ + *.patch \ + *.moc \ + *.xpm \ + *.dox +RECURSIVE = yes +EXCLUDE = +EXCLUDE_SYMLINKS = NO +EXCLUDE_PATTERNS = +EXAMPLE_PATH = +EXAMPLE_PATTERNS = * +EXAMPLE_RECURSIVE = NO +IMAGE_PATH = +INPUT_FILTER = +FILTER_PATTERNS = +FILTER_SOURCE_FILES = NO +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- +SOURCE_BROWSER = NO +INLINE_SOURCES = NO +STRIP_CODE_COMMENTS = YES +REFERENCED_BY_RELATION = YES +REFERENCES_RELATION = YES +REFERENCES_LINK_SOURCE = YES +USE_HTAGS = NO +VERBATIM_HEADERS = YES +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- +ALPHABETICAL_INDEX = NO +COLS_IN_ALPHA_INDEX = 5 +IGNORE_PREFIX = +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- +GENERATE_HTML = YES +HTML_OUTPUT = html +HTML_FILE_EXTENSION = .html +HTML_HEADER = +HTML_FOOTER = +HTML_STYLESHEET = +HTML_ALIGN_MEMBERS = YES +GENERATE_HTMLHELP = NO +CHM_FILE = +HHC_LOCATION = +GENERATE_CHI = NO +BINARY_TOC = NO +TOC_EXPAND = NO +DISABLE_INDEX = NO +ENUM_VALUES_PER_LINE = 4 +GENERATE_TREEVIEW = NO +TREEVIEW_WIDTH = 250 +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- +GENERATE_LATEX = YES +LATEX_OUTPUT = latex +LATEX_CMD_NAME = latex +MAKEINDEX_CMD_NAME = makeindex +COMPACT_LATEX = NO +PAPER_TYPE = a4wide +EXTRA_PACKAGES = +LATEX_HEADER = +PDF_HYPERLINKS = NO +USE_PDFLATEX = NO +LATEX_BATCHMODE = NO +LATEX_HIDE_INDICES = NO +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- +GENERATE_RTF = NO +RTF_OUTPUT = rtf +COMPACT_RTF = NO +RTF_HYPERLINKS = NO +RTF_STYLESHEET_FILE = +RTF_EXTENSIONS_FILE = +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- +GENERATE_MAN = NO +MAN_OUTPUT = man +MAN_EXTENSION = .3 +MAN_LINKS = NO +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- +GENERATE_XML = yes +XML_OUTPUT = xml +XML_SCHEMA = +XML_DTD = +XML_PROGRAMLISTING = YES +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- +GENERATE_AUTOGEN_DEF = NO +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- +GENERATE_PERLMOD = NO +PERLMOD_LATEX = NO +PERLMOD_PRETTY = YES +PERLMOD_MAKEVAR_PREFIX = +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- +ENABLE_PREPROCESSING = YES +MACRO_EXPANSION = NO +EXPAND_ONLY_PREDEF = NO +SEARCH_INCLUDES = YES +INCLUDE_PATH = +INCLUDE_FILE_PATTERNS = +PREDEFINED = +EXPAND_AS_DEFINED = +SKIP_FUNCTION_MACROS = YES +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- +TAGFILES = +GENERATE_TAGFILE = afedit.tag +ALLEXTERNALS = NO +EXTERNAL_GROUPS = YES +PERL_PATH = /usr/bin/perl +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- +CLASS_DIAGRAMS = YES +HIDE_UNDOC_RELATIONS = YES +HAVE_DOT = NO +CLASS_GRAPH = YES +COLLABORATION_GRAPH = YES +GROUP_GRAPHS = YES +UML_LOOK = NO +TEMPLATE_RELATIONS = NO +INCLUDE_GRAPH = YES +INCLUDED_BY_GRAPH = YES +CALL_GRAPH = NO +CALLER_GRAPH = NO +GRAPHICAL_HIERARCHY = YES +DIRECTORY_GRAPH = YES +DOT_IMAGE_FORMAT = png +DOT_PATH = +DOTFILE_DIRS = +MAX_DOT_GRAPH_WIDTH = 1024 +MAX_DOT_GRAPH_HEIGHT = 1024 +MAX_DOT_GRAPH_DEPTH = 1000 +DOT_TRANSPARENT = NO +DOT_MULTI_TARGETS = NO +GENERATE_LEGEND = YES +DOT_CLEANUP = YES +#--------------------------------------------------------------------------- +# Configuration::additions related to the search engine +#--------------------------------------------------------------------------- +SEARCHENGINE = NO diff --git a/afedit/afedit.kdevelop b/afedit/afedit.kdevelop new file mode 100644 index 0000000..544aaa5 --- /dev/null +++ b/afedit/afedit.kdevelop @@ -0,0 +1,245 @@ + + + + Mark Teel + mteel@teelworks.com + 0.50 + KDevTrollProject + C++ + + Qt + + + afedit + . + false + + + + + + + + + + + false + false + + + false + *.o,*.lo,CVS + + + + + true + 3 + 3 + ExternalDesigner + /usr/share/qt3 + /usr/bin/qmake-qt3 + /usr/bin/designer-qt3 + + + + + false + true + true + 250 + 400 + 250 + false + 0 + true + true + false + std=_GLIBCXX_STD;__gnu_cxx=std + true + false + false + false + true + true + true + false + .; + + + + set + m_,_ + theValue + true + true + + + false + true + Vertical + + + + + bash + bash_bugs + clanlib + fortran_bugs_gcc + gnome1 + gnustep + gtk + gtk_bugs + haskell + haskell_bugs_ghc + java_bugs_gcc + java_bugs_sun + kde2book + opengl + pascal_bugs_fp + php + php_bugs + perl + perl_bugs + python + python_bugs + qt-kdev3 + ruby + ruby_bugs + sdl + stl + sw + w3c-dom-level2-html + w3c-svg + w3c-uaag10 + wxwidgets_bugs + + + KDE Libraries (Doxygen) + + + + + + + + /usr/bin/gdb + true + false + false + + + + + + true + true + 10 + + + + + + /home/mteel/dev/wview/trunk/wview/afedit/./bin/afedit + + executable + + /home/mteel/dev/wview/trunk/wview/afedit + true + false + false + false + false + + + + + + /home/mteel/dev/wview/trunk/wview/afedit + + + + + + + src + + + + true + false + 1 + false + + 0 + + + + 2 + false + true + + + + + + + + + + + + + + + + Doxygen Documentation Collection + afedit.tag + + + + afedit + afedit + Afedit + AFEDIT + Mark Teel + mteel@teelworks.com + GPL + COPYING + + + 0.1 + 2007 + /home/mteel/dev/wview/trunk/wview/afedit + + + + .h + .cpp + + + + false + false + + afedit + 0.1 + + + + + + + + + false + false + false + 0 + false + false + false + false + + + diff --git a/afedit/afedit.kdevelop.pcs b/afedit/afedit.kdevelop.pcs new file mode 100644 index 0000000000000000000000000000000000000000..ffeea102d39175adfd000d25f87af744cc3f8c91 GIT binary patch literal 29156 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/afedit/afedit.pro b/afedit/afedit.pro new file mode 100644 index 0000000..20f1cba --- /dev/null +++ b/afedit/afedit.pro @@ -0,0 +1,6 @@ +SUBDIRS += src +TEMPLATE = subdirs +CONFIG += release \ + warn_on \ + qt \ + thread diff --git a/afedit/afedit.spec b/afedit/afedit.spec new file mode 100644 index 0000000..98e88c2 --- /dev/null +++ b/afedit/afedit.spec @@ -0,0 +1,46 @@ +# This spec file was generated by KDevelop +# Please report any problem to KDevelop Team +# Thanks to Matthias Saou for his explanations on http://freshrpms.net/docs/fight.html + +Name: afedit +Version: 0.1 +Release: +Vendor: +Copyright: +Summary: +Group: +Packager: +BuildRoot: %{_tmppath}/%{name}-root +Source: + +%description + + +%prep +%setup +CFLAGS="$RPM_OPT_FLAGS" CXXFLAGS="$RPM_OPT_FLAGS" ./configure \ +--target= +--disable-debug --enable-debug=no + +%build +%configure +make + +%install +rm -rf %{buildroot} +%makeinstall + +%clean +rm -rf %{buildroot} + +%post -p /sbin/ldconfig +%postun -p /sbin/ldconfig +%files +%defattr(-, root, root) +%doc AUTHORS COPYING ChangeLog NEWS README TODO +%{_bindir}/* +%{_libdir}/*.so.* +%{_datadir}/%{name} +%{_mandir}/man8/* +%changelog + diff --git a/afedit/bin/afedit b/afedit/bin/afedit new file mode 100755 index 0000000000000000000000000000000000000000..81a9c938ebc57165d4298ba61765a9755389bc71 GIT binary patch literal 115184 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 z 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 Z_jE4uWf0v3q4!YlMpI+dQBoK2e*wK;&KLjy literal 0 HcmV?d00001 diff --git a/afedit/images/editcopy b/afedit/images/editcopy new file mode 100644 index 0000000000000000000000000000000000000000..9f5f0c6d6080310f30298e1c9ef7342b3ea6d730 GIT binary patch literal 248 zcmV8)K_!M|q-f$D!~PiL(ntzbb5bwl(@8 z=N&es9gnom8?xp}+`s>Wjcrw^%rVoHBX@rATu_h=uZn41(Gq`$DQ>Aj)t|HMkE~=r mHNVj|zqvMk%l-FP=KTJyzh=gXGo3)!FnGH9xvXIv!|eR5UJM`E&#MFT3@SaYnPiGgMYxo7PjOTTV(f*absDL zo7_ck-bcVkGTmNV%r)@IcVe!1c{be7brV;9dGw4+C#nwa|xb^rhX07*qoM6N<$g6c_g82|tP literal 0 HcmV?d00001 diff --git a/afedit/images/filenew b/afedit/images/filenew new file mode 100644 index 0000000000000000000000000000000000000000..138cd8c36cf6cdf473c5a4c79347b76d999a048a GIT binary patch literal 173 zcmeAS@N?(olHy`uVBq!ia0vp^Vj#@H1|*Mc$*~4fC7v#hAr`%FuO8$)puod=A^)XW zqmIC)|94w21-EWnInjfk)t*D@gQk$c4~?TMZyvcf?X32!o39xd()jcr915IadE`5f zk4tV`wyDMWNlmU#HhudbvSwBPEm{AAOG~7c*yqm`l%4jfYoboYLGE`c&)6lu)=gqw X>#D_a#xFntXg`CetDnm{r-UW|Gn7G> literal 0 HcmV?d00001 diff --git a/afedit/images/fileopen b/afedit/images/fileopen new file mode 100644 index 0000000000000000000000000000000000000000..6d47e005551b1c940050616ab035a4a6746e3ad1 GIT binary patch literal 210 zcmeAS@N?(olHy`uVBq!ia0vp^Vj#@H1|*Mc$*~4fGd*1#Lo9mVPI2UGRp4=n?>uf+ zpSY(yR>Nb7M#BGdH$%Gj^$D}s$xdo4|I)Zn`Hr^9w=Y$;k9MT4Xt8%-oT0^S8=rKv znPHXGb=ClvSv#+?FE3uPWS9S>MfZ(uQjS@_-rMwa$J%?R&r~S9c1k;DSrYrJ;dB1_ zv}5@T=h%1XZz*`gdiKD+3l)>U^@)p2N{-LSv3vh*mreg&v)m>y@$Te{cXD$>8bg K=d#Wzp$P!t9#|{@ literal 0 HcmV?d00001 diff --git a/afedit/images/filesave b/afedit/images/filesave new file mode 100644 index 0000000000000000000000000000000000000000..241479c2f713a6cd0633ebc793c57aaf35f512a9 GIT binary patch literal 217 zcmeAS@N?(olHy`uVBq!ia0vp^Vj#@H1|*Mc$*~4f3p`yMLo9l)PW9z#P!MRHf6*=D zVehk~OGq}>=~znUgY6&NKYo;PXVp^dE7M(j`}e$=-@^JEwwigjcAPVH zVPSZ|W!dxM=LC~WnmUJT`t}C%zmBkCeHmgR-^Em|Rcj^Bxxdx-=+&FSt1R>UElzEm z`RLL^j@1vte&`uYuRrbE|I_Ju?7n@M9y(98n*8n5TH8dyFW$BOP5eI(u+82oclNDe RPYlql44$rjF6*2UngHR&TR;E+ literal 0 HcmV?d00001 diff --git a/afedit/images/print b/afedit/images/print new file mode 100644 index 0000000000000000000000000000000000000000..de962684641cdc832d8ff0751f40414cea3d8c89 GIT binary patch literal 725 zcmV;`0xJE9P)*-+47HZ)9_!(x^|hb3=NKSE()0vqPCge6d6E{owpnMrJhZNW;i z{CCUPoQN)HQ|z1+CmAdtO3^UAoL=J66jcLL zj%+algq%-4cx7r+(yx@~muLJ}{QA_CY;+MppPqh&vM9s<#8>zVqc92*B#go+tk>)C z@kzuH#Z-QApZ_b^gYE}4=e%$PAAk4a=mX$8-}UZ|>ybdb_F z!22T3qZx=0Ye?-jI9ycR6vXjh-?_mvv>|`kfEnp<7}sle`ejkzMWUW&w7xlrF4mnm z0~H|V1n{=Sh-Zl3Y+6qo^|&<&YXGr!C=`n|bZ=)nInsFA2}S86U}fh8t;u#b8XyXG zxZ*G?srzhtV=|fY4ePLM$!dJG`nm_|nr$^`4Oq)L7z|h}?jmFFePb1fK_x&HoimFt z(y+1}7(2}|Lj;z~CG+{b*Uz$ykGBtgzrCSq#Pdhk8$Rhg%MP;bv!15qa>?W4V-Grq z&}y{`tJNFZ`v0Y9ZOYD>-CqM#%XnWIlj(%p+gtAM?_0dT(e|DUKkOui00000NkvXX Hu0mjfR9jK@ literal 0 HcmV?d00001 diff --git a/afedit/images/redo b/afedit/images/redo new file mode 100644 index 0000000000000000000000000000000000000000..44109228e8b94cd7871385b9e510c8e612e59e73 GIT binary patch literal 173 zcmeAS@N?(olHy`uVBq!ia0vp^Vj#@H1|*Mc$*~4fC7v#hAr`%FCmrNEpupjfdtcYO z<^Nm0#SvL*7gHZJ^DVV&W$~!qZ{x3E^`ugq5q*WIez3^JK=zG9jp^_f{3#%uqq*pLR X=4os!v3F|&+RxzW>gTe~DWM4fctAv? literal 0 HcmV?d00001 diff --git a/afedit/images/searchfind b/afedit/images/searchfind new file mode 100644 index 0000000000000000000000000000000000000000..1a9836f04a46117de8dacd11b3f5e51ff831169f GIT binary patch literal 662 zcmV;H0%`q;P)o?m5QaZz3RdjIB6!ecHj|*j9B9_U<_+>1fq@Y;l$pX*s4|;X@ZgEvv0J7Bx1DB^ z)|s}`nf~Z*XXo30ckkeT)8fZV*LAaV4(~lt6#yfGwH9kFS65ey7b(92w{1HQhXc7S zSgzM#1OV?G+4s+7vG?5+frTERPVL`s47VliV`(4>^j6No6Y7`&M*4D zpNGRh{oy^!O^vFePDt(&#)3Eq!3UO`8j76b@%XAyTsY^*t);4K0K{JsoM+M_lZ=Sw z$Y9YJvDK2%Il8W!&x5ClWwl08G=Li&E+0unz~fK}WC-dNM3BPZy+7|*Q^MqipaGvv zg!P0NL4#tjK#q$e$s|w^lQB)xS@0Wxm=qBpB@{pv@s>;rycM1f$8ISXC@+}+*Hj)1A&LIOzW1cV4y@IC^NeR7m# z$+}wOMu#!xr=R5lV6|Fd0>th>rin~5^2-dd22m&!`1qN!EV;S4S-8>h`1nZEG_-B| z;+y_nt?&E!csw#XhZuqEv>*);QK;*h?RL8W7=~f)x{m#Rk1>YrcFStDdgA_B8(r7U z-g{zo-EMB02Jby}T|X)Q$LjljzP-Jjt+jJmmh=7n{rsZi-_^sz w!`w6t(=@T!Z2le?PGj8f_oS5mCiWlc3%pETS=JQF^Z)<=07*qoM6N<$f*>tFwg3PC literal 0 HcmV?d00001 diff --git a/afedit/images/undo b/afedit/images/undo new file mode 100644 index 0000000000000000000000000000000000000000..f314aa538b915d184730ca958cee32dd2a876d54 GIT binary patch literal 172 zcmeAS@N?(olHy`uVBq!ia0vp^Vj#@H1|*Mc$*~4f#hxyXAr`%FCmrN#P~dRb_&!Zm z=D%-y){2gWWtl(C_%-ZqFeH48bc(ks{A$MZpl_x&X9wd2jS%syC-sWwEN<6Y$>HJ1EYI{Y@JAvvan?%<;O#CtBp1XwHxux^&%{w~Zf+1;* W#>U6j8E*ouXYh3Ob6Mw<&;$T*phAQI literal 0 HcmV?d00001 diff --git a/afedit/src/.moc/moc_aboutafedit.cpp b/afedit/src/.moc/moc_aboutafedit.cpp new file mode 100644 index 0000000..6de332a --- /dev/null +++ b/afedit/src/.moc/moc_aboutafedit.cpp @@ -0,0 +1,101 @@ +/**************************************************************************** +** AboutAFEdit meta object code from reading C++ file 'aboutafedit.h' +** +** Created: Mon Jul 9 22:50:58 2007 +** by: The Qt MOC ($Id: qt/moc_yacc.cpp 3.3.7 edited Oct 19 16:22 $) +** +** WARNING! All changes made in this file will be lost! +*****************************************************************************/ + +#undef QT_NO_COMPAT +#include "../.ui/aboutafedit.h" +#include +#include + +#include +#if !defined(Q_MOC_OUTPUT_REVISION) || (Q_MOC_OUTPUT_REVISION != 26) +#error "This file was generated using the moc from 3.3.7. It" +#error "cannot be used with the include files from this version of Qt." +#error "(The moc has changed too much.)" +#endif + +const char *AboutAFEdit::className() const +{ + return "AboutAFEdit"; +} + +QMetaObject *AboutAFEdit::metaObj = 0; +static QMetaObjectCleanUp cleanUp_AboutAFEdit( "AboutAFEdit", &AboutAFEdit::staticMetaObject ); + +#ifndef QT_NO_TRANSLATION +QString AboutAFEdit::tr( const char *s, const char *c ) +{ + if ( qApp ) + return qApp->translate( "AboutAFEdit", s, c, QApplication::DefaultCodec ); + else + return QString::fromLatin1( s ); +} +#ifndef QT_NO_TRANSLATION_UTF8 +QString AboutAFEdit::trUtf8( const char *s, const char *c ) +{ + if ( qApp ) + return qApp->translate( "AboutAFEdit", s, c, QApplication::UnicodeUTF8 ); + else + return QString::fromUtf8( s ); +} +#endif // QT_NO_TRANSLATION_UTF8 + +#endif // QT_NO_TRANSLATION + +QMetaObject* AboutAFEdit::staticMetaObject() +{ + if ( metaObj ) + return metaObj; + QMetaObject* parentObject = QDialog::staticMetaObject(); + static const QUMethod slot_0 = {"languageChange", 0, 0 }; + static const QMetaData slot_tbl[] = { + { "languageChange()", &slot_0, QMetaData::Protected } + }; + metaObj = QMetaObject::new_metaobject( + "AboutAFEdit", parentObject, + slot_tbl, 1, + 0, 0, +#ifndef QT_NO_PROPERTIES + 0, 0, + 0, 0, +#endif // QT_NO_PROPERTIES + 0, 0 ); + cleanUp_AboutAFEdit.setMetaObject( metaObj ); + return metaObj; +} + +void* AboutAFEdit::qt_cast( const char* clname ) +{ + if ( !qstrcmp( clname, "AboutAFEdit" ) ) + return this; + return QDialog::qt_cast( clname ); +} + +bool AboutAFEdit::qt_invoke( int _id, QUObject* _o ) +{ + switch ( _id - staticMetaObject()->slotOffset() ) { + case 0: languageChange(); break; + default: + return QDialog::qt_invoke( _id, _o ); + } + return TRUE; +} + +bool AboutAFEdit::qt_emit( int _id, QUObject* _o ) +{ + return QDialog::qt_emit(_id,_o); +} +#ifndef QT_NO_PROPERTIES + +bool AboutAFEdit::qt_property( int id, int f, QVariant* v) +{ + return QDialog::qt_property( id, f, v); +} + +bool AboutAFEdit::qt_static_property( QObject* , int , int , QVariant* ){ return FALSE; } +#endif // QT_NO_PROPERTIES diff --git a/afedit/src/.moc/moc_mainform.cpp b/afedit/src/.moc/moc_mainform.cpp new file mode 100644 index 0000000..5baff68 --- /dev/null +++ b/afedit/src/.moc/moc_mainform.cpp @@ -0,0 +1,129 @@ +/**************************************************************************** +** MainForm meta object code from reading C++ file 'mainform.h' +** +** Created: Mon Jul 9 22:50:57 2007 +** by: The Qt MOC ($Id: qt/moc_yacc.cpp 3.3.7 edited Oct 19 16:22 $) +** +** WARNING! All changes made in this file will be lost! +*****************************************************************************/ + +#undef QT_NO_COMPAT +#include "../.ui/mainform.h" +#include +#include + +#include +#if !defined(Q_MOC_OUTPUT_REVISION) || (Q_MOC_OUTPUT_REVISION != 26) +#error "This file was generated using the moc from 3.3.7. It" +#error "cannot be used with the include files from this version of Qt." +#error "(The moc has changed too much.)" +#endif + +const char *MainForm::className() const +{ + return "MainForm"; +} + +QMetaObject *MainForm::metaObj = 0; +static QMetaObjectCleanUp cleanUp_MainForm( "MainForm", &MainForm::staticMetaObject ); + +#ifndef QT_NO_TRANSLATION +QString MainForm::tr( const char *s, const char *c ) +{ + if ( qApp ) + return qApp->translate( "MainForm", s, c, QApplication::DefaultCodec ); + else + return QString::fromLatin1( s ); +} +#ifndef QT_NO_TRANSLATION_UTF8 +QString MainForm::trUtf8( const char *s, const char *c ) +{ + if ( qApp ) + return qApp->translate( "MainForm", s, c, QApplication::UnicodeUTF8 ); + else + return QString::fromUtf8( s ); +} +#endif // QT_NO_TRANSLATION_UTF8 + +#endif // QT_NO_TRANSLATION + +QMetaObject* MainForm::staticMetaObject() +{ + if ( metaObj ) + return metaObj; + QMetaObject* parentObject = QMainWindow::staticMetaObject(); + static const QUMethod slot_0 = {"fileOpen", 0, 0 }; + static const QUMethod slot_1 = {"fileSave", 0, 0 }; + static const QUMethod slot_2 = {"fileSaveAs", 0, 0 }; + static const QUMethod slot_3 = {"fileExit", 0, 0 }; + static const QUMethod slot_4 = {"helpContents", 0, 0 }; + static const QUMethod slot_5 = {"helpAbout", 0, 0 }; + static const QUMethod slot_6 = {"archiveTable_selectionChanged", 0, 0 }; + static const QUParameter param_slot_7[] = { + { "row", &static_QUType_int, 0, QUParameter::In }, + { "col", &static_QUType_int, 0, QUParameter::In } + }; + static const QUMethod slot_7 = {"archiveTable_valueChanged", 2, param_slot_7 }; + static const QUMethod slot_8 = {"languageChange", 0, 0 }; + static const QMetaData slot_tbl[] = { + { "fileOpen()", &slot_0, QMetaData::Public }, + { "fileSave()", &slot_1, QMetaData::Public }, + { "fileSaveAs()", &slot_2, QMetaData::Public }, + { "fileExit()", &slot_3, QMetaData::Public }, + { "helpContents()", &slot_4, QMetaData::Public }, + { "helpAbout()", &slot_5, QMetaData::Public }, + { "archiveTable_selectionChanged()", &slot_6, QMetaData::Public }, + { "archiveTable_valueChanged(int,int)", &slot_7, QMetaData::Public }, + { "languageChange()", &slot_8, QMetaData::Protected } + }; + metaObj = QMetaObject::new_metaobject( + "MainForm", parentObject, + slot_tbl, 9, + 0, 0, +#ifndef QT_NO_PROPERTIES + 0, 0, + 0, 0, +#endif // QT_NO_PROPERTIES + 0, 0 ); + cleanUp_MainForm.setMetaObject( metaObj ); + return metaObj; +} + +void* MainForm::qt_cast( const char* clname ) +{ + if ( !qstrcmp( clname, "MainForm" ) ) + return this; + return QMainWindow::qt_cast( clname ); +} + +bool MainForm::qt_invoke( int _id, QUObject* _o ) +{ + switch ( _id - staticMetaObject()->slotOffset() ) { + case 0: fileOpen(); break; + case 1: fileSave(); break; + case 2: fileSaveAs(); break; + case 3: fileExit(); break; + case 4: helpContents(); break; + case 5: helpAbout(); break; + case 6: archiveTable_selectionChanged(); break; + case 7: archiveTable_valueChanged((int)static_QUType_int.get(_o+1),(int)static_QUType_int.get(_o+2)); break; + case 8: languageChange(); break; + default: + return QMainWindow::qt_invoke( _id, _o ); + } + return TRUE; +} + +bool MainForm::qt_emit( int _id, QUObject* _o ) +{ + return QMainWindow::qt_emit(_id,_o); +} +#ifndef QT_NO_PROPERTIES + +bool MainForm::qt_property( int id, int f, QVariant* v) +{ + return QMainWindow::qt_property( id, f, v); +} + +bool MainForm::qt_static_property( QObject* , int , int , QVariant* ){ return FALSE; } +#endif // QT_NO_PROPERTIES diff --git a/afedit/src/.ui/aboutafedit.cpp b/afedit/src/.ui/aboutafedit.cpp new file mode 100644 index 0000000..bd09393 --- /dev/null +++ b/afedit/src/.ui/aboutafedit.cpp @@ -0,0 +1,92 @@ +/**************************************************************************** +** Form implementation generated from reading ui file 'aboutafedit.ui' +** +** Created: Mon Jul 9 22:50:56 2007 +** by: The User Interface Compiler ($Id: qt/main.cpp 3.3.7 edited Aug 31 2005 $) +** +** WARNING! All changes made in this file will be lost! +****************************************************************************/ + +#include "aboutafedit.h" + +#include +#include +#include +#include +#include +#include + +/* + * Constructs a AboutAFEdit as a child of 'parent', with the + * name 'name' and widget flags set to 'f'. + * + * The dialog will by default be modeless, unless you set 'modal' to + * TRUE to construct a modal dialog. + */ +AboutAFEdit::AboutAFEdit( QWidget* parent, const char* name, bool modal, WFlags fl ) + : QDialog( parent, name, modal, fl ) +{ + if ( !name ) + setName( "AboutAFEdit" ); + setSizePolicy( QSizePolicy( (QSizePolicy::SizeType)5, (QSizePolicy::SizeType)5, 0, 0, sizePolicy().hasHeightForWidth() ) ); + setBackgroundOrigin( QDialog::WindowOrigin ); + setSizeGripEnabled( TRUE ); + setModal( TRUE ); + + textLabel1 = new QLabel( this, "textLabel1" ); + textLabel1->setGeometry( QRect( 80, 60, 350, 41 ) ); + QFont textLabel1_font( textLabel1->font() ); + textLabel1_font.setPointSize( 20 ); + textLabel1->setFont( textLabel1_font ); + + QWidget* privateLayoutWidget = new QWidget( this, "Layout1" ); + privateLayoutWidget->setGeometry( QRect( 420, 190, 80, 33 ) ); + Layout1 = new QHBoxLayout( privateLayoutWidget, 0, 6, "Layout1"); + + buttonOk = new QPushButton( privateLayoutWidget, "buttonOk" ); + buttonOk->setAutoDefault( TRUE ); + buttonOk->setDefault( TRUE ); + Layout1->addWidget( buttonOk ); + + VersionLabel_2 = new QLabel( this, "VersionLabel_2" ); + VersionLabel_2->setGeometry( QRect( 140, 150, 221, 31 ) ); + QFont VersionLabel_2_font( VersionLabel_2->font() ); + VersionLabel_2_font.setPointSize( 12 ); + VersionLabel_2->setFont( VersionLabel_2_font ); + + VersionLabel = new QLabel( this, "VersionLabel" ); + VersionLabel->setGeometry( QRect( 140, 110, 221, 31 ) ); + QFont VersionLabel_font( VersionLabel->font() ); + VersionLabel_font.setPointSize( 12 ); + VersionLabel->setFont( VersionLabel_font ); + VersionLabel->setFrameShadow( QLabel::Raised ); + languageChange(); + resize( QSize(516, 238).expandedTo(minimumSizeHint()) ); + clearWState( WState_Polished ); + + // signals and slots connections + connect( buttonOk, SIGNAL( clicked() ), this, SLOT( accept() ) ); +} + +/* + * Destroys the object and frees any allocated resources + */ +AboutAFEdit::~AboutAFEdit() +{ + // no need to delete child widgets, Qt does it all for us +} + +/* + * Sets the strings of the subwidgets using the current + * language. + */ +void AboutAFEdit::languageChange() +{ + setCaption( tr( "About wview Archive File Editor" ) ); + textLabel1->setText( tr( "wview Archive File Editor" ) ); + buttonOk->setText( tr( "&OK" ) ); + buttonOk->setAccel( QKeySequence( QString::null ) ); + VersionLabel_2->setText( tr( "(c) 2007, Mark Teel" ) ); + VersionLabel->setText( QString::null ); +} + diff --git a/afedit/src/.ui/aboutafedit.h b/afedit/src/.ui/aboutafedit.h new file mode 100644 index 0000000..40ac0a8 --- /dev/null +++ b/afedit/src/.ui/aboutafedit.h @@ -0,0 +1,44 @@ +/**************************************************************************** +** Form interface generated from reading ui file 'aboutafedit.ui' +** +** Created: Mon Jul 9 22:50:53 2007 +** by: The User Interface Compiler ($Id: qt/main.cpp 3.3.7 edited Aug 31 2005 $) +** +** WARNING! All changes made in this file will be lost! +****************************************************************************/ + +#ifndef ABOUTAFEDIT_H +#define ABOUTAFEDIT_H + +#include +#include + +class QVBoxLayout; +class QHBoxLayout; +class QGridLayout; +class QSpacerItem; +class QLabel; +class QPushButton; + +class AboutAFEdit : public QDialog +{ + Q_OBJECT + +public: + AboutAFEdit( QWidget* parent = 0, const char* name = 0, bool modal = FALSE, WFlags fl = 0 ); + ~AboutAFEdit(); + + QLabel* textLabel1; + QPushButton* buttonOk; + QLabel* VersionLabel_2; + QLabel* VersionLabel; + +protected: + QHBoxLayout* Layout1; + +protected slots: + virtual void languageChange(); + +}; + +#endif // ABOUTAFEDIT_H diff --git a/afedit/src/.ui/mainform.cpp b/afedit/src/.ui/mainform.cpp new file mode 100644 index 0000000..b9f5031 --- /dev/null +++ b/afedit/src/.ui/mainform.cpp @@ -0,0 +1,146 @@ +/**************************************************************************** +** Form implementation generated from reading ui file 'mainform.ui' +** +** Created: Mon Jul 9 22:50:55 2007 +** by: The User Interface Compiler ($Id: qt/main.cpp 3.3.7 edited Aug 31 2005 $) +** +** WARNING! All changes made in this file will be lost! +****************************************************************************/ + +#include "mainform.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../mainform.ui.h" +/* + * Constructs a MainForm as a child of 'parent', with the + * name 'name' and widget flags set to 'f'. + * + */ +MainForm::MainForm( QWidget* parent, const char* name, WFlags fl ) + : QMainWindow( parent, name, fl ) +{ + (void)statusBar(); + if ( !name ) + setName( "MainForm" ); + setSizePolicy( QSizePolicy( (QSizePolicy::SizeType)7, (QSizePolicy::SizeType)7, 0, 0, sizePolicy().hasHeightForWidth() ) ); + setMinimumSize( QSize( 21, 114 ) ); + setCentralWidget( new QWidget( this, "qt_central_widget" ) ); + + textLabel1 = new QLabel( centralWidget(), "textLabel1" ); + textLabel1->setGeometry( QRect( 10, 850, 91, 21 ) ); + textLabel1->setAlignment( int( QLabel::AlignVCenter | QLabel::AlignRight ) ); + + archiveTable = new QTable( centralWidget(), "archiveTable" ); + archiveTable->setGeometry( QRect( 0, 0, 1320, 840 ) ); + archiveTable->setMouseTracking( FALSE ); + archiveTable->setLineWidth( 2 ); + archiveTable->setNumRows( 8928 ); + archiveTable->setNumCols( 67 ); + archiveTable->setSelectionMode( QTable::Single ); + + lineEdit_Hint = new QLineEdit( centralWidget(), "lineEdit_Hint" ); + lineEdit_Hint->setGeometry( QRect( 120, 850, 1120, 21 ) ); + lineEdit_Hint->setAcceptDrops( FALSE ); + lineEdit_Hint->setReadOnly( TRUE ); + + // actions + fileOpenAction = new QAction( this, "fileOpenAction" ); + fileOpenAction->setIconSet( QIconSet( QPixmap::fromMimeSource( "" ) ) ); + fileSaveAction = new QAction( this, "fileSaveAction" ); + fileSaveAction->setIconSet( QIconSet( QPixmap::fromMimeSource( "" ) ) ); + fileSaveAsAction = new QAction( this, "fileSaveAsAction" ); + fileExitAction = new QAction( this, "fileExitAction" ); + helpContentsAction = new QAction( this, "helpContentsAction" ); + helpAboutAction = new QAction( this, "helpAboutAction" ); + + + // toolbars + + + // menubar + MenuBar = new QMenuBar( this, "MenuBar" ); + + + fileMenu = new QPopupMenu( this ); + fileOpenAction->addTo( fileMenu ); + fileSaveAction->addTo( fileMenu ); + fileSaveAsAction->addTo( fileMenu ); + fileMenu->insertSeparator(); + fileExitAction->addTo( fileMenu ); + MenuBar->insertItem( QString(""), fileMenu, 1 ); + + helpMenu = new QPopupMenu( this ); + helpContentsAction->addTo( helpMenu ); + helpMenu->insertSeparator(); + helpAboutAction->addTo( helpMenu ); + MenuBar->insertItem( QString(""), helpMenu, 2 ); + + languageChange(); + resize( QSize(1320, 946).expandedTo(minimumSizeHint()) ); + clearWState( WState_Polished ); + + // signals and slots connections + connect( fileOpenAction, SIGNAL( activated() ), this, SLOT( fileOpen() ) ); + connect( fileSaveAction, SIGNAL( activated() ), this, SLOT( fileSave() ) ); + connect( fileSaveAsAction, SIGNAL( activated() ), this, SLOT( fileSaveAs() ) ); + connect( fileExitAction, SIGNAL( activated() ), this, SLOT( fileExit() ) ); + connect( helpContentsAction, SIGNAL( activated() ), this, SLOT( helpContents() ) ); + connect( helpAboutAction, SIGNAL( activated() ), this, SLOT( helpAbout() ) ); + connect( archiveTable, SIGNAL( selectionChanged() ), this, SLOT( archiveTable_selectionChanged() ) ); + connect( archiveTable, SIGNAL( valueChanged(int,int) ), this, SLOT( archiveTable_valueChanged(int,int) ) ); + init(); +} + +/* + * Destroys the object and frees any allocated resources + */ +MainForm::~MainForm() +{ + // no need to delete child widgets, Qt does it all for us +} + +/* + * Sets the strings of the subwidgets using the current + * language. + */ +void MainForm::languageChange() +{ + setCaption( tr( "wview Archive File Editor" ) ); + textLabel1->setText( tr( "Hint:" ) ); + fileOpenAction->setText( tr( "Open" ) ); + fileOpenAction->setMenuText( tr( "&Open..." ) ); + fileOpenAction->setAccel( tr( "Ctrl+O" ) ); + fileSaveAction->setText( tr( "Save" ) ); + fileSaveAction->setMenuText( tr( "&Save" ) ); + fileSaveAction->setAccel( tr( "Ctrl+S" ) ); + fileSaveAsAction->setText( tr( "Save As" ) ); + fileSaveAsAction->setMenuText( tr( "Save &As..." ) ); + fileSaveAsAction->setAccel( QString::null ); + fileExitAction->setText( tr( "Exit" ) ); + fileExitAction->setMenuText( tr( "E&xit" ) ); + fileExitAction->setAccel( QString::null ); + helpContentsAction->setText( tr( "Contents" ) ); + helpContentsAction->setMenuText( tr( "&Contents..." ) ); + helpContentsAction->setAccel( QString::null ); + helpAboutAction->setText( tr( "About" ) ); + helpAboutAction->setMenuText( tr( "&About" ) ); + helpAboutAction->setAccel( QString::null ); + if (MenuBar->findItem(1)) + MenuBar->findItem(1)->setText( tr( "&File" ) ); + if (MenuBar->findItem(2)) + MenuBar->findItem(2)->setText( tr( "&Help" ) ); +} + diff --git a/afedit/src/.ui/mainform.h b/afedit/src/.ui/mainform.h new file mode 100644 index 0000000..639d230 --- /dev/null +++ b/afedit/src/.ui/mainform.h @@ -0,0 +1,69 @@ +/**************************************************************************** +** Form interface generated from reading ui file 'mainform.ui' +** +** Created: Mon Jul 9 22:50:53 2007 +** by: The User Interface Compiler ($Id: qt/main.cpp 3.3.7 edited Aug 31 2005 $) +** +** WARNING! All changes made in this file will be lost! +****************************************************************************/ + +#ifndef MAINFORM_H +#define MAINFORM_H + +#include +#include + +class QVBoxLayout; +class QHBoxLayout; +class QGridLayout; +class QSpacerItem; +class QAction; +class QActionGroup; +class QToolBar; +class QPopupMenu; +class QLabel; +class QTable; +class QLineEdit; + +class MainForm : public QMainWindow +{ + Q_OBJECT + +public: + MainForm( QWidget* parent = 0, const char* name = 0, WFlags fl = WType_TopLevel ); + ~MainForm(); + + QLabel* textLabel1; + QTable* archiveTable; + QLineEdit* lineEdit_Hint; + QMenuBar *MenuBar; + QPopupMenu *fileMenu; + QPopupMenu *helpMenu; + QAction* fileOpenAction; + QAction* fileSaveAction; + QAction* fileSaveAsAction; + QAction* fileExitAction; + QAction* helpContentsAction; + QAction* helpAboutAction; + +public slots: + virtual void fileOpen(); + virtual void fileSave(); + virtual void fileSaveAs(); + virtual void fileExit(); + virtual void helpContents(); + virtual void helpAbout(); + virtual void archiveTable_selectionChanged(); + virtual void archiveTable_valueChanged( int row, int col ); + +protected: + +protected slots: + virtual void languageChange(); + +private: + virtual void init(); + +}; + +#endif // MAINFORM_H diff --git a/afedit/src/.ui/qmake_image_collection.cpp b/afedit/src/.ui/qmake_image_collection.cpp new file mode 100644 index 0000000..bcfc2f9 --- /dev/null +++ b/afedit/src/.ui/qmake_image_collection.cpp @@ -0,0 +1,295 @@ +/**************************************************************************** +** Image collection for project 'afedit'. +** +** Generated from reading image files: +** ../images/filenew +** ../images/fileopen +** ../images/filesave +** ../images/print +** ../images/undo +** ../images/redo +** ../images/editcut +** ../images/editcopy +** ../images/editpaste +** ../images/searchfind +** +** Created: Mon Jul 9 22:50:57 2007 +** by: The User Interface Compiler ($Id: qt/embed.cpp 3.3.7 edited Aug 31 2005 $) +** +** WARNING! All changes made in this file will be lost! +****************************************************************************/ + +#include +#include +#include +#include + +// ../images/filenew +static const unsigned char image_0_data[] = { + 0x00,0x00,0x07,0x90,0x78,0x9c,0xed,0xcd,0xe1,0x09,0x00,0x20,0x08,0x84, + 0x51,0x27,0x72,0x22,0xf7,0x71,0x2f,0x17,0x2b,0x5c,0xc0,0xab,0x0b,0xa2, + 0xc0,0x0f,0xee,0x97,0xf0,0x14,0xb9,0xd6,0x58,0xdc,0xb6,0x5b,0x95,0x77, + 0x55,0x65,0x6c,0xe8,0x9a,0x19,0x63,0x43,0xd7,0xdd,0x19,0x1b,0xba,0x11, + 0xc1,0xd8,0xd0,0xcd,0x08,0x1b,0xba,0xc5,0x68,0x17,0xfc,0x6b,0xb7,0xdd, + 0x76,0x1f,0x71,0x0f,0xf6,0x6d,0x13,0xc0,0x23,0x49,0x3f +}; + +// ../images/fileopen +static const unsigned char image_1_data[] = { + 0x00,0x00,0x07,0x90,0x78,0x9c,0xed,0x90,0xdb,0x0e,0xc0,0x20,0x08,0x43, + 0xf9,0xf6,0x65,0xff,0xcd,0x1e,0x96,0x4c,0x71,0x60,0x0a,0xa9,0x3e,0xd9, + 0xa4,0xc1,0x78,0x39,0x60,0x45,0x8e,0x8a,0xd2,0xc1,0xde,0x79,0x85,0x19, + 0xf5,0x19,0xd7,0x2c,0xcd,0xfe,0xc0,0x60,0x2a,0xb0,0x5f,0x61,0xb6,0x4c, + 0xf4,0xd5,0x57,0xfd,0xbb,0xd1,0xfb,0xb0,0x9f,0x61,0xa2,0x15,0xe0,0xa6, + 0x99,0x20,0x77,0xfb,0xbc,0x78,0xce,0xae,0xa7,0xcc,0xeb,0xce,0xb9,0x67, + 0xf3,0x99,0xff,0x7c,0x39,0x4c,0x9b,0x2f,0x8f,0xd9,0xe6,0xe5,0x32,0xc5, + 0xe4,0xcc,0x63,0xca,0x22,0xa6,0x9d,0x37,0xef,0xed,0x7a,0x00,0x84,0x95, + 0xf6,0x66 +}; + +// ../images/filesave +static const unsigned char image_2_data[] = { + 0x00,0x00,0x07,0x90,0x78,0x9c,0x63,0x60,0xa0,0x29,0xf8,0x4f,0x65,0x8c, + 0x30,0xb7,0xa9,0x05,0x82,0x81,0xec,0x83,0x07,0x0f,0x92,0x84,0x91,0xf5, + 0x0c,0x56,0x73,0x2f,0x6e,0x3d,0x85,0x6e,0x16,0x49,0xe1,0x00,0xd2,0x8f, + 0x6c,0x06,0x2e,0xf7,0x22,0xeb,0x1f,0x48,0x73,0x89,0x0d,0xdf,0x51,0x73, + 0x89,0x34,0x17,0x21,0x47,0x3c,0xc6,0xd4,0x8b,0xdd,0x5c,0x4a,0xf0,0x40, + 0x98,0x4b,0x4e,0x59,0x43,0x25,0x73,0x69,0x15,0x6f,0xa3,0xe6,0x92,0x64, + 0x2e,0x03,0x59,0x69,0x00,0x3b,0xa6,0x29,0x00,0x00,0xa4,0x3b,0x14,0x82 +}; + +// ../images/print +static const unsigned char image_3_data[] = { + 0x00,0x00,0x07,0x90,0x78,0x9c,0x95,0x94,0x21,0x90,0x83,0x30,0x10,0x45, + 0x4f,0x9e,0x44,0x62,0x91,0x48,0x24,0x16,0x79,0xf2,0x24,0xb6,0x12,0x59, + 0x59,0x8b,0x8c,0x8c,0xc4,0x46,0x22,0x91,0x58,0x24,0x32,0x12,0x8b,0x44, + 0x56,0xee,0xed,0x5f,0x12,0x9a,0x52,0x7a,0x53,0xc2,0xfc,0x59,0x48,0xc3, + 0xcb,0xdf,0x25,0xdb,0xaf,0xaf,0xe3,0xd1,0x75,0x1d,0xf5,0x7d,0x4f,0xe3, + 0x38,0x3e,0x69,0x18,0x06,0x11,0x7e,0x83,0xda,0xb6,0x95,0xf8,0x06,0xf3, + 0x32,0xb0,0x76,0x59,0x16,0x9a,0xe7,0xf9,0x45,0x98,0xf7,0x9a,0xa6,0xe9, + 0x34,0xd7,0x7b,0x83,0xf6,0x7e,0x43,0xdf,0x88,0x9f,0x72,0x7d,0x1d,0x42, + 0x85,0x73,0xb8,0x87,0xce,0xd4,0x01,0xfb,0x63,0x7d,0xe8,0x67,0x5f,0x57, + 0x3f,0x0f,0xf6,0x99,0x1a,0xec,0xfd,0xed,0xe5,0xf7,0x39,0xcb,0xc5,0xfa, + 0xbd,0xbf,0x90,0xe9,0x23,0xf2,0xfa,0x94,0xfb,0x8e,0x77,0xa4,0x33,0x7e, + 0xe1,0x01,0xdf,0x1e,0x67,0x0a,0xe7,0xe8,0x48,0xd6,0x5a,0x89,0xc6,0x18, + 0x51,0xf7,0x8f,0x6f,0xec,0xdf,0x34,0x0d,0x5d,0xab,0xab,0xf8,0xc0,0xbb, + 0xe0,0x1f,0x45,0xcf,0xd7,0x4a,0x53,0xf9,0x5b,0x52,0x51,0x14,0x54,0x55, + 0xd5,0x8b,0x7f,0x61,0x6a,0x4d,0xf5,0xad,0xa6,0xfa,0x7a,0xa3,0xdb,0x3f, + 0x52,0xbc,0x46,0xdf,0x14,0xe9,0x5a,0xb1,0x87,0x8a,0x8a,0x3c,0xa7,0x34, + 0x49,0x28,0x89,0x63,0xe1,0xc3,0x7f,0xe8,0x73,0xb2,0x13,0x2d,0xe8,0x29, + 0x78,0xb1,0x90,0x7d,0x92,0x1d,0x39,0xb2,0xcc,0xd8,0x50,0x39,0x96,0xa4, + 0x07,0x4d,0xa6,0x31,0xc2,0x4a,0xe2,0x44,0x14,0x47,0xb1,0xec,0x03,0xa6, + 0x61,0xe6,0x32,0x2f,0xf4,0xc9,0x60,0x3a,0xa5,0x7c,0x7d,0xf3,0x85,0x88, + 0xba,0xc0,0x7b,0x96,0x65,0xe2,0x37,0x8a,0x22,0xe1,0xb7,0xa6,0xa5,0xfb, + 0xfd,0xfe,0x11,0x13,0x83,0xb3,0xa1,0x0b,0x55,0x94,0xf1,0xc5,0x55,0x59, + 0xe7,0x38,0x37,0xd4,0x07,0x5c,0x4e,0xdf,0x71,0x8d,0xcb,0x91,0xfb,0x7f, + 0x18,0x25,0xda,0x30,0x7f,0xae,0xc9,0x3c,0x3d,0x6a,0xb3,0x46,0x4b,0xbd, + 0xed,0xa5,0x2e,0xfe,0x3d,0xad,0x94,0xd4,0x60,0xe5,0xc6,0xf2,0x3d,0xbb, + 0xb6,0x93,0x73,0x02,0xef,0xd8,0xa7,0xe5,0x9a,0x21,0xa2,0x76,0xad,0x93, + 0x71,0xf3,0x8f,0xb9,0x96,0x8c,0x6e,0xe4,0x5b,0x83,0x59,0x5d,0x2a,0xe6, + 0x46,0x9b,0xdf,0x95,0xdb,0xae,0xdc,0x80,0xf7,0x50,0x13,0xc4,0x40,0xc2, + 0x5c,0x85,0x73,0x11,0x72,0x63,0xf8,0xe5,0xfd,0x36,0x4f,0xef,0x38,0x78, + 0x7f,0xc7,0x82,0xf0,0x0c,0x5f,0xaa,0xae,0x1d,0x77,0xad,0xc3,0xef,0xcf, + 0x8f,0xcc,0x3f,0x79,0xdc,0x31,0x0e,0x9f,0xb7,0xfc,0xd7,0x1a,0x28,0x77, + 0x8e,0xe1,0x17,0x5e,0xd1,0xaf,0xf0,0x8b,0xfd,0xf0,0x1b,0xf2,0x51,0xca, + 0x45,0x3c,0xef,0xef,0x45,0x7a,0x8b,0x8d,0xbf,0xf7,0x75,0x70,0xf9,0xa3, + 0x27,0xd0,0x77,0xe8,0x5b,0xdf,0x2f,0x09,0xc7,0x54,0x94,0x6e,0xca,0x52, + 0x28,0x13,0xe5,0x99,0x57,0xee,0xf4,0x98,0xf7,0xcc,0xfd,0x7f,0xcd,0xa5, + 0xe4,0x3e,0xcf,0x0b,0x51,0x9e,0xbb,0x77,0x02,0xa5,0xcc,0x17,0xf1,0xbe, + 0xf0,0x06,0x45,0xdf,0x91,0xf4,0x1b,0x6a,0x15,0xf2,0xfe,0x00,0x71,0x07, + 0x39,0xaf +}; + +// ../images/undo +static const unsigned char image_4_data[] = { + 0x00,0x00,0x07,0x90,0x78,0x9c,0x63,0x60,0x18,0x05,0xc3,0x1d,0xb4,0x30, + 0x30,0xfc,0xc7,0x8a,0x9b,0x5a,0xfe,0xe3,0xd3,0x43,0xb2,0x79,0x68,0x18, + 0x9f,0x5e,0x7c,0xe6,0x11,0xb4,0x17,0x8b,0xbb,0xd1,0xf5,0x13,0x63,0x1e, + 0x21,0x77,0xe1,0xf3,0x2b,0x31,0x66,0x92,0x6a,0x2e,0xb1,0x66,0x0e,0x16, + 0xf7,0xd2,0x32,0x7c,0x89,0x31,0x9b,0x98,0x74,0x4c,0x0a,0x00,0x99,0x43, + 0x6e,0x7c,0xe0,0x35,0x97,0x06,0x66,0x8e,0x82,0xe1,0x09,0x00,0xae,0x76, + 0x84,0x98 +}; + +// ../images/redo +static const unsigned char image_5_data[] = { + 0x00,0x00,0x07,0x90,0x78,0x9c,0x63,0x60,0x18,0x05,0xc3,0x01,0xb4,0x34, + 0xb5,0xfc,0x6f,0x61,0x60,0xc0,0x8a,0xc9,0x36,0x13,0x87,0x79,0xc4,0x9a, + 0x8f,0x4d,0x0e,0xd9,0x9d,0xc4,0xda,0x8b,0x4d,0x0e,0x97,0x1e,0x52,0xfd, + 0x85,0x8d,0x4f,0xae,0xb9,0xd8,0xdc,0x4e,0x2d,0x73,0x71,0x99,0x3d,0x18, + 0xdd,0x4b,0x4a,0x5a,0x21,0x26,0x3d,0x10,0x32,0x13,0x97,0x5e,0x14,0x35, + 0x40,0x7b,0x88,0x71,0x3b,0xb1,0x80,0x1a,0xf9,0x6b,0x14,0x8c,0x02,0x74, + 0x00,0x00,0x3b,0x0a,0x84,0x98 +}; + +// ../images/editcut +static const unsigned char image_6_data[] = { + 0x00,0x00,0x07,0x90,0x78,0x9c,0xe5,0xd2,0x41,0x0a,0x00,0x20,0x08,0x04, + 0xc0,0xde,0xee,0xc7,0xed,0x92,0x20,0xa2,0x91,0xb6,0xd2,0xa1,0xbd,0x24, + 0x85,0x83,0x84,0x63,0x3c,0x09,0x27,0xef,0x7f,0x72,0x19,0xe4,0x5a,0xe3, + 0xd6,0x8c,0x1c,0xa4,0xcb,0x4e,0x8d,0xb4,0x91,0x66,0xa7,0x2b,0x36,0x34, + 0xb4,0x66,0x25,0xa0,0x4d,0xca,0x93,0xfa,0xd6,0xd7,0x9e,0x3e,0x6d,0x5d, + 0x31,0x23,0xb7,0x62,0x47,0xfd,0x9e,0x73,0x6a,0xef,0x7a,0xbb,0x5c,0xef, + 0xbd,0xc3,0xcd,0xfc,0xef,0xc9,0x3c,0xd5,0x5d,0x43,0xed,0x69,0x47,0x26, + 0xd9,0x6b,0x53,0x0d +}; + +// ../images/editcopy +static const unsigned char image_7_data[] = { + 0x00,0x00,0x07,0x90,0x78,0x9c,0xd5,0x91,0x41,0x0a,0xc0,0x20,0x0c,0x04, + 0x7d,0x98,0xaf,0xcb,0xd5,0x37,0x8b,0xc5,0x43,0x21,0x48,0x77,0x13,0x13, + 0x7b,0x70,0x21,0x97,0xac,0x8c,0x23,0x96,0x72,0x5d,0x86,0x31,0x61,0x2e, + 0x4a,0x92,0xbd,0x32,0x86,0xde,0xa9,0xee,0xa8,0xef,0xe2,0x7e,0xcc,0x57, + 0x8c,0x89,0xfa,0x0a,0xe9,0x0d,0xf6,0xa7,0xef,0xbb,0x17,0xf2,0x9e,0xd9, + 0xf5,0x5a,0x11,0x1b,0xfa,0x68,0x2e,0xea,0x7a,0x6b,0x88,0x9d,0xf2,0x9d, + 0x01,0xec,0x94,0x2f,0xf9,0xcb,0xb0,0x2f,0xba,0x2b,0xeb,0xeb,0xe1,0xee, + 0xfa,0xb2,0xf3,0x7f,0xfb,0xb2,0x49,0xf8,0xd2,0x24,0x7c,0x5d,0xdc,0x9b, + 0x7c,0x77,0xc7,0xc3,0x8d,0xe6,0x01,0xba,0xca,0xfb,0x3b +}; + +// ../images/editpaste +static const unsigned char image_8_data[] = { + 0x00,0x00,0x07,0x90,0x78,0x9c,0xc5,0x94,0x0d,0x0a,0xc0,0x20,0x08,0x46, + 0x3d,0xfb,0xe8,0x66,0x83,0xae,0xe5,0x2a,0x68,0x6b,0xa2,0x96,0xda,0x98, + 0xf0,0x21,0xf4,0xf3,0x7a,0x4c,0x18,0xc0,0x2f,0x85,0x4c,0x76,0x30,0x10, + 0x10,0x9f,0x48,0x67,0x22,0x4c,0x3b,0xbb,0xad,0xa7,0x23,0x21,0x94,0xb4, + 0x3e,0x72,0xe9,0x3b,0xda,0x79,0xc2,0xa5,0x67,0xf2,0x99,0xa7,0x11,0xd8, + 0x6f,0xdf,0xbe,0x67,0xe4,0xd2,0x7b,0x33,0xdf,0xd5,0x58,0x7c,0xbd,0x7d, + 0xea,0xeb,0xed,0x56,0xdf,0xfa,0xee,0x42,0xac,0xbe,0xf5,0x8e,0x56,0x7d, + 0x3f,0x39,0x7c,0xfb,0x3d,0x9a,0x91,0xcb,0xb0,0xb7,0xf8,0x32,0xec,0x6d, + 0xbe,0x84,0x1d,0xf6,0x15,0xe7,0x18,0xf0,0x95,0xde,0x8a,0xfa,0x6a,0x5c, + 0xaf,0xaf,0xf6,0xcd,0xef,0xd9,0x49,0xff,0x00,0xbf,0xaf,0x5a,0xab,0x9e, + 0xcc,0xdc,0x3e,0xa9,0x0b,0xdb,0x19,0x41,0x63 +}; + +// ../images/searchfind +static const unsigned char image_9_data[] = { + 0x00,0x00,0x07,0x90,0x78,0x9c,0xd5,0x94,0xa1,0x92,0xe3,0x30,0x10,0x44, + 0xf7,0x57,0x02,0xf3,0x09,0xa1,0xf9,0x0c,0x53,0x43,0x43,0x41,0x53,0x41, + 0x41,0x41,0x51,0x41,0x41,0x41,0x51,0x41,0x41,0x43,0x51,0x43,0x43,0xc3, + 0xbe,0xe9,0x71,0x16,0xdc,0xd5,0x55,0xd9,0x7b,0x9b,0xda,0xaa,0x73,0x55, + 0x57,0x12,0x47,0x7e,0xea,0xe9,0x19,0xf9,0xe3,0xe3,0xff,0xbd,0x72,0xce, + 0xf0,0xde,0x63,0x9e,0x67,0x18,0x33,0xa9,0xf8,0x3d,0x84,0x80,0x52,0x0a, + 0xfe,0x85,0x99,0x52,0x12,0x86,0x81,0x0f,0x16,0xb5,0x25,0xb4,0x7e,0x28, + 0x15,0xee,0x73,0xf0,0xb9,0xef,0x57,0x99,0xc3,0x30,0x20,0x44,0x8f,0x0d, + 0x4d,0xb5,0xee,0x15,0x7d,0x2b,0x58,0xb6,0x8c,0xba,0x46,0xcc,0x8e,0xfe, + 0xcd,0x65,0xdf,0x5c,0x67,0xc4,0x67,0x2e,0x41,0x39,0xe4,0xed,0xbd,0x02, + 0x4b,0xc1,0x5e,0x0b,0x7a,0x8e,0xc8,0xcd,0xa3,0x2c,0x1e,0xd6,0x19,0x38, + 0xe7,0x2e,0x71,0x99,0x1d,0x6b,0xdf,0xb1,0x28,0x17,0xbd,0xa1,0x97,0x8c, + 0xd6,0xa2,0x6a,0x93,0x5a,0x16,0x1f,0x90,0xaa,0x13,0x79,0x4c,0xd3,0x78, + 0x29,0x0f,0x6b,0x67,0xd4,0x25,0x69,0xed,0xbd,0x67,0xf1,0x97,0x0e,0x7f, + 0xab,0xb0,0x9a,0x53,0xbf,0xdd,0x47,0xc4,0x64,0xf5,0xfe,0x6c,0x27,0xed, + 0xe3,0x19,0x97,0xbd,0x5a,0x56,0xa9,0x77,0x2f,0x92,0x63,0x50,0x95,0x1e, + 0x90,0xa5,0xee,0xd6,0x02,0x36,0xe6,0x60,0x2d,0x42,0x91,0xbe,0x89,0x5f, + 0xeb,0x0d,0xac,0xfc,0xbe,0xc2,0x65,0xdf,0xd9,0x1f,0xf2,0x4a,0xf7,0xca, + 0x5c,0xca,0x8b,0xe9,0x1d,0x7c,0x9a,0x11,0x85,0x1b,0xb3,0x83,0xbb,0xc8, + 0x65,0xcf,0x98,0x43,0x5b,0x93,0xf2,0x54,0xaf,0xfa,0x8b,0xf4,0x88,0x3e, + 0x43,0x15,0x65,0xb2,0xad,0xe6,0x70,0xa5,0x77,0x5c,0x43,0x1f,0xb5,0x47, + 0xe5,0x69,0x7f,0x9a,0x45,0x49,0xf2,0x19,0x59,0xbf,0xcc,0x74,0x3e,0x14, + 0xc4,0xf7,0x64,0x06,0x70,0x2e,0xcf,0xb8,0x9f,0xe7,0xa1,0x48,0x96,0xb1, + 0xda,0x43,0xe2,0xb1,0xc9,0x2c,0x33,0x57,0x97,0x46,0xc9,0x61,0x52,0x59, + 0xff,0xb5,0x19,0xa6,0x67,0xce,0x7d,0xac,0x47,0x8e,0x5a,0x7b,0x34,0x9a, + 0x25,0x7d,0xda,0x30,0xa8,0xc6,0x71,0xd4,0x59,0x68,0x4b,0x53,0xcf,0x67, + 0x7c,0xfe,0xaf,0xef,0x04,0x39,0xaf,0x21,0x8b,0x5f,0x4a,0xb2,0x64,0xa6, + 0xec,0x19,0x67,0x40,0xcf,0xe3,0x6b,0xbe,0xc8,0xe4,0x1e,0xbc,0x77,0x96, + 0x09,0xd9,0xf4,0xcd,0xb9,0x27,0x9f,0xfd,0xa1,0x26,0x33,0xca,0xbd,0xe9, + 0xb7,0xe7,0x6b,0xad,0xea,0xe3,0x7e,0xbf,0xe3,0xf1,0x78,0xe8,0x73,0x67, + 0xde,0x79,0x96,0xe8,0x8b,0x6b,0xf9,0x6e,0x8b,0x31,0x82,0x9c,0xbf,0xad, + 0xe5,0x7f,0xcf,0xe7,0x53,0xf9,0x7f,0xee,0xfd,0xdd,0x4b,0xdf,0x2f,0xd2, + 0xcb,0xdb,0xed,0xa6,0xde,0xe9,0xe5,0x5d,0x6c,0x5e,0xac,0x91,0x5c,0xf2, + 0xe9,0xfd,0x9d,0x6c,0x66,0xf8,0xd9,0xcf,0x77,0x72,0x7f,0xe2,0xfa,0x05, + 0x8a,0xcd,0x52,0x07 +}; + +static struct EmbedImage { + int width, height, depth; + const unsigned char *data; + ulong compressed; + int numColors; + const QRgb *colorTable; + bool alpha; + const char *name; +} embed_image_vec[] = { + { 22, 22, 32, (const unsigned char*)image_0_data, 81, 0, 0, TRUE, "filenew" }, + { 22, 22, 32, (const unsigned char*)image_1_data, 114, 0, 0, TRUE, "fileopen" }, + { 22, 22, 32, (const unsigned char*)image_2_data, 98, 0, 0, TRUE, "filesave" }, + { 22, 22, 32, (const unsigned char*)image_3_data, 520, 0, 0, TRUE, "print" }, + { 22, 22, 32, (const unsigned char*)image_4_data, 86, 0, 0, TRUE, "undo" }, + { 22, 22, 32, (const unsigned char*)image_5_data, 90, 0, 0, TRUE, "redo" }, + { 22, 22, 32, (const unsigned char*)image_6_data, 102, 0, 0, TRUE, "editcut" }, + { 22, 22, 32, (const unsigned char*)image_7_data, 123, 0, 0, TRUE, "editcopy" }, + { 22, 22, 32, (const unsigned char*)image_8_data, 149, 0, 0, TRUE, "editpaste" }, + { 22, 22, 32, (const unsigned char*)image_9_data, 480, 0, 0, TRUE, "searchfind" }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0 } +}; + +static QImage uic_findImage( const QString& name ) +{ + for ( int i=0; embed_image_vec[i].data; i++ ) { + if ( QString::fromUtf8(embed_image_vec[i].name) == name ) { + QByteArray baunzip; + baunzip = qUncompress( embed_image_vec[i].data, + embed_image_vec[i].compressed ); + QImage img((uchar*)baunzip.data(), + embed_image_vec[i].width, + embed_image_vec[i].height, + embed_image_vec[i].depth, + (QRgb*)embed_image_vec[i].colorTable, + embed_image_vec[i].numColors, + QImage::BigEndian + ); + img = img.copy(); + if ( embed_image_vec[i].alpha ) + img.setAlphaBuffer(TRUE); + return img; + } + } + return QImage(); +} + +class MimeSourceFactory_afedit : public QMimeSourceFactory +{ +public: + MimeSourceFactory_afedit() {} + ~MimeSourceFactory_afedit() {} + const QMimeSource* data( const QString& abs_name ) const { + const QMimeSource* d = QMimeSourceFactory::data( abs_name ); + if ( d || abs_name.isNull() ) return d; + QImage img = uic_findImage( abs_name ); + if ( !img.isNull() ) + ((QMimeSourceFactory*)this)->setImage( abs_name, img ); + return QMimeSourceFactory::data( abs_name ); + }; +}; + +static QMimeSourceFactory* factory = 0; + +void qInitImages_afedit() +{ + if ( !factory ) { + factory = new MimeSourceFactory_afedit; + QMimeSourceFactory::defaultFactory()->addFactory( factory ); + } +} + +void qCleanupImages_afedit() +{ + if ( factory ) { + QMimeSourceFactory::defaultFactory()->removeFactory( factory ); + delete factory; + factory = 0; + } +} + +class StaticInitImages_afedit +{ +public: + StaticInitImages_afedit() { qInitImages_afedit(); } +#if defined(Q_OS_SCO) || defined(Q_OS_UNIXWARE) + ~StaticInitImages_afedit() { } +#else + ~StaticInitImages_afedit() { qCleanupImages_afedit(); } +#endif +}; + +static StaticInitImages_afedit staticImages; diff --git a/afedit/src/Makefile b/afedit/src/Makefile new file mode 100644 index 0000000..50288b2 --- /dev/null +++ b/afedit/src/Makefile @@ -0,0 +1,206 @@ +############################################################################# +# Makefile for building: ../bin/afedit +# Generated by qmake (1.07a) (Qt 3.3.7) on: Mon Jul 9 22:49:51 2007 +# Project: src.pro +# Template: app +# Command: $(QMAKE) -o Makefile src.pro +############################################################################# + +####### Compiler, tools and options + +CC = gcc +CXX = g++ +LEX = flex +YACC = yacc +CFLAGS = -pipe -DAFEDIT_VERSION="0.50" -Wall -W -O2 -D_REENTRANT -DQT_NO_DEBUG -DQT_THREAD_SUPPORT -DQT_SHARED -DQT_TABLET_SUPPORT +CXXFLAGS = -pipe -DAFEDIT_VERSION="0.50" -Wall -W -O2 -D_REENTRANT -DQT_NO_DEBUG -DQT_THREAD_SUPPORT -DQT_SHARED -DQT_TABLET_SUPPORT +LEXFLAGS = +YACCFLAGS= -d +INCPATH = -I/usr/share/qt3/mkspecs/default -I. -I/usr/include/qt3 -I.ui/ -I. -I.moc/ +LINK = g++ +LFLAGS = +LIBS = $(SUBLIBS) -L/usr/share/qt3/lib -L/usr/X11R6/lib -lqt-mt -lXext -lX11 -lm -lpthread +AR = ar cqs +RANLIB = +MOC = /usr/share/qt3/bin/moc +UIC = /usr/share/qt3/bin/uic +QMAKE = qmake +TAR = tar -cf +GZIP = gzip -9f +COPY = cp -f +COPY_FILE= $(COPY) +COPY_DIR = $(COPY) -r +INSTALL_FILE= $(COPY_FILE) +INSTALL_DIR = $(COPY_DIR) +DEL_FILE = rm -f +SYMLINK = ln -sf +DEL_DIR = rmdir +MOVE = mv -f +CHK_DIR_EXISTS= test -d +MKDIR = mkdir -p + +####### Output directory + +OBJECTS_DIR = .obj/ + +####### Files + +HEADERS = mainform.ui.h \ + afedit.h \ + formutils.h \ + archiveFile.h +SOURCES = main.cpp \ + formutils.cpp \ + archiveFile.cpp +OBJECTS = .obj/main.o \ + .obj/formutils.o \ + .obj/archiveFile.o \ + .obj/mainform.o \ + .obj/aboutafedit.o \ + .obj/qmake_image_collection.o +FORMS = mainform.ui \ + aboutafedit.ui +UICDECLS = .ui/mainform.h \ + .ui/aboutafedit.h +UICIMPLS = .ui/mainform.cpp \ + .ui/aboutafedit.cpp +SRCMOC = .moc/moc_mainform.cpp \ + .moc/moc_aboutafedit.cpp +OBJMOC = .obj/moc_mainform.o \ + .obj/moc_aboutafedit.o +DIST = src.pro +QMAKE_TARGET = afedit +DESTDIR = ../bin/ +TARGET = ../bin/afedit + +first: all +####### Implicit rules + +.SUFFIXES: .c .o .cpp .cc .cxx .C + +.cpp.o: + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o $@ $< + +.cc.o: + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o $@ $< + +.cxx.o: + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o $@ $< + +.C.o: + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o $@ $< + +.c.o: + $(CC) -c $(CFLAGS) $(INCPATH) -o $@ $< + +####### Build rules + +all: Makefile $(TARGET) + +$(TARGET): $(UICDECLS) $(OBJECTS) $(OBJMOC) + test -d ../bin/ || mkdir -p ../bin/ + $(LINK) $(LFLAGS) -o $(TARGET) $(OBJECTS) $(OBJMOC) $(OBJCOMP) $(LIBS) + +mocables: $(SRCMOC) +uicables: $(UICDECLS) $(UICIMPLS) + +$(MOC): + ( cd $(QTDIR)/src/moc && $(MAKE) ) + +Makefile: src.pro /usr/share/qt3/mkspecs/default/qmake.conf /usr/share/qt3/lib/libqt-mt.prl + $(QMAKE) -o Makefile src.pro +qmake: + @$(QMAKE) -o Makefile src.pro + +dist: + @mkdir -p .obj/afedit && $(COPY_FILE) --parents $(SOURCES) $(HEADERS) $(FORMS) $(DIST) .obj/afedit/ && $(COPY_FILE) --parents ../images/filenew ../images/fileopen ../images/filesave ../images/print ../images/undo ../images/redo ../images/editcut ../images/editcopy ../images/editpaste ../images/searchfind .obj/afedit/ && $(COPY_FILE) --parents mainform.ui.h .obj/afedit/ && ( cd `dirname .obj/afedit` && $(TAR) afedit.tar afedit && $(GZIP) afedit.tar ) && $(MOVE) `dirname .obj/afedit`/afedit.tar.gz . && $(DEL_FILE) -r .obj/afedit + +mocclean: + -$(DEL_FILE) $(OBJMOC) + -$(DEL_FILE) $(SRCMOC) + +uiclean: + -$(DEL_FILE) $(UICIMPLS) $(UICDECLS) + +yaccclean: +lexclean: +clean: mocclean uiclean + -$(DEL_FILE) $(OBJECTS) + -$(DEL_FILE) .ui/qmake_image_collection.cpp + -$(DEL_FILE) *~ core *.core + + +####### Sub-libraries + +distclean: clean + -$(DEL_FILE) ../bin/$(TARGET) $(TARGET) + + +FORCE: + +####### Compile + +.obj/main.o: main.cpp .ui/mainform.h + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o .obj/main.o main.cpp + +.obj/formutils.o: formutils.cpp formutils.h \ + archiveFile.h \ + afedit.h + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o .obj/formutils.o formutils.cpp + +.obj/archiveFile.o: archiveFile.cpp archiveFile.h \ + afedit.h + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o .obj/archiveFile.o archiveFile.cpp + +.ui/mainform.h: mainform.ui + $(UIC) mainform.ui -o .ui/mainform.h + +.ui/mainform.cpp: .ui/mainform.h mainform.ui mainform.ui.h + $(UIC) mainform.ui -i mainform.h -o .ui/mainform.cpp + +.ui/aboutafedit.h: aboutafedit.ui + $(UIC) aboutafedit.ui -o .ui/aboutafedit.h + +.ui/aboutafedit.cpp: .ui/aboutafedit.h aboutafedit.ui + $(UIC) aboutafedit.ui -i aboutafedit.h -o .ui/aboutafedit.cpp + +.obj/mainform.o: .ui/mainform.cpp mainform.ui.h \ + .ui/mainform.h + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o .obj/mainform.o .ui/mainform.cpp + +.obj/aboutafedit.o: .ui/aboutafedit.cpp .ui/aboutafedit.h + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o .obj/aboutafedit.o .ui/aboutafedit.cpp + +.obj/moc_mainform.o: .moc/moc_mainform.cpp .ui/mainform.h + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o .obj/moc_mainform.o .moc/moc_mainform.cpp + +.obj/moc_aboutafedit.o: .moc/moc_aboutafedit.cpp .ui/aboutafedit.h + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o .obj/moc_aboutafedit.o .moc/moc_aboutafedit.cpp + +.moc/moc_mainform.cpp: $(MOC) .ui/mainform.h + $(MOC) .ui/mainform.h -o .moc/moc_mainform.cpp + +.moc/moc_aboutafedit.cpp: $(MOC) .ui/aboutafedit.h + $(MOC) .ui/aboutafedit.h -o .moc/moc_aboutafedit.cpp + +.obj/qmake_image_collection.o: .ui/qmake_image_collection.cpp + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o .obj/qmake_image_collection.o .ui/qmake_image_collection.cpp + +.ui/qmake_image_collection.cpp: ../images/filenew \ + ../images/fileopen \ + ../images/filesave \ + ../images/print \ + ../images/undo \ + ../images/redo \ + ../images/editcut \ + ../images/editcopy \ + ../images/editpaste \ + ../images/searchfind + $(UIC) -embed afedit ../images/filenew ../images/fileopen ../images/filesave ../images/print ../images/undo ../images/redo ../images/editcut ../images/editcopy ../images/editpaste ../images/searchfind -o .ui/qmake_image_collection.cpp + +####### Install + +install: + +uninstall: + diff --git a/afedit/src/aboutafedit.ui b/afedit/src/aboutafedit.ui new file mode 100644 index 0000000..f1ce512 --- /dev/null +++ b/afedit/src/aboutafedit.ui @@ -0,0 +1,152 @@ + +AboutAFEdit + + + AboutAFEdit + + + + 0 + 0 + 516 + 238 + + + + + 5 + 5 + 0 + 0 + + + + WindowOrigin + + + About wview Archive File Editor + + + true + + + true + + + + textLabel1 + + + + 80 + 60 + 350 + 41 + + + + + 20 + + + + wview Archive File Editor + + + + + Layout1 + + + + 420 + 190 + 80 + 33 + + + + + unnamed + + + 0 + + + 6 + + + + buttonOk + + + &OK + + + + + + true + + + true + + + + + + + VersionLabel_2 + + + + 140 + 150 + 221 + 31 + + + + + 12 + + + + (c) 2007, Mark Teel + + + + + VersionLabel + + + + 140 + 110 + 221 + 31 + + + + + 12 + + + + Raised + + + + + + + + + buttonOk + clicked() + AboutAFEdit + accept() + + + + diff --git a/afedit/src/afedit.h b/afedit/src/afedit.h new file mode 100644 index 0000000..4aca37b --- /dev/null +++ b/afedit/src/afedit.h @@ -0,0 +1,40 @@ +/*************************************************************************** + * Copyright (C) 2007 by Mark Teel * + * mteel@teelworks.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#pragma once + +#define AFEDIT_COLUMNS 66 + + +#ifndef MAX +#define MAX(x,y) ((x >= y) ? x : y) +#endif + +#ifndef MIN +#define MIN(x,y) ((x <= y) ? x : y) +#endif + + +/* ... typedefs +*/ +typedef unsigned char UCHAR; +typedef unsigned short USHORT; +typedef unsigned int UINT; +typedef unsigned long ULONG; +typedef unsigned long long ULONGLONG; diff --git a/afedit/src/archiveFile.cpp b/afedit/src/archiveFile.cpp new file mode 100644 index 0000000..1ea9125 --- /dev/null +++ b/afedit/src/archiveFile.cpp @@ -0,0 +1,540 @@ +/*************************************************************************** + * Copyright (C) 2007 by Mark Teel * + * mteel@teelworks.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#include "archiveFile.h" + +static char idCode[16] = {'W','D','A','T','5','.','0',0,0,0,0,0,0,0,5,0}; + +static int insertTimeValue (UCHAR *valarray, int index, USHORT value) +{ + int fIndex; + + fIndex = (index/2) * 3; + if ((index % 2) == 0) + { + valarray[fIndex] = value & 0xFF; + valarray[fIndex+2] = (valarray[fIndex+2] & 0xF0) | ((value >> 8) & 0x0F); + } + else + { + valarray[fIndex+1] = value & 0xFF; + valarray[fIndex+2] = (valarray[fIndex+2] & 0x0F) | ((value >> 4) & 0xF0); + } + + return value; +} + +// *************************************************************************** +// * Calculated Weather Data +// *************************************************************************** +// calculate the heat index +static float CalculateHeatIndex (float temp, float humidity) +{ + double T1, T2, T3, T4, T5, T6, T7, T8, T9, result; + + if (temp < 75.0) + { + return temp; + } + + T1 = -42.379; + T2 = 2.04901523 * temp; + T3 = 10.14333127 * humidity; + T4 = -0.22475541 * temp * humidity; + T5 = -6.83783E-3 * (temp * temp); + T6 = -5.481717E-2 * (humidity * humidity); + T7 = 1.22874E-3 * (temp * temp) * humidity; + T8 = 8.5282E-4 * temp * (humidity * humidity); + T9 = -1.99E-6 * (temp * temp) * (humidity * humidity); + result = T1 + T2 + T3 + T4 + T5 + T6 + T7 + T8 + T9; + + return (float)result; +} + +// calculate the wind chill +static float CalculateWindChill (float temp, float windspeed) +{ + double T1, T2, T3, T4; + double result; + + if (temp >= 50.0 || windspeed <= 3.0) + { + return temp; + } + + T1 = 35.74; + T2 = 0.6215 * temp; + T3 = -1.0 * (35.75 * pow (windspeed, 0.16)); + T4 = 0.4275 * temp * pow (windspeed, 0.16); + result = T1 + T2 + T3 + T4; + + return (float)result; +} + +// calculate the dewpoint +static float CalculateDewpoint (float temp, float humidity) +{ + float Tc = (5.0/9.0)*(temp - 32.0); + float Es = 6.11 * pow ((double)10.0, (double)(7.5 * (Tc/(237.7 + Tc)))); + float E = (humidity * Es)/100; + float Tdc = (-430.22 + 237.7 * log(E))/(-log(E)+19.08); + return ((9.0/5.0) * Tdc + 32); +} + + +// Class API +CArchiveFile::CArchiveFile (char* FullPath, bool write) +{ + dayIndex = 1; + recordIndex = 0; + Valid = false; + + if (write) + { + IsWrite = true; + filePtr = fopen (FullPath, "w"); + if (filePtr) + { + IsWriteStarted = false; + Valid = true; + } + } + else + { + IsWrite = false; + filePtr = fopen (FullPath, "r"); + if (filePtr) + { + // Read the file header + if (fread (&fileHdr, sizeof(fileHdr), 1, filePtr) != 1) + { + fclose (filePtr); + } + else + { + Valid = true; + } + } + } +} + +CArchiveFile::~CArchiveFile () +{ + if (Valid) + { + fclose (filePtr); + Valid = false; + } +} + +ArchiveRecord* CArchiveFile::GetNext (int* day) +{ + if (! Valid) + { + return 0; + } + + for (int i = dayIndex; i < 32; i ++) + { + if (fileHdr.dayIndex[i].recordsInDay == 0) + { + // we're done with this day + dayIndex ++; + recordIndex = 0; + continue; + } + + if (recordIndex >= (fileHdr.dayIndex[i].recordsInDay - 2)) + { + dayIndex ++; + recordIndex = 0; + continue; + } + + // goto the archive record + if (fseek (filePtr, + sizeof(fileHdr) + + DBFILES_RECORD_SIZE * (fileHdr.dayIndex[i].startPos + 2 + recordIndex), + SEEK_SET) + == -1) + { + fclose (filePtr); + Valid = false; + return 0; + } + + // read the archive record + memset (&arcRecord, 0, sizeof(arcRecord)); + if (fread (&arcRecord, sizeof(arcRecord), 1, filePtr) != 1) + { + fclose (filePtr); + Valid = false; + return 0; + } + + // we have a winner + recordIndex ++; + *day = dayIndex; + return &arcRecord; + } + + // If here, we have exhausted the file + fclose (filePtr); + Valid = false; + return 0; +} + +bool CArchiveFile::PutNext (ArchiveRecord* record, int day) +{ + float chill, dew, heat; + int dayRecs; + + if (! Valid) + { + return false; + } + + chill = CalculateWindChill ((float)record->outsideTemp/10, (float)record->windSpeed/10); + dew = CalculateDewpoint ((float)record->outsideTemp/10, (float)record->outsideHum/10); + heat = CalculateHeatIndex ((float)record->outsideTemp/10, (float)record->outsideHum/10); + + if (! IsWriteStarted) + { + // Create an empty header + memset (&fileHdr, 0, sizeof (fileHdr)); + memcpy (fileHdr.idCode, idCode, sizeof (idCode)); + IsWriteStarted = true; + } + + if (fileHdr.dayIndex[day].recordsInDay == 0) + { + // Create the day's summary record + memset (&sumRecord, 0, sizeof (sumRecord)); + sumRecord.dataSpan = record->archiveInterval; + sumRecord.hiOutTemp = record->hiOutsideTemp; + insertTimeValue (sumRecord.timeValues1, + High_Outside_Temperature, + record->packedTime); + sumRecord.lowOutTemp = record->lowOutsideTemp; + insertTimeValue (sumRecord.timeValues1, + Low_Outside_Temperature, + record->packedTime); + sumRecord.hiInTemp = record->insideTemp; + insertTimeValue (sumRecord.timeValues1, + High_Inside_Temperature, + record->packedTime); + sumRecord.lowInTemp = record->insideTemp; + insertTimeValue (sumRecord.timeValues1, + Low_Inside_Temperature, + record->packedTime); + sumRecord.avgOutTemp = record->outsideTemp; + sumRecord.avgInTemp = record->insideTemp; + sumRecord.hiChill = (int)(chill*10); + insertTimeValue (sumRecord.timeValues1, + High_Wind_Chill, + record->packedTime); + sumRecord.lowChill = sumRecord.hiChill; + insertTimeValue (sumRecord.timeValues1, + Low_Wind_Chill, + record->packedTime); + sumRecord.hiDew = (int)(dew*10); + insertTimeValue (sumRecord.timeValues1, + High_Dew_Point, + record->packedTime); + sumRecord.lowDew = sumRecord.hiDew; + insertTimeValue (sumRecord.timeValues1, + Low_Dew_Point, + record->packedTime); + sumRecord.avgChill = sumRecord.hiChill; + sumRecord.avgDew = sumRecord.hiDew; + sumRecord.hiOutHum = record->outsideHum; + insertTimeValue (sumRecord.timeValues1, + High_Outside_Humidity, + record->packedTime); + sumRecord.lowOutHum = record->outsideHum; + insertTimeValue (sumRecord.timeValues1, + Low_Outside_Humidity, + record->packedTime); + sumRecord.hiInHum = record->insideHum; + insertTimeValue (sumRecord.timeValues1, + High_Inside_Humidity, + record->packedTime); + sumRecord.lowInHum = record->insideHum; + insertTimeValue (sumRecord.timeValues1, + Low_Inside_Humidity, + record->packedTime); + sumRecord.avgOutHum = sumRecord.hiOutHum; + sumRecord.hiBar = record->barometer; + insertTimeValue (sumRecord.timeValues1, + High_Barometer, + record->packedTime); + sumRecord.lowBar = record->barometer; + insertTimeValue (sumRecord.timeValues1, + Low_Barometer, + record->packedTime); + sumRecord.avgBar = record->barometer; + sumRecord.hiSpeed = record->hiWindSpeed; + insertTimeValue (sumRecord.timeValues1, + High_Wind_Speed, + record->packedTime); + sumRecord.avgSpeed = record->windSpeed; + sumRecord.dailyWindRunTotal = 0x8000; + sumRecord.hi10MinSpeed = 0x8000; + sumRecord.dirHiSpeed = record->hiWindDirection; + sumRecord.hi10MinDir = 0xff; + sumRecord.dailyRainTotal = (record->rain & 0x0FFF) * 10; + sumRecord.hiRainRate = record->hiRainRate; + insertTimeValue (sumRecord.timeValues1, + High_Rain_Rate, + record->packedTime); + sumRecord.dailyUVDose = 0x8000; + sumRecord.hiUV = record->hiUV; + sumRecord.dataType2 = 3; + sumRecord.todaysWeather = 0; + sumRecord.numWindPackets = record->numWindSamples; + sumRecord.hiSolar = record->hisolarRad; + sumRecord.dailySolarEnergy = 0x8000; + sumRecord.minSunlight = 0x8000; + sumRecord.dailyETTotal = record->ET; + sumRecord.hiHeat = (int)(heat*10); + insertTimeValue (sumRecord.timeValues1, + High_Outside_Heat_Index, + record->packedTime); + sumRecord.lowHeat = sumRecord.hiHeat; + insertTimeValue (sumRecord.timeValues1, + Low_Outside_Heat_Index, + record->packedTime); + sumRecord.avgHeat = sumRecord.hiHeat; + } + + if (sumRecord.dataType1 == 0) + { + fileHdr.dayIndex[day].startPos = fileHdr.totalRecords; + fileHdr.totalRecords += 3; + fileHdr.dayIndex[day].recordsInDay += 3; + sumRecord.dataSpan = record->archiveInterval; + } + else + { + // roll in this archive record's data + fileHdr.totalRecords += 1; + fileHdr.dayIndex[day].recordsInDay += 1; + sumRecord.dataSpan += record->archiveInterval; + dayRecs = fileHdr.dayIndex[day].recordsInDay - 2; + + if (record->hiOutsideTemp > sumRecord.hiOutTemp) + { + sumRecord.hiOutTemp = record->hiOutsideTemp; + insertTimeValue (sumRecord.timeValues1, + High_Outside_Temperature, + record->packedTime); + } + if (record->lowOutsideTemp < sumRecord.lowOutTemp) + { + sumRecord.lowOutTemp = record->lowOutsideTemp; + insertTimeValue (sumRecord.timeValues1, + Low_Outside_Temperature, + record->packedTime); + } + if (record->insideTemp > sumRecord.hiInTemp) + { + sumRecord.hiInTemp = record->insideTemp; + insertTimeValue (sumRecord.timeValues1, + High_Inside_Temperature, + record->packedTime); + } + if (record->insideTemp < sumRecord.lowInTemp) + { + sumRecord.lowInTemp = record->insideTemp; + insertTimeValue (sumRecord.timeValues1, + Low_Inside_Temperature, + record->packedTime); + } + sumRecord.avgOutTemp = (((dayRecs-1)*sumRecord.avgOutTemp)+record->outsideTemp)/dayRecs; + sumRecord.avgInTemp = (((dayRecs-1)*sumRecord.avgInTemp)+record->insideTemp)/dayRecs; + if ((int)(chill*10) > sumRecord.hiChill) + { + sumRecord.hiChill = (int)(chill*10); + insertTimeValue (sumRecord.timeValues1, + High_Wind_Chill, + record->packedTime); + } + if ((int)(chill*10) < sumRecord.lowChill) + { + sumRecord.lowChill = (int)(chill*10); + insertTimeValue (sumRecord.timeValues1, + Low_Wind_Chill, + record->packedTime); + } + if ((int)(dew*10) > sumRecord.hiDew) + { + sumRecord.hiDew = (int)(dew*10); + insertTimeValue (sumRecord.timeValues1, + High_Dew_Point, + record->packedTime); + } + if ((int)(dew*10) < sumRecord.lowDew) + { + sumRecord.lowDew = (int)(dew*10); + insertTimeValue (sumRecord.timeValues1, + Low_Dew_Point, + record->packedTime); + } + sumRecord.avgChill = (((dayRecs-1)*sumRecord.avgChill)+(int)(chill*10))/dayRecs; + sumRecord.avgDew = (((dayRecs-1)*sumRecord.avgDew)+(int)(dew*10))/dayRecs; + if (record->outsideHum > sumRecord.hiOutHum) + { + sumRecord.hiOutHum = record->outsideHum; + insertTimeValue (sumRecord.timeValues1, + High_Outside_Humidity, + record->packedTime); + } + if (record->outsideHum < sumRecord.lowOutHum) + { + sumRecord.lowOutHum = record->outsideHum; + insertTimeValue (sumRecord.timeValues1, + Low_Outside_Humidity, + record->packedTime); + } + if (record->insideHum > sumRecord.hiInHum) + { + sumRecord.hiInHum = record->insideHum; + insertTimeValue (sumRecord.timeValues1, + High_Inside_Humidity, + record->packedTime); + } + if (record->insideHum < sumRecord.lowInHum) + { + sumRecord.lowInHum = record->insideHum; + insertTimeValue (sumRecord.timeValues1, + Low_Inside_Humidity, + record->packedTime); + } + sumRecord.avgOutHum = (((dayRecs-1)*sumRecord.avgOutHum)+record->outsideHum)/dayRecs; + if (record->barometer > sumRecord.hiBar) + { + sumRecord.hiBar = record->barometer; + insertTimeValue (sumRecord.timeValues1, + High_Barometer, + record->packedTime); + } + if (record->barometer < sumRecord.lowBar) + { + sumRecord.lowBar = record->barometer; + insertTimeValue (sumRecord.timeValues1, + Low_Barometer, + record->packedTime); + } + sumRecord.avgBar = (((dayRecs-1)*sumRecord.avgBar)+record->barometer)/dayRecs; + if (record->hiUV > sumRecord.hiUV) + { + sumRecord.hiUV = record->hiUV; + insertTimeValue (sumRecord.timeValues1, + High_UV, + record->packedTime); + } + if (record->hisolarRad > sumRecord.hiSolar) + { + sumRecord.hiSolar = record->hisolarRad; + insertTimeValue (sumRecord.timeValues2, + High_Solar_Rad, + record->packedTime); + } + if (record->hiWindSpeed > sumRecord.hiSpeed) + { + sumRecord.hiSpeed = record->hiWindSpeed; + insertTimeValue (sumRecord.timeValues1, + High_Wind_Speed, + record->packedTime); + } + sumRecord.avgSpeed = (((dayRecs-1)*sumRecord.avgSpeed)+record->windSpeed)/dayRecs; + sumRecord.dailyRainTotal += (record->rain & 0x0FFF); + if (record->hiRainRate > sumRecord.hiRainRate) + { + sumRecord.hiRainRate = record->hiRainRate; + insertTimeValue (sumRecord.timeValues1, + High_Rain_Rate, + record->packedTime); + } + if ((int)(heat*10) > sumRecord.hiHeat) + { + sumRecord.hiHeat = (int)(heat*10); + insertTimeValue (sumRecord.timeValues2, + High_Outside_Heat_Index, + record->packedTime); + } + if ((int)(heat*10) < sumRecord.lowHeat) + { + sumRecord.lowHeat = (int)(heat*10); + insertTimeValue (sumRecord.timeValues2, + Low_Outside_Heat_Index, + record->packedTime); + } + sumRecord.avgHeat = (((dayRecs-1)*sumRecord.avgHeat)+(int)(heat*10))/dayRecs; + } + + // write out the hdr and the new records + if (fseek (filePtr, 0, SEEK_SET) == -1) + { + fclose (filePtr); + Valid = false; + return false; + } + + if (fwrite (&fileHdr, sizeof(fileHdr), 1, filePtr) != 1) + { + fclose (filePtr); + Valid = false; + return false; + } + + if (fseek (filePtr, + sizeof(fileHdr) + (DBFILES_RECORD_SIZE * fileHdr.dayIndex[day].startPos), + SEEK_SET) + == -1) + { + fclose (filePtr); + Valid = false; + return false; + } + + sumRecord.dataType1 = 2; + if (fwrite (&sumRecord, sizeof(sumRecord), 1, filePtr) != 1) + { + fclose (filePtr); + Valid = false; + return false; + } + + if (fseek (filePtr, 0, SEEK_END) == -1) + { + fclose (filePtr); + Valid = false; + return false; + } + if (fwrite (record, sizeof(*record), 1, filePtr) != 1) + { + fclose (filePtr); + Valid = false; + return false; + } + + return true; +} diff --git a/afedit/src/archiveFile.h b/afedit/src/archiveFile.h new file mode 100644 index 0000000..7510039 --- /dev/null +++ b/afedit/src/archiveFile.h @@ -0,0 +1,226 @@ +/*************************************************************************** + * Copyright (C) 2007 by Mark Teel * + * mteel@teelworks.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#pragma once + +// System include files +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Library include files +#include "afedit.h" + +#define DBFILES_RECORD_SIZE 88 + +enum DailySummaryTimeIndexes +{ + High_Outside_Temperature = 0, + Low_Outside_Temperature = 1, + High_Inside_Temperature = 2, + Low_Inside_Temperature = 3, + High_Wind_Chill = 4, + Low_Wind_Chill = 5, + High_Dew_Point = 6, + Low_Dew_Point = 7, + High_Outside_Humidity = 8, + Low_Outside_Humidity = 9, + High_Inside_Humidity = 10, + Low_Inside_Humidity = 11, + High_Barometer = 12, + Low_Barometer = 13, + High_Wind_Speed = 14, + High_Average_Wind_Speed = 15, + High_Rain_Rate = 16, + High_UV = 17, + + High_Solar_Rad = 0, + High_Outside_Heat_Index = 1, + Low_Outside_Heat_Index = 2, + High_Outside_THSW_Index = 3, + Low_Outside_THSW_Index = 4, + High_Outside_THW_Index = 5, + Low_Outside_THW_Index = 6, + High_Outside_Wet_Bulb_Temp = 7, + Low_Outside_Wet_Bulb_Temp = 8 +}; + + +// ... this describes each day in an archive file +typedef struct +{ + int16_t recordsInDay; // includes the daily summary record (counts as 2) + int startPos; // The index (starting at 0) of the daily summary record +}__attribute__ ((packed)) DayIndex; + +// ... Header for each archive file +typedef struct +{ + char idCode [16]; // = {'W','D','A','T','5','.','0',0,0,0,0,0,0,0,5,0} + int totalRecords; + DayIndex dayIndex [32]; // index records for each day. Index 0 is not used + // (i.e. the 1'st is at index 1, not index 0) +}__attribute__ ((packed)) HeaderBlock; + + +// ... After the Header are a series of 88-byte records with one of the following +// ... formats. Note that each day will begin with a daily summary record. + +// ... Daily Summary Record (176 bytes) +typedef struct +{ + uint8_t dataType1; + uint8_t reserved1; // this will cause rest of fields to start on an even address + + int16_t dataSpan; // total # minutes accounted for by physical recs for this day + int16_t hiOutTemp, lowOutTemp; // tenths of a degree F + int16_t hiInTemp, lowInTemp; // tenths of a degree F + int16_t avgOutTemp, avgInTemp; // tenths of a degree F (integrated over the day) + int16_t hiChill, lowChill; // tenths of a degree F + int16_t hiDew, lowDew; // tenths of a degree F + int16_t avgChill, avgDew; // tenths of a degree F + int16_t hiOutHum, lowOutHum; // tenths of a percent + int16_t hiInHum, lowInHum; // tenths of a percent + int16_t avgOutHum; // tenths of a percent + int16_t hiBar, lowBar; // thousanths of an inch Hg + int16_t avgBar; // thousanths of an inch Hg + int16_t hiSpeed, avgSpeed; // tenths of an MPH + int16_t dailyWindRunTotal; // 1/10'th of an mile + int16_t hi10MinSpeed; // the highest average wind speed record + uint8_t dirHiSpeed, hi10MinDir; // direction code (0-15, 255) + int16_t dailyRainTotal; // 1/1000'th of an inch + int16_t hiRainRate; // 1/100'th inch/hr ??? + int16_t dailyUVDose; // 1/10'th of a standard MED + uint8_t hiUV; // tenth of a UV Index + uint8_t timeValues1[27]; // space for 18 time values (see below) + + uint8_t dataType2; // start of 2nd summary record + uint8_t reserved2; // this will cause rest of fields to start on an even address + + // this field is not used now. + uint16_t todaysWeather; // bitmapped weather conditions (Fog, T-Storm, hurricane, etc) + + int16_t numWindPackets; // # of valid packets containing wind data, + // this is used to indicate reception quality + int16_t hiSolar; // Watts per meter squared + int16_t dailySolarEnergy; // 1/10'th Ly + int16_t minSunlight; // number of accumulated minutes where the avg solar rad > 150 + int16_t dailyETTotal; // 1/1000'th of an inch + int16_t hiHeat, lowHeat; // tenths of a degree F + int16_t avgHeat; // tenths of a degree F + int16_t hiTHSW, lowTHSW; // tenths of a degree F + int16_t hiTHW, lowTHW; // tenths of a degree F + + int16_t integratedHeatDD65; // integrated Heating Degree Days (65F threshold) + // tenths of a degree F - Day + + // Wet bulb values are not calculated + int16_t hiWetBulb, lowWetBulb; // tenths of a degree F + int16_t avgWetBulb; // tenths of a degree F + + uint8_t dirBins[24]; // space for 16 direction bins + // (Used to calculate monthly dominent Dir) + + uint8_t timeValues2[15]; // space for 10 time values (see below) + + int16_t integratedCoolDD65; // integrated Cooling Degree Days (65F threshold) + // tenths of a degree F - Day + uint8_t reserved3[11]; +}__attribute__ ((packed)) DailySummaryRecord; + +// ... standard archive record (88 bytes) +typedef struct +{ + uint8_t dataType; + uint8_t archiveInterval; // number of minutes in the archive + uint8_t iconFlags; // Icon associated with this record, plus Edit flags + uint8_t moreFlags; // Tx Id, etc. + + int16_t packedTime; // minutes past midnight of the end of the archive period + int16_t outsideTemp; // tenths of a degree F + int16_t hiOutsideTemp; // tenths of a degree F + int16_t lowOutsideTemp; // tenths of a degree F + int16_t insideTemp; // tenths of a degree F + int16_t barometer; // thousanths of an inch Hg + int16_t outsideHum; // tenths of a percent + int16_t insideHum; // tenths of a percent + uint16_t rain; // number of clicks + rain collector type code + int16_t hiRainRate; // clicks per hour + int16_t windSpeed; // tenths of an MPH + int16_t hiWindSpeed; // tenths of an MPH + uint8_t windDirection; // direction code (0-15, 255) + uint8_t hiWindDirection; // direction code (0-15, 255) + int16_t numWindSamples; // number of valid ISS packets containing wind data + // this is a good indication of reception + int16_t solarRad, hisolarRad; // Watts per meter squared + uint8_t UV, hiUV; // tenth of a UV Index + + uint8_t leafTemp[4]; // (whole degrees F) + 90 + + int16_t newSensors[7]; // reserved for future use + uint8_t forecast; // forecast code during the archive interval + + uint8_t ET; // thousandths of an inch + + uint8_t soilTemp[6]; // (whole degrees F) + 90 + uint8_t soilMoisture[6]; // centibars of dryness + uint8_t leafWetness[4]; // Leaf Wetness code (0-15, 255) + uint8_t extraTemp[7]; // (whole degrees F) + 90 + uint8_t extraHum[7]; // whole percent +}__attribute__ ((packed)) ArchiveRecord; + + +// API +class CArchiveFile +{ +public: + // Open the file and set the record pointer to the first record + CArchiveFile (char* FullPath, bool write = false); + ~CArchiveFile (); + + bool IsValid () { return Valid; } + + // If reading... + ArchiveRecord* GetNext (int* day); + + // If writing... + bool PutNext (ArchiveRecord* record, int day); + +private: + bool Valid; + bool IsWrite; + bool IsWriteStarted; + + FILE* filePtr; + HeaderBlock fileHdr; + DailySummaryRecord sumRecord; + + int dayIndex; + int recordIndex; + ArchiveRecord arcRecord; + +}; + diff --git a/afedit/src/fileopen.xpm b/afedit/src/fileopen.xpm new file mode 100644 index 0000000..880417e --- /dev/null +++ b/afedit/src/fileopen.xpm @@ -0,0 +1,22 @@ +/* XPM */ +static const char *fileopen[] = { +" 16 13 5 1", +". c #040404", +"# c #808304", +"a c None", +"b c #f3f704", +"c c #f3f7f3", +"aaaaaaaaa...aaaa", +"aaaaaaaa.aaa.a.a", +"aaaaaaaaaaaaa..a", +"a...aaaaaaaa...a", +".bcb.......aaaaa", +".cbcbcbcbc.aaaaa", +".bcbcbcbcb.aaaaa", +".cbcb...........", +".bcb.#########.a", +".cb.#########.aa", +".b.#########.aaa", +"..#########.aaaa", +"...........aaaaa" +}; diff --git a/afedit/src/fileprint.xpm b/afedit/src/fileprint.xpm new file mode 100644 index 0000000..6ada912 --- /dev/null +++ b/afedit/src/fileprint.xpm @@ -0,0 +1,24 @@ +/* XPM */ +static const char *fileprint[] = { +" 16 14 6 1", +". c #000000", +"# c #848284", +"a c #c6c3c6", +"b c #ffff00", +"c c #ffffff", +"d c None", +"ddddd.........dd", +"dddd.cccccccc.dd", +"dddd.c.....c.ddd", +"ddd.cccccccc.ddd", +"ddd.c.....c....d", +"dd.cccccccc.a.a.", +"d..........a.a..", +".aaaaaaaaaa.a.a.", +".............aa.", +".aaaaaa###aa.a.d", +".aaaaaabbbaa...d", +".............a.d", +"d.aaaaaaaaa.a.dd", +"dd...........ddd" +}; diff --git a/afedit/src/filesave.xpm b/afedit/src/filesave.xpm new file mode 100644 index 0000000..bd6870f --- /dev/null +++ b/afedit/src/filesave.xpm @@ -0,0 +1,22 @@ +/* XPM */ +static const char *filesave[] = { +" 14 14 4 1", +". c #040404", +"# c #808304", +"a c #bfc2bf", +"b c None", +"..............", +".#.aaaaaaaa.a.", +".#.aaaaaaaa...", +".#.aaaaaaaa.#.", +".#.aaaaaaaa.#.", +".#.aaaaaaaa.#.", +".#.aaaaaaaa.#.", +".##........##.", +".############.", +".##.........#.", +".##......aa.#.", +".##......aa.#.", +".##......aa.#.", +"b............." +}; diff --git a/afedit/src/formutils.cpp b/afedit/src/formutils.cpp new file mode 100644 index 0000000..a9d8092 --- /dev/null +++ b/afedit/src/formutils.cpp @@ -0,0 +1,314 @@ +/*************************************************************************** + * Copyright (C) 2007 by Mark Teel * + * mteel@teelworks.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#include "formutils.h" + + +static QString& ExtractArchiveColumn (ArchiveRecord *record, int column) +{ + static QString ArcCol; + + ArcCol.truncate (0); + + if (column >= AFEDIT_COLUMNS) + { + return ArcCol; + } + + switch (column) + { + case 0: ArcCol.setNum ((USHORT)record->dataType); break; + case 1: ArcCol.setNum ((USHORT)record->archiveInterval); break; + case 2: ArcCol.setNum ((USHORT)record->iconFlags, 16); break; + case 3: ArcCol.setNum ((USHORT)record->moreFlags, 16); break; + case 4: ArcCol.setNum ((USHORT)record->packedTime); break; + case 5: ArcCol.setNum ((float)record->outsideTemp/10.0, 'f', 1); break; + case 6: ArcCol.setNum ((float)record->hiOutsideTemp/10.0, 'f', 1); break; + case 7: ArcCol.setNum ((float)record->lowOutsideTemp/10.0, 'f', 1); break; + case 8: ArcCol.setNum ((float)record->insideTemp/10.0, 'f', 1); break; + case 9: ArcCol.setNum ((float)record->barometer/1000.0, 'f', 3); break; + case 10: ArcCol.setNum ((float)record->outsideHum/10.0, 'f', 1); break; + case 11: ArcCol.setNum ((float)record->insideHum/10.0, 'f', 1); break; + case 12: ArcCol.setNum ((USHORT)record->rain, 16); break; + case 13: ArcCol.setNum ((USHORT)record->hiRainRate); break; + case 14: ArcCol.setNum ((float)record->windSpeed/10.0, 'f', 1); break; + case 15: ArcCol.setNum ((float)record->hiWindSpeed/10.0, 'f', 1); break; + case 16: ArcCol.setNum ((USHORT)record->windDirection); break; + case 17: ArcCol.setNum ((USHORT)record->hiWindDirection); break; + case 18: ArcCol.setNum ((USHORT)record->numWindSamples); break; + case 19: ArcCol.setNum ((USHORT)record->solarRad); break; + case 20: ArcCol.setNum ((USHORT)record->hisolarRad); break; + case 21: ArcCol.setNum ((float)record->UV/10.0, 'f', 1); break; + case 22: ArcCol.setNum ((float)record->hiUV/10.0, 'f', 1); break; + case 23: ArcCol.setNum ((USHORT)record->leafTemp[0] - 90); break; + case 24: ArcCol.setNum ((USHORT)record->leafTemp[1] - 90); break; + case 25: ArcCol.setNum ((USHORT)record->leafTemp[2] - 90); break; + case 26: ArcCol.setNum ((USHORT)record->leafTemp[3] - 90); break; + case 27: ArcCol.setNum ((USHORT)record->newSensors[0]); break; + case 28: ArcCol.setNum ((USHORT)record->newSensors[1]); break; + case 29: ArcCol.setNum ((USHORT)record->newSensors[2]); break; + case 30: ArcCol.setNum ((USHORT)record->newSensors[3]); break; + case 31: ArcCol.setNum ((USHORT)record->newSensors[4]); break; + case 32: ArcCol.setNum ((USHORT)record->newSensors[5]); break; + case 33: ArcCol.setNum ((USHORT)record->newSensors[6]); break; + case 34: ArcCol.setNum ((USHORT)record->forecast); break; + case 35: ArcCol.setNum ((float)record->ET/1000.0, 'f', 3); break; + case 36: ArcCol.setNum ((USHORT)record->soilTemp[0] - 90); break; + case 37: ArcCol.setNum ((USHORT)record->soilTemp[1] - 90); break; + case 38: ArcCol.setNum ((USHORT)record->soilTemp[2] - 90); break; + case 39: ArcCol.setNum ((USHORT)record->soilTemp[3] - 90); break; + case 40: ArcCol.setNum ((USHORT)record->soilTemp[4] - 90); break; + case 41: ArcCol.setNum ((USHORT)record->soilTemp[5] - 90); break; + case 42: ArcCol.setNum ((USHORT)record->soilMoisture[0]); break; + case 43: ArcCol.setNum ((USHORT)record->soilMoisture[1]); break; + case 44: ArcCol.setNum ((USHORT)record->soilMoisture[2]); break; + case 45: ArcCol.setNum ((USHORT)record->soilMoisture[3]); break; + case 46: ArcCol.setNum ((USHORT)record->soilMoisture[4]); break; + case 47: ArcCol.setNum ((USHORT)record->soilMoisture[5]); break; + case 48: ArcCol.setNum ((USHORT)record->leafWetness[0]); break; + case 49: ArcCol.setNum ((USHORT)record->leafWetness[1]); break; + case 50: ArcCol.setNum ((USHORT)record->leafWetness[2]); break; + case 51: ArcCol.setNum ((USHORT)record->leafWetness[3]); break; + case 52: ArcCol.setNum ((USHORT)record->extraTemp[0] - 90); break; + case 53: ArcCol.setNum ((USHORT)record->extraTemp[1] - 90); break; + case 54: ArcCol.setNum ((USHORT)record->extraTemp[2] - 90); break; + case 55: ArcCol.setNum ((USHORT)record->extraTemp[3] - 90); break; + case 56: ArcCol.setNum ((USHORT)record->extraTemp[4] - 90); break; + case 57: ArcCol.setNum ((USHORT)record->extraTemp[5] - 90); break; + case 58: ArcCol.setNum ((USHORT)record->extraTemp[6] - 90); break; + case 59: ArcCol.setNum ((USHORT)record->extraHum[0]); break; + case 60: ArcCol.setNum ((USHORT)record->extraHum[1]); break; + case 61: ArcCol.setNum ((USHORT)record->extraHum[2]); break; + case 62: ArcCol.setNum ((USHORT)record->extraHum[3]); break; + case 63: ArcCol.setNum ((USHORT)record->extraHum[4]); break; + case 64: ArcCol.setNum ((USHORT)record->extraHum[5]); break; + case 65: ArcCol.setNum ((USHORT)record->extraHum[6]); break; + } + + return ArcCol; +} + +static void insertArchiveColumn (ArchiveRecord *record, int column, QString& field) +{ + if (column > AFEDIT_COLUMNS) + { + return; + } + + switch (column) + { + case 0: record->dataType = field.toUShort(); break; + case 1: record->archiveInterval = field.toUShort(); break; + case 2: record->iconFlags = field.toUShort(0, 16); break; + case 3: record->moreFlags = field.toUShort(0, 16); break; + case 4: record->packedTime = field.toUShort(); break; + case 5: record->outsideTemp = (short)(field.toFloat() * 10); break; + case 6: record->hiOutsideTemp = (short)(field.toFloat() * 10); break; + case 7: record->lowOutsideTemp = (short)(field.toFloat() * 10); break; + case 8: record->insideTemp = (short)(field.toFloat() * 10); break; + case 9: record->barometer = (USHORT)(field.toFloat() * 1000); break; + case 10: record->outsideHum = (USHORT)(field.toFloat() * 10); break; + case 11: record->insideHum = (USHORT)(field.toFloat() * 10); break; + case 12: record->rain = field.toUShort(0, 16); break; + case 13: record->hiRainRate = field.toUShort(); break; + case 14: record->windSpeed = (USHORT)(field.toFloat() * 10); break; + case 15: record->hiWindSpeed = (USHORT)(field.toFloat() * 10); break; + case 16: record->windDirection = field.toUShort(); break; + case 17: record->hiWindDirection = field.toUShort(); break; + case 18: record->numWindSamples = field.toUShort(); break; + case 19: record->solarRad = field.toUShort(); break; + case 20: record->hisolarRad = field.toUShort(); break; + case 21: record->UV = (USHORT)(field.toFloat() * 10); break; + case 22: record->hiUV = (USHORT)(field.toFloat() * 10); break; + case 23: record->leafTemp[0] = field.toShort() + 90; break; + case 24: record->leafTemp[1] = field.toShort() + 90; break; + case 25: record->leafTemp[2] = field.toShort() + 90; break; + case 26: record->leafTemp[3] = field.toShort() + 90; break; + case 27: record->newSensors[0] = field.toShort(); break; + case 28: record->newSensors[1] = field.toShort(); break; + case 29: record->newSensors[2] = field.toShort(); break; + case 30: record->newSensors[3] = field.toShort(); break; + case 31: record->newSensors[4] = field.toShort(); break; + case 32: record->newSensors[5] = field.toShort(); break; + case 33: record->newSensors[6] = field.toShort(); break; + case 34: record->forecast = field.toUShort(); break; + case 35: record->ET = (USHORT)(field.toFloat() * 1000); break; + case 36: record->soilTemp[0] = field.toShort() + 90; break; + case 37: record->soilTemp[1] = field.toShort() + 90; break; + case 38: record->soilTemp[2] = field.toShort() + 90; break; + case 39: record->soilTemp[3] = field.toShort() + 90; break; + case 40: record->soilTemp[4] = field.toShort() + 90; break; + case 41: record->soilTemp[5] = field.toShort() + 90; break; + case 42: record->soilMoisture[0] = field.toUShort(); break; + case 43: record->soilMoisture[1] = field.toUShort(); break; + case 44: record->soilMoisture[2] = field.toUShort(); break; + case 45: record->soilMoisture[3] = field.toUShort(); break; + case 46: record->soilMoisture[4] = field.toUShort(); break; + case 47: record->soilMoisture[5] = field.toUShort(); break; + case 48: record->leafWetness[0] = field.toUShort(); break; + case 49: record->leafWetness[1] = field.toUShort(); break; + case 50: record->leafWetness[2] = field.toUShort(); break; + case 51: record->leafWetness[3] = field.toUShort(); break; + case 52: record->extraTemp[0] = field.toShort() + 90; break; + case 53: record->extraTemp[1] = field.toShort() + 90; break; + case 54: record->extraTemp[2] = field.toShort() + 90; break; + case 55: record->extraTemp[3] = field.toShort() + 90; break; + case 56: record->extraTemp[4] = field.toShort() + 90; break; + case 57: record->extraTemp[5] = field.toShort() + 90; break; + case 58: record->extraTemp[6] = field.toShort() + 90; break; + case 59: record->extraHum[0] = field.toUShort(); break; + case 60: record->extraHum[1] = field.toUShort(); break; + case 61: record->extraHum[2] = field.toUShort(); break; + case 62: record->extraHum[3] = field.toUShort(); break; + case 63: record->extraHum[4] = field.toUShort(); break; + case 64: record->extraHum[5] = field.toUShort(); break; + case 65: record->extraHum[6] = field.toUShort(); break; + } +} + + +bool formUtilsGetFile (QString& fullPath) +{ + QString str; + + fullPath.truncate (0); + + QFileDialog* fd = new QFileDialog (0, "open file dialog", TRUE); + fd->setMode (QFileDialog::ExistingFile); + fd->setFilter ("Archive Files (*.wlk *.WLK)"); + fd->setViewMode (QFileDialog::Detail); + fd->setDir ("/home"); + fd->setCaption ("Choose Archive File to Edit"); + if (fd->exec() == QDialog::Accepted) + { + fullPath = fd->selectedFile (); + return true; + } + else + { + return false; + } +} + +bool formUtilsGetSaveAsFile (QString& fullPath) +{ + fullPath.truncate (0); + + QFileDialog* fd = new QFileDialog (0, "save as file dialog", TRUE); + fd->setMode (QFileDialog::AnyFile); + fd->setFilter ("Archive Files (*.wlk *.WLK)"); + fd->setViewMode (QFileDialog::Detail); + fd->setDir ("/home"); + fd->setCaption ("Save As"); + if (fd->exec() == QDialog::Accepted) + { + fullPath = fd->selectedFile (); + if ((fullPath.right(4) != ".wlk") && (fullPath.right(4) != ".WLK")) + { + fullPath += ".wlk"; + } + return true; + } + else + { + return false; + } +} + +static void setRowHeader (QTable* table, int row, ArchiveRecord* record, int day) +{ + QString Header; + + Header.sprintf ("%2.2d %2.2d:%2.2d", + day, + record->packedTime/60, + record->packedTime%60); + table->verticalHeader()->setLabel (row, Header); +} + +void formUtilsPopulateTable (QString& fullPath, QTable* table) +{ + bool Done = false; + ArchiveRecord* recordStore; + int row = 0; + int column, day; + CArchiveFile ArchiveFile ((char *)fullPath.ascii ()); + + + while (! Done) + { + recordStore = ArchiveFile.GetNext (&day); + if (recordStore != 0) + { + // Set the header + setRowHeader (table, row, recordStore, day); + + // Set the hidden day field + QString TempStr; + TempStr.sprintf ("%d", day); + table->setItem (row, AFEDIT_COLUMNS, new QTableItem (table, QTableItem::WhenCurrent, TempStr)); + + // Populate a row in our table + for (column = 0; column < AFEDIT_COLUMNS; column ++) + { + table->setItem (row, + column, + new QTableItem (table, + QTableItem::WhenCurrent, + ExtractArchiveColumn (recordStore, column))); + } + + row ++; + } + else + { + Done = true; + } + } +} + +void formUtilsWriteFile (QString& fullPath, QTable* table) +{ + ArchiveRecord recordStore; + int day; + CArchiveFile ArchiveFile ((char *)fullPath.ascii (), true); + + if (! ArchiveFile.IsValid()) + { + return; + } + + for (int row = 0; table->text(row, AFEDIT_COLUMNS) != QString::null; row ++) + { + // Build a record from this row's data + memset (&recordStore, 0xFF, sizeof(recordStore)); + for (int column = 0; column < AFEDIT_COLUMNS; column ++) + { + QString field = table->text(row, column); + if (field != QString::null) + { + insertArchiveColumn (&recordStore, column, field); + } + } + + // Write it to the file + day = (table->text(row, AFEDIT_COLUMNS)).toUShort(); + ArchiveFile.PutNext (&recordStore, day); + } +} diff --git a/afedit/src/formutils.h b/afedit/src/formutils.h new file mode 100644 index 0000000..8c21856 --- /dev/null +++ b/afedit/src/formutils.h @@ -0,0 +1,32 @@ +/*************************************************************************** + * Copyright (C) 2007 by Mark Teel * + * mteel@teelworks.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#pragma once + +#include "archiveFile.h" +#include "afedit.h" +#include +#include + +// API prototypes +bool formUtilsGetFile (QString& fullPath); +bool formUtilsGetSaveAsFile (QString& fullPath); + +void formUtilsPopulateTable (QString& fullPath, QTable* table); +void formUtilsWriteFile (QString& fullPath, QTable* table); diff --git a/afedit/src/main.cpp b/afedit/src/main.cpp new file mode 100644 index 0000000..2683141 --- /dev/null +++ b/afedit/src/main.cpp @@ -0,0 +1,11 @@ +#include +#include "mainform.h" + +int main( int argc, char ** argv ) +{ + QApplication a( argc, argv ); + MainForm w; + w.show(); + a.connect( &a, SIGNAL( lastWindowClosed() ), &a, SLOT( quit() ) ); + return a.exec(); +} diff --git a/afedit/src/mainform.ui b/afedit/src/mainform.ui new file mode 100644 index 0000000..0debcc3 --- /dev/null +++ b/afedit/src/mainform.ui @@ -0,0 +1,278 @@ + +MainForm + + + MainForm + + + + 0 + 0 + 1320 + 946 + + + + + 7 + 7 + 0 + 0 + + + + + 21 + 114 + + + + wview Archive File Editor + + + + textLabel1 + + + + 10 + 850 + 91 + 21 + + + + Hint: + + + AlignVCenter|AlignRight + + + + + archiveTable + + + + 0 + 0 + 1320 + 840 + + + + false + + + 2 + + + 8928 + + + 67 + + + Single + + + + + lineEdit_Hint + + + + 120 + 850 + 1120 + 21 + + + + false + + + true + + + + + + MenuBar + + + + + + + + + + + + + + + + + + + + fileOpenAction + + + + + + Open + + + &Open... + + + Ctrl+O + + + + + fileSaveAction + + + + + + Save + + + &Save + + + Ctrl+S + + + + + fileSaveAsAction + + + Save As + + + Save &As... + + + + + + + + fileExitAction + + + Exit + + + E&xit + + + + + + + + helpContentsAction + + + Contents + + + &Contents... + + + + + + + + helpAboutAction + + + About + + + &About + + + + + + + + + fileOpenAction + activated() + MainForm + fileOpen() + + + fileSaveAction + activated() + MainForm + fileSave() + + + fileSaveAsAction + activated() + MainForm + fileSaveAs() + + + fileExitAction + activated() + MainForm + fileExit() + + + helpContentsAction + activated() + MainForm + helpContents() + + + helpAboutAction + activated() + MainForm + helpAbout() + + + archiveTable + selectionChanged() + MainForm + archiveTable_selectionChanged() + + + archiveTable + valueChanged(int,int) + MainForm + archiveTable_valueChanged(int,int) + + + + mainform.ui.h + + + fileOpen() + fileSave() + fileSaveAs() + fileExit() + helpContents() + helpAbout() + archiveTable_selectionChanged() + archiveTable_valueChanged( int row, int col ) + + + init() + + + + diff --git a/afedit/src/mainform.ui.h b/afedit/src/mainform.ui.h new file mode 100644 index 0000000..7ad4fa6 --- /dev/null +++ b/afedit/src/mainform.ui.h @@ -0,0 +1,361 @@ +/**************************************************************************** +** ui.h extension file, included from the uic-generated form implementation. +** +** If you want to add, delete, or rename functions or slots, use +** Qt Designer to update this file, preserving your code. +** +** You should not define a constructor or destructor in this file. +** Instead, write your code in functions called init() and destroy(). +** These will automatically be called by the form's constructor and +** destructor. +*****************************************************************************/ + +#include "afedit.h" +#include +#include +#include "formutils.h" +#include ".ui/aboutafedit.h" + +static QString FullPathName; +static char* AFEDIT_COL_HEADERS[AFEDIT_COLUMNS] = +{ + " Type ", + "Interval ", + "IconFlags ", + "MoreFlags ", + "PackedTime ", + " OutTemp ", + "HiOutTemp ", + "LowOutTemp ", + "insideTemp ", + "Barometer ", + " OutHum ", + "InsideHum ", + " Rain ", + "HiRainRate ", + "WindSpeed ", + "HiWindSpeed ", + "WindDir ", + "HiWindDir ", + "NumSamples ", + "SolarRad ", + "HiSolarRad ", + " UV ", + " hiUV ", + "LeafTemp1 ", + "LeafTemp2 ", + "LeafTemp3 ", + "LeafTemp4 ", + "NewSensor1 ", + "NewSensor2 ", + "NewSensor3 ", + "NewSensor4 ", + "NewSensor5 ", + "NewSensor6 ", + "NewSensor7 ", + "Forecast ", + " ET ", + "SoilTemp1 ", + "SoilTemp2 ", + "SoilTemp3 ", + "SoilTemp4 ", + "SoilTemp5 ", + "SoilTemp6 ", + "SoilMoist1 ", + "SoilMoist2 ", + "SoilMoist3 ", + "SoilMoist4 ", + "SoilMoist5 ", + "SoilMoist6 ", + "LeafWet1 ", + "LeafWet2 ", + "LeafWet3 ", + "LeafWet4 ", + "ExtraTemp1 ", + "ExtraTemp2 ", + "ExtraTemp3 ", + "ExtraTemp4 ", + "ExtraTemp5 ", + "ExtraTemp6 ", + "ExtraTemp7 ", + "ExtraHum1 ", + "ExtraHum2 ", + "ExtraHum3 ", + "ExtraHum4 ", + "ExtraHum5 ", + "ExtraHum6 ", + "ExtraHum7 " +}; + +static char* AFEDIT_COL_HELPERS[AFEDIT_COLUMNS] = +{ + "decimal: Record Data Type (always = 1)", + "decimal: minutes in the archive record", + "hexidecimal: Icon associated with this record and edit flags", + "hexidecimal: Tx ID, etc.", + "decimal: minutes past midnight of the end of the archive period", + "decimal: degrees (-90 - 160)", + "decimal: degrees (-90 - 160)", + "decimal: degrees (-90 - 160)", + "decimal: degrees (-90 - 160)", + "decimal: inches Hg", + "decimal: percent (0 - 100)", + "decimal: percent (0 - 100)", + "hexidecimal: number of clicks + rain collector type code", + "decimal: number of clicks per hour", + "decimal: miles per hour", + "decimal: miles per hour", + "decimal: direction code (0-15, 255)", + "decimal: direction code (0-15, 255)", + "decimal: number of valid ISS packets containing wind data", + "decimal: watts per meter squared", + "decimal: watts per meter squared", + "decimal: UV index (0 - 15)", + "decimal: UV index (0 - 15)", + "decimal: degrees (-90 - 160)", + "decimal: degrees (-90 - 160)", + "decimal: degrees (-90 - 160)", + "decimal: degrees (-90 - 160)", + "decimal: New Sensor 1", + "decimal: New Sensor 2", + "decimal: New Sensor 3", + "decimal: New Sensor 4", + "decimal: New Sensor 5", + "decimal: New Sensor 6", + "decimal: New Sensor 7", + "decimal: forecast code during the archive interval", + "decimal: inches", + "decimal: degrees (-90 - 160)", + "decimal: degrees (-90 - 160)", + "decimal: degrees (-90 - 160)", + "decimal: degrees (-90 - 160)", + "decimal: degrees (-90 - 160)", + "decimal: degrees (-90 - 160)", + "decimal: centibars of dryness", + "decimal: centibars of dryness", + "decimal: centibars of dryness", + "decimal: centibars of dryness", + "decimal: centibars of dryness", + "decimal: centibars of dryness", + "decimal: Leaf Wetness code (0-15, 255)", + "decimal: Leaf Wetness code (0-15, 255)", + "decimal: Leaf Wetness code (0-15, 255)", + "decimal: Leaf Wetness code (0-15, 255)", + "decimal: degrees (-90 - 160)", + "decimal: degrees (-90 - 160)", + "decimal: degrees (-90 - 160)", + "decimal: degrees (-90 - 160)", + "decimal: degrees (-90 - 160)", + "decimal: degrees (-90 - 160)", + "decimal: degrees (-90 - 160)", + "decimal: percent (0 - 100)", + "decimal: percent (0 - 100)", + "decimal: percent (0 - 100)", + "decimal: percent (0 - 100)", + "decimal: percent (0 - 100)", + "decimal: percent (0 - 100)", + "decimal: percent (0 - 100)" +}; + +static struct ColumnRanges +{ + float min; + float max; +} ValueChecker[AFEDIT_COLUMNS] = +{ + { 1.0, 1.0 }, + { 5.0, 30.0 }, + { 0.0, 65535.0 }, + { 0.0, 65535.0 }, + { 5.0, 1440.0 }, + { -90.0, 160.0 }, + { -90.0, 160.0 }, + { -90.0, 160.0 }, + { -90.0, 160.0 }, + { 28.0, 33.0 }, + { 0.0, 100.0 }, + { 0.0, 100.0 }, + { 0.0, 65535.0 }, + { 0.0, 50000.0 }, + { 0.0, 200.0 }, + { 0.0, 200.0 }, + { 0.0, 255.0 }, + { 0.0, 255.0 }, + { 0.0, 32767.0 }, + { 0.0, 2000.0 }, + { 0.0, 2000.0 }, + { 0.0, 15.0 }, + { 0.0, 15.0 }, + { -90.0, 160.0 }, + { -90.0, 160.0 }, + { -90.0, 160.0 }, + { -90.0, 160.0 }, + { 0.0, 65535.0 }, + { 0.0, 65535.0 }, + { 0.0, 65535.0 }, + { 0.0, 65535.0 }, + { 0.0, 65535.0 }, + { 0.0, 65535.0 }, + { 0.0, 65535.0 }, + { 0.0, 65535.0 }, + { 0.0, 200.0 }, + { -90.0, 160.0 }, + { -90.0, 160.0 }, + { -90.0, 160.0 }, + { -90.0, 160.0 }, + { -90.0, 160.0 }, + { -90.0, 160.0 }, + { 0.0, 200.0 }, + { 0.0, 200.0 }, + { 0.0, 200.0 }, + { 0.0, 200.0 }, + { 0.0, 200.0 }, + { 0.0, 200.0 }, + { 0.0, 255.0 }, + { 0.0, 255.0 }, + { 0.0, 255.0 }, + { 0.0, 255.0 }, + { -90.0, 160.0 }, + { -90.0, 160.0 }, + { -90.0, 160.0 }, + { -90.0, 160.0 }, + { -90.0, 160.0 }, + { -90.0, 160.0 }, + { -90.0, 160.0 }, + { 0.0, 100.0 }, + { 0.0, 100.0 }, + { 0.0, 100.0 }, + { 0.0, 100.0 }, + { 0.0, 100.0 }, + { 0.0, 100.0 }, + { 0.0, 100.0 } +}; + +void MainForm::init() +{ + FullPathName = QString::null; + for (int i = 0; i < AFEDIT_COLUMNS; i ++) + { + archiveTable->setColumnStretchable (i, false); + archiveTable->setItem (0, i, new QTableItem (archiveTable, QTableItem::WhenCurrent, tr(AFEDIT_COL_HEADERS[i]))); + archiveTable->adjustColumn (i); + archiveTable->clearCell (0, i); + archiveTable->horizontalHeader()->setLabel (i, tr(AFEDIT_COL_HEADERS[i])); + } + archiveTable->hideColumn (AFEDIT_COLUMNS); + + QString HelpText = AFEDIT_COL_HELPERS[archiveTable->currentColumn()]; + lineEdit_Hint->setText (HelpText); + +} + + +void MainForm::fileOpen() +{ + QString fullPath, Caption; + + if (formUtilsGetFile (fullPath)) + { + FullPathName = fullPath; + Caption = "wview Archive File Editor: "; + Caption += FullPathName; + setCaption (Caption); + + formUtilsPopulateTable (FullPathName, archiveTable); + } + return; +} + + +void MainForm::fileSave() +{ + if (FullPathName != QString::null) + { + // Write out the new file + formUtilsWriteFile (FullPathName, archiveTable); + } +} + + +void MainForm::fileSaveAs() +{ + QString fullPath, Caption; + + if (formUtilsGetSaveAsFile (fullPath)) + { + FullPathName = fullPath; + Caption = "wview Archive File Editor: "; + Caption += FullPathName; + setCaption (Caption); + + // Write out the new file + formUtilsWriteFile (FullPathName, archiveTable); + } + return; + +} + + +void MainForm::fileExit() +{ + exit (0); +} + + +void MainForm::helpContents() +{ + +} + + +void MainForm::helpAbout() +{ + AboutAFEdit AboutDialog; + QString TempStr; + + TempStr.sprintf ("Version %.2f", AFEDIT_VERSION); + AboutDialog.VersionLabel->setText (TempStr); + AboutDialog.exec(); +} + +void MainForm::archiveTable_selectionChanged() +{ + QString HelpText = AFEDIT_COL_HELPERS[archiveTable->currentColumn()]; + lineEdit_Hint->setText (HelpText); +} + +void MainForm::archiveTable_valueChanged( int row, int col ) +{ + float ValToCheck; + QString field = archiveTable->text(row, col); + + if (field == QString::null) + { + return; + } + + if (col == 2 || col == 3 || col == 12) + { + // hex value + ValToCheck = (float)field.toUShort(0, 16); + } + else + { + ValToCheck = field.toFloat(); + } + + // range check it + if ((ValToCheck < ValueChecker[col].min) || (ValToCheck > ValueChecker[col].max)) + { + // squawk about it + field.sprintf ("New value: %.1f is OUT OF RANGE (%.1f - %.1f) for column %s:\n" + "You should fix this value now at row %d, column %d.", + ValToCheck, + ValueChecker[col].min, + ValueChecker[col].max, + AFEDIT_COL_HEADERS[col], + row + 1, + col + 1); + QMessageBox::information (this, "wview Archive Editor", field); + } +} diff --git a/afedit/src/src.pro b/afedit/src/src.pro new file mode 100644 index 0000000..644d6c9 --- /dev/null +++ b/afedit/src/src.pro @@ -0,0 +1,37 @@ +TEMPLATE = app +LANGUAGE = C++ +CONFIG += release warn_on thread qt + +HEADERS += mainform.ui.h \ + afedit.h \ + formutils.h \ + archiveFile.h +SOURCES += main.cpp \ + formutils.cpp \ + archiveFile.cpp +FORMS = mainform.ui \ + aboutafedit.ui +IMAGES = ../images/filenew \ + ../images/fileopen \ + ../images/filesave \ + ../images/print \ + ../images/undo \ + ../images/redo \ + ../images/editcut \ + ../images/editcopy \ + ../images/editpaste \ + ../images/searchfind + +QMAKE_CFLAGS += -DAFEDIT_VERSION="0.50" +QMAKE_CXXFLAGS += -DAFEDIT_VERSION="0.50" +TARGET = ../bin/afedit + + + + + +unix{ + UI_DIR = .ui + MOC_DIR = .moc + OBJECTS_DIR = .obj +} diff --git a/afedit/templates/cpp b/afedit/templates/cpp new file mode 100644 index 0000000..afe9a28 --- /dev/null +++ b/afedit/templates/cpp @@ -0,0 +1,19 @@ +/*************************************************************************** + * Copyright (C) 2007 by Mark Teel * + * mteel@teelworks.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ diff --git a/afedit/templates/h b/afedit/templates/h new file mode 100644 index 0000000..afe9a28 --- /dev/null +++ b/afedit/templates/h @@ -0,0 +1,19 @@ +/*************************************************************************** + * Copyright (C) 2007 by Mark Teel * + * mteel@teelworks.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ diff --git a/alarms/Makefile.am b/alarms/Makefile.am new file mode 100755 index 0000000..bfd3c71 --- /dev/null +++ b/alarms/Makefile.am @@ -0,0 +1,44 @@ +# Makefile - wvalarmd + +# files to include in the distro but not build + +#define the executable to be built +bin_PROGRAMS = wvalarmd + +# define include directories +INCLUDES = \ + -I$(top_srcdir)/common \ + -I$(prefix)/include \ + -D_GNU_SOURCE \ + -DWV_CONFIG_DIR=\"$(sysconfdir)/wview\" \ + -DWV_RUN_DIR=\"$(localstatedir)/wview\" \ + -DBUILD_WVALARMD + +# define the sources +wvalarmd_SOURCES = \ + $(top_srcdir)/common/wvutils.c \ + $(top_srcdir)/common/wvconfig.c \ + $(top_srcdir)/common/dbsqlite.c \ + $(top_srcdir)/common/datafeed.c \ + $(top_srcdir)/common/datafeed.h \ + $(top_srcdir)/common/status.c \ + $(top_srcdir)/common/status.h \ + $(top_srcdir)/alarms/alarms.c \ + $(top_srcdir)/common/datadefs.h \ + $(top_srcdir)/common/dbsqlite.h \ + $(top_srcdir)/common/services.h \ + $(top_srcdir)/common/sysdefs.h \ + $(top_srcdir)/common/wvconfig.h \ + $(top_srcdir)/alarms/alarms.h + + +# define libraries +wvalarmd_LDADD = + +# define library directories +wvalarmd_LDFLAGS = -L$(prefix)/lib -L$(prefix)/usr/lib -L/usr/lib + +if CROSSCOMPILE +wvalarmd_LDFLAGS += $(prefix)/lib/crt1.o $(prefix)/lib/crti.o $(prefix)/lib/crtn.o +endif + diff --git a/alarms/alarms.c b/alarms/alarms.c new file mode 100644 index 0000000..889a459 --- /dev/null +++ b/alarms/alarms.c @@ -0,0 +1,1650 @@ +/*--------------------------------------------------------------------------- + + FILENAME: + alarms.c + + PURPOSE: + Provide the wview alarms generator entry point. + + REVISION HISTORY: + Date Engineer Revision Remarks + 01/10/04 M.S. Teel 0 Original + + NOTES: + + + LICENSE: + Copyright (c) 2004, Mark S. Teel (mark@teel.ws) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +/* ... System include files +*/ +#include +#include +#include + +/* ... Library include files +*/ +#include + +/* ... Local include files +*/ +#include +#include + + +/* ... global memory declarations +*/ + +/* ... global memory referenced +*/ + +/* ... static (local) memory declarations +*/ +static WVIEW_ALARM_WORK alarmsWork; + +static char* alarmsStatusLabels[STATUS_STATS_MAX] = +{ + "Alarms defined", + "Alarm scripts invoked", + "Datafeed clients", + "Datafeed packets sent" +}; + + +/* ... methods +*/ + +static int executeScript (WVIEW_ALARM *alarm) +{ + int retVal; + char *args[5]; + char path[_MAX_PATH]; + char type[64]; + char thresh[64]; + char trigVal[64]; + + wvstrncpy (path, alarm->scriptToRun, _MAX_PATH); + args[0] = path; + + sprintf (type, "%d", alarm->type); + args[1] = type; + + sprintf (thresh, "%.3f", alarm->bound); + args[2] = thresh; + + sprintf (trigVal, "%.3f", alarm->triggerValue); + args[3] = trigVal; + + args[4] = 0; + + retVal = fork (); + if (retVal == -1) + { + return -1; + } + else if (retVal == 0) + { + // we are the child! + if (execv (path, args) == -1) + { + radMsgLog(PRI_HIGH, "executeScript: execv(%s) failed: %s", path, strerror(errno)); + exit(-1); + } + exit(0); + } + else + { + // we are the parent, pause a bit to let the script run + // and avoid a process "storm" + radUtilsSleep (50); // 50 ms + } + + return 0; +} + +static int readAlarmsConfig (void) +{ + WVIEW_ALARM *alarm; + int i, numAlarms = 0; + int iValue; + char type[_MAX_PATH]; + int boolMAX; + double thresh; + int abate; + char exec[_MAX_PATH]; + char conftype[64]; + const char* temp; + + if (wvconfigInit(FALSE) == ERROR) + { + radMsgLog (PRI_CATASTROPHIC, "wvconfigInit failed!"); + return ERROR; + } + + // Is the alarms daemon enabled? + iValue = wvconfigGetBooleanValue(configItem_ENABLE_ALARMS); + if (iValue == ERROR || iValue == 0) + { + wvconfigExit (); + return ERROR_ABORT; + } + + // set the wview verbosity setting + if (wvutilsSetVerbosity (WV_VERBOSE_WVALARMD) == ERROR) + { + radMsgLog (PRI_CATASTROPHIC, "wvutilsSetVerbosity failed!"); + wvconfigExit (); + return ERROR_ABORT; + } + + // get the metric units flag: + iValue = wvconfigGetBooleanValue(configItem_ALARMS_STATION_METRIC); + if (iValue == ERROR) + { + alarmsWork.isMetric = 0; + } + else + { + alarmsWork.isMetric = iValue; + } + + // get the do test flag: + iValue = wvconfigGetBooleanValue(configItem_ALARMS_DO_TEST); + if (iValue <= 0) + { + alarmsWork.doTest = FALSE; + } + else + { + alarmsWork.doTest = TRUE; + alarmsWork.doTestNumber = wvconfigGetINTValue(configItem_ALARMS_DO_TEST_NUMBER); + } + + for (i = 1; i <= ALARMS_MAX; i ++) + { + sprintf (conftype, "ALARMS_%1.1d_TYPE", i); + temp = wvconfigGetStringValue(conftype); + if (temp == NULL) + { + // No type defined - continue: + continue; + } + wvstrncpy(type, temp, _MAX_PATH); + + sprintf (conftype, "ALARMS_%1.1d_MAX", i); + boolMAX = wvconfigGetBooleanValue(conftype); + if (boolMAX == ERROR) + { + continue; + } + + sprintf (conftype, "ALARMS_%1.1d_THRESHOLD", i); + thresh = wvconfigGetDOUBLEValue(conftype); + + sprintf (conftype, "ALARMS_%1.1d_ABATEMENT", i); + abate = wvconfigGetINTValue(conftype); + + sprintf (conftype, "ALARMS_%1.1d_EXECUTE", i); + temp = wvconfigGetStringValue(conftype); + if (temp == NULL) + { + // No type defined - continue: + continue; + } + wvstrncpy(exec, temp, _MAX_PATH); + + alarm = (WVIEW_ALARM *) malloc (sizeof (*alarm)); + if (alarm == NULL) + { + for (alarm = (WVIEW_ALARM *)radListRemoveFirst (&alarmsWork.alarmList); + alarm != NULL; + alarm = (WVIEW_ALARM *)radListRemoveFirst (&alarmsWork.alarmList)) + { + free (alarm); + } + + return ERROR; + } + memset (alarm, 0, sizeof (*alarm)); + + + // get the type + if (!strcmp (type, "Barometer")) + { + alarm->type = Barometer; + } + else if (!strcmp (type, "InsideTemp")) + { + alarm->type = InsideTemp; + } + else if (!strcmp (type, "InsideHumidity")) + { + alarm->type = InsideHumidity; + } + else if (!strcmp (type, "OutsideTemp")) + { + alarm->type = OutsideTemp; + } + else if (!strcmp (type, "WindSpeed")) + { + alarm->type = WindSpeed; + } + else if (!strcmp (type, "TenMinuteAvgWindSpeed")) + { + alarm->type = TenMinuteAvgWindSpeed; + } + else if (!strcmp (type, "WindDirection")) + { + alarm->type = WindDirection; + } + else if (!strcmp (type, "OutsideHumidity")) + { + alarm->type = OutsideHumidity; + } + else if (!strcmp (type, "RainRate")) + { + alarm->type = RainRate; + } + else if (!strcmp (type, "StormRain")) + { + alarm->type = StormRain; + } + else if (!strcmp (type, "DayRain")) + { + alarm->type = DayRain; + } + else if (!strcmp (type, "MonthRain")) + { + alarm->type = MonthRain; + } + else if (!strcmp (type, "YearRain")) + { + alarm->type = YearRain; + } + else if (!strcmp (type, "TxBatteryStatus")) + { + alarm->type = TxBatteryStatus; + } + else if (!strcmp (type, "ConsoleBatteryVoltage")) + { + alarm->type = ConsoleBatteryVoltage; + } + else if (!strcmp (type, "DewPoint")) + { + alarm->type = DewPoint; + } + else if (!strcmp (type, "WindChill")) + { + alarm->type = WindChill; + } + else if (!strcmp (type, "HeatIndex")) + { + alarm->type = HeatIndex; + } + else if (!strcmp (type, "Radiation")) + { + alarm->type = Radiation; + } + else if (!strcmp (type, "UV")) + { + alarm->type = UV; + } + else if (!strcmp (type, "ET")) + { + alarm->type = ET; + } + else if (!strcmp (type, "ExtraTemp1")) + { + alarm->type = ExtraTemp1; + } + else if (!strcmp (type, "ExtraTemp2")) + { + alarm->type = ExtraTemp2; + } + else if (!strcmp (type, "ExtraTemp3")) + { + alarm->type = ExtraTemp3; + } + else if (!strcmp (type, "SoilTemp1")) + { + alarm->type = SoilTemp1; + } + else if (!strcmp (type, "SoilTemp2")) + { + alarm->type = SoilTemp2; + } + else if (!strcmp (type, "SoilTemp3")) + { + alarm->type = SoilTemp3; + } + else if (!strcmp (type, "SoilTemp4")) + { + alarm->type = SoilTemp4; + } + else if (!strcmp (type, "LeafTemp1")) + { + alarm->type = LeafTemp1; + } + else if (!strcmp (type, "LeafTemp2")) + { + alarm->type = LeafTemp2; + } + else if (!strcmp (type, "ExtraHumid1")) + { + alarm->type = ExtraHumid1; + } + else if (!strcmp (type, "ExtraHumid2")) + { + alarm->type = ExtraHumid2; + } + else if (!strcmp (type, "Wxt510Hail")) + { + alarm->type = Wxt510Hail; + } + else if (!strcmp (type, "Wxt510Hailrate")) + { + alarm->type = Wxt510Hailrate; + } + else if (!strcmp (type, "Wxt510HeatingTemp")) + { + alarm->type = Wxt510HeatingTemp; + } + else if (!strcmp (type, "Wxt510HeatingVoltage")) + { + alarm->type = Wxt510HeatingVoltage; + } + else if (!strcmp (type, "Wxt510SupplyVoltage")) + { + alarm->type = Wxt510SupplyVoltage; + } + else if (!strcmp (type, "Wxt510ReferenceVoltage")) + { + alarm->type = Wxt510ReferenceVoltage; + } + else + { + free (alarm); + radMsgLog (PRI_MEDIUM, "invalid alarm type %s - skipping...", + type); + continue; + } + + // do the max/min flag + alarm->isMax = boolMAX; + + // now the bounding value + alarm->bound = (float)thresh; + + // now the abatement seconds + alarm->abateSecs = abate; + + // finally, the alarm script + wvstrncpy (alarm->scriptToRun, exec, WVIEW_ALARM_SCRIPT_LENGTH); + + radListAddToEnd (&alarmsWork.alarmList, (NODE_PTR)alarm); + } + + wvconfigExit (); + return (radListGetNumberOfNodes (&alarmsWork.alarmList)); +} + +static int waitForWviewDaemon (void) +{ + WVIEW_MSG_REQUEST msg; + char srcQName[QUEUE_NAME_LENGTH+1]; + UINT msgType; + UINT length; + void *recvBfr; + int retVal, done = FALSE; + + // enable message reception from the radlib router for the archive path + radMsgRouterMessageRegister (WVIEW_MSG_TYPE_STATION_INFO); + + msg.requestType = WVIEW_RQST_TYPE_STATION_INFO; + + if (radMsgRouterMessageSend (WVIEW_MSG_TYPE_REQUEST, &msg, sizeof(msg)) == ERROR) + { + radMsgLog (PRI_HIGH, "waitForWviewDaemon: radMsgRouterMessageSend failed!"); + radMsgRouterMessageDeregister (WVIEW_MSG_TYPE_STATION_INFO); + return ERROR; + } + + statusUpdate(STATUS_WAITING_FOR_WVIEW); + + // now wait for the response here + while (!done) + { + radUtilsSleep (50); + + if ((retVal = radQueueRecv (radProcessQueueGetID (), + srcQName, + &msgType, + &recvBfr, + &length)) + == FALSE) + { + continue; + } + else if (retVal == ERROR) + { + radMsgLog (PRI_STATUS, "waitForWviewDaemon: queue is closed!"); + statusUpdateMessage("waitForWviewDaemon: queue is closed!"); + radMsgRouterMessageDeregister (WVIEW_MSG_TYPE_STATION_INFO); + return ERROR; + } + + // is this what we want? + if (msgType == WVIEW_MSG_TYPE_STATION_INFO) + { + // yes! + done = TRUE; + } + else if (msgType == WVIEW_MSG_TYPE_SHUTDOWN) + { + radMsgLog (PRI_HIGH, "waitForWviewDaemon: received shutdown from wviewd"); + statusUpdateMessage("waitForWviewDaemon: received shutdown from wviewd"); + radMsgRouterMessageDeregister (WVIEW_MSG_TYPE_STATION_INFO); + return ERROR; + } + + // release the received buffer + radBufferRls (recvBfr); + } + + // disable message reception from the radlib router for the archive path + radMsgRouterMessageDeregister (WVIEW_MSG_TYPE_STATION_INFO); + + return OK; +} + +static int WriteArchiveToClient(RADSOCK_ID client, ARCHIVE_PKT* archive) +{ + ARCHIVE_PKT dummy; + + // write the frame start so clients may sync to the beginning of each + // data update + if (radSocketWriteExact(client, + (void *)DF_ARCHIVE_START_FRAME, + DF_START_FRAME_LENGTH) + != DF_START_FRAME_LENGTH) + { + return ERROR; + } + + // If the rec is NULL, send a done indicator: + if (archive == NULL) + { + dummy.dateTime = 0; + radSocketWriteExact(client, &dummy, sizeof(dummy)); + } + else + { + // write out the archive data in network byte order: + if (radSocketWriteExact(client, archive, sizeof(*archive)) != sizeof(*archive)) + { + return ERROR; + } + } + + statusIncrementStat(ALARM_STATS_PKTS_SENT); + return OK; +} + +static void pushArchiveToClients(ARCHIVE_PKT* archive) +{ + WVIEW_ALARM_CLIENT *client, *oldClient; + ARCHIVE_PKT networkArchive; + + datafeedConvertArchive_HTON(&networkArchive, archive); + + // Push to each socket client: + for (client = (WVIEW_ALARM_CLIENT *) radListGetFirst (&alarmsWork.clientList); + client != NULL; + client = (WVIEW_ALARM_CLIENT *) radListGetNext (&alarmsWork.clientList, + (NODE_PTR)client)) + { + // Check for archive sync in progress: + if (client->syncInProgress) + { + continue; + } + + // Write start frame and archive packet on the socket: + if (WriteArchiveToClient(client->client, &networkArchive) == ERROR) + { + // write error, bail on this guy + radMsgLog (PRI_HIGH, "ARCHIVE: write error to client %s:%d - closing socket...", + radSocketGetHost (client->client), + radSocketGetPort (client->client)); + statusDecrementStat(ALARM_STATS_CLIENTS); + radProcessIODeRegisterDescriptorByFd(radSocketGetDescriptor(client->client)); + radSocketDestroy (client->client); + oldClient = client; + client = (WVIEW_ALARM_CLIENT *) + radListGetPrevious (&alarmsWork.clientList, (NODE_PTR)client); + radListRemove (&alarmsWork.clientList, (NODE_PTR)oldClient); + free (oldClient); + continue; + } + } + + return; +} + +static void pushLoopToClients(LOOP_PKT* loopData) +{ + WVIEW_ALARM_CLIENT *client, *oldClient; + LOOP_PKT networkLoop; + + datafeedConvertLOOP_HTON(&networkLoop, loopData); + + // Push to each socket client: + for (client = (WVIEW_ALARM_CLIENT *) radListGetFirst (&alarmsWork.clientList); + client != NULL; + client = (WVIEW_ALARM_CLIENT *) radListGetNext (&alarmsWork.clientList, + (NODE_PTR)client)) + { + // write the frame start so clients may sync to the beginning of each + // data update + if (radSocketWriteExact (client->client, + (void *)DF_LOOP_START_FRAME, + DF_START_FRAME_LENGTH) + != DF_START_FRAME_LENGTH) + { + // write error, bail on this guy + radMsgLog (PRI_HIGH, "LOOP: write error to client %s:%d - closing socket...", + radSocketGetHost (client->client), + radSocketGetPort (client->client)); + statusDecrementStat(ALARM_STATS_CLIENTS); + radProcessIODeRegisterDescriptorByFd(radSocketGetDescriptor(client->client)); + radSocketDestroy (client->client); + oldClient = client; + client = (WVIEW_ALARM_CLIENT *) + radListGetPrevious (&alarmsWork.clientList, (NODE_PTR)client); + radListRemove (&alarmsWork.clientList, (NODE_PTR)oldClient); + free (oldClient); + continue; + } + + // write out the loop data in network byte order: + if (radSocketWriteExact(client->client, &networkLoop, sizeof(networkLoop)) + != sizeof(networkLoop)) + { + // write error, bail on this guy + radMsgLog (PRI_HIGH, "LOOP: write error to client %s:%d - closing socket...", + radSocketGetHost (client->client), + radSocketGetPort (client->client)); + statusDecrementStat(ALARM_STATS_CLIENTS); + radProcessIODeRegisterDescriptorByFd(radSocketGetDescriptor(client->client)); + radSocketDestroy (client->client); + oldClient = client; + client = (WVIEW_ALARM_CLIENT *) + radListGetPrevious (&alarmsWork.clientList, (NODE_PTR)client); + radListRemove (&alarmsWork.clientList, (NODE_PTR)oldClient); + free (oldClient); + continue; + } + + statusIncrementStat(ALARM_STATS_PKTS_SENT); + } + + return; +} + +static void processAlarms (LOOP_PKT *loopData) +{ + WVIEW_ALARM *alarm; + float tempFloat; + + // process the local alarms: + for (alarm = (WVIEW_ALARM *) radListGetFirst (&alarmsWork.alarmList); + alarm != NULL; + alarm = (WVIEW_ALARM *) radListGetNext (&alarmsWork.alarmList, + (NODE_PTR)alarm)) + { + // first check to see if we are in abatement + if (alarm->triggered) + { + if ((radTimeGetSECSinceEpoch () - alarm->abateStart) < alarm->abateSecs) + { + // abatement - go to the next alarm + continue; + } + else + { + // clear trigger for future alarms + alarm->triggered = FALSE; + } + } + + // switch on alarm type + switch (alarm->type) + { + case Barometer: + if (alarmsWork.isMetric) + { + tempFloat = wvutilsConvertINHGToHPA(loopData->barometer); + } + else + { + tempFloat = loopData->barometer; + } + break; + + case InsideTemp: + if (alarmsWork.isMetric) + { + tempFloat = wvutilsConvertFToC(loopData->inTemp); + } + else + { + tempFloat = loopData->inTemp; + } + break; + + case InsideHumidity: + tempFloat = (float)loopData->inHumidity; + break; + + case OutsideTemp: + if (alarmsWork.isMetric) + { + tempFloat = wvutilsConvertFToC(loopData->outTemp); + } + else + { + tempFloat = loopData->outTemp; + } + break; + + case WindSpeed: + if (alarmsWork.isMetric) + { + tempFloat = wvutilsConvertMPHToKPH(loopData->windSpeedF); + } + else + { + tempFloat = loopData->windSpeedF; + } + break; + + case TenMinuteAvgWindSpeed: + if (alarmsWork.isMetric) + { + tempFloat = wvutilsConvertMPHToKPH((float)loopData->tenMinuteAvgWindSpeed); + } + else + { + tempFloat = (float)loopData->tenMinuteAvgWindSpeed; + } + break; + + case WindDirection: + tempFloat = (float)loopData->windDir; + break; + + case OutsideHumidity: + tempFloat = (float)loopData->outHumidity; + break; + + case RainRate: + if (alarmsWork.isMetric) + { + tempFloat = wvutilsConvertRainINToMetric(loopData->rainRate); + } + else + { + tempFloat = loopData->rainRate; + } + break; + + case StormRain: + if (alarmsWork.isMetric) + { + tempFloat = wvutilsConvertRainINToMetric(loopData->stormRain); + } + else + { + tempFloat = loopData->stormRain; + } + break; + + case DayRain: + if (alarmsWork.isMetric) + { + tempFloat = wvutilsConvertRainINToMetric(loopData->dayRain); + } + else + { + tempFloat = loopData->dayRain; + } + break; + + case MonthRain: + if (alarmsWork.isMetric) + { + tempFloat = wvutilsConvertRainINToMetric(loopData->monthRain); + } + else + { + tempFloat = loopData->monthRain; + } + break; + + case YearRain: + if (alarmsWork.isMetric) + { + tempFloat = wvutilsConvertRainINToMetric(loopData->yearRain); + } + else + { + tempFloat = loopData->yearRain; + } + break; + + case TxBatteryStatus: + tempFloat = (float)loopData->txBatteryStatus; + break; + + case ConsoleBatteryVoltage: + tempFloat = (((float)loopData->consBatteryVoltage * 300)/512)/100; + break; + + case DewPoint: + if (alarmsWork.isMetric) + { + tempFloat = wvutilsConvertFToC(loopData->dewpoint); + } + else + { + tempFloat = loopData->dewpoint; + } + break; + + case WindChill: + if (alarmsWork.isMetric) + { + tempFloat = wvutilsConvertFToC(loopData->windchill); + } + else + { + tempFloat = loopData->windchill; + } + break; + + case HeatIndex: + if (alarmsWork.isMetric) + { + tempFloat = wvutilsConvertFToC(loopData->heatindex); + } + else + { + tempFloat = loopData->heatindex; + } + break; + + case Radiation: + tempFloat = (float)loopData->radiation; + break; + + case UV: + tempFloat = (float)loopData->UV; + break; + + case ET: + if (alarmsWork.isMetric) + { + tempFloat = wvutilsConvertRainINToMetric(loopData->dayET); + } + else + { + tempFloat = loopData->dayET; + } + break; + + case ExtraTemp1: + if (alarmsWork.isMetric) + { + tempFloat = wvutilsConvertFToC(loopData->extraTemp[0]); + } + else + { + tempFloat = loopData->extraTemp[0]; + } + break; + + case ExtraTemp2: + if (alarmsWork.isMetric) + { + tempFloat = wvutilsConvertFToC(loopData->extraTemp[1]); + } + else + { + tempFloat = loopData->extraTemp[1]; + } + break; + + case ExtraTemp3: + if (alarmsWork.isMetric) + { + tempFloat = wvutilsConvertFToC(loopData->extraTemp[2]); + } + else + { + tempFloat = loopData->extraTemp[2]; + } + break; + + case SoilTemp1: + if (alarmsWork.isMetric) + { + tempFloat = wvutilsConvertFToC(loopData->soilTemp1); + } + else + { + tempFloat = loopData->soilTemp1; + } + break; + + case SoilTemp2: + if (alarmsWork.isMetric) + { + tempFloat = wvutilsConvertFToC(loopData->soilTemp2); + } + else + { + tempFloat = loopData->soilTemp2; + } + break; + + case SoilTemp3: + if (alarmsWork.isMetric) + { + tempFloat = wvutilsConvertFToC(loopData->soilTemp3); + } + else + { + tempFloat = loopData->soilTemp3; + } + break; + + case SoilTemp4: + if (alarmsWork.isMetric) + { + tempFloat = wvutilsConvertFToC(loopData->soilTemp4); + } + else + { + tempFloat = loopData->soilTemp4; + } + break; + + case LeafTemp1: + if (alarmsWork.isMetric) + { + tempFloat = wvutilsConvertFToC(loopData->leafTemp1); + } + else + { + tempFloat = loopData->leafTemp1; + } + break; + + case LeafTemp2: + if (alarmsWork.isMetric) + { + tempFloat = wvutilsConvertFToC(loopData->leafTemp2); + } + else + { + tempFloat = loopData->leafTemp2; + } + break; + + case ExtraHumid1: + tempFloat = (float)loopData->extraHumidity[0]; + break; + + case ExtraHumid2: + tempFloat = (float)loopData->extraHumidity[1]; + break; + + case Wxt510Hail: + if (alarmsWork.isMetric) + { + tempFloat = wvutilsConvertRainINToMetric(loopData->wxt510Hail); + } + else + { + tempFloat = loopData->wxt510Hail; + } + break; + + case Wxt510Hailrate: + if (alarmsWork.isMetric) + { + tempFloat = wvutilsConvertRainINToMetric(loopData->wxt510Hailrate); + } + else + { + tempFloat = loopData->wxt510Hailrate; + } + break; + + case Wxt510HeatingTemp: + if (alarmsWork.isMetric) + { + tempFloat = wvutilsConvertFToC(loopData->wxt510HeatingTemp); + } + else + { + tempFloat = loopData->wxt510HeatingTemp; + } + break; + + case Wxt510HeatingVoltage: + tempFloat = loopData->wxt510HeatingVoltage; + break; + + case Wxt510SupplyVoltage: + tempFloat = loopData->wxt510SupplyVoltage; + break; + + case Wxt510ReferenceVoltage: + tempFloat = loopData->wxt510ReferenceVoltage; + break; + + default: + // no match, blow it off + continue; + } + + // see if we tripped the breaker here + if (alarm->isMax) + { + if (tempFloat >= alarm->bound) + { + // we did! + alarm->triggered = TRUE; + alarm->triggerValue = tempFloat; + alarm->abateStart = radTimeGetSECSinceEpoch (); + + // run user script here + statusIncrementStat(ALARM_STATS_SCRIPTS_RUN); + if (executeScript (alarm) != 0) + { + radMsgLog (PRI_MEDIUM, + "processAlarms: script %s failed", + alarm->scriptToRun); + } + } + } + else + { + if (tempFloat <= alarm->bound) + { + // we did! + alarm->triggered = TRUE; + alarm->triggerValue = tempFloat; + alarm->abateStart = radTimeGetSECSinceEpoch (); + + // run user script here + statusIncrementStat(ALARM_STATS_SCRIPTS_RUN); + if (executeScript (alarm) != 0) + { + radMsgLog (PRI_MEDIUM, + "processAlarms: script %s failed", + alarm->scriptToRun); + } + } + } + } + + return; +} + +/* ... system initialization +*/ +static int alarmsSysInit (WVIEW_ALARM_WORK *work) +{ + char devPath[256], temp[64]; + struct stat fileData; + + /* ... check for our daemon's pid file, don't run if it isn't there + */ + sprintf (devPath, "%s/%s", WVIEW_RUN_DIR, WVD_LOCK_FILE_NAME); + if (stat (devPath, &fileData) != 0) + { + radMsgLogInit (PROC_NAME_ALARMS, TRUE, TRUE); + radMsgLog (PRI_CATASTROPHIC, + "wviewd process is not running - aborting!"); + radMsgLogExit (); + return ERROR; + } + + sprintf (work->pidFile, "%s/%s", WVIEW_RUN_DIR, ALARMS_LOCK_FILE_NAME); + sprintf (work->statusFile, "%s/%s", WVIEW_STATUS_DIRECTORY, ALARMS_STATUS_FILE_NAME); + sprintf (work->fifoFile, "%s/dev/%s", WVIEW_RUN_DIR, PROC_NAME_ALARMS); + sprintf (work->daemonQname, "%s/dev/%s", WVIEW_RUN_DIR, PROC_NAME_DAEMON); + sprintf (work->wviewdir, "%s", WVIEW_RUN_DIR); + + /* ... check for our pid file, don't run if it IS there + */ + if (stat (work->pidFile, &fileData) == 0) + { + radMsgLogInit (PROC_NAME_ALARMS, TRUE, TRUE); + radMsgLog (PRI_CATASTROPHIC, + "lock file %s exists, older copy may be running - aborting!", + work->pidFile); + radMsgLogExit (); + return ERROR; + } + + return OK; +} + +/* ... system exit +*/ +static int alarmsSysExit (WVIEW_ALARM_WORK *work) +{ + struct stat fileData; + + /* ... delete our pid file + */ + if (stat (work->pidFile, &fileData) == 0) + { + unlink (work->pidFile); + } + + return OK; +} + +static void defaultSigHandler (int signum) +{ + int retVal; + + switch (signum) + { + case SIGHUP: + // user wants us to change the verbosity setting + retVal = wvutilsToggleVerbosity (); + radMsgLog (PRI_STATUS, "wvalarmd: SIGHUP - toggling log verbosity %s", + ((retVal == 0) ? "OFF" : "ON")); + + radProcessSignalCatch(signum, defaultSigHandler); + return; + + case SIGPIPE: + // we have a far end socket disconnection, we'll handle it in the + // "read/write" code + alarmsWork.sigpipe = TRUE; + radProcessSignalCatch(signum, defaultSigHandler); + break; + + case SIGCHLD: + wvutilsWaitForChildren(); + radProcessSignalCatch(signum, defaultSigHandler); + break; + + case SIGILL: + case SIGBUS: + case SIGFPE: + case SIGSEGV: + case SIGXFSZ: + case SIGSYS: + // unrecoverable radProcessSignalCatch- we must exit right now! + radMsgLog (PRI_CATASTROPHIC, + "wvalarmd: recv catastrophic signal %d: aborting!", + signum); + abort (); + + default: + // can we allow the process to exit normally? + if (!alarmsWork.inMainLoop) + { + // NO! - we gotta bail here! + statusUpdateMessage("wvalarmd: recv signal exiting now!"); + radMsgLog (PRI_HIGH, "wvalarmd: recv signal %d: exiting now!", signum); + if (alarmsWork.dataFeedServer) + radSocketDestroy (alarmsWork.dataFeedServer); + + radMsgRouterExit (); + alarmsSysExit (&alarmsWork); + radProcessExit (); + radSystemExit (WVIEW_SYSTEM_ID); + exit (1); + } + + // we can allow the process to exit normally... + statusUpdateMessage("wvalarmd: recv catastrophic signal exiting gracefully!"); + radMsgLog (PRI_HIGH, "wvalarmd: recv signal %d: exiting gracefully!", signum); + + alarmsWork.exiting = TRUE; + radProcessSetExitFlag (); + + radProcessSignalCatch(signum, defaultSigHandler); + break; + } + + return; +} + +static void msgHandler +( + char *srcQueueName, + UINT msgType, + void *msg, + UINT length, + void *userData +) +{ + LOOP_PKT* loopData; + ARCHIVE_PKT* archiveData; + + switch (msgType) + { + case WVIEW_MSG_TYPE_LOOP_DATA_SVC: + { + loopData = &(((WVIEW_MSG_LOOP_DATA*)msg)->loopData); + + // Push LOOP to clients: + pushLoopToClients(loopData); + + // process alarms + processAlarms(loopData); + break; + } + case WVIEW_MSG_TYPE_ARCHIVE_DATA: + { + archiveData = &(((WVIEW_MSG_ARCHIVE_DATA*)msg)->archiveData); + + // Push Archive to clients: + pushArchiveToClients(archiveData); + break; + } + case WVIEW_MSG_TYPE_POLL: + { + WVIEW_MSG_POLL* pPoll = (WVIEW_MSG_POLL*)msg; + wvutilsSendPMONPollResponse (pPoll->mask, PMON_PROCESS_WVALARMD); + break; + } + } + + return; +} + +static void evtHandler +( + UINT eventsRx, + UINT rxData, + void *userData +) +{ + return; +} + +static void timerHandler (void *parm) +{ + return; +} + +static WVIEW_ALARM_CLIENT* FindClient(RADSOCK_ID clientSock) +{ + WVIEW_ALARM_CLIENT* node; + + for (node = (WVIEW_ALARM_CLIENT*)radListGetFirst(&alarmsWork.clientList); + node != NULL; + node = (WVIEW_ALARM_CLIENT*)radListGetNext(&alarmsWork.clientList, (NODE_PTR)node)) + { + if (node->client == clientSock) + { + // found him! + return node; + } + } + + return NULL; +} + +static void RemoveClient(RADSOCK_ID clientSock) +{ + WVIEW_ALARM_CLIENT* node; + + for (node = (WVIEW_ALARM_CLIENT*)radListGetFirst(&alarmsWork.clientList); + node != NULL; + node = (WVIEW_ALARM_CLIENT*)radListGetNext(&alarmsWork.clientList, (NODE_PTR)node)) + { + if (node->client == clientSock) + { + // found him! + statusDecrementStat(ALARM_STATS_CLIENTS); + radListRemove(&alarmsWork.clientList, (NODE_PTR)node); + radProcessIODeRegisterDescriptorByFd(radSocketGetDescriptor(node->client)); + radSocketDestroy(node->client); + free(node); + return; + } + } +} + +static void SendNextArchiveRecord(RADSOCK_ID client, uint32_t dateTime) +{ + ARCHIVE_PKT recordStore; + ARCHIVE_PKT networkStore; + WVIEW_ALARM_CLIENT* alarmClient; + + alarmClient = FindClient(client); + if (alarmClient == NULL) + { + radMsgLog (PRI_HIGH, "SendNextArchiveRecord: failed to get client!"); + return; + } + + if (dbsqliteArchiveInit() == ERROR) + { + radMsgLog (PRI_HIGH, "SendNextArchiveRecord: failed to open archive db!"); + return; + } + + if (dbsqliteArchiveGetNextRecord((time_t)dateTime, &recordStore) == ERROR) + { + WriteArchiveToClient(client, NULL); + alarmClient->syncInProgress = FALSE; + return; + } + + dbsqliteArchiveExit(); + + + // Mark the sync in progress: + alarmClient->syncInProgress = TRUE; + + // OK, send the bloody thing: + datafeedConvertArchive_HTON(&networkStore, &recordStore); + if (WriteArchiveToClient(client, &networkStore) == ERROR) + { + statusUpdateMessage("SendNextArchiveRecord: failed to write archive record!"); + radMsgLog (PRI_HIGH, "SendNextArchiveRecord: failed to write archive record!"); + return; + } + + return; +} + +static void ClientDataRX (int fd, void *userData) +{ + RADSOCK_ID client = (RADSOCK_ID)userData; + int retVal; + uint32_t dateTime; + + retVal = datafeedSyncStartOfFrame(client); + switch (retVal) + { + case ERROR: + /* problems! - bail out */ + statusUpdateMessage("ClientDataRX: socket error during sync - disconnecting"); + radMsgLog (PRI_HIGH, "ClientDataRX: socket error during sync - disconnecting"); + RemoveClient(client); + break; + + case ERROR_ABORT: + // This guy has bailed out: + statusUpdateMessage("ClientDataRX: socket far-end closed"); + radMsgLog (PRI_MEDIUM, "ClientDataRX: socket far-end closed"); + RemoveClient(client); + break; + + case FALSE: + radMsgLog (PRI_STATUS, "ClientDataRX: RX sync failure - ignoring"); + break; + + case DF_RQST_ARCHIVE_PKT_TYPE: + // OK, read the unix time sent to retrieve the record: + if (radSocketReadExact(client, (void *)&dateTime, sizeof(dateTime)) + != sizeof (dateTime)) + { + statusUpdateMessage("ClientDataRX: socket read error - disconnecting"); + radMsgLog (PRI_HIGH, "ClientDataRX: socket read error - disconnecting"); + RemoveClient(client); + break; + } + + // Convert from network byte order: + dateTime = ntohl(dateTime); + + // Now we have the date and time, get busy: + SendNextArchiveRecord(client, dateTime); + break; + + default: + statusUpdateMessage("ClientDataRX: socket error during sync - disconnecting"); + radMsgLog (PRI_HIGH, "ClientDataRX: socket error during sync - disconnecting"); + RemoveClient(client); + break; + } + + return; +} + +static void dataFeedAccept (int fd, void *userData) +{ + RADSOCK_ID newConnection; + WVIEW_ALARM_CLIENT *client; + + newConnection = radSocketServerAcceptConnection(alarmsWork.dataFeedServer); + if (newConnection == NULL) + { + statusUpdateMessage("dataFeed: accept connection failed!"); + radMsgLog (PRI_MEDIUM, "dataFeed: accept connection failed!"); + return; + } + + // stick him on the data feed client list + client = (WVIEW_ALARM_CLIENT *) malloc(sizeof(*client)); + if (client == NULL) + { + radMsgLog (PRI_MEDIUM, "dataFeedAccept: malloc failed!"); + radSocketDestroy(newConnection); + return; + } + memset(client, 0, sizeof (*client)); + + client->client = newConnection; + + radSocketSetBlocking(client->client, TRUE); + + // add it to our descriptors of interest: + if (radProcessIORegisterDescriptor(radSocketGetDescriptor(client->client), + ClientDataRX, + (void*)client->client) + == ERROR) + { + statusUpdateMessage("dataFeedAccept: register descriptor failed!"); + radMsgLog (PRI_MEDIUM, "dataFeedAccept: register descriptor failed!"); + radSocketDestroy(client->client); + return; + } + + radListAddToEnd(&alarmsWork.clientList, (NODE_PTR)client); + + statusIncrementStat(ALARM_STATS_CLIENTS); + radMsgLog (PRI_STATUS, "dataFeed: client %s:%d accepted...", + radSocketGetHost (client->client), + radSocketGetPort (client->client)); + + return; +} + + +/* ... the main entry point for the alarm process +*/ +int main (int argc, char *argv[]) +{ + void (*alarmHandler)(int); + int retVal; + FILE *pidfile; + WVIEW_ALARM *alarm; + int runAsDaemon = TRUE; + + if (argc > 1) + { + if (!strcmp(argv[1], "-f")) + { + runAsDaemon = FALSE; + } + } + + memset (&alarmsWork, 0, sizeof (alarmsWork)); + radListReset (&alarmsWork.alarmList); + radListReset (&alarmsWork.clientList); + + /* ... initialize some system stuff first + */ + retVal = alarmsSysInit (&alarmsWork); + if (retVal == ERROR) + { + radMsgLogInit (PROC_NAME_ALARMS, FALSE, TRUE); + radMsgLog (PRI_CATASTROPHIC, "wvalarmd sysinit failed!"); + radMsgLogExit (); + exit (1); + } + else if (retVal == ERROR_ABORT) + { + exit (2); + } + + + /* ... call the global radlib system init function + */ + if (radSystemInit (WVIEW_SYSTEM_ID) == ERROR) + { + radMsgLogInit (PROC_NAME_ALARMS, TRUE, TRUE); + radMsgLog (PRI_CATASTROPHIC, "radSystemInit failed!"); + radMsgLogExit (); + exit (1); + } + + + /* ... call the radlib process init function + */ + if (radProcessInit (PROC_NAME_ALARMS, + alarmsWork.fifoFile, + PROC_NUM_TIMERS_ALARMS, + runAsDaemon, // TRUE for daemon + msgHandler, + evtHandler, + NULL) + == ERROR) + { + printf ("\nradProcessInit failed: %s\n\n", PROC_NAME_ALARMS); + radSystemExit (WVIEW_SYSTEM_ID); + exit (1); + } + + alarmsWork.myPid = getpid (); + pidfile = fopen (alarmsWork.pidFile, "w"); + if (pidfile == NULL) + { + radMsgLog (PRI_CATASTROPHIC, "lock file create failed!"); + radProcessExit (); + radSystemExit (WVIEW_SYSTEM_ID); + exit (1); + } + fprintf (pidfile, "%d", getpid ()); + fclose (pidfile); + + alarmHandler = radProcessSignalGetHandler (SIGALRM); + radProcessSignalCatchAll (defaultSigHandler); + radProcessSignalCatch (SIGALRM, alarmHandler); + radProcessSignalRelease(SIGABRT); + + + // grab all of our alarm definitions from the config database + retVal = readAlarmsConfig (); + if (retVal == ERROR_ABORT) + { + radMsgLog (PRI_HIGH, "ALARM daemon not enabled - exiting..."); + alarmsSysExit (&alarmsWork); + radProcessExit (); + radSystemExit (WVIEW_SYSTEM_ID); + exit (0); + } + else if (retVal < 0) + { + radMsgLog (PRI_HIGH, "readAlarmsConfig failed - " + "is there a problem with the wview config database?"); + alarmsSysExit (&alarmsWork); + radProcessExit (); + radSystemExit (WVIEW_SYSTEM_ID); + exit (1); + } + else + { + radMsgLog (PRI_STATUS, + "alarms: added %d alarm definitions", + retVal); + } + + if (statusInit(alarmsWork.statusFile, alarmsStatusLabels) == ERROR) + { + radMsgLog (PRI_HIGH, "ALARM status init failed - exiting..."); + alarmsSysExit (&alarmsWork); + radProcessExit (); + radSystemExit (WVIEW_SYSTEM_ID); + exit (1); + } + + statusUpdate(STATUS_BOOTING); + statusUpdateStat(ALARM_STATS_ALARMS, retVal); + + + // wait a bit here before continuing + radUtilsSleep (500); + + + // register with the radlib message router + if (radMsgRouterInit (WVIEW_RUN_DIR) == ERROR) + { + statusUpdateMessage("radMsgRouterInit failed!"); + radMsgLog (PRI_HIGH, "radMsgRouterInit failed!"); + statusUpdate(STATUS_ERROR); + alarmsSysExit (&alarmsWork); + radProcessExit (); + radSystemExit (WVIEW_SYSTEM_ID); + exit (1); + } + + // enable message reception from the radlib router for SHUTDOWN msgs + radMsgRouterMessageRegister (WVIEW_MSG_TYPE_SHUTDOWN); + + // wait for the wview daemon to be ready + if (waitForWviewDaemon () == ERROR) + { + radMsgLog (PRI_HIGH, "waitForWviewDaemon failed"); + statusUpdate(STATUS_ERROR); + radMsgRouterExit (); + alarmsSysExit (&alarmsWork); + radProcessExit (); + radSystemExit (WVIEW_SYSTEM_ID); + exit (1); + } + + // finally, initialize our data feed socket + alarmsWork.dataFeedServer = radSocketServerCreate(WV_DATAFEED_PORT); + if (alarmsWork.dataFeedServer == NULL) + { + statusUpdateMessage("radSocketServerCreate failed"); + radMsgLog (PRI_HIGH, "radSocketServerCreate failed..."); + statusUpdate(STATUS_ERROR); + radMsgRouterExit (); + alarmsSysExit (&alarmsWork); + radProcessExit (); + radSystemExit (WVIEW_SYSTEM_ID); + exit (1); + } + + // add it to our descriptors of interest + if (radProcessIORegisterDescriptor(radSocketGetDescriptor(alarmsWork.dataFeedServer), + dataFeedAccept, + NULL) + == ERROR) + { + statusUpdateMessage("radProcessIORegisterDescriptor server failed"); + radMsgLog (PRI_HIGH, "radProcessIORegisterDescriptor failed..."); + statusUpdate(STATUS_ERROR); + radSocketDestroy (alarmsWork.dataFeedServer); + radMsgRouterExit (); + alarmsSysExit (&alarmsWork); + radProcessExit (); + radSystemExit (WVIEW_SYSTEM_ID); + exit (1); + } + + // enable message reception from the radlib router for loop data + radMsgRouterMessageRegister (WVIEW_MSG_TYPE_LOOP_DATA_SVC); + + // enable message reception from the radlib router for archive data + radMsgRouterMessageRegister (WVIEW_MSG_TYPE_ARCHIVE_DATA); + + // enable message reception from the radlib router for POLL msgs + radMsgRouterMessageRegister (WVIEW_MSG_TYPE_POLL); + + + // enter normal processing + alarmsWork.inMainLoop = TRUE; + statusUpdate(STATUS_RUNNING); + statusUpdateMessage("Normal operation"); + radMsgLog (PRI_STATUS, "running..."); + + + // Do we need to trigger a test alarm? + if (alarmsWork.doTest) + { + if (1 <= alarmsWork.doTestNumber && alarmsWork.doTestNumber <= ALARMS_MAX) + { + // Generate the bad boy: + retVal = 1; + for (alarm = (WVIEW_ALARM *) radListGetFirst (&alarmsWork.alarmList); + retVal <= ALARMS_MAX && alarm != NULL; + alarm = (WVIEW_ALARM *) radListGetNext (&alarmsWork.alarmList, + (NODE_PTR)alarm)) + { + if (retVal == alarmsWork.doTestNumber) + { + // This is the one to test: + alarm->triggerValue = -1; + + // run user script here + if (executeScript(alarm) != 0) + { + radMsgLog (PRI_MEDIUM, + "Test Alarm %d: script %s failed", + retVal, alarm->scriptToRun); + } + else + { + radMsgLog (PRI_MEDIUM, + "Test Alarm %d: script %s executed", + retVal, alarm->scriptToRun); + } + retVal = ALARMS_MAX; + } + + retVal ++; + } + } + else + { + radMsgLog (PRI_MEDIUM, "Test Alarm: bad alarm index %d given!", + alarmsWork.doTestNumber); + } + } + + + while (!alarmsWork.exiting) + { + // wait on something interesting + if (radProcessWait (0) == ERROR) + { + alarmsWork.exiting = TRUE; + } + } + + + statusUpdateMessage("exiting normally"); + radMsgLog (PRI_STATUS, "exiting normally..."); + statusUpdate(STATUS_SHUTDOWN); + + radSocketDestroy (alarmsWork.dataFeedServer); + radMsgRouterExit (); + alarmsSysExit (&alarmsWork); + radProcessExit (); + radSystemExit (WVIEW_SYSTEM_ID); + exit (0); +} + diff --git a/alarms/alarms.h b/alarms/alarms.h new file mode 100644 index 0000000..a660b28 --- /dev/null +++ b/alarms/alarms.h @@ -0,0 +1,181 @@ +#ifndef INC_alarmsh +#define INC_alarmsh +/*--------------------------------------------------------------------------- + + FILENAME: + alarms.h + + PURPOSE: + Provide the wview alarm process definitions. + + REVISION HISTORY: + Date Engineer Revision Remarks + 05/22/2005 M.S. Teel 0 Original + + NOTES: + + + LICENSE: + Copyright (c) 2004, Mark S. Teel (mark@teel.ws) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +/* ... System include files +*/ +#include +#include +#include +#include +#include +#include +#include + +/* ... Library include files +*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* ... Local include files +*/ +#include +#include +#include +#include +#include +#include + + +/* ... API definitions +*/ + +// define alarm types here +typedef enum +{ + Barometer = 0, + InsideTemp, + InsideHumidity, + OutsideTemp, + WindSpeed, + TenMinuteAvgWindSpeed, + WindDirection, + OutsideHumidity, + RainRate, + StormRain, + DayRain, + MonthRain, + YearRain, + TxBatteryStatus, + ConsoleBatteryVoltage, + DewPoint, + WindChill, + HeatIndex, + Radiation, + UV, + ET, + + // VP-specific values + ExtraTemp1, + ExtraTemp2, + ExtraTemp3, + SoilTemp1, + SoilTemp2, + SoilTemp3, + SoilTemp4, + LeafTemp1, + LeafTemp2, + ExtraHumid1, + ExtraHumid2, + + // WXT-510-specific values + Wxt510Hail, + Wxt510Hailrate, + Wxt510HeatingTemp, + Wxt510HeatingVoltage, + Wxt510SupplyVoltage, + Wxt510ReferenceVoltage + +} ALARM_TYPE; + + +/* !!!!!!!!!!!!!!!!!! HIDDEN, NOT FOR API USE !!!!!!!!!!!!!!!!!! +*/ + +#define WVIEW_ALARM_SCRIPT_LENGTH 512 + +typedef enum +{ + ALARM_STATS_ALARMS = 0, + ALARM_STATS_SCRIPTS_RUN, + ALARM_STATS_CLIENTS, + ALARM_STATS_PKTS_SENT +} ALARM_STATS; + + +// define an alarm definition structure +typedef struct +{ + NODE node; + int type; // see above (ALARM_TYPE) + int isMax; // if != 0, value is an upper bound + // else it is a lower bound + float bound; + int abateSecs; // alarm abatement after trigger + uint32_t abateStart; + char scriptToRun[WVIEW_ALARM_SCRIPT_LENGTH]; + int triggered; // to prevent repeat notifications + float triggerValue; +} WVIEW_ALARM; + +typedef struct +{ + NODE node; + RADSOCK_ID client; + int syncInProgress; +} WVIEW_ALARM_CLIENT; + +typedef struct +{ + pid_t myPid; + char pidFile[128]; + char fifoFile[128]; + char statusFile[128]; + char daemonQname[128]; + char wviewdir[128]; + int isMetric; + int doTest; + int doTestNumber; + RADLIST alarmList; + RADLIST clientList; + RADSOCK_ID dataFeedServer; + int inMainLoop; + int sigpipe; + int exiting; +} WVIEW_ALARM_WORK; + +/* !!!!!!!!!!!!!!!!!!!! END HIDDEN SECTION !!!!!!!!!!!!!!!!!!!!! +*/ + +// Define the maximum number of alarm definitions: +#define ALARMS_MAX 10 + + +/* ... API function prototypes +*/ + + + +#endif diff --git a/alarms/sample-datafeed-client/Makefile b/alarms/sample-datafeed-client/Makefile new file mode 100755 index 0000000..c354752 --- /dev/null +++ b/alarms/sample-datafeed-client/Makefile @@ -0,0 +1,80 @@ +############################################################################### +# # +# Makefile for the alarm datafeed client example # +# # +# Name Date Description # +# ------------------------------------------------------------------------- # +# MS Teel 01/10/02 Initial Creation # +# # +############################################################################### +# Define the C compiler and its options +CC = gcc +CC_OPTS = -Wall -g -O2 +SYS_DEFINES = \ + -D_GNU_SOURCE + +# Define the Linker and its options +LD = gcc +LD_OPTS = + +# Define the Library creation utility and it's options +LIB_EXE = ar +LIB_EXE_OPTS = -rv + +# Define the dependancy generator +DEP = gcc -MM + +################################ R U L E S ################################## +# Generic rule for c files +%.o: %.c + @echo "Building $@" + $(CC) $(CC_OPTS) $(SYS_DEFINES) $(DEFINES) $(INCLUDES) -c $< -o $@ + + +# Define some general usage vars +# Libraries +LIBS = \ + -lc \ + -lz \ + -lrad \ + -lsqlite3 + +LIBPATH = -L/usr/local/lib -L/usr/lib + +# Declare build defines +DEFINES = \ + -D_DEBUG + +# Any build defines listed above should also be copied here +INCLUDES = \ + -I. \ + -I../../common \ + -I/usr/local/include + +########################### T A R G E T I N F O ############################ +EXE_IMAGE = datafeedClient + +TEST_OBJS = \ + ../../common/datafeed.o \ + ./datafeedClient.o + + +######################### E X P O R T E D V A R S ######################### + + +################################ R U L E S ################################## + +$(EXE_IMAGE): $(TEST_OBJS) + @echo "Linking $@..." + $(LD) $(LD_OPTS) $(LIBPATH) -o $@ $(TEST_OBJS) $(LIBS) + + +all: clean $(EXE_IMAGE) + + +# Cleanup rules... +clean: + rm -rf \ + $(EXE_IMAGE) \ + $(TEST_OBJS) + diff --git a/alarms/sample-datafeed-client/datafeedClient.c b/alarms/sample-datafeed-client/datafeedClient.c new file mode 100755 index 0000000..65a7980 --- /dev/null +++ b/alarms/sample-datafeed-client/datafeedClient.c @@ -0,0 +1,203 @@ +/*--------------------------------------------------------------------- + + FILE NAME: + datafeedClient.c + + PURPOSE: + Example wview alarm data feed client. + + REVISION HISTORY: + Date Programmer Revision Function + 06/05/2005 M.S. Teel 0 Original + + ASSUMPTIONS: + None. + +------------------------------------------------------------------------*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +// Include the wview data definitions: +#include + + +/* ... global references +*/ + +/* ... local memory +*/ +static int ProcessDone; +static RADSOCK_ID ClientSocket; + +/* ... methods +*/ +static void defaultSigHandler (int signum) +{ + switch (signum) + { + case SIGPIPE: + // we have a far end ClientSocket disconnection, we'll handle it in the + // "read/write" code + ProcessDone = TRUE; + printf("datafeedClient: caught SIGPIPE: set exit flag...\n"); + radProcessSignalCatch(signum, defaultSigHandler); + break; + + case SIGCHLD: + radProcessSignalCatch(signum, defaultSigHandler); + break; + + case SIGILL: + case SIGBUS: + case SIGFPE: + case SIGSEGV: + case SIGXFSZ: + case SIGSYS: + abort (); + + default: + // exit now, cleaning up: + printf("datafeedClient: caught signal %d: exiting...\n", signum); + radSocketDestroy(ClientSocket); + exit(0); + } + + return; +} + +/* ... the entry point - if no hostname or IP is given, localhost is used +*/ +int main (int argc, char *argv[]) +{ + int retVal; + char temp[128]; + LOOP_PKT loopData; + LOOP_PKT hostLoopData; + ARCHIVE_PKT archiveRecord; + ARCHIVE_PKT hostRecord; + uint32_t dateTime = 0; + void (*alarmHandler)(int); + + printf("datafeedClient: Begin...\n"); + + if (argc < 2) + strcpy (temp, "localhost"); + else + strcpy (temp, argv[1]); + + alarmHandler = radProcessSignalGetHandler (SIGALRM); + radProcessSignalCatchAll (defaultSigHandler); + radProcessSignalCatch (SIGALRM, alarmHandler); + + ClientSocket = radSocketClientCreate(temp, WV_DATAFEED_PORT); + if (ClientSocket == NULL) + { + printf("datafeedClient: failed to connect to server!\n"); + exit (1); + } + + // Request an archive record (the first on the remote wview server): + // write the frame start: + if (radSocketWriteExact(ClientSocket, + (void *)DF_RQST_ARCHIVE_START_FRAME, + DF_START_FRAME_LENGTH) + != DF_START_FRAME_LENGTH) + { + printf("datafeedClient: ClientSocket write sync error!\n"); + exit (1); + } + + // write out the uint32_t dateTime for next record after that dateTime: + // convert to network byte order: + dateTime = 0; + dateTime = htonl(dateTime); + if (radSocketWriteExact(ClientSocket, &dateTime, sizeof(dateTime)) != sizeof(dateTime)) + { + exit (1); + } + + printf("datafeedClient: Requested first archive record...\n"); + + + /* now loop, waiting to get the start frame sequence */ + ProcessDone = FALSE; + while (! ProcessDone) + { + /* try to find the start frame (this blocks if the ClientSocket is empty) */ + retVal = datafeedSyncStartOfFrame(ClientSocket); + switch (retVal) + { + case ERROR: + /* problems! - bail out */ + printf("datafeedClient: ClientSocket error during sync\n"); + ProcessDone = TRUE; + break; + + case FALSE: + printf("datafeedClient: packet out of frame...\n"); + break; + + case DF_LOOP_PKT_TYPE: + /* OK, we have a loop update coming (this may block) */ + if (radSocketReadExact(ClientSocket, (void *)&loopData, sizeof (loopData)) + != sizeof (loopData)) + { + printf("datafeedClient: ClientSocket read error - abort!\n"); + ProcessDone = TRUE; + continue; + } + + // Convert from network byte order: + datafeedConvertLOOP_NTOH(&hostLoopData, &loopData); + + /* process the data ... for example, will just log receipt */ + printf("dataFeedClient:%s:%d:received LOOP update: %.1f\n", + radSocketGetHost (ClientSocket), + radSocketGetPort (ClientSocket), + hostLoopData.outTemp); + break; + + case DF_ARCHIVE_PKT_TYPE: + /* OK, we have an archive coming (this may block) */ + if (radSocketReadExact(ClientSocket, (void *)&archiveRecord, sizeof(archiveRecord)) + != sizeof (archiveRecord)) + { + printf("datafeedClient: ClientSocket read error - abort!\n"); + ProcessDone = TRUE; + continue; + } + + // Convert from network byte order: + datafeedConvertArchive_NTOH(&hostRecord, &archiveRecord); + + /* process the data ... for example, will just log receipt */ + printf("dataFeedClient:%s:%d:received archive update: %d\n", + radSocketGetHost (ClientSocket), + radSocketGetPort (ClientSocket), + (int)hostRecord.dateTime); + break; + } + } + + printf("datafeedClient: exiting..."); + radSocketDestroy(ClientSocket); + exit (0); +} + diff --git a/bin/alarms/placeholder.txt b/bin/alarms/placeholder.txt new file mode 100644 index 0000000..75eecaf --- /dev/null +++ b/bin/alarms/placeholder.txt @@ -0,0 +1 @@ +Automake placeholder. diff --git a/bin/archive/readme.txt b/bin/archive/readme.txt new file mode 100644 index 0000000..39bfcfa --- /dev/null +++ b/bin/archive/readme.txt @@ -0,0 +1,2 @@ +wview-archive.sdb - Empty "starter" archive database +wview-archive.sql - SQL file for archive table creation diff --git a/bin/archive/wview-archive.sql b/bin/archive/wview-archive.sql new file mode 100644 index 0000000..30b169b --- /dev/null +++ b/bin/archive/wview-archive.sql @@ -0,0 +1,56 @@ +BEGIN TRANSACTION; +CREATE TABLE archive ( + dateTime INTEGER NOT NULL UNIQUE PRIMARY KEY, + usUnits INTEGER NOT NULL, + interval INTEGER NOT NULL, + barometer REAL, + pressure REAL, + altimeter REAL, + inTemp REAL, + outTemp REAL, + inHumidity REAL, + outHumidity REAL, + windSpeed REAL, + windDir REAL, + windGust REAL, + windGustDir REAL, + rainRate REAL, + rain REAL, + dewpoint REAL, + windchill REAL, + heatindex REAL, + ET REAL, + radiation REAL, + UV REAL, + extraTemp1 REAL, + extraTemp2 REAL, + extraTemp3 REAL, + soilTemp1 REAL, + soilTemp2 REAL, + soilTemp3 REAL, + soilTemp4 REAL, + leafTemp1 REAL, + leafTemp2 REAL, + extraHumid1 REAL, + extraHumid2 REAL, + soilMoist1 REAL, + soilMoist2 REAL, + soilMoist3 REAL, + soilMoist4 REAL, + leafWet1 REAL, + leafWet2 REAL, + rxCheckPercent REAL, + txBatteryStatus REAL, + consBatteryVoltage REAL, + hail REAL, + hailRate REAL, + heatingTemp REAL, + heatingVoltage REAL, + supplyVoltage REAL, + referenceVoltage REAL, + windBatteryStatus REAL, + rainBatteryStatus REAL, + outTempBatteryStatus REAL, + inTempBatteryStatus REAL +); +COMMIT; diff --git a/bin/img/Archive/placeholder.txt b/bin/img/Archive/placeholder.txt new file mode 100644 index 0000000..75eecaf --- /dev/null +++ b/bin/img/Archive/placeholder.txt @@ -0,0 +1 @@ +Automake placeholder. diff --git a/bin/img/Clouds.jpg b/bin/img/Clouds.jpg new file mode 100755 index 0000000000000000000000000000000000000000..6b187715851ae3c235d401dab07fc76edc210056 GIT binary patch literal 2494 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 literal 0 HcmV?d00001 diff --git a/bin/img/NOAA/placeholder.txt b/bin/img/NOAA/placeholder.txt new file mode 100644 index 0000000..75eecaf --- /dev/null +++ b/bin/img/NOAA/placeholder.txt @@ -0,0 +1 @@ +Automake placeholder. diff --git a/bin/img/above-clouds.jpg b/bin/img/above-clouds.jpg new file mode 100755 index 0000000000000000000000000000000000000000..ecdebe5576a84825660689871721fb2ce20a0e36 GIT binary patch literal 3862 zcmb7FX*kpm7yZp*>@~yKht$|ZjEZT*AVMn3ShAF&#lB3|WG~X7F-W!)V^36;No3Gy zY>_1y8Gj@GStDzudG+aizrN?WpYFNm+~+>`!{Lr`rvSL6nS~hu+Jibs9RRp2zyyGR z!GCKH(7oc}PMBu{v5c>oL;o@+FC{j#JOlZG^q&QMi z1Sy96YXXAoX`np(JUskJVL@T!{|#;%fZzkJ0u%`75CBGiAP5k*6OaY~Fa!hw{tGD2 zUI+G<(}e?I5Cj6|0YjlM$ln{MA_uj3j6@IFT#gGOBQZ|~(Z+S(VPaQ;>8s*8 z|Jd#cCH|iOzn%bS&vTDJA^!IF_WhScfDRf3X@ldqG~Sn@!B_r z>Yy_1pwRJEa$?!S@MB`i>QjXpJY7;L`DOEWW+>h`{Ry&d)4#OT)ZW2qo3GgSR^iC$ zc{sikBZaG})qmRpQS5(u+IO}B%^%C0uoIK;i4hVNC!eX14vaSRW-Ot7?%p^gK$Fj6 zzYRE_(haZfFlSDYu1DGVxY!FZ1IrwuigEjE^nRtOO${8llNN48wK^E#r)b0)@|z!U zG7Xlpy*}JLcaFG@Y@g+!cpVXgRmV%5ApU-nZe{<$@?KGIutJ%nLY)nJXgZ}?Bjp`h z?#66R!b?yYddy)`q_WOl#T@0of59<40fKtjnJAovvDk$fI`?*6Mjnm&`RP@&-b8)vP+>VwH3#%WBpO!`>Thm2k3NcH} zh&Eh)mHp9a>f>cSWrL<)HqU&dVc)-ozY4}rEagrdjU@2b$+OUu@7lS2T50?88!IP6 zMb;`|gq4Z8g1i8#f23Q9Cb{S6Q?q}>g3tdO^GxrNM`}#6WFllcCG1k+XKGj37*kbw zl@H%v)B%z0YF!`Jlw4YzHrkhMdwe)zZnHA)YgFgF35fPVb`$%K_tubmB4k>$y{2?6 z&G2H_c(&_BagrIDV2|Sh<*SsH@#^mC6Sv-TE+fM=DEW{$TZ`(_dp&AOGj#+y7kGVs zs1zscUu-Qzuhj}loeu#XFmonOXfq9xl#kM_q}#i{xxPae-%*En$O!byWx<&=n%gOF z8*x?;&0t;jT^?(^n!aJSM8Zt1HniF-{cZSs-GpE6gU^wcnh)cH zN`&BxF}j~n4ndzgQ>MM_ud<<{aro2N2FEm0p3DcgiCx&8N}e-0OfTaZPU@+`4)XdI zRhV`&MVCN%Ct%JpxEMB>Xz?gdu)$0V)}CV&99&PYin09DUO}E?&W6afCahSUD6>M$hww`? zn$>CwF6jRZE9xsuRDAF2H=`frkwPT+XncL1JeG>*0ylOmZ5O#f$?bWExHeka(xrAA zndd^q-TS+ijOD1oKloOYcn_c_NhD{NI=r!GL?&vqj%D00$?0@+NqtEvNQxFF`Up6d zyLMEdUjMt6H4^{Uc~t5v?JOeNCf<`6TeP)Vy7tA$e=N)XjZsrj!KD_+u)oH^#(~-* zi~U(yMp)F(fCT$*M|gId4|gdwq#7Ok($J6FALUpZA`_BYyoxFY;{#mtr*Z^G;D)8pHi&+77Q23yp5V zZBav~`Y_l_@N5Vk*Vt8x8{)s7RAyC*OgkHk4ZOLzF*BW<=;Fk>x!@_0`~rNldVnVX z({qV(vB&VNnPY~IDny{SbBeg?@UoQ)Xg^>v!O>D_gAcS$@}YSRHqLN??;VF18bb$3 znNf?k+*caCPa5cCuausJm@yAM!7?nc8xy0UyK;v+Gdom;N|KqN)8!}E3<@)a`|?LQ z>bbFyp!XY+8^!pH_Ce@J#K9lxmcd2Wqket8Wh1a9yip=v+qUNX2J_B?sY(e5cIEBptL%Sk?u;}k3&hS(Y!!>oN|>!;D$jTr zlkz>bs*FZF?psAmA{sz*j-#)7FY|C}&9B8&cQ_shexsRm-E$ zaGz`=Zx{4+yIRG%VqyshYR=v}QFCmA^-A{dV+z3*V#7V62X;+P-1qpzN*f`#$-qri znxERGzOk$YxVf|#btn?>7?{UdvAwHW7Kgo z!i=M-gp;drWia0lza~yh z6#Y(|>wTM-zO%&zUL8mt66L325HAukqJ<6Oo&5j#J{ErUF1cjqZm7)2`WeyK@yn_B zk+QYg&E4pY{Ha~yA^ey9m@*Dv^};~bFi^~?f%+ToBu!RmRFlcn4y`|~?pz?o z+kfIDRd?{r!UeY~dA6jvdjGUMe4)Q|Byl&2TXYu@4g@pSh1b94LV zCiC{g1=tPt%6RV0`t0eD2!$hW=QQ39Z9KXBuVcm(?o{z(9sg)-P5kJ4=OU*pn>Lx1 z_}k3l5KF=r`3vUPa!tt~k0LN8Feo48pv{^HQ8iD|i9H_rIp{O-mz_^DQN2g8Hvw-g z6!Crzw_30Ic0R4|=@*hssd3`LkOM_!YJ;OK$Cli3*r0?8GG3k(#@wE6)UAEfi9v%> z`}vh<3l6k5`)m|)G`k>%msZBp?w7jtz{z;Gx99>*1HSR+rUAFqoULAz(CIyVo6?~S z*kRBo&98Ps0-o-=0!G6b{uq<~;qjsJr#0CzcGNpC)Le zBW`;IHr1>GovHb zX9f`;&O<8=9mSxN$eI59P}x=X(kWyA6I%sN4CRachw=UMDH^c#SsZW!;-Bh{O#i*)?97KX2@0F2M#%%pMbx zp!1w%-lKIt??3Sq5s?g7GC8BrA>-NFMr^U{*t$Du^zgQ+*}j=r17hn&Mp45RK3>O% z&JpMI=yI6XhgycT0@*d_n>ps>#$*VMQ7mAJstZl$%RoRaD+ z%ir6!>41tU>5BGF5^l5_Y}Q+BLB|m)E+~FF=N5vB?~00YtTVSkrUmjVM-5k;!zfPO z4d-0H6KugNbFH@E%C+#{^2kV0TPiQWzQDMLyqEQ!gFP_0E=bu9AxD)dQ7yj>IP-Nj z(1M-s!%I|1NRhBfswG8L`8q{`(+V55bk!qB^`FzRrFsfCjNHr>^uimetPoNn_TQ7j zTAR~Rsg;D{{l3y)&`soIO&TLUv^2=2#9;vw_biT$^0TF$X$Z5^DOc7<#Re4~f9^K^ enIEbR@tyNOIZr5>Zr@E`OK-8vQkA&G{rn$%N#~FN literal 0 HcmV?d00001 diff --git a/bin/noaa/placeholder.txt b/bin/noaa/placeholder.txt new file mode 100644 index 0000000..75eecaf --- /dev/null +++ b/bin/noaa/placeholder.txt @@ -0,0 +1 @@ +Automake placeholder. diff --git a/common/beaufort.h b/common/beaufort.h new file mode 100755 index 0000000..e6e9b97 --- /dev/null +++ b/common/beaufort.h @@ -0,0 +1,45 @@ +#ifndef INC_beauforth +#define INC_beauforth +/*--------------------------------------------------------------------------- + + FILENAME: + beaufort.h + + PURPOSE: + Provide the beaufort wind scale definitions. + + REVISION HISTORY: + Date Engineer Revision Remarks + 04/16/07 M.S. Teel 0 Original + + NOTES: + + + LICENSE: + Copyright (c) 2007, Mark S. Teel (mark@teel.ws) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +// Define the Beaufort scale thresholds (use <= comparison) +enum BeaufortScaleValues +{ + Beaufort_Calm = 0, + Beaufort_LightAir = 3, + Beaufort_LightBreeze = 7, + Beaufort_GentleBreeze = 12, + Beaufort_ModerateBreeze = 18, + Beaufort_FreshBreeze = 24, + Beaufort_StrongBreeze = 31, + Beaufort_NearGale = 38, + Beaufort_Gale = 46, + Beaufort_SevereGale = 54, + Beaufort_Storm = 63, + Beaufort_ViolentStorm = 72, + Beaufort_Hurricane = 83 +}; + +#endif + diff --git a/common/datadefs.h b/common/datadefs.h new file mode 100755 index 0000000..cc10b07 --- /dev/null +++ b/common/datadefs.h @@ -0,0 +1,370 @@ +#ifndef INC_datadefsh +#define INC_datadefsh +/*--------------------------------------------------------------------------- + + FILENAME: + datadefs.h + + PURPOSE: + Provide the wview data definitions. + + REVISION HISTORY: + Date Engineer Revision Remarks + 08/24/03 M.S. Teel 0 Original + 03/23/2008 W. Krenn 1 add WXT510 specials + 07/07/2009 M.S. Teel 2 Remove pragma packed + from internal structs + + NOTES: + + + LICENSE: + Copyright (c) 2004, Mark S. Teel (mark@teel.ws) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +/* ... System include files +*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* ... Library include files +*/ +#ifndef WVIEW_DATA_ONLY +#include +#else +#include +#include +#endif + +/* ... Local include files +*/ + +/* ... data definitions +*/ + +// Define the wind direction consensus data: +#define WAVG_BIN_SIZE 18 +#define WAVG_INTERVAL (2*WAVG_BIN_SIZE) +#define WAVG_NUM_BINS (720/WAVG_INTERVAL) +#define WAVG_CONSENSUS_BINS 6 +#define WAVG_TOTAL_BINS (WAVG_NUM_BINS + WAVG_CONSENSUS_BINS) +typedef struct +{ + int16_t bins[WAVG_TOTAL_BINS]; +} WAVG, *WAVG_ID; + + +// Define the data value indices for historical data: +typedef enum +{ + DATA_INDEX_barometer = 0, + DATA_INDEX_pressure, + DATA_INDEX_altimeter, + DATA_INDEX_inTemp, + DATA_INDEX_outTemp, + DATA_INDEX_inHumidity, + DATA_INDEX_outHumidity, + DATA_INDEX_windSpeed, + DATA_INDEX_windDir, + DATA_INDEX_windGust, + DATA_INDEX_windGustDir, + DATA_INDEX_rainRate, + DATA_INDEX_rain, + DATA_INDEX_dewpoint, + DATA_INDEX_windchill, + DATA_INDEX_heatindex, + DATA_INDEX_rxCheckPercent, + DATA_INDEX_BASIC_MAX, + DATA_INDEX_ET = DATA_INDEX_BASIC_MAX, + DATA_INDEX_radiation, + DATA_INDEX_UV, + DATA_INDEX_extraTemp1, + DATA_INDEX_extraTemp2, + DATA_INDEX_extraTemp3, + DATA_INDEX_soilTemp1, + DATA_INDEX_soilTemp2, + DATA_INDEX_soilTemp3, + DATA_INDEX_soilTemp4, + DATA_INDEX_leafTemp1, + DATA_INDEX_leafTemp2, + DATA_INDEX_extraHumid1, + DATA_INDEX_extraHumid2, + DATA_INDEX_soilMoist1, + DATA_INDEX_soilMoist2, + DATA_INDEX_soilMoist3, + DATA_INDEX_soilMoist4, + DATA_INDEX_leafWet1, + DATA_INDEX_leafWet2, + DATA_INDEX_txBatteryStatus, + DATA_INDEX_consBatteryVoltage, + DATA_INDEX_hail, + DATA_INDEX_hailrate, + DATA_INDEX_heatingTemp, + DATA_INDEX_heatingVoltage, + DATA_INDEX_supplyVoltage, + DATA_INDEX_referenceVoltage, + DATA_INDEX_windBatteryStatus, + DATA_INDEX_rainBatteryStatus, + DATA_INDEX_outTempBatteryStatus, + DATA_INDEX_inTempBatteryStatus, + + DATA_INDEX_MAX +} Data_Indices; + + +#define DATA_INDEX_MAX(x) ((x) ? DATA_INDEX_MAX : DATA_INDEX_BASIC_MAX) + + +// Define sensor types: +typedef enum +{ + SENSOR_INTEMP = 0, + SENSOR_OUTTEMP, + SENSOR_INHUMID, + SENSOR_OUTHUMID, + SENSOR_BP, + SENSOR_WSPEED, + SENSOR_WGUST, + SENSOR_DEWPOINT, + SENSOR_RAIN, // cumulative - no avg + SENSOR_RAINRATE, + SENSOR_WCHILL, + SENSOR_HINDEX, + SENSOR_ET, // cumulative - no avg + SENSOR_UV, + SENSOR_SOLRAD, + SENSOR_HAIL, + SENSOR_HAILRATE, + SENSOR_MAX +} SENSOR_TYPES; + +// Define sensor time frames: +typedef enum +{ + STF_INTERVAL = 0, + STF_HOUR, + STF_DAY, + STF_WEEK, + STF_MONTH, + STF_YEAR, + STF_ALL, + STF_MAX +} SENSOR_TIMEFRAMES; + + +// the sensor data type: +typedef struct +{ + float low; + time_t time_low; + float high; + time_t time_high; + float when_high; + float cumulative; + int samples; + int debug; +} WV_SENSOR; + + +// ... for NOAA_DATA structure +typedef enum +{ + NOAA_TYPE_TEMP_HIGH = 0, + NOAA_TYPE_TEMP_LOW, + NOAA_TYPE_TEMP_MEAN, + NOAA_TYPE_RAIN, + NOAA_TYPE_WIND_MEAN, + NOAA_TYPE_WIND_HIGH, + NOAA_TYPE_WIND_DIR, + NOAA_TYPE_MAX +} NOAA_TYPE; + + +// internal HILOW data store +typedef struct +{ + WV_SENSOR sensor[STF_MAX][SENSOR_MAX]; + WAVG wind[STF_MAX]; + + float hourchangetemp; // difference in degrees + int16_t hourchangewind; // difference in mph + int16_t hourchangewinddir; // difference in degrees + int16_t hourchangehumid; // difference in % + float hourchangedewpt; // difference in degrees + float hourchangebarom; // difference in inches + + float daychangetemp; // difference in degrees + int16_t daychangewind; // difference in mph + int16_t daychangewinddir; // difference in degrees + int16_t daychangehumid; // difference in % + float daychangedewpt; // difference in degrees + float daychangebarom; // difference in inches + + float weekchangetemp; // difference in degrees + int16_t weekchangewind; // difference in mph + int16_t weekchangewinddir; // difference in degrees + int16_t weekchangehumid; // difference in % + float weekchangedewpt; // difference in degrees + float weekchangebarom; // difference in inches + +} SENSOR_STORE; + + +// LOOP_PKT - define the LOOP (current readings) data format for IPM msgs and +// station sensor polling; those required for archive generation +// and HILOW are marked "!" and MUST be provided by the station. +#define WVIEW_NUM_EXTRA_SENSORS 16 +typedef struct +{ + // minimum required fields - station must provide these: + float barometer; /* ! inches */ + float stationPressure; /* ! inches */ + float altimeter; /* ! inches */ + float inTemp; /* ! degrees F */ + float outTemp; /* ! degrees F */ + uint16_t inHumidity; /* ! percent */ + uint16_t outHumidity; /* ! percent */ + float windSpeedF; /* ! mph */ + uint16_t windDir; /* ! degrees */ + float windGustF; /* ! mph */ + uint16_t windGustDir; /* ! degrees */ + float rainRate; /* ! in/hr */ + float sampleRain; /* ! inches */ + float sampleET; /* ! ET */ + uint16_t radiation; /* ! watts/m^3 */ + float UV; /* ! UV index */ + float dewpoint; /* ! degrees F */ + float windchill; /* ! degrees F */ + float heatindex; /* ! degrees F */ + + // computed values - station should not alter these + float stormRain; /* inches */ + int32_t stormStart; /* time_t */ + float dayRain; /* inches */ + float monthRain; /* inches */ + float yearRain; /* inches */ + float dayET; /* inches */ + float monthET; /* inches */ + float yearET; /* inches */ + float intervalAvgWCHILL; /* degrees F */ + float intervalAvgWSPEEDF; /* mph */ + uint16_t yearRainMonth; /* 1-12 Rain Start Month */ + + // --- The following may or may not be supported for a given station --- + + // Vantage Pro + uint16_t rxCheckPercent; /* 0 - 100 */ + uint16_t tenMinuteAvgWindSpeed; /* mph */ + uint16_t forecastIcon; /* VP only */ + uint16_t forecastRule; /* VP only */ + uint16_t txBatteryStatus; /* VP only */ + uint16_t consBatteryVoltage; /* VP only */ + float soilTemp1; /* degrees F */ + float soilTemp2; /* degrees F */ + float soilTemp3; /* degrees F */ + float soilTemp4; /* degrees F */ + float leafTemp1; /* degrees F */ + float leafTemp2; /* degrees F */ + uint8_t soilMoist1; + uint8_t soilMoist2; + uint8_t leafWet1; + uint8_t leafWet2; + + // Vaisala WXT-510 + float wxt510Hail; /* inches */ + float wxt510Hailrate; /* in/hr */ + float wxt510HeatingTemp; /* degrees F */ + float wxt510HeatingVoltage; /* volts */ + float wxt510SupplyVoltage; /* volts */ + float wxt510ReferenceVoltage; /* volts */ + float wxt510RainDuration; /* sec */ + float wxt510RainPeakRate; /* volts */ + float wxt510HailDuration; /* sec */ + float wxt510HailPeakRate; /* volts */ + float wxt510Rain; /* inches */ + + // WMR918/968 + float wmr918Pool; /* degrees F */ + uint8_t wmr918Humid3; /* percent */ + uint8_t wmr918Tendency; /* WMR's Forecast */ + uint8_t wmr918WindBatteryStatus; + uint8_t wmr918RainBatteryStatus; + uint8_t wmr918OutTempBatteryStatus; + uint8_t wmr918InTempBatteryStatus; + uint8_t wmr918poolTempBatteryStatus; + uint8_t wmr918extra1BatteryStatus; + uint8_t wmr918extra2BatteryStatus; + uint8_t wmr918extra3BatteryStatus; + + // Generic extra sensor and status support: + float extraTemp[WVIEW_NUM_EXTRA_SENSORS]; + uint16_t extraHumidity[WVIEW_NUM_EXTRA_SENSORS]; + uint8_t windBatteryStatus; + uint8_t rainBatteryStatus; + uint8_t outTempBatteryStatus; + uint8_t consoleBatteryStatus; + uint8_t uvBatteryStatus; + uint8_t solarBatteryStatus; + uint8_t extraTempBatteryStatus[WVIEW_NUM_EXTRA_SENSORS]; +} LOOP_PKT; + + +// ARCHIVE_PKT - define the ARCHIVE data format for IPM msgs and archive record +// processing: +#define ARCHIVE_VALUE_NULL -100000 +typedef struct +{ + // minimum required fields - station must provide these: + int32_t dateTime; /* record date and time */ + int32_t usUnits; /* US units (0 or 1) */ + int32_t interval; /* Archive Interval in minutes */ + float value[DATA_INDEX_MAX]; +} ARCHIVE_PKT; + + +// ... this retrieves historical summations from the dbsqliteArchiveGetAverages method +typedef struct +{ + int samples[DATA_INDEX_MAX]; + time_t startTime; + float values[DATA_INDEX_MAX]; +} HISTORY_DATA; + + +// ... this structure retrieves NOAA data from the dbsqliteNOAAGetDay utility +// ... and represents a record in the NOAA database wview-noaa.sdb +typedef struct +{ + int16_t year; + int16_t month; + int16_t day; + float meanTemp; + float highTemp; + char highTempTime[8]; + float lowTemp; + char lowTempTime[8]; + float heatDegDays; + float coolDegDays; + float rain; + float avgWind; + float highWind; + char highWindTime[8]; + int32_t domWindDir; +} NOAA_DAY_REC; + + +#endif + diff --git a/common/datafeed.c b/common/datafeed.c new file mode 100755 index 0000000..cae6ef7 --- /dev/null +++ b/common/datafeed.c @@ -0,0 +1,428 @@ +/*--------------------------------------------------------------------------- + + FILENAME: + datafeed.c + + PURPOSE: + Define the datafeed API. + + REVISION HISTORY: + Date Engineer Revision Remarks + 12/16/2009 M.S. Teel 0 Original + 01/02/2011 M.S. Teel 1 Remove fixed point translation; + & P. Sanchez add htonf and ntohf utilities. + + NOTES: + + + LICENSE: + Copyright (c) 2009, Mark S. Teel (mark@teel.ws) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +// ... System header files +#include +#include + + +// ... Local header files +#define DATAFEED_INSTANTIATE TRUE +#include + + +// ... methods + +/* Start of slurped code. + Originally: http://beej.us/guide/bgnet/examples/pack.c + Root URL: http://beej.us/guide/bgnet +*/ +uint32_t htonf(float f) +{ + uint32_t p; + uint32_t sign; + + if (f <= ARCHIVE_VALUE_NULL) + { + f = -32767.0; + } + + if (f < 0) + { + sign = 1; + f = -f; + } + else + { + sign = 0; + } + + p = ((((uint32_t)f)&0x7fff)<<16) | (sign<<31); // whole part and sign + p |= (uint32_t)(((f - (int)f) * 65536.0f))&0xffff; // fraction + + return p; +} + +float ntohf(uint32_t p) +{ + float f = ((p>>16)&0x7fff); // whole part + f += (p&0xffff) / 65536.0f; // fraction + + if (((p>>31)&0x1) == 0x1) + { + f = -f; + } // sign bit set + + if (f <= -32767.0) + { + f = ARCHIVE_VALUE_NULL; + } + + return f; +} +// end of slurped code + +static uint16_t swapShortNTOH(uint8_t* item) +{ + uint16_t* sh_item = (uint16_t*)item; + uint16_t retVal; + + retVal = ntohs(*sh_item); + return retVal; +} + +static uint16_t swapShortHTON(uint8_t* item) +{ + uint16_t* sh_item = (uint16_t*)item; + uint16_t retVal; + + retVal = htons(*sh_item); + return retVal; +} + +static int ReadExact(RADSOCK_ID socket, void *bfr, int len, uint32_t msTimeout) +{ + int rval, index = 0; + uint32_t cumTime = 0; + uint64_t readTime; + uint8_t *ptr = (uint8_t *)bfr; + + while (index < len && cumTime < msTimeout) + { + readTime = radTimeGetMSSinceEpoch (); + rval = radSocketReadExact(socket, &ptr[index], len - index); + if (rval < 0) + { + if (errno != EINTR && errno != EAGAIN) + { + radMsgLog (PRI_HIGH, "ReadExact ERROR: %s", strerror(errno)); + return ERROR; + } + } + else + { + index += rval; + } + + readTime = radTimeGetMSSinceEpoch () - readTime; + cumTime += (uint32_t)readTime; + if (index < len && cumTime < msTimeout) + { + readTime = radTimeGetMSSinceEpoch (); + radUtilsSleep (9); + readTime = radTimeGetMSSinceEpoch () - readTime; + cumTime += (uint32_t)readTime; + } + } + + return ((index == len) ? len : 0); +} + +int datafeedSyncStartOfFrame(RADSOCK_ID socket) +{ + uint16_t start; + int retVal; + + retVal = ReadExact(socket, (void *)&start, sizeof (uint16_t), DF_WAIT_FIRST); + if (retVal == ERROR) + { + radMsgLog (PRI_HIGH, "datafeed: socket read 1 error - abort!"); + return ERROR; + } + else if (retVal == 0) + { + return ERROR_ABORT; + } + else if (retVal != sizeof(uint16_t)) + { + return FALSE; + } + else if (start != DF_LOOP_START_FRAME[0]) + { + return FALSE; + } + + if (ReadExact(socket, (void *)&start, sizeof (uint16_t), DF_WAIT_MORE) + != sizeof (uint16_t)) + { + radMsgLog (PRI_HIGH, "datafeed: socket read 2 error - abort!"); + return ERROR; + } + else if (start != DF_LOOP_START_FRAME[1]) + { + return FALSE; + } + + if (ReadExact(socket, (void *)&start, sizeof (uint16_t), DF_WAIT_MORE) + != sizeof (uint16_t)) + { + radMsgLog (PRI_HIGH, "datafeed: socket read 3 error - abort!"); + return ERROR; + } + else if (start != DF_LOOP_START_FRAME[2]) + { + return FALSE; + } + + if (ReadExact(socket, (void *)&start, sizeof (uint16_t), DF_WAIT_MORE) + != sizeof (uint16_t)) + { + radMsgLog (PRI_HIGH, "datafeed: socket read 4 error - abort!"); + return ERROR; + } + else if (start == DF_LOOP_START_FRAME[3]) + { + return (int)DF_LOOP_START_FRAME[3]; + } + else if (start == DF_ARCHIVE_START_FRAME[3]) + { + return (int)DF_ARCHIVE_START_FRAME[3]; + } + else if (start == DF_RQST_ARCHIVE_START_FRAME[3]) + { + return (int)DF_RQST_ARCHIVE_START_FRAME[3]; + } + + return FALSE; +} + + +int datafeedConvertLOOP_HTON(LOOP_PKT* dest, LOOP_PKT* src) +{ + uint16_t* tempshort; + + dest->barometer = htonf(src->barometer); + dest->stationPressure = htonf(src->stationPressure); + dest->altimeter = htonf(src->altimeter); + dest->inTemp = htonf(src->inTemp); + dest->outTemp = htonf(src->outTemp); + + dest->inHumidity = htons(src->inHumidity); + dest->outHumidity = htons(src->outHumidity); + dest->windSpeedF = htonf(src->windSpeedF); + dest->windDir = htons(src->windDir); + dest->windGustF = htonf(src->windGustF); + dest->windGustDir = htons(src->windGustDir); + + dest->rainRate = htonf(src->rainRate); + dest->sampleRain = htonf(src->sampleRain); + dest->sampleET = htonf(src->sampleET); + + dest->radiation = htons(src->radiation); + + dest->UV = htonf(src->UV); + dest->dewpoint = htonf(src->dewpoint); + dest->windchill = htonf(src->windchill); + dest->heatindex = htonf(src->heatindex); + dest->stormRain = htonf(src->stormRain); + + dest->stormStart = htonl(src->stormStart); + + dest->dayRain = htonf(src->dayRain); + dest->monthRain = htonf(src->monthRain); + dest->yearRain = htonf(src->yearRain); + dest->dayET = htonf(src->dayET); + dest->monthET = htonf(src->monthET); + dest->yearET = htonf(src->yearET); + dest->intervalAvgWCHILL = htonf(src->intervalAvgWCHILL); + + dest->intervalAvgWSPEEDF = htonf(src->intervalAvgWSPEEDF); + dest->yearRainMonth = htons(src->yearRainMonth); + dest->rxCheckPercent = htons(src->rxCheckPercent); + dest->tenMinuteAvgWindSpeed = htons(src->tenMinuteAvgWindSpeed); + dest->forecastIcon = htons(src->forecastIcon); + dest->forecastRule = htons(src->forecastRule); + dest->txBatteryStatus = htons(src->txBatteryStatus); + dest->consBatteryVoltage = htons(src->consBatteryVoltage); + + dest->extraTemp[0] = htonf(src->extraTemp[0]); + dest->extraTemp[1] = htonf(src->extraTemp[1]); + dest->extraTemp[2] = htonf(src->extraTemp[2]); + dest->soilTemp1 = htonf(src->soilTemp1); + dest->soilTemp2 = htonf(src->soilTemp2); + dest->soilTemp3 = htonf(src->soilTemp3); + dest->soilTemp4 = htonf(src->soilTemp4); + dest->leafTemp1 = htonf(src->leafTemp1); + dest->leafTemp2 = htonf(src->leafTemp2); + + dest->extraHumidity[0] = src->extraHumidity[0]; + dest->soilMoist1 = src->soilMoist1; + dest->leafWet1 = src->leafWet1; + dest->extraHumidity[1] = src->extraHumidity[1]; + dest->soilMoist2 = src->soilMoist2; + dest->leafWet2 = src->leafWet2; + + dest->wxt510Hail = htonf(src->wxt510Hail); + dest->wxt510Hailrate = htonf(src->wxt510Hailrate); + dest->wxt510HeatingTemp = htonf(src->wxt510HeatingTemp); + dest->wxt510HeatingVoltage = htonf(src->wxt510HeatingVoltage); + dest->wxt510SupplyVoltage = htonf(src->wxt510SupplyVoltage); + dest->wxt510ReferenceVoltage = htonf(src->wxt510ReferenceVoltage); + dest->wxt510RainDuration = htonf(src->wxt510RainDuration); + dest->wxt510RainPeakRate = htonf(src->wxt510RainPeakRate); + dest->wxt510HailDuration = htonf(src->wxt510HailDuration); + dest->wxt510HailPeakRate = htonf(src->wxt510HailPeakRate); + dest->wxt510Rain = htonf(src->wxt510Rain); + dest->wmr918Pool = htonf(src->wmr918Pool); + + tempshort = (uint16_t*)&(dest->wmr918Humid3); + *tempshort = swapShortHTON(&dest->wmr918Humid3); + tempshort = (uint16_t*)&(dest->wmr918WindBatteryStatus); + *tempshort = swapShortHTON(&dest->wmr918WindBatteryStatus); + tempshort = (uint16_t*)&(dest->wmr918OutTempBatteryStatus); + *tempshort = swapShortHTON(&dest->wmr918OutTempBatteryStatus); + tempshort = (uint16_t*)&(dest->wmr918poolTempBatteryStatus); + *tempshort = swapShortHTON(&dest->wmr918poolTempBatteryStatus); + tempshort = (uint16_t*)&(dest->wmr918extra2BatteryStatus); + *tempshort = swapShortHTON(&dest->wmr918extra2BatteryStatus); + + return OK; +} + +int datafeedConvertLOOP_NTOH(LOOP_PKT* dest, LOOP_PKT* src) +{ + uint16_t* tempshort; + + dest->barometer = ntohf(src->barometer); + dest->stationPressure = ntohf(src->stationPressure); + dest->altimeter = ntohf(src->altimeter); + dest->inTemp = ntohf(src->inTemp); + dest->outTemp = ntohf(src->outTemp); + + dest->inHumidity = ntohs(src->inHumidity); + dest->outHumidity = ntohs(src->outHumidity); + dest->windSpeedF = ntohf(src->windSpeedF); + dest->windDir = ntohs(src->windDir); + dest->windGustF = ntohf(src->windGustF); + dest->windGustDir = ntohs(src->windGustDir); + + dest->rainRate = ntohf(src->rainRate); + dest->sampleRain = ntohf(src->sampleRain); + dest->sampleET = ntohf(src->sampleET); + + dest->radiation = ntohs(src->radiation); + + dest->UV = ntohf(src->UV); + dest->dewpoint = ntohf(src->dewpoint); + dest->windchill = ntohf(src->windchill); + dest->heatindex = ntohf(src->heatindex); + dest->stormRain = ntohf(src->stormRain); + + dest->stormStart = ntohl(src->stormStart); + + dest->dayRain = ntohf(src->dayRain); + dest->monthRain = ntohf(src->monthRain); + dest->yearRain = ntohf(src->yearRain); + dest->dayET = ntohf(src->dayET); + dest->monthET = ntohf(src->monthET); + dest->yearET = ntohf(src->yearET); + dest->intervalAvgWCHILL = ntohf(src->intervalAvgWCHILL); + + dest->intervalAvgWSPEEDF = ntohf(src->intervalAvgWSPEEDF); + dest->yearRainMonth = ntohs(src->yearRainMonth); + dest->rxCheckPercent = ntohs(src->rxCheckPercent); + dest->tenMinuteAvgWindSpeed = ntohs(src->tenMinuteAvgWindSpeed); + dest->forecastIcon = ntohs(src->forecastIcon); + dest->forecastRule = ntohs(src->forecastRule); + dest->txBatteryStatus = ntohs(src->txBatteryStatus); + dest->yearRainMonth = ntohs(src->yearRainMonth); + dest->consBatteryVoltage = ntohs(src->consBatteryVoltage); + + dest->extraTemp[0] = ntohf(src->extraTemp[0]); + dest->extraTemp[1] = ntohf(src->extraTemp[1]); + dest->extraTemp[2] = ntohf(src->extraTemp[2]); + dest->soilTemp1 = ntohf(src->soilTemp1); + dest->soilTemp2 = ntohf(src->soilTemp2); + dest->soilTemp3 = ntohf(src->soilTemp3); + dest->soilTemp4 = ntohf(src->soilTemp4); + dest->leafTemp1 = ntohf(src->leafTemp1); + dest->leafTemp2 = ntohf(src->leafTemp2); + + tempshort = (uint16_t*)&(dest->extraHumidity[0]); + *tempshort = swapShortNTOH((uint8_t*)&dest->extraHumidity[0]); + tempshort = (uint16_t*)&(dest->soilMoist1); + *tempshort = swapShortNTOH(&dest->soilMoist1); + tempshort = (uint16_t*)&(dest->leafWet1); + *tempshort = swapShortNTOH(&dest->leafWet1); + + dest->wxt510Hail = ntohf(src->wxt510Hail); + dest->wxt510Hailrate = ntohf(src->wxt510Hailrate); + dest->wxt510HeatingTemp = ntohf(src->wxt510HeatingTemp); + dest->wxt510HeatingVoltage = ntohf(src->wxt510HeatingVoltage); + dest->wxt510SupplyVoltage = ntohf(src->wxt510SupplyVoltage); + dest->wxt510ReferenceVoltage = ntohf(src->wxt510ReferenceVoltage); + dest->wxt510RainDuration = ntohf(src->wxt510RainDuration); + dest->wxt510RainPeakRate = ntohf(src->wxt510RainPeakRate); + dest->wxt510HailDuration = ntohf(src->wxt510HailDuration); + dest->wxt510HailPeakRate = ntohf(src->wxt510HailPeakRate); + dest->wxt510Rain = ntohf(src->wxt510Rain); + dest->wmr918Pool = ntohf(src->wmr918Pool); + + tempshort = (uint16_t*)&(dest->wmr918Humid3); + *tempshort = swapShortNTOH(&dest->wmr918Humid3); + tempshort = (uint16_t*)&(dest->wmr918WindBatteryStatus); + *tempshort = swapShortNTOH(&dest->wmr918WindBatteryStatus); + tempshort = (uint16_t*)&(dest->wmr918OutTempBatteryStatus); + *tempshort = swapShortNTOH(&dest->wmr918OutTempBatteryStatus); + tempshort = (uint16_t*)&(dest->wmr918poolTempBatteryStatus); + *tempshort = swapShortNTOH(&dest->wmr918poolTempBatteryStatus); + tempshort = (uint16_t*)&(dest->wmr918extra2BatteryStatus); + *tempshort = swapShortNTOH(&dest->wmr918extra2BatteryStatus); + + return OK; +} + +int datafeedConvertArchive_HTON(ARCHIVE_PKT* dest, ARCHIVE_PKT* src) +{ + int index; + + dest->dateTime = htonl(src->dateTime); + dest->usUnits = htonl(src->usUnits); + dest->interval = htonl(src->interval); + + for (index = 0; index < DATA_INDEX_MAX; index ++) + { + dest->value[index] = htonf(src->value[index]); + } + + return OK; +} + +int datafeedConvertArchive_NTOH(ARCHIVE_PKT* dest, ARCHIVE_PKT* src) +{ + int index; + + dest->dateTime = ntohl(src->dateTime); + dest->usUnits = ntohl(src->usUnits); + dest->interval = ntohl(src->interval); + + for (index = 0; index < DATA_INDEX_MAX; index ++) + { + dest->value[index] = ntohf(src->value[index]); + } + + return OK; +} + diff --git a/common/datafeed.h b/common/datafeed.h new file mode 100755 index 0000000..68928fb --- /dev/null +++ b/common/datafeed.h @@ -0,0 +1,121 @@ +#ifndef INC_datafeedh +#define INC_datafeedh +/*--------------------------------------------------------------------------- + + FILENAME: + datafeed.h + + PURPOSE: + Define the datafeed API. + + REVISION HISTORY: + Date Engineer Revision Remarks + 12/16/2009 M.S. Teel 0 Original + + NOTES: + + + LICENSE: + Copyright (c) 2009, Mark S. Teel (mark@teel.ws) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +// ... includes +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define WVIEW_DATA_ONLY TRUE +#include + + +// ... macro definitions + +// The listen port for wview - clients connect here: +#define WV_DATAFEED_PORT 11011 + + +// ... typedefs + +// Define the start frame sequences for loop and archive packets: +#define DF_START_FRAME_LENGTH 8 +#define DF_LOOP_PKT_TYPE 1 +#define DF_ARCHIVE_PKT_TYPE 2 +#define DF_RQST_ARCHIVE_PKT_TYPE 3 + +// Define some times: +#define DF_WAIT_FIRST 500 +#define DF_WAIT_MORE 250 + + +#ifdef DATAFEED_INSTANTIATE +const uint16_t DF_LOOP_START_FRAME[4] = +{ + 0xF388, + 0xC6A2, + 0xDADA, + 0x0001 +}; +const uint16_t DF_ARCHIVE_START_FRAME[4] = +{ + 0xF388, + 0xC6A2, + 0xDADA, + 0x0002 +}; + +// This one is sent by the client to request the next archive record after the +// dateTime submitted; allows the client to synchronize archive data: +const uint16_t DF_RQST_ARCHIVE_START_FRAME[4] = +{ + 0xF388, + 0xC6A2, + 0xDADA, + 0x0003 +}; +#else +extern const uint16_t DF_LOOP_START_FRAME[4]; +extern const uint16_t DF_ARCHIVE_START_FRAME[4]; +extern const uint16_t DF_RQST_ARCHIVE_START_FRAME[4]; +#endif + + +// ... API prototypes + +// Frame sync utility: +// Returns DF_LOOP_PKT_TYPE, DF_ARCHIVE_PKT_TYPE or DF_RQST_ARCHIVE_PKT_TYPE +// if a valid frame header of one of those types is received, FALSE if not a +// valid frame header and ERROR if there is a socket error: +extern int datafeedSyncStartOfFrame(RADSOCK_ID socket); + + +// LOOP_PKT byteorder and fixed point conversions: +extern int datafeedConvertLOOP_HTON(LOOP_PKT* dest, LOOP_PKT* src); +extern int datafeedConvertLOOP_NTOH(LOOP_PKT* dest, LOOP_PKT* src); + +// ARCHIVE_PKT byteorder and fixed point conversions: +extern int datafeedConvertArchive_HTON(ARCHIVE_PKT* dest, ARCHIVE_PKT* src); +extern int datafeedConvertArchive_NTOH(ARCHIVE_PKT* dest, ARCHIVE_PKT* src); + + +#endif + diff --git a/common/dbdatabase.c b/common/dbdatabase.c new file mode 100755 index 0000000..5aa95cf --- /dev/null +++ b/common/dbdatabase.c @@ -0,0 +1,935 @@ +/*--------------------------------------------------------------------------- + + FILENAME: + dbdatabase.c + + PURPOSE: + Provide the SQL database archiving API methods. + + REVISION HISTORY: + Date Engineer Revision Remarks + 4/18/2005 M.S. Teel 0 Original + + NOTES: + + + LICENSE: + Copyright (c) 2004, Mark S. Teel (mark@teel.ws) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +// ... System header files +#include + + +// ... Local header files +#include + + +// ... local memory + +static DBDATABASE_WORK dbdbWork; +static int dbTableExists; +static char dupQuery[256]; +static int archiveExtendedIndex = 16; // where extended begins +static DB_ARCHIVE_DESCRIPTION archiveDescription[] = + { + {"RecordTime", FIELD_DATETIME | FIELD_PRI_KEY | FIELD_UNIQUE_INDEX | FIELD_NOT_NULL, 16}, + {"ArcInt", FIELD_INT, 0}, + {"OutTemp", FIELD_FLOAT, 0}, + {"InTemp", FIELD_FLOAT, 0}, + {"Barometer", FIELD_FLOAT, 0}, + {"OutHumid", FIELD_FLOAT, 0}, + {"InHumid", FIELD_FLOAT, 0}, + {"Rain", FIELD_FLOAT, 0}, + {"HiRainRate", FIELD_FLOAT, 0}, + {"WindSpeed", FIELD_FLOAT, 0}, + {"HiWindSpeed", FIELD_FLOAT, 0}, + {"WindDir", FIELD_INT, 0}, + {"HiWindDir", FIELD_INT, 0}, + {"Dewpoint", FIELD_FLOAT, 0}, + {"WindChill", FIELD_FLOAT, 0}, + {"HeatIndex", FIELD_FLOAT, 0}, + {"solarRad", FIELD_INT, 0}, + {"UV", FIELD_FLOAT, 0}, + {"ET", FIELD_FLOAT, 0}, + {"leafTemp1", FIELD_INT, 0}, + {"leafTemp2", FIELD_INT, 0}, + {"leafWet1", FIELD_INT, 0}, + {"leafWet2", FIELD_INT, 0}, + {"soilTemp1", FIELD_INT, 0}, + {"soilTemp2", FIELD_INT, 0}, + {"soilTemp3", FIELD_INT, 0}, + {"soilTemp4", FIELD_INT, 0}, + {"soilMoist1", FIELD_INT, 0}, + {"soilMoist2", FIELD_INT, 0}, + {"soilMoist3", FIELD_INT, 0}, + {"soilMoist4", FIELD_INT, 0}, + {"extraHumid1", FIELD_INT, 0}, + {"extraHumid2", FIELD_INT, 0}, + {"extraTemp1", FIELD_INT, 0}, + {"extraTemp2", FIELD_INT, 0}, + {"extraTemp3", FIELD_INT, 0}, + {NULL, 0, 0} + }; + + +// ... define methods here + +int dbdatabaseInit +( + int isEnabled, + int useExtended, + char *host, + char *username, + char *password, + char *databaseName, + char *tableName, + int isMetricUnits +) +{ + dbTableExists = FALSE; + + memset (&dbdbWork, 0, sizeof (dbdbWork)); + + dbdbWork.isEnabled = isEnabled; + dbdbWork.isExtended = useExtended; + + if (!dbdbWork.isEnabled) + return OK; + + if (host == NULL) + strcpy (dbdbWork.host, "NA"); + else + wvstrncpy (dbdbWork.host, host, sizeof(dbdbWork.host)); + + wvstrncpy (dbdbWork.username, username, sizeof(dbdbWork.username)); + wvstrncpy (dbdbWork.password, password, sizeof(dbdbWork.password)); + wvstrncpy (dbdbWork.databaseName, databaseName, sizeof(dbdbWork.databaseName)); + wvstrncpy (dbdbWork.tableName, tableName, sizeof(dbdbWork.tableName)); + dbdbWork.isMetricUnits = isMetricUnits; + + return OK; +} + +int dbdatabaseOpen (void) +{ + char *host; + int i, retVal, maxLoop; + ROW_ID row; + + if (!dbdbWork.isEnabled) + return ERROR; + + if (dbdbWork.dbId != NULL) + { + // try to close it + raddatabaseClose (dbdbWork.dbId); + dbdbWork.dbId = NULL; + } + + if (!strcmp (dbdbWork.host, "NA")) + host = NULL; + else + host = dbdbWork.host; + + // does the database exist? + if ((dbdbWork.dbId = raddatabaseOpen (host, + dbdbWork.username, + dbdbWork.password, + dbdbWork.databaseName)) + == NULL) + { + radMsgLog (PRI_MEDIUM, "dbdatabaseOpen: %s databaseOpen failed!", + dbdbWork.databaseName); + return ERROR; + } + + // open the table (or create it) + if (!dbTableExists) + { + if (!raddatabaseTableIfExists (dbdbWork.dbId, dbdbWork.tableName)) + { + row = raddatabaseRowDescriptionCreate (); + if (row == NULL) + { + radMsgLog (PRI_MEDIUM, "dbdatabaseOpen: databaseRowDescriptionCreate failed!"); + raddatabaseClose (dbdbWork.dbId); + dbdbWork.dbId = NULL; + return ERROR; + } + + if (dbdbWork.isExtended) + maxLoop = 255; + else + maxLoop = archiveExtendedIndex; + + // loop through adding all defined fields + for (i = 0; i < maxLoop && archiveDescription[i].name != NULL; i ++) + { + retVal = raddatabaseRowDescriptionAddField (row, + archiveDescription[i].name, + archiveDescription[i].type, + archiveDescription[i].maxLength); + if (retVal == ERROR) + { + radMsgLog (PRI_MEDIUM, "dbdatabaseOpen: " + "RowDescriptionAddField failed!"); + raddatabaseRowDescriptionDelete (row); + raddatabaseClose (dbdbWork.dbId); + dbdbWork.dbId = NULL; + return ERROR; + } + } + + // ... now try to create the table + retVal = raddatabaseTableCreate (dbdbWork.dbId, dbdbWork.tableName, row); + if (retVal == ERROR) + { + radMsgLog (PRI_MEDIUM, "dbdatabaseOpen: " + "raddatabaseTableCreate failed!"); + raddatabaseRowDescriptionDelete (row); + raddatabaseClose (dbdbWork.dbId); + dbdbWork.dbId = NULL; + return ERROR; + } + + raddatabaseRowDescriptionDelete (row); + dbTableExists = TRUE; + } + else + { + dbTableExists = TRUE; + } + } + + return OK; +} + +void dbdatabaseClose (void) +{ + raddatabaseClose (dbdbWork.dbId); + dbdbWork.dbId = NULL; + return; +} + + +int dbdatabaseInsertRecord (ARCHIVE_PKT* newRecord) +{ + FIELD_ID field; + char *host, dtime[64]; + RESULT_SET_ID resultId; + ROW_ID row; + + if (!dbdbWork.isEnabled) + return DBDB_INSERT_ERROR; + + // build the proper date/time field format + sprintf (dtime, + "%4.4d-%2.2d-%2.2d %2.2d:%2.2d:%2.2d", + wvutilsGetYear(newRecord->dateTime), + wvutilsGetMonth(newRecord->dateTime), + wvutilsGetDay(newRecord->dateTime), + wvutilsGetHour(newRecord->dateTime), + wvutilsGetMin(newRecord->dateTime), + 0); + + // first check for a duplicate + sprintf (dupQuery, "SELECT RecordTime FROM %s WHERE RecordTime='%s'", + dbdbWork.tableName, dtime); + if (raddatabaseQuery (dbdbWork.dbId, dupQuery, TRUE) == OK) + { + resultId = raddatabaseGetResults (dbdbWork.dbId); + if (resultId != NULL) + { + row = raddatabaseResultsGetFirst (resultId); + if (row != NULL) + { + // looks like a duplicate... + raddatabaseReleaseResults (dbdbWork.dbId, resultId); + return DBDB_INSERT_DUPLICATE; + } + + raddatabaseReleaseResults (dbdbWork.dbId, resultId); + } + } + + // let's add a row here + row = raddatabaseTableDescriptionGet (dbdbWork.dbId, dbdbWork.tableName); + if (row == NULL) + { + radMsgLog (PRI_MEDIUM, + "dbdatabaseInsertRecord: databaseTableDescriptionGet failed!"); + return DBDB_INSERT_ERROR; + } + + field = raddatabaseFieldGet (row, archiveDescription[0].name); + if (field == NULL) + { + radMsgLog (PRI_CATASTROPHIC, "dbdatabaseInsertRecord: FieldGet DateTime failed!"); + raddatabaseRowDescriptionDelete (row); + return DBDB_INSERT_ERROR; + } + raddatabaseFieldSetDateTimeValue (field, dtime, strlen (dtime)); + + field = raddatabaseFieldGet (row, archiveDescription[1].name); + if (field == NULL) + { + radMsgLog (PRI_CATASTROPHIC, "dbdatabaseInsertRecord: FieldGet 1 failed!"); + raddatabaseRowDescriptionDelete (row); + return DBDB_INSERT_ERROR; + } + raddatabaseFieldSetIntValue (field, (int)newRecord->interval); + + field = raddatabaseFieldGet (row, archiveDescription[2].name); + if (field == NULL) + { + radMsgLog (PRI_CATASTROPHIC, "dbdatabaseInsertRecord: FieldGet 2 failed!"); + raddatabaseRowDescriptionDelete (row); + return DBDB_INSERT_ERROR; + } + if (dbdbWork.isMetricUnits) + raddatabaseFieldSetFloatValue (field, + wvutilsConvertFToC ((float)newRecord->value[DATA_INDEX_outTemp])); + else + raddatabaseFieldSetFloatValue (field, (float)newRecord->value[DATA_INDEX_outTemp]); + + field = raddatabaseFieldGet (row, archiveDescription[3].name); + if (field == NULL) + { + radMsgLog (PRI_CATASTROPHIC, "dbdatabaseInsertRecord: FieldGet 3 failed!"); + raddatabaseRowDescriptionDelete (row); + return DBDB_INSERT_ERROR; + } + if (dbdbWork.isMetricUnits) + raddatabaseFieldSetFloatValue (field, + wvutilsConvertFToC ((float)newRecord->value[DATA_INDEX_inTemp])); + else + raddatabaseFieldSetFloatValue (field, (float)newRecord->value[DATA_INDEX_inTemp]); + + field = raddatabaseFieldGet (row, archiveDescription[4].name); + if (field == NULL) + { + radMsgLog (PRI_CATASTROPHIC, "dbdatabaseInsertRecord: FieldGet 4 failed!"); + raddatabaseRowDescriptionDelete (row); + return DBDB_INSERT_ERROR; + } + if (dbdbWork.isMetricUnits) + raddatabaseFieldSetFloatValue (field, + wvutilsConvertINHGToHPA ((float)newRecord->value[DATA_INDEX_barometer])); + else + raddatabaseFieldSetFloatValue (field, (float)newRecord->value[DATA_INDEX_barometer]); + + field = raddatabaseFieldGet (row, archiveDescription[5].name); + if (field == NULL) + { + radMsgLog (PRI_CATASTROPHIC, "dbdatabaseInsertRecord: FieldGet 5 failed!"); + raddatabaseRowDescriptionDelete (row); + return DBDB_INSERT_ERROR; + } + raddatabaseFieldSetFloatValue (field, (float)newRecord->value[DATA_INDEX_outHumidity]); + + field = raddatabaseFieldGet (row, archiveDescription[6].name); + if (field == NULL) + { + radMsgLog (PRI_CATASTROPHIC, "dbdatabaseInsertRecord: FieldGet 6 failed!"); + raddatabaseRowDescriptionDelete (row); + return DBDB_INSERT_ERROR; + } + raddatabaseFieldSetFloatValue (field, (float)newRecord->value[DATA_INDEX_inHumidity]); + + field = raddatabaseFieldGet (row, archiveDescription[7].name); + if (field == NULL) + { + radMsgLog (PRI_CATASTROPHIC, "dbdatabaseInsertRecord: FieldGet 7 failed!"); + raddatabaseRowDescriptionDelete (row); + return DBDB_INSERT_ERROR; + } + if (dbdbWork.isMetricUnits) + raddatabaseFieldSetFloatValue (field, + wvutilsConvertRainINToMetric (newRecord->value[DATA_INDEX_rain])); + else + raddatabaseFieldSetFloatValue (field, newRecord->value[DATA_INDEX_rain]); + + field = raddatabaseFieldGet (row, archiveDescription[8].name); + if (field == NULL) + { + radMsgLog (PRI_CATASTROPHIC, "dbdatabaseInsertRecord: FieldGet 8 failed!"); + raddatabaseRowDescriptionDelete (row); + return DBDB_INSERT_ERROR; + } + if (dbdbWork.isMetricUnits) + raddatabaseFieldSetFloatValue (field, + wvutilsConvertRainINToMetric (newRecord->value[DATA_INDEX_rainRate])); + else + raddatabaseFieldSetFloatValue (field, newRecord->value[DATA_INDEX_rainRate]); + + field = raddatabaseFieldGet (row, archiveDescription[9].name); + if (field == NULL) + { + radMsgLog (PRI_CATASTROPHIC, "dbdatabaseInsertRecord: FieldGet 9 failed!"); + raddatabaseRowDescriptionDelete (row); + return DBDB_INSERT_ERROR; + } + if (dbdbWork.isMetricUnits) + raddatabaseFieldSetFloatValue (field, + wvutilsConvertMPHToKPH ((float)newRecord->value[DATA_INDEX_windSpeed])); + else + raddatabaseFieldSetFloatValue (field, (float)newRecord->value[DATA_INDEX_windSpeed]); + + field = raddatabaseFieldGet (row, archiveDescription[10].name); + if (field == NULL) + { + radMsgLog (PRI_CATASTROPHIC, "dbdatabaseInsertRecord: FieldGet 10 failed!"); + raddatabaseRowDescriptionDelete (row); + return DBDB_INSERT_ERROR; + } + if (dbdbWork.isMetricUnits) + raddatabaseFieldSetFloatValue (field, + wvutilsConvertMPHToKPH ((float)newRecord->value[DATA_INDEX_windGust])); + else + raddatabaseFieldSetFloatValue (field, (float)newRecord->value[DATA_INDEX_windGust]); + + field = raddatabaseFieldGet (row, archiveDescription[11].name); + if (field == NULL) + { + radMsgLog (PRI_CATASTROPHIC, "dbdatabaseInsertRecord: FieldGet 11 failed!"); + raddatabaseRowDescriptionDelete (row); + return DBDB_INSERT_ERROR; + } + raddatabaseFieldSetIntValue (field, (int)newRecord->value[DATA_INDEX_windDir]); + + field = raddatabaseFieldGet (row, archiveDescription[12].name); + if (field == NULL) + { + radMsgLog (PRI_CATASTROPHIC, "dbdatabaseInsertRecord: FieldGet 12 failed!"); + raddatabaseRowDescriptionDelete (row); + return DBDB_INSERT_ERROR; + } + raddatabaseFieldSetIntValue (field, (int)newRecord->value[DATA_INDEX_windGustDir]); + + field = raddatabaseFieldGet (row, archiveDescription[13].name); + if (field == NULL) + { + radMsgLog (PRI_CATASTROPHIC, "dbdatabaseInsertRecord: FieldGet 13 failed!"); + raddatabaseRowDescriptionDelete (row); + return DBDB_INSERT_ERROR; + } + if (dbdbWork.isMetricUnits) + raddatabaseFieldSetFloatValue (field, + wvutilsConvertFToC (newRecord->value[DATA_INDEX_dewpoint])); + else + raddatabaseFieldSetFloatValue (field, newRecord->value[DATA_INDEX_dewpoint]); + + field = raddatabaseFieldGet (row, archiveDescription[14].name); + if (field == NULL) + { + radMsgLog (PRI_CATASTROPHIC, "dbdatabaseInsertRecord: FieldGet 14 failed!"); + raddatabaseRowDescriptionDelete (row); + return DBDB_INSERT_ERROR; + } + if (dbdbWork.isMetricUnits) + raddatabaseFieldSetFloatValue (field, + wvutilsConvertFToC (newRecord->value[DATA_INDEX_windchill])); + else + raddatabaseFieldSetFloatValue (field, newRecord->value[DATA_INDEX_windchill]); + + field = raddatabaseFieldGet (row, archiveDescription[15].name); + if (field == NULL) + { + radMsgLog (PRI_CATASTROPHIC, "dbdatabaseInsertRecord: FieldGet 15 failed!"); + raddatabaseRowDescriptionDelete (row); + return DBDB_INSERT_ERROR; + } + if (dbdbWork.isMetricUnits) + raddatabaseFieldSetFloatValue (field, + wvutilsConvertFToC (newRecord->value[DATA_INDEX_heatindex])); + else + raddatabaseFieldSetFloatValue (field, newRecord->value[DATA_INDEX_heatindex]); + + + // Are we storing extended values too? + if (dbdbWork.isExtended) + { + // ... add the extended values + field = raddatabaseFieldGet (row, archiveDescription[16].name); + if (field == NULL) + { + radMsgLog (PRI_CATASTROPHIC, "dbdatabaseInsertRecord: FieldGet 16 failed!"); + raddatabaseRowDescriptionDelete (row); + return DBDB_INSERT_ERROR; + } + raddatabaseFieldSetIntValue (field, (int)newRecord->value[DATA_INDEX_radiation]); + + field = raddatabaseFieldGet (row, archiveDescription[17].name); + if (field == NULL) + { + radMsgLog (PRI_CATASTROPHIC, "dbdatabaseInsertRecord: FieldGet 17 failed!"); + raddatabaseRowDescriptionDelete (row); + return DBDB_INSERT_ERROR; + } + if (newRecord->value[DATA_INDEX_UV] < 0) + { + //no value + raddatabaseFieldSetFloatValue (field, -1.0); + } + else + { + raddatabaseFieldSetFloatValue (field, (float)newRecord->value[DATA_INDEX_UV]); + } + + field = raddatabaseFieldGet (row, archiveDescription[18].name); + if (field == NULL) + { + radMsgLog (PRI_CATASTROPHIC, "dbdatabaseInsertRecord: FieldGet 18 failed!"); + raddatabaseRowDescriptionDelete (row); + return DBDB_INSERT_ERROR; + } + if (newRecord->value[DATA_INDEX_ET] < 0) + { + // no value + raddatabaseFieldSetFloatValue (field, -1.0); + } + else + { + if (dbdbWork.isMetricUnits) + raddatabaseFieldSetFloatValue (field, + wvutilsConvertRainINToMetric ((float)newRecord->value[DATA_INDEX_ET])); + else + raddatabaseFieldSetFloatValue (field, (float)newRecord->value[DATA_INDEX_ET]); + } + + field = raddatabaseFieldGet (row, archiveDescription[19].name); + if (field == NULL) + { + radMsgLog (PRI_CATASTROPHIC, "dbdatabaseInsertRecord: FieldGet 19 failed!"); + raddatabaseRowDescriptionDelete (row); + return DBDB_INSERT_ERROR; + } + if (newRecord->value[DATA_INDEX_leafTemp1] <= ARCHIVE_VALUE_NULL) + { + // no value + raddatabaseFieldSetIntValue (field, -1); + } + else + { + if (dbdbWork.isMetricUnits) + raddatabaseFieldSetIntValue (field, + (int)wvutilsConvertFToC ((float)(newRecord->value[DATA_INDEX_leafTemp1]))); + else + raddatabaseFieldSetIntValue (field, (int)newRecord->value[DATA_INDEX_leafTemp1]); + } + + field = raddatabaseFieldGet (row, archiveDescription[20].name); + if (field == NULL) + { + radMsgLog (PRI_CATASTROPHIC, "dbdatabaseInsertRecord: FieldGet 20 failed!"); + raddatabaseRowDescriptionDelete (row); + return DBDB_INSERT_ERROR; + } + if (newRecord->value[DATA_INDEX_leafTemp2] <= ARCHIVE_VALUE_NULL) + { + // no value + raddatabaseFieldSetIntValue (field, -1); + } + else + { + if (dbdbWork.isMetricUnits) + raddatabaseFieldSetIntValue (field, + (int)wvutilsConvertFToC ((float)(newRecord->value[DATA_INDEX_leafTemp2]))); + else + raddatabaseFieldSetIntValue (field, (int)newRecord->value[DATA_INDEX_leafTemp2]); + } + + field = raddatabaseFieldGet (row, archiveDescription[21].name); + if (field == NULL) + { + radMsgLog (PRI_CATASTROPHIC, "dbdatabaseInsertRecord: FieldGet 21 failed!"); + raddatabaseRowDescriptionDelete (row); + return DBDB_INSERT_ERROR; + } + if (newRecord->value[DATA_INDEX_leafWet1] <= ARCHIVE_VALUE_NULL) + { + // no value + raddatabaseFieldSetIntValue (field, -1); + } + else + { + raddatabaseFieldSetIntValue (field, (int)newRecord->value[DATA_INDEX_leafWet1]); + } + + field = raddatabaseFieldGet (row, archiveDescription[22].name); + if (field == NULL) + { + radMsgLog (PRI_CATASTROPHIC, "dbdatabaseInsertRecord: FieldGet 22 failed!"); + raddatabaseRowDescriptionDelete (row); + return DBDB_INSERT_ERROR; + } + if (newRecord->value[DATA_INDEX_leafWet2] <= ARCHIVE_VALUE_NULL) + { + // no value + raddatabaseFieldSetIntValue (field, -1); + } + else + { + raddatabaseFieldSetIntValue (field, (int)newRecord->value[DATA_INDEX_leafWet2]); + } + + field = raddatabaseFieldGet (row, archiveDescription[23].name); + if (field == NULL) + { + radMsgLog (PRI_CATASTROPHIC, "dbdatabaseInsertRecord: FieldGet 23 failed!"); + raddatabaseRowDescriptionDelete (row); + return DBDB_INSERT_ERROR; + } + if (newRecord->value[DATA_INDEX_soilTemp1] <= ARCHIVE_VALUE_NULL) + { + // no value + raddatabaseFieldSetIntValue (field, -1); + } + else + { + if (dbdbWork.isMetricUnits) + raddatabaseFieldSetIntValue (field, + (int)wvutilsConvertFToC ((float)(newRecord->value[DATA_INDEX_soilTemp1]))); + else + raddatabaseFieldSetIntValue (field, (int)newRecord->value[DATA_INDEX_soilTemp1]); + } + + field = raddatabaseFieldGet (row, archiveDescription[24].name); + if (field == NULL) + { + radMsgLog (PRI_CATASTROPHIC, "dbdatabaseInsertRecord: FieldGet 24 failed!"); + raddatabaseRowDescriptionDelete (row); + return DBDB_INSERT_ERROR; + } + if (newRecord->value[DATA_INDEX_soilTemp2] <= ARCHIVE_VALUE_NULL) + { + // no value + raddatabaseFieldSetIntValue (field, -1); + } + else + { + if (dbdbWork.isMetricUnits) + raddatabaseFieldSetIntValue (field, + (int)wvutilsConvertFToC ((float)(newRecord->value[DATA_INDEX_soilTemp2]))); + else + raddatabaseFieldSetIntValue (field, (int)newRecord->value[DATA_INDEX_soilTemp2]); + } + + field = raddatabaseFieldGet (row, archiveDescription[25].name); + if (field == NULL) + { + radMsgLog (PRI_CATASTROPHIC, "dbdatabaseInsertRecord: FieldGet 25 failed!"); + raddatabaseRowDescriptionDelete (row); + return DBDB_INSERT_ERROR; + } + if (newRecord->value[DATA_INDEX_soilTemp3] <= ARCHIVE_VALUE_NULL) + { + // no value + raddatabaseFieldSetIntValue (field, -1); + } + else + { + if (dbdbWork.isMetricUnits) + raddatabaseFieldSetIntValue (field, + (int)wvutilsConvertFToC ((float)(newRecord->value[DATA_INDEX_soilTemp3]))); + else + raddatabaseFieldSetIntValue (field, (int)newRecord->value[DATA_INDEX_soilTemp3]); + } + + field = raddatabaseFieldGet (row, archiveDescription[26].name); + if (field == NULL) + { + radMsgLog (PRI_CATASTROPHIC, "dbdatabaseInsertRecord: FieldGet 26 failed!"); + raddatabaseRowDescriptionDelete (row); + return DBDB_INSERT_ERROR; + } + if (newRecord->value[DATA_INDEX_soilTemp4] <= ARCHIVE_VALUE_NULL) + { + // no value + raddatabaseFieldSetIntValue (field, -1); + } + else + { + if (dbdbWork.isMetricUnits) + raddatabaseFieldSetIntValue (field, + (int)wvutilsConvertFToC ((float)(newRecord->value[DATA_INDEX_soilTemp4]))); + else + raddatabaseFieldSetIntValue (field, (int)newRecord->value[DATA_INDEX_soilTemp4]); + } + + field = raddatabaseFieldGet (row, archiveDescription[27].name); + if (field == NULL) + { + radMsgLog (PRI_CATASTROPHIC, "dbdatabaseInsertRecord: FieldGet 27 failed!"); + raddatabaseRowDescriptionDelete (row); + return DBDB_INSERT_ERROR; + } + if (newRecord->value[DATA_INDEX_soilMoist1] <= ARCHIVE_VALUE_NULL) + { + // no value + raddatabaseFieldSetIntValue (field, -1); + } + else + { + raddatabaseFieldSetIntValue (field, (int)newRecord->value[DATA_INDEX_soilMoist1]); + } + + field = raddatabaseFieldGet (row, archiveDescription[28].name); + if (field == NULL) + { + radMsgLog (PRI_CATASTROPHIC, "dbdatabaseInsertRecord: FieldGet 28 failed!"); + raddatabaseRowDescriptionDelete (row); + return DBDB_INSERT_ERROR; + } + if (newRecord->value[DATA_INDEX_soilMoist2] <= ARCHIVE_VALUE_NULL) + { + // no value + raddatabaseFieldSetIntValue (field, -1); + } + else + { + raddatabaseFieldSetIntValue (field, (int)newRecord->value[DATA_INDEX_soilMoist2]); + } + + field = raddatabaseFieldGet (row, archiveDescription[29].name); + if (field == NULL) + { + radMsgLog (PRI_CATASTROPHIC, "dbdatabaseInsertRecord: FieldGet 29 failed!"); + raddatabaseRowDescriptionDelete (row); + return DBDB_INSERT_ERROR; + } + if (newRecord->value[DATA_INDEX_soilMoist3] <= ARCHIVE_VALUE_NULL) + { + // no value + raddatabaseFieldSetIntValue (field, -1); + } + else + { + raddatabaseFieldSetIntValue (field, (int)newRecord->value[DATA_INDEX_soilMoist3]); + } + + field = raddatabaseFieldGet (row, archiveDescription[30].name); + if (field == NULL) + { + radMsgLog (PRI_CATASTROPHIC, "dbdatabaseInsertRecord: FieldGet 30 failed!"); + raddatabaseRowDescriptionDelete (row); + return DBDB_INSERT_ERROR; + } + if (newRecord->value[DATA_INDEX_soilMoist4] <= ARCHIVE_VALUE_NULL) + { + // no value + raddatabaseFieldSetIntValue (field, -1); + } + else + { + raddatabaseFieldSetIntValue (field, (int)newRecord->value[DATA_INDEX_soilMoist4]); + } + + field = raddatabaseFieldGet (row, archiveDescription[31].name); + if (field == NULL) + { + radMsgLog (PRI_CATASTROPHIC, "dbdatabaseInsertRecord: FieldGet 31 failed!"); + raddatabaseRowDescriptionDelete (row); + return DBDB_INSERT_ERROR; + } + if (newRecord->value[DATA_INDEX_extraHumid1] <= ARCHIVE_VALUE_NULL) + { + // no value + raddatabaseFieldSetIntValue (field, -1); + } + else + { + raddatabaseFieldSetIntValue (field, (int)newRecord->value[DATA_INDEX_extraHumid1]); + } + + field = raddatabaseFieldGet (row, archiveDescription[32].name); + if (field == NULL) + { + radMsgLog (PRI_CATASTROPHIC, "dbdatabaseInsertRecord: FieldGet 32 failed!"); + raddatabaseRowDescriptionDelete (row); + return DBDB_INSERT_ERROR; + } + if (newRecord->value[DATA_INDEX_extraHumid2] <= ARCHIVE_VALUE_NULL) + { + // no value + raddatabaseFieldSetIntValue (field, -1); + } + else + { + raddatabaseFieldSetIntValue (field, (int)newRecord->value[DATA_INDEX_extraHumid2]); + } + + field = raddatabaseFieldGet (row, archiveDescription[33].name); + if (field == NULL) + { + radMsgLog (PRI_CATASTROPHIC, "dbdatabaseInsertRecord: FieldGet 33 failed!"); + raddatabaseRowDescriptionDelete (row); + return DBDB_INSERT_ERROR; + } + if (newRecord->value[DATA_INDEX_extraTemp1] <= ARCHIVE_VALUE_NULL) + { + // no value + raddatabaseFieldSetIntValue (field, -1); + } + else + { + if (dbdbWork.isMetricUnits) + raddatabaseFieldSetIntValue (field, + (int)wvutilsConvertFToC ((float)(newRecord->value[DATA_INDEX_extraTemp1]))); + else + raddatabaseFieldSetIntValue (field, (int)newRecord->value[DATA_INDEX_extraTemp1]); + } + + field = raddatabaseFieldGet (row, archiveDescription[34].name); + if (field == NULL) + { + radMsgLog (PRI_CATASTROPHIC, "dbdatabaseInsertRecord: FieldGet 34 failed!"); + raddatabaseRowDescriptionDelete (row); + return DBDB_INSERT_ERROR; + } + if (newRecord->value[DATA_INDEX_extraTemp2] <= ARCHIVE_VALUE_NULL) + { + // no value + raddatabaseFieldSetIntValue (field, -1); + } + else + { + if (dbdbWork.isMetricUnits) + raddatabaseFieldSetIntValue (field, + (int)wvutilsConvertFToC ((float)(newRecord->value[DATA_INDEX_extraTemp2]))); + else + raddatabaseFieldSetIntValue (field, (int)newRecord->value[DATA_INDEX_extraTemp2]); + } + + field = raddatabaseFieldGet (row, archiveDescription[35].name); + if (field == NULL) + { + radMsgLog (PRI_CATASTROPHIC, "dbdatabaseInsertRecord: FieldGet 35 failed!"); + raddatabaseRowDescriptionDelete (row); + return DBDB_INSERT_ERROR; + } + if (newRecord->value[DATA_INDEX_extraTemp3] <= ARCHIVE_VALUE_NULL) + { + // no value + raddatabaseFieldSetIntValue (field, -1); + } + else + { + if (dbdbWork.isMetricUnits) + raddatabaseFieldSetIntValue (field, + (int)wvutilsConvertFToC ((float)(newRecord->value[DATA_INDEX_extraTemp3]))); + else + raddatabaseFieldSetIntValue (field, (int)newRecord->value[DATA_INDEX_extraTemp3]); + } + } + + + // ... insert the row + if (raddatabaseTableInsertRow (dbdbWork.dbId, dbdbWork.tableName, row) == ERROR) + { + radMsgLog (PRI_MEDIUM, "dbdatabaseInsertRecord: TableInsertRow failed!"); + raddatabaseRowDescriptionDelete (row); + return DBDB_INSERT_ERROR; + } + + raddatabaseRowDescriptionDelete (row); + return DBDB_INSERT_OK; +} + + +int dbdatabaseGetDateOfLastEntry (time_t* retTime) +{ + char lastQuery[1024]; + int dbRecordYear,dbRecordMonth,dbRecordDay,dbRecordMinute,dbRecordHour; + + ROW_ID row; + RESULT_SET_ID resultId; + FIELD_ID field; + struct tm bknTime; + + // TODO: it should be possible to do most of this function (shifting, ANDing, casting etc) + // in this SQL statement... + sprintf( lastQuery, "SELECT (EXTRACT(YEAR FROM recordTime)) AS year, (EXTRACT(MONTH FROM recordTime)) AS month, (EXTRACT(DAY FROM recordTime)) AS day, (EXTRACT(HOUR FROM recordTime)) AS hour, (EXTRACT(MINUTE FROM recordTime)) AS minute FROM %s ORDER BY recordTime DESC LIMIT 1", dbdbWork.tableName); + if (raddatabaseQuery (dbdbWork.dbId, lastQuery, TRUE) == OK) + { + resultId = raddatabaseGetResults (dbdbWork.dbId); + if (resultId != NULL) + { + row = raddatabaseResultsGetFirst (resultId); + if (row != NULL) + { + // mysql and pgresql return different data types from the query, so... + field = raddatabaseFieldGet (row, "year"); + if ( raddatabaseFieldGetType(field) == FIELD_DOUBLE ) + { + dbRecordYear = (int) raddatabaseFieldGetDoubleValue (field); + } + else + { + dbRecordYear = raddatabaseFieldGetBigIntValue (field); + } + + field = raddatabaseFieldGet (row, "month"); + if ( raddatabaseFieldGetType(field) == FIELD_DOUBLE ) + { + dbRecordMonth = (int) raddatabaseFieldGetDoubleValue (field); + } + else + { + dbRecordMonth = raddatabaseFieldGetBigIntValue (field); + } + + field = raddatabaseFieldGet (row, "day"); + if ( raddatabaseFieldGetType(field) == FIELD_DOUBLE ) + { + dbRecordDay = (int) raddatabaseFieldGetDoubleValue (field); + } + else + { + dbRecordDay = raddatabaseFieldGetBigIntValue (field); + } + + field = raddatabaseFieldGet (row, "hour"); + if ( raddatabaseFieldGetType(field) == FIELD_DOUBLE ) + { + dbRecordHour = (int) raddatabaseFieldGetDoubleValue (field); + } + else + { + dbRecordHour = raddatabaseFieldGetBigIntValue (field); + } + + field = raddatabaseFieldGet (row, "minute"); + if ( raddatabaseFieldGetType(field) == FIELD_DOUBLE ) + { + dbRecordMinute = (int) raddatabaseFieldGetDoubleValue (field); + } + else + { + dbRecordMinute = raddatabaseFieldGetBigIntValue (field); + } + + bknTime.tm_year = dbRecordYear - 1900; + bknTime.tm_mon = dbRecordMonth - 1; + bknTime.tm_mday = dbRecordDay; + bknTime.tm_hour = dbRecordHour; + bknTime.tm_min = dbRecordMinute; + bknTime.tm_sec = 0; + bknTime.tm_isdst = -1; + *retTime = mktime(&bknTime); + raddatabaseReleaseResults (dbdbWork.dbId, resultId); + return OK; + } + + raddatabaseReleaseResults (dbdbWork.dbId, resultId); + return ERROR; + } + } + return ERROR; +} + diff --git a/common/dbdatabase.h b/common/dbdatabase.h new file mode 100755 index 0000000..fcaed31 --- /dev/null +++ b/common/dbdatabase.h @@ -0,0 +1,103 @@ +#ifndef INC_dbdatabaseh +#define INC_dbdatabaseh +/*--------------------------------------------------------------------------- + + FILENAME: + dbdatabase.h + + PURPOSE: + Define the SQL database archiving API. + + REVISION HISTORY: + Date Engineer Revision Remarks + 04/18/2005 M.S. Teel 0 Original + + NOTES: + + + LICENSE: + Copyright (c) 2004, Mark S. Teel (mark@teel.ws) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +// ... includes +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + + +// ... macro definitions + + +// ... typedefs + +typedef struct +{ + char *name; + UINT type; + int maxLength; +} DB_ARCHIVE_DESCRIPTION; + + +typedef struct +{ + DATABASE_ID dbId; + int isEnabled; + int isExtended; + char host[512]; + char username[64]; + char password[64]; + char databaseName[64]; + char tableName[64]; + int isMetricUnits; +} DBDATABASE_WORK; + + + +// ... API prototypes + +extern int dbdatabaseInit +( + int isEnabled, + int useExtended, + char *host, + char *username, + char *password, + char *databaseName, + char *tableName, + int isMetricUnits +); + +extern int dbdatabaseOpen (void); +extern void dbdatabaseClose (void); +extern int dbdatabaseGetDateOfLastEntry (time_t* retTime); + + +// return values for dbdatabaseInsertRecord +#define DBDB_INSERT_OK 0 +#define DBDB_INSERT_ERROR -1 +#define DBDB_INSERT_DUPLICATE 1 + +extern int dbdatabaseInsertRecord (ARCHIVE_PKT* newRecord); + +#endif + + diff --git a/common/dbsqlite.c b/common/dbsqlite.c new file mode 100755 index 0000000..f15176b --- /dev/null +++ b/common/dbsqlite.c @@ -0,0 +1,1210 @@ +//---------------------------------------------------------------------------- +// +// FILENAME: +// dbsqlite.c +// +// PURPOSE: +// Provide the weather station archive database utilities. +// +// REVISION HISTORY: +// Date Engineer Revision Remarks +// 08/31/2008 M.S. Teel 0 Original +// +// NOTES: +// +// +// LICENSE: +// Copyright (c) 2008, Mark S. Teel (mark@teel.ws) +// +// This source code is released for free distribution under the terms +// of the GNU General Public License. +// +//---------------------------------------------------------------------------- + +// ... System include files +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// ... Library include files +#include + +// ... Local include files +#include + +// ... global memory declarations + + +// ... local memory: + +static SQLITE_DATABASE_ID archiveDB = NULL; +static const char* ArchiveValueName[DATA_INDEX_MAX] = +{ + "barometer", + "pressure", + "altimeter", + "inTemp", + "outTemp", + "inHumidity", + "outHumidity", + "windSpeed", + "windDir", + "windGust", + "windGustDir", + "rainRate", + "rain", + "dewpoint", + "windchill", + "heatindex", + "rxCheckPercent", + "ET", + "radiation", + "UV", + "extraTemp1", + "extraTemp2", + "extraTemp3", + "soilTemp1", + "soilTemp2", + "soilTemp3", + "soilTemp4", + "leafTemp1", + "leafTemp2", + "extraHumid1", + "extraHumid2", + "soilMoist1", + "soilMoist2", + "soilMoist3", + "soilMoist4", + "leafWet1", + "leafWet2", + "txBatteryStatus", + "consBatteryVoltage", + "hail", + "hailRate", + "heatingTemp", + "heatingVoltage", + "supplyVoltage", + "referenceVoltage", + "windBatteryStatus", + "rainBatteryStatus", + "outTempBatteryStatus", + "inTempBatteryStatus" +}; + +static char DefaultArchivePath[_MAX_PATH] = { 0 }; + + +static float noConversion (float value) +{ + return value; +} + + +// Define the jump tables for conversion utilities: + +static float (*imperialToMetric_convertors[]) (float value) = +{ + wvutilsConvertINHGToHPA, + wvutilsConvertINHGToHPA, + wvutilsConvertINHGToHPA, + wvutilsConvertFToC, + wvutilsConvertFToC, + noConversion, + noConversion, + wvutilsGetWindSpeed, + noConversion, + wvutilsGetWindSpeed, + noConversion, + wvutilsConvertRainINToMetric, + wvutilsConvertRainINToMetric, + wvutilsConvertFToC, + wvutilsConvertFToC, + wvutilsConvertFToC, + noConversion, + wvutilsConvertRainINToMetric, + noConversion, + noConversion, + wvutilsConvertFToC, + wvutilsConvertFToC, + wvutilsConvertFToC, + wvutilsConvertFToC, + wvutilsConvertFToC, + wvutilsConvertFToC, + wvutilsConvertFToC, + wvutilsConvertFToC, + wvutilsConvertFToC, + noConversion, + noConversion, + noConversion, + noConversion, + noConversion, + noConversion, + noConversion, + noConversion, + noConversion, + noConversion, + wvutilsConvertRainINToMetric, + wvutilsConvertRainINToMetric, + noConversion, + noConversion, + noConversion, + noConversion, + noConversion, + noConversion, + noConversion, + noConversion +}; + +static float (*metricToImperial_convertors[]) (float value) = +{ + wvutilsConvertHPAToINHG, + wvutilsConvertHPAToINHG, + wvutilsConvertHPAToINHG, + wvutilsConvertCToF, + wvutilsConvertCToF, + noConversion, + noConversion, + wvutilsGetWindSpeed, + noConversion, + wvutilsGetWindSpeed, + noConversion, + wvutilsConvertRainMetricToIN, + wvutilsConvertRainMetricToIN, + wvutilsConvertCToF, + wvutilsConvertCToF, + wvutilsConvertCToF, + noConversion, + wvutilsConvertRainMetricToIN, + noConversion, + noConversion, + wvutilsConvertCToF, + wvutilsConvertCToF, + wvutilsConvertCToF, + wvutilsConvertCToF, + wvutilsConvertCToF, + wvutilsConvertCToF, + wvutilsConvertCToF, + wvutilsConvertCToF, + wvutilsConvertCToF, + noConversion, + noConversion, + noConversion, + noConversion, + noConversion, + noConversion, + noConversion, + noConversion, + noConversion, + noConversion, + wvutilsConvertRainMetricToIN, + wvutilsConvertRainMetricToIN, + noConversion, + noConversion, + noConversion, + noConversion, + noConversion, + noConversion, + noConversion, + noConversion +}; + + +// ... ----- static (local) methods ----- + +static const char* getArchiveDBFilename(void) +{ + static char dbFileName[_MAX_PATH]; + + if (strlen(DefaultArchivePath) > 0) + { + sprintf (dbFileName, "%s/%s", DefaultArchivePath, WVIEW_ARCHIVE_DATABASE); + } + else + { + sprintf (dbFileName, "%s/%s", wvutilsGetArchivePath(), WVIEW_ARCHIVE_DATABASE); + } + + return dbFileName; +} + +static int getDBData(SQLITE_DIRECT_ROW row, ARCHIVE_PKT* data) +{ + SQLITE_FIELD_ID field; + Data_Indices index; + + field = radsqlitedirectFieldGet(row, "dateTime"); + if (field == NULL) + { + radMsgLog (PRI_MEDIUM, "dbsqlite: radsqlitedirectFieldGet failed!"); + return ERROR; + } + else + { + data->dateTime = (int32_t)radsqliteFieldGetBigIntValue(field); + } + + field = radsqlitedirectFieldGet(row, "usUnits"); + if (field == NULL) + { + radMsgLog (PRI_MEDIUM, "dbsqlite: radsqlitedirectFieldGet failed!"); + return ERROR; + } + else + { + data->usUnits = (long)radsqliteFieldGetBigIntValue(field); + } + + field = radsqlitedirectFieldGet(row, "interval"); + if (field == NULL) + { + radMsgLog (PRI_MEDIUM, "dbsqlite: radsqlitedirectFieldGet failed!"); + return ERROR; + } + else + { + data->interval = (long)radsqliteFieldGetBigIntValue(field); + } + + for (index = DATA_INDEX_barometer; index < DATA_INDEX_MAX; index ++) + { + field = radsqlitedirectFieldGet(row, ArchiveValueName[index]); + if (field == NULL || FIELD_IS_NULL(field)) + { + data->value[index] = ARCHIVE_VALUE_NULL; + } + else + { + data->value[index] = (float)radsqliteFieldGetDoubleValue(field); + } + } + + return OK; +} + +static int insertDBData(ARCHIVE_PKT* data) +{ + SQLITE_ROW_ID row; + SQLITE_FIELD_ID field; + Data_Indices index; + + if (archiveDB == NULL) + { + radMsgLog (PRI_HIGH, "dbsqlite: failed to open %s!", getArchiveDBFilename()); + return ERROR; + } + + row = radsqliteTableDescriptionGet (archiveDB, "archive"); + if (row == NULL) + { + printf ("dbsqlite: databaseTableDescriptionGet failed!"); + return ERROR; + } + + field = radsqliteFieldGet(row, "dateTime"); + if (field == NULL) + { + radMsgLog (PRI_MEDIUM, "dbsqlite: radsqliteFieldGet failed!"); + radsqliteRowDescriptionDelete(row); + return ERROR; + } + else + { + radsqliteFieldSetBigIntValue(field, (uint64_t)data->dateTime); + } + + field = radsqliteFieldGet(row, "usUnits"); + if (field == NULL) + { + radMsgLog (PRI_MEDIUM, "dbsqlite: radsqliteFieldGet failed!"); + radsqliteRowDescriptionDelete(row); + return ERROR; + } + else + { + radsqliteFieldSetBigIntValue(field, (uint64_t)data->usUnits); + } + + field = radsqliteFieldGet(row, "interval"); + if (field == NULL) + { + radMsgLog (PRI_MEDIUM, "dbsqlite: radsqliteFieldGet failed!"); + radsqliteRowDescriptionDelete(row); + return ERROR; + } + else + { + radsqliteFieldSetBigIntValue(field, (uint64_t)data->interval); + } + + for (index = DATA_INDEX_barometer; index < DATA_INDEX_MAX; index ++) + { + field = radsqliteFieldGet(row, ArchiveValueName[index]); + if (field == NULL) + { + radMsgLog (PRI_MEDIUM, "dbsqlite: radsqliteFieldGet %s failed!", ArchiveValueName[index]); + radsqliteRowDescriptionDelete(row); + return ERROR; + } + else + { + if (data->value[index] <= ARCHIVE_VALUE_NULL) + { + radsqliteFieldSetToNull(field); + } + else + { + radsqliteFieldSetDoubleValue(field, (double)data->value[index]); + } + } + } + + // insert the row: + if (radsqliteTableInsertRow(archiveDB, "archive", row) == ERROR) + { + radMsgLog (PRI_HIGH, "dbsqlite: radsqliteTableInsertRow failed!"); + radsqliteRowDescriptionDelete(row); + return ERROR; + } + + radsqliteRowDescriptionDelete(row); + return OK; +} + +static int lastWDIR; +#if defined(BUILD_HTMLGEND) || defined(BUILD_WVIEWD) + +// return num minutes processed or error +static int rollIntoAverages +( + int isMetricUnits, + WAVG_ID windId, + time_t startTime, + HISTORY_DATA* store, + int numMins +) +{ + char query[DB_SQLITE_QUERY_LENGTH_MAX]; + int recordIsUSUnits, mins = 0; + float value; + time_t endTime = startTime + (numMins * 60); + SQLITE_DIRECT_ROW rowDescr; + SQLITE_FIELD_ID field; + Data_Indices index; + + if (archiveDB == NULL) + { + radMsgLog (PRI_HIGH, "rollIntoAverages: failed to open %s!", getArchiveDBFilename()); + return ERROR; + } + + // Build the query: + sprintf (query, "SELECT * FROM archive WHERE dateTime >= '%d' AND dateTime < '%d' ORDER BY dateTime ASC", + (int)startTime, (int)endTime); + + // Execute the query: + if (radsqlitedirectQuery(archiveDB, query, TRUE) == ERROR) + { + return ERROR; + } + + for (rowDescr = radsqlitedirectGetRow(archiveDB); + rowDescr != NULL; + rowDescr = radsqlitedirectGetRow(archiveDB)) + { + field = radsqlitedirectFieldGet(rowDescr, "interval"); + if (field == NULL) + { + radMsgLog (PRI_MEDIUM, "rollIntoAverages: radsqlitedirectFieldGet failed!"); + radsqlitedirectReleaseResults(archiveDB); + return ERROR; + } + else + { + mins += (int)radsqliteFieldGetBigIntValue(field); + } + + field = radsqlitedirectFieldGet(rowDescr, "usUnits"); + if (field == NULL) + { + radMsgLog (PRI_MEDIUM, "rollIntoAverages: radsqlitedirectFieldGet failed!"); + radsqlitedirectReleaseResults(archiveDB); + return ERROR; + } + else + { + recordIsUSUnits = (int)radsqliteFieldGetBigIntValue(field); + } + + for (index = DATA_INDEX_barometer; index < DATA_INDEX_MAX; index ++) + { + field = radsqlitedirectFieldGet(rowDescr, ArchiveValueName[index]); + if (field == NULL) + { + radMsgLog (PRI_MEDIUM, "rollIntoAverages: radsqlitedirectFieldGet %s failed!", ArchiveValueName[index]); + radsqlitedirectReleaseResults(archiveDB); + return ERROR; + } + else + { + if (! FIELD_IS_NULL(field)) + { + value = (float)radsqliteFieldGetDoubleValue(field); + + // Check for wview NULL value: + if (value > ARCHIVE_VALUE_NULL) + { + store->samples[index] += 1; + + // Handle WIND separately: + if (index == DATA_INDEX_windSpeed || index == DATA_INDEX_windGust) + { + if (recordIsUSUnits) + { + store->values[index] += wvutilsGetWindSpeed(value); + } + else + { + store->values[index] += wvutilsGetWindSpeedMetric(value); + } + } + else + { + if (isMetricUnits & recordIsUSUnits) + { + store->values[index] += (*imperialToMetric_convertors[index])(value); + } + else if (! isMetricUnits & ! recordIsUSUnits) + { + store->values[index] += (*metricToImperial_convertors[index])(value); + } + else + { + store->values[index] += value; + } + } + } + } + } + } + + field = radsqlitedirectFieldGet(rowDescr, ArchiveValueName[DATA_INDEX_windDir]); + if (field == NULL) + { + radMsgLog (PRI_MEDIUM, "rollIntoAverages: radsqlitedirectFieldGet failed!"); + radsqlitedirectReleaseResults(archiveDB); + return ERROR; + } + else if (! FIELD_IS_NULL(field)) + { + value = (float)radsqliteFieldGetDoubleValue(field); + if (value >= 0 && value < 360) + { + lastWDIR = (int)value; + } + windAverageAddValue (windId, lastWDIR); + } + } + + radsqlitedirectReleaseResults(archiveDB); + + + // Finally, check to be sure values were found, if not, set to ARCHIVE_VALUE_NULL: + for (index = DATA_INDEX_barometer; index < DATA_INDEX_MAX; index ++) + { + if (store->samples[index] == 0) + { + // set value to NULL: + store->values[index] = ARCHIVE_VALUE_NULL; + } + } + + return mins; +} +#endif + +static time_t getNewestDateTime (ARCHIVE_PKT *newRec) +{ + char query[DB_SQLITE_QUERY_LENGTH_MAX]; + SQLITE_DIRECT_ROW rowDescr; + SQLITE_FIELD_ID field; + time_t retVal; + + if (archiveDB == NULL) + { + radMsgLog (PRI_HIGH, "getNewestDateTime: failed to open %s!", getArchiveDBFilename()); + return ERROR; + } + + // Build the query: + sprintf (query, "SELECT MAX(dateTime) AS 'max' FROM archive"); + + // Execute the query: + if (radsqlitedirectQuery(archiveDB, query, TRUE) == ERROR) + { + radMsgLog (PRI_HIGH, "getNewestDateTime: radsqlitedirectQuery failed!"); + return ERROR; + } + + rowDescr = radsqlitedirectGetRow(archiveDB); + if (rowDescr == NULL) + { + radMsgLog (PRI_MEDIUM, + "getNewestDateTime: radsqlitedirectGetRow failed!"); + radsqlitedirectReleaseResults(archiveDB); + return ERROR; + } + + field = radsqlitedirectFieldGet(rowDescr, "max"); + if (field == NULL) + { + radMsgLog (PRI_MEDIUM, + "getNewestDateTime: radsqlitedirectFieldGet failed!"); + radsqlitedirectReleaseResults(archiveDB); + return ERROR; + } + + retVal = (time_t)radsqliteFieldGetBigIntValue(field); + + // Clean up: + radsqlitedirectReleaseResults(archiveDB); + + // Now grab the entire row: + sprintf (query, "SELECT * FROM archive WHERE dateTime = '%d'", (int)retVal); + + // Execute the query: + if (radsqlitedirectQuery(archiveDB, query, TRUE) == ERROR) + { + radMsgLog (PRI_HIGH, "getNewestDateTime: radsqlitedirectQuery failed!"); + return ERROR; + } + + rowDescr = radsqlitedirectGetRow(archiveDB); + if (rowDescr == NULL) + { + radMsgLog (PRI_MEDIUM, + "getNewestDateTime: radsqlitedirectGetRow failed!"); + radsqlitedirectReleaseResults(archiveDB); + return ERROR; + } + + // finally copy it to the internal PKT: + getDBData(rowDescr, newRec); + + radsqlitedirectReleaseResults(archiveDB); + return retVal; +} + +static int getNextRecord (time_t dateTime, ARCHIVE_PKT* newRec) +{ + char query[DB_SQLITE_QUERY_LENGTH_MAX]; + SQLITE_DIRECT_ROW rowDescr; + SQLITE_FIELD_ID field; + time_t retVal; + + if (archiveDB == NULL) + { + radMsgLog (PRI_HIGH, "getNextRecord: failed to open %s!", getArchiveDBFilename()); + return ERROR; + } + + // Build the query: + sprintf (query, "SELECT MIN(dateTime) AS 'min' FROM archive WHERE dateTime > '%d'", + (int)dateTime); + + // Execute the query: + if (radsqlitedirectQuery(archiveDB, query, TRUE) == ERROR) + { + return ERROR; + } + + rowDescr = radsqlitedirectGetRow(archiveDB); + if (rowDescr == NULL) + { + radsqlitedirectReleaseResults(archiveDB); + return ERROR; + } + + field = radsqlitedirectFieldGet(rowDescr, "min"); + if (field == NULL) + { + radMsgLog (PRI_MEDIUM, + "getNextRecord: radsqlitedirectFieldGet failed!"); + radsqlitedirectReleaseResults(archiveDB); + return ERROR; + } + + retVal = (time_t)radsqliteFieldGetBigIntValue(field); + + // Clean up: + radsqlitedirectReleaseResults(archiveDB); + + if ((int)retVal == 0) + { + return ERROR; + } + + // Now grab the entire row: + sprintf (query, "SELECT * FROM archive WHERE dateTime = '%d'", (int)retVal); + + // Execute the query: + if (radsqlitedirectQuery(archiveDB, query, TRUE) == ERROR) + { + return ERROR; + } + + rowDescr = radsqlitedirectGetRow(archiveDB); + if (rowDescr == NULL) + { + radsqlitedirectReleaseResults(archiveDB); + return ERROR; + } + + // finally copy it to the internal PKT: + getDBData(rowDescr, newRec); + + radsqlitedirectReleaseResults(archiveDB); + return retVal; +} + +static int getRecord (time_t dateTime, ARCHIVE_PKT* newRec) +{ + char query[DB_SQLITE_QUERY_LENGTH_MAX]; + SQLITE_DIRECT_ROW rowDescr; + + if (archiveDB == NULL) + { + radMsgLog (PRI_HIGH, "getRecord: failed to open %s!", getArchiveDBFilename()); + return ERROR; + } + + // grab the entire row: + sprintf (query, "SELECT * FROM archive WHERE dateTime = '%d'", (int)dateTime); + + // Execute the query: + if (radsqlitedirectQuery(archiveDB, query, TRUE) == ERROR) + { + return ERROR; + } + + rowDescr = radsqlitedirectGetRow(archiveDB); + if (rowDescr == NULL) + { + radsqlitedirectReleaseResults(archiveDB); + return ERROR; + } + + // finally copy it to the internal PKT: + getDBData(rowDescr, newRec); + + radsqlitedirectReleaseResults(archiveDB); + return OK; +} + +static int getFirstRecord(time_t startTime, time_t stopTime, ARCHIVE_PKT* recordStore) +{ + char query[DB_SQLITE_QUERY_LENGTH_MAX]; + SQLITE_DIRECT_ROW rowDescr; + + if (archiveDB == NULL) + { + radMsgLog (PRI_HIGH, "getFirstRecord: failed to open %s!", getArchiveDBFilename()); + return ERROR; + } + + // grab the entire row: + sprintf (query, "SELECT * FROM archive WHERE dateTime BETWEEN '%d' AND '%d' ORDER BY dateTime ASC", + (int)startTime, (int)stopTime); + + // Execute the query: + if (radsqlitedirectQuery(archiveDB, query, TRUE) == ERROR) + { + return ERROR; + } + + rowDescr = radsqlitedirectGetRow(archiveDB); + if (rowDescr == NULL) + { + radsqlitedirectReleaseResults(archiveDB); + return ERROR; + } + + // finally copy it to the internal PKT: + getDBData(rowDescr, recordStore); + + radsqlitedirectReleaseResults(archiveDB); + return OK; +} + +static int getCount (char* whereClause) +{ + char query[DB_SQLITE_QUERY_LENGTH_MAX]; + SQLITE_DIRECT_ROW rowDescr; + SQLITE_FIELD_ID field; + int retVal; + + if (archiveDB == NULL) + { + radMsgLog (PRI_HIGH, "getCount: failed to open %s!", getArchiveDBFilename()); + return ERROR; + } + + // Build the query: + sprintf (query, "SELECT COUNT(*) AS 'number' FROM archive where %s", + whereClause); + + // Execute the query: + if (radsqlitedirectQuery(archiveDB, query, TRUE) == ERROR) + { + radMsgLog (PRI_HIGH, "getCount: radsqlitedirectQuery failed!"); + return ERROR; + } + + rowDescr = radsqlitedirectGetRow(archiveDB); + if (rowDescr == NULL) + { + radMsgLog (PRI_MEDIUM, + "getCount: radsqlitedirectGetRow failed!"); + radsqlitedirectReleaseResults(archiveDB); + return ERROR; + } + + field = radsqlitedirectFieldGet(rowDescr, "number"); + if (field == NULL) + { + radMsgLog (PRI_MEDIUM, + "getCount: radsqlitedirectFieldGet failed!"); + radsqlitedirectReleaseResults(archiveDB); + return ERROR; + } + + retVal = (int)radsqliteFieldGetBigIntValue(field); + + // Clean up: + radsqlitedirectReleaseResults(archiveDB); + + return retVal; +} + + +// ##################### API Functions ##################### + +// Initialize the database interface (returns OK or ERROR): +int dbsqliteArchiveInit(void) +{ + archiveDB = radsqliteOpen (getArchiveDBFilename()); + if (archiveDB == NULL) + { + radMsgLog (PRI_HIGH, "dbsqliteArchiveInit: failed to open %s!", getArchiveDBFilename()); + return ERROR; + } + + return OK; +} + +// Clean up the database interface: +void dbsqliteArchiveExit(void) +{ + if (archiveDB) + radsqliteClose(archiveDB); +} + + +// PRAGMA statement to modify the operation of the SQLite library +int dbsqliteArchivePragmaSet(char *pragma, char *setting) +{ + char query[DB_SQLITE_QUERY_LENGTH_MAX]; + + // Check SQLite version if a journalling pragma: + if (!strcmp(pragma, "journal_mode")) + { + if (SQLITE_VERSION_NUMBER < 3005009) + { + // Not supported: + return OK; + } + } + + sprintf (query, "PRAGMA %s = %s", pragma, setting); + + // Execute the query: + if (radsqliteQuery(archiveDB, query, FALSE) == ERROR) + { + return ERROR; + } + + return OK; +} + +// ... Reset the default archive database location (used by conversion utils); +void dbsqliteArchiveSetPath (char* newPath) +{ + wvstrncpy(DefaultArchivePath, newPath, _MAX_PATH); +} + +char* dbsqliteArchiveGetPath (void) +{ + return DefaultArchivePath; +} + +#if defined(BUILD_HTMLGEND) || defined(BUILD_WVIEWD) + +// ... calculate averages over a given period of time +// ... (given in arcInterval minute samples); +// ... this will zero out the HISTORY_DATA store before beginning +// ... the processing; +// ... returns OK or ERROR + +int dbsqliteArchiveGetAverages +( + int isMetricUnits, + int arcInterval, + HISTORY_DATA *store, + time_t startTime, + int numSamples +) +{ + int retVal; + int numMins = numSamples * arcInterval; + WAVG windAvg; + + memset (store, 0, sizeof (HISTORY_DATA)); + store->startTime = startTime; + + // create the wind average object + windAverageReset (&windAvg); + + retVal = rollIntoAverages(isMetricUnits, + &windAvg, + startTime, + store, + numMins); + + if (retVal > 0) + { + store->values[DATA_INDEX_windDir] = windAverageCompute (&windAvg); + } + + return retVal; +} +#endif + +#if defined(BUILD_HTMLGEND) + +// write out all ASCII archive records for the given day to 'filename' +int dbsqliteWriteDailyArchiveReport +( + char *filename, + time_t timeval, + int isMetric, + int arcInterval, + void (*writeHeader) (FILE *file) +) +{ + struct tm locTime; + time_t startTime, stopTime; + struct stat fileStatus; + char query[DB_SQLITE_QUERY_LENGTH_MAX]; + SQLITE_DIRECT_ROW rowDescr; + ARCHIVE_PKT arcRecord; + int numrecs = 0; + + localtime_r (&timeval, &locTime); + locTime.tm_hour = 0; + locTime.tm_min = 0; + locTime.tm_sec = 0; + locTime.tm_isdst = -1; + startTime = (time_t)mktime(&locTime); + stopTime = startTime + WV_SECONDS_IN_DAY; + + if (stat (filename, &fileStatus) != -1) + { + // exists, delete it + unlink (filename); + } + + if (archiveDB == NULL) + { + radMsgLog (PRI_HIGH, "dbsqliteWriteDailyArchiveReport: failed to open %s!", getArchiveDBFilename()); + return ERROR; + } + + // grab the rows: + sprintf (query, "SELECT * FROM archive WHERE dateTime >= '%d' AND dateTime < '%d' ORDER BY dateTime ASC", + (int)startTime, (int)stopTime); + + // Execute the query: + if (radsqlitedirectQuery(archiveDB, query, TRUE) == ERROR) + { + return ERROR; + } + + for (rowDescr = radsqlitedirectGetRow(archiveDB); + rowDescr != NULL; + rowDescr = radsqlitedirectGetRow(archiveDB)) + { + // copy it to the internal PKT: + if (getDBData(rowDescr, &arcRecord) == ERROR) + { + radMsgLog(PRI_MEDIUM, "dbsqliteWriteDailyArchiveReport: getDBData failed!"); + continue; + } + + // call our little updater + dbsqliteUpdateDailyArchiveReport(filename, &arcRecord, writeHeader, isMetric); + + numrecs ++; + } + + radsqlitedirectReleaseResults(archiveDB); + + if (numrecs > 0) + { + return OK; + } + else + { + return ERROR; + } +} + +// update (or create) the current day's ASCII archive records file +int dbsqliteUpdateDailyArchiveReport +( + char *file, + ARCHIVE_PKT *data, + void (*writeHeaderFcn) (FILE *file), + int isMetric +) + +{ + FILE *outfile; + char temp[256]; + struct stat fileStatus; + int writeHeader = FALSE; + + if (stat (file, &fileStatus) == -1) + { + // new file, write a header + writeHeader = TRUE; + } + + // first, take care of creating/opening the file + outfile = fopen (file, "a"); + if (outfile == NULL) + { + return ERROR; + } + + if (writeHeader) + { + // callback the user with the supplied routine + (*writeHeaderFcn)(outfile); + } + + // append the new record + + if (!isMetric) + { + sprintf (temp, + "%4.4d%2.2d%2.2d %2.2d:%2.2d\t%.1f\t%.1f\t%.1f\t%.0f\t%.1f\t%.0f\t%.0f\t%.0f\t%.2f\t%.3f\t%.0f\t%.3f\t%.1f\n", + wvutilsGetYear(data->dateTime), + wvutilsGetMonth(data->dateTime), + wvutilsGetDay(data->dateTime), + wvutilsGetHour(data->dateTime), + wvutilsGetMin(data->dateTime), + data->value[DATA_INDEX_outTemp], + data->value[DATA_INDEX_windchill], + data->value[DATA_INDEX_heatindex], + data->value[DATA_INDEX_outHumidity], + data->value[DATA_INDEX_dewpoint], + data->value[DATA_INDEX_windSpeed], + data->value[DATA_INDEX_windGust], + data->value[DATA_INDEX_windDir], + data->value[DATA_INDEX_rain], + data->value[DATA_INDEX_barometer], + data->value[DATA_INDEX_radiation], + data->value[DATA_INDEX_ET], + data->value[DATA_INDEX_UV]); + } + else + { + // "--Timestamp---\tTemp\tChill\tHIndex\tHumid\tDewpt\tWind\tHiWind\tWindDir\tRain\tBarom\tSolar\tET\tUV\n" + sprintf (temp, + "%4.4d%2.2d%2.2d %2.2d:%2.2d\t%.1f\t%.1f\t%.1f\t%.0f\t%.1f\t%.0f\t%.0f\t%.0f\t%.1f\t%.1f\t%.0f\t%.3f\t%.1f\n", + wvutilsGetYear(data->dateTime), + wvutilsGetMonth(data->dateTime), + wvutilsGetDay(data->dateTime), + wvutilsGetHour(data->dateTime), + wvutilsGetMin(data->dateTime), + wvutilsConvertFToC(data->value[DATA_INDEX_outTemp]), + wvutilsConvertFToC(data->value[DATA_INDEX_windchill]), + wvutilsConvertFToC(data->value[DATA_INDEX_heatindex]), + data->value[DATA_INDEX_outHumidity], + wvutilsConvertFToC(data->value[DATA_INDEX_dewpoint]), + wvutilsConvertMPHToKPH(data->value[DATA_INDEX_windSpeed]), + wvutilsConvertMPHToKPH(data->value[DATA_INDEX_windGust]), + data->value[DATA_INDEX_windDir], + wvutilsConvertRainINToMetric(data->value[DATA_INDEX_rain]), + wvutilsConvertINHGToHPA(data->value[DATA_INDEX_barometer]), + data->value[DATA_INDEX_radiation], + wvutilsConvertRainINToMetric(data->value[DATA_INDEX_ET]), + data->value[DATA_INDEX_UV]); + } + + if (fwrite (temp, 1, strlen(temp), outfile) != strlen(temp)) + { + fclose (outfile); + return ERROR; + } + + + // cleanup and bail out + fclose (outfile); + + if (writeHeader) + return 1; + else + return OK; +} +#endif + + +// ... append an archive record to the archive database; +// ... returns OK or ERROR + +int dbsqliteArchiveStoreRecord (ARCHIVE_PKT* record) +{ + char fileName[128]; + + insertDBData(record); + sprintf (fileName, "%s/export/%s", wvutilsGetConfigPath(), WVIEW_ARCHIVE_MARKER_FILE); + wvutilsWriteMarkerFile(fileName, record->dateTime); + return OK; +} + + +// ... search the archive path for the most recent archive record date; +// ... returns OK or ERROR if no archives found + +time_t dbsqliteArchiveGetNewestTime (ARCHIVE_PKT* newestRecord) +{ + time_t retVal; + + retVal = getNewestDateTime(newestRecord); + return retVal; +} + + +// ... search the archive database for the next archive record after 'dateTime'; +// ... returns time of next record or ERROR if no archives found + +time_t dbsqliteArchiveGetNextRecord (time_t dateTime, ARCHIVE_PKT* recordStore) +{ + time_t retVal; + + retVal = getNextRecord(dateTime, recordStore); + return retVal; +} + + +// ... search the archive database for the archive record with timestamp 'dateTime'; +// ... returns OK or ERROR if no archive record found + +int dbsqliteArchiveGetRecord (time_t dateTime, ARCHIVE_PKT* recordStore) +{ + int retVal; + + retVal = getRecord(dateTime, recordStore); + return retVal; +} + +// ... search the archive database for the first archive record in the given +// ... timestamp range; +// ... returns OK or ERROR if no archive record found + +int dbsqliteArchiveGetFirstRecord +( + time_t startTime, + time_t stopTime, + ARCHIVE_PKT* recordStore +) +{ + int retVal; + + retVal = getFirstRecord(startTime, stopTime, recordStore); + return retVal; +} + +int dbsqliteArchiveExecutePerRecord +( + void (*function)(ARCHIVE_PKT* rec, void* data), + void* userData, + time_t startTime, + time_t stopTime, + char* selectClause +) +{ + char query[DB_SQLITE_QUERY_LENGTH_MAX]; + SQLITE_DIRECT_ROW rowDescr; + ARCHIVE_PKT rowData; + int numrecs = 0; + + if (archiveDB == NULL) + { + radMsgLog (PRI_HIGH, "dbsqliteArchiveExecutePerRecord: failed to open %s!", getArchiveDBFilename()); + return ERROR; + } + + if (selectClause) + { + sprintf (query, "SELECT dateTime,usUnits,interval,%s FROM archive WHERE dateTime >= '%d' AND dateTime < '%d' ORDER BY dateTime ASC", + selectClause, (int)startTime, (int)stopTime); + } + else + { + // grab the entire row: + sprintf (query, "SELECT * FROM archive WHERE dateTime >= '%d' AND dateTime < '%d' ORDER BY dateTime ASC", + (int)startTime, (int)stopTime); + } + + // Execute the query: + if (radsqlitedirectQuery(archiveDB, query, TRUE) == ERROR) + { + return ERROR; + } + + for (rowDescr = radsqlitedirectGetRow(archiveDB); + rowDescr != NULL; + rowDescr = radsqlitedirectGetRow(archiveDB)) + { + if (getDBData(rowDescr, &rowData) == ERROR) + { + radMsgLog (PRI_HIGH, "dbsqliteArchiveExecutePerRecord: getDBData failed!"); + radsqlitedirectReleaseResults(archiveDB); + return ERROR; + } + + numrecs ++; + + // Call the supplied function: + (*function)(&rowData, userData); + } + + radsqlitedirectReleaseResults(archiveDB); + return numrecs; +} + +int dbsqliteArchiveGetCount(char* whereClause) +{ + return (getCount(whereClause)); +} + diff --git a/common/dbsqlite.h b/common/dbsqlite.h new file mode 100755 index 0000000..a1dc33c --- /dev/null +++ b/common/dbsqlite.h @@ -0,0 +1,287 @@ +#ifndef INC_dbsqliteh +#define INC_dbsqliteh +/*--------------------------------------------------------------------------- + + FILENAME: + dbsqlite.h + + PURPOSE: + Provide the wview daemon archive database definitions. + + REVISION HISTORY: + Date Engineer Revision Remarks + 08/31/2008 M.S. Teel 0 Original + + NOTES: + + + LICENSE: + Copyright (c) 2008, Mark S. Teel (mark@teel.ws) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +/* ... System include files +*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* ... Library include files +*/ +#include + +/* ... Local include files +*/ +#include +#include +#include + + +#define FIELD_IS_NULL(x) (((x->type & SQLITE_FIELD_VALUE_IS_NULL) == 0) ? FALSE : TRUE) + + +/* ... API definitions +*/ + +/* !!!!!!!!!!!!!!!!!! HIDDEN, NOT FOR API USE !!!!!!!!!!!!!!!!!! +*/ + + + +/* !!!!!!!!!!!!!!!!!!!! END HIDDEN SECTION !!!!!!!!!!!!!!!!!!!!! +*/ + + +/* ... API methods +*/ + +// ---------------------- History Computation --------------------- + +/* ... calculate averages over a given period of time + ... (given in arcInterval samples); + ... this will zero out the HISTORY_DATA store before beginning + ... the processing; + ... returns number of minutes found or ERROR +*/ +extern int dbsqliteArchiveGetAverages +( + int isMetricUnits, + int arcInterval, + HISTORY_DATA *store, + time_t startTime, + int numSamples +); + + +// --------------------- ARCHIVE Day History ---------------------- + +extern int dbsqliteWriteDailyArchiveReport +( + char *filename, + time_t timeval, + int isMetric, + int arcInterval, + void (*writeHeader) (FILE *file) +); + +extern int dbsqliteUpdateDailyArchiveReport +( + char *file, + ARCHIVE_PKT *data, + void (*writeHeader) (FILE *file), + int isMetric +); + + +// ----------------------- Archive Database ----------------------- + +// Note: wview processes now open the database session at init and do not close +// until the process is exiting... + +// Initialize the database interface (returns OK or ERROR): +extern int dbsqliteArchiveInit(void); + +// Clean up the database interface: +extern void dbsqliteArchiveExit(void); + +// set a PRAGMA to modify the operation of the SQLite library: +// Returns: OK or ERROR +extern int dbsqliteArchivePragmaSet(char* pragma, char* setting); + +// ... Reset the default archive database location (used by conversion utils); +extern void dbsqliteArchiveSetPath (char* newPath); +extern char* dbsqliteArchiveGetPath (void); + +// ... append an archive record to the archive database; +// ... returns OK or ERROR +extern int dbsqliteArchiveStoreRecord (ARCHIVE_PKT* record); + +// ... search the archive database for the most recent archive record date; +// ... returns time or ERROR if no archives found +extern time_t dbsqliteArchiveGetNewestTime (ARCHIVE_PKT* newestRecord); + +// ... search the archive database for the next archive record after 'dateTime'; +// ... returns new time or ERROR if no archive found +extern time_t dbsqliteArchiveGetNextRecord (time_t dateTime, ARCHIVE_PKT* recordStore); + +// ... search the archive database for the archive record with timestamp 'dateTime'; +// ... returns OK or ERROR if no archive record found + +extern int dbsqliteArchiveGetRecord (time_t dateTime, ARCHIVE_PKT* recordStore); + +// ... search the archive database for the first archive record in the given +// ... timestamp range; +// ... returns OK or ERROR if no archive record found + +extern int dbsqliteArchiveGetFirstRecord +( + time_t startTime, + time_t stopTime, + ARCHIVE_PKT* recordStore +); + +// ... execute the given function for each record in the given timestamp range; +// ... 'selectClause' if not NULL can contain specific columns of interest +// ... (dateTime,usUnits,interval are always included); +// ... returns the number of records found or ERROR + +extern int dbsqliteArchiveExecutePerRecord +( + void (*function)(ARCHIVE_PKT* rec, void* data), + void* userData, + time_t startTime, + time_t stopTime, + char* selectClause +); + +// ... Retrieve the number of records matching the given "where" clause; +// ... Returns the count or ERROR + +extern int dbsqliteArchiveGetCount(char* whereClause); + + + +// ------------------------ HILOW Database ------------------------ +// Initialize the HILOW database: +// IF HILOW tables are to be synced to the archive database, 'update' +// should be TRUE; +// returns OK or ERROR +extern int dbsqliteHiLowInit(int update); + +extern void dbsqliteHiLowExit(void); + +// set a PRAGMA to modify the operation of the SQLite library: +// Returns: OK or ERROR +extern int dbsqliteHiLowPragmaSet(char* pragma, char* setting); + + +// ... Update sensors for the given hour and time frame: +// ... Returns number of records processed or ERROR +extern int dbsqliteHiLowGetHour +( + time_t hour, + SENSOR_STORE* sensors, + SENSOR_TIMEFRAMES timeFrame +); + +// ... Update sensors for the given day and time frame: +// ... Returns number of records processed or ERROR +extern int dbsqliteHiLowGetDay +( + time_t day, + SENSOR_STORE* sensors, + SENSOR_TIMEFRAMES timeFrame +); + +// ... Update sensors for the given month and time frame: +// ... Returns number of records processed or ERROR +extern int dbsqliteHiLowGetMonth +( + time_t month, + SENSOR_STORE* sensors, + SENSOR_TIMEFRAMES timeFrame, + int yearRainFlag +); + +// ... Update database with a timestamped LOOP sample: +// ... No duplicate checking is done; +// ... Returns OK or ERROR +extern int dbsqliteHiLowStoreSample +( + time_t timestamp, + LOOP_PKT* sample +); + +// ... Update database with an archive record in lieu of LOOP samples: +// ... No duplicate checking is done; +// ... Returns OK or ERROR +extern int dbsqliteHiLowStoreArchive +( + ARCHIVE_PKT* record +); + +// ... Update database with an archive record, ignoring cumulative values: +// ... No duplicate checking is done; +// ... Returns OK or ERROR +extern int dbsqliteHiLowUpdateArchive (ARCHIVE_PKT* record); + +// ... Retrieve the last HILOW hour in the database; +// ... Returns time or ERROR +extern time_t dbsqliteHiLowGetLastUpdate(void); + + + +#ifdef BUILD_HTMLGEND +// ----------------------- History Database ----------------------- + +// Initialize the day history table: +extern void dbsqliteHistoryInit (void); + +// Insert a day record in the history table: +// Returns OK or ERROR +extern int dbsqliteHistoryInsertDay(HISTORY_DATA* data); + +// Retrieve a day from the history table: +// Returns OK or ERROR if not found +extern int dbsqliteHistoryGetDay(time_t date, HISTORY_DATA* store); + + +// ------------------------- NOAA Database ------------------------ + +extern int dbsqliteNOAAInit(void); +extern void dbsqliteNOAAExit(void); + +/* ... dbsqliteNOAAGetDay: summarize day records for NOAA reports; + ... this will zero out the NOAA_DAY_REC store before beginning; + ... returns OK or ERROR if day not found in archives +*/ +extern int dbsqliteNOAAGetDay(NOAA_DAY_REC *store, time_t day); + +/* ... dbsqliteNOAAComputeNorms: compute monthly and yearly norms +*/ +extern int dbsqliteNOAAComputeNorms +( + float *temps, + float *rains, + float *yearTemp, + float *yearRain +); + +/* ... dbsqliteNOAAUpdate: bring the NOAA database up-to date: +*/ +extern void dbsqliteNOAAUpdate(void); + +#endif + +#endif + diff --git a/common/dbsqliteHiLow.c b/common/dbsqliteHiLow.c new file mode 100755 index 0000000..4123bcb --- /dev/null +++ b/common/dbsqliteHiLow.c @@ -0,0 +1,1662 @@ +//---------------------------------------------------------------------------- +// +// FILENAME: +// dbsqliteHiLow.c +// +// PURPOSE: +// Provide the weather station HILOW database utilities. +// +// REVISION HISTORY: +// Date Engineer Revision Remarks +// 08/31/2008 M.S. Teel 0 Original +// +// NOTES: +// +// +// LICENSE: +// Copyright (c) 2008, Mark S. Teel (mark@teel.ws) +// +// This source code is released for free distribution under the terms +// of the GNU General Public License. +// +//---------------------------------------------------------------------------- + +// ... System include files +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// ... Library include files +#include + +// ... Local include files +#include + + +// ... local memory: + +static SQLITE_DATABASE_ID hilowDB = NULL; + +static char* sensorTables[SENSOR_MAX] = +{ + "inTemp", + "outTemp", + "inHumidity", + "outHumidity", + "baromPressure", + "windSpeed", + "windGust", + "dewPoint", + "rain", + "rainRate", + "windChill", + "heatIndex", + "ET", + "UV", + "solarRadiation", + "hail", + "hailRate" +}; + + +// ... ----- static (local) methods ----- + +static const char* hilowGetDBFilename(void) +{ + static char dbHiLowFileName[_MAX_PATH]; + + if (strlen(dbsqliteArchiveGetPath()) > 0) + { + sprintf (dbHiLowFileName, "%s/%s", dbsqliteArchiveGetPath(), WVIEW_HILOW_DATABASE); + } + else + { + sprintf (dbHiLowFileName, "%s/%s", wvutilsGetArchivePath(), WVIEW_HILOW_DATABASE); + } + + return dbHiLowFileName; +} + +static int hilowExtractValues (SQLITE_DIRECT_ROW row, WV_SENSOR* newRec) +{ + SQLITE_FIELD_ID field; + + field = radsqlitedirectFieldGet(row, "low"); + if (field == NULL) + { + return ERROR; + } + else + { + newRec->low = (float)radsqliteFieldGetDoubleValue(field); + } + + field = radsqlitedirectFieldGet(row, "timeLow"); + if (field == NULL) + { + return ERROR; + } + else + { + newRec->time_low = (time_t)radsqliteFieldGetBigIntValue(field); + } + + field = radsqlitedirectFieldGet(row, "high"); + if (field == NULL) + { + return ERROR; + } + else + { + newRec->high = (float)radsqliteFieldGetDoubleValue(field); + } + + field = radsqlitedirectFieldGet(row, "timeHigh"); + if (field == NULL) + { + return ERROR; + } + else + { + newRec->time_high = (time_t)radsqliteFieldGetBigIntValue(field); + } + + field = radsqlitedirectFieldGet(row, "whenHigh"); + if (field == NULL) + { + return ERROR; + } + else + { + newRec->when_high = (float)radsqliteFieldGetDoubleValue(field); + } + + field = radsqlitedirectFieldGet(row, "cumulative"); + if (field == NULL) + { + return ERROR; + } + else + { + newRec->cumulative = (float)radsqliteFieldGetDoubleValue(field); + } + + field = radsqlitedirectFieldGet(row, "samples"); + if (field == NULL) + { + return ERROR; + } + else + { + newRec->samples = (int)radsqliteFieldGetBigIntValue(field); + } + + return OK; +} + +static int hilowGetRecord (time_t dateTime, SENSOR_TYPES type, WV_SENSOR* newRec) +{ + char query[DB_SQLITE_QUERY_LENGTH_MAX]; + SQLITE_DIRECT_ROW row; + + if (hilowDB == NULL) + { + radMsgLog (PRI_HIGH, "hilowGetRecord: failed to open %s!", hilowGetDBFilename()); + return ERROR; + } + + // grab the entire row: + sprintf (query, "SELECT * FROM %s WHERE dateTime = '%d'", + sensorTables[type], (int)dateTime); + + // Execute the query: + if (radsqlitedirectQuery(hilowDB, query, TRUE) == ERROR) + { + return ERROR; + } + + row = radsqlitedirectGetRow(hilowDB); + if (row == NULL) + { + radsqlitedirectReleaseResults(hilowDB); + return ERROR; + } + + // copy it to the internal sensor: + if (hilowExtractValues(row, newRec) == ERROR) + { + radsqlitedirectReleaseResults(hilowDB); + radMsgLog (PRI_MEDIUM, "hilowGetRecord: radsqlitedirectFieldGet failed!"); + return ERROR; + } + + radsqlitedirectReleaseResults(hilowDB); + return OK; +} + +static int hilowGetWindRecord (time_t dateTime, int binIndex) +{ + char query[DB_SQLITE_QUERY_LENGTH_MAX]; + SQLITE_DIRECT_ROW row; + SQLITE_FIELD_ID field; + char binName[16]; + int retVal; + + if (hilowDB == NULL) + { + radMsgLog (PRI_HIGH, "hilowGetWindRecord: failed to open %s!", hilowGetDBFilename()); + return ERROR; + } + + // grab the entire row: + sprintf (query, "SELECT * FROM %s WHERE dateTime = '%d'", + WVIEW_HILOW_WINDDIR_TABLE, (int)dateTime); + + // Execute the query: + if (radsqlitedirectQuery(hilowDB, query, TRUE) == ERROR) + { + return ERROR; + } + + row = radsqlitedirectGetRow(hilowDB); + if (row == NULL) + { + radsqlitedirectReleaseResults(hilowDB); + return ERROR; + } + + sprintf(binName, "bin%d", binIndex); + + // retrieve current value: + field = radsqlitedirectFieldGet(row, binName); + if (field == NULL) + { + radMsgLog (PRI_MEDIUM, "hilowGetWindRecord: radsqliteFieldGet failed!"); + radsqlitedirectReleaseResults(hilowDB); + return ERROR; + } + else + { + retVal = radsqliteFieldGetBigIntValue(field); + } + + radsqlitedirectReleaseResults(hilowDB); + return retVal; +} + +static int hilowInsertData(time_t timestamp, SENSOR_TYPES type, float value, float whenHigh) +{ + SQLITE_ROW_ID row; + SQLITE_FIELD_ID field; + time_t hilowTime; + struct tm bknTime; + WV_SENSOR store; + char query[DB_SQLITE_QUERY_LENGTH_MAX]; + + if (value <= ARCHIVE_VALUE_NULL) + { + return ERROR; + } + + hilowTime = timestamp; + localtime_r(&hilowTime, &bknTime); + bknTime.tm_min = 0; + bknTime.tm_sec = 0; + bknTime.tm_isdst = -1; + hilowTime = mktime(&bknTime); + + // First see if the record exists: + if (hilowGetRecord(hilowTime, type, &store) == OK) + { + // Found the guy, just update him: + store.samples ++; + store.cumulative += value; + if (store.low > value) + { + // New low: + store.low = value; + store.time_low = timestamp; + } + if (store.high < value) + { + // New high: + store.high = value; + store.time_high = timestamp; + store.when_high = whenHigh; + } + + // Update the database: + sprintf(query, + "UPDATE %s SET low = '%.3f', timeLow = '%d', " + "high = '%.3f', timeHigh = '%d', whenHigh = '%.3f', " + "cumulative = '%.6f', samples = '%d' " + "WHERE dateTime = '%d'", + sensorTables[type], + store.low, (int)store.time_low, + store.high, (int)store.time_high, store.when_high, + store.cumulative, store.samples, + (int)hilowTime); + + // Execute the query: + if (radsqliteQuery(hilowDB, query, FALSE) == ERROR) + { + radMsgLog (PRI_HIGH, "dbsqliteHiLow: query failed"); + return ERROR; + } + } + else + { + // Must create a new record: + row = radsqliteTableDescriptionGet(hilowDB, sensorTables[type]); + if (row == NULL) + { + radMsgLog (PRI_HIGH, "dbsqliteHiLow: databaseTableDescriptionGet failed!"); + return ERROR; + } + + field = radsqliteFieldGet(row, "dateTime"); + if (field == NULL) + { + radMsgLog (PRI_HIGH, "dbsqliteHiLow: radsqliteFieldGet failed!"); + radsqliteRowDescriptionDelete(row); + return ERROR; + } + else + { + radsqliteFieldSetBigIntValue(field, (uint64_t)hilowTime); + } + + field = radsqliteFieldGet(row, "low"); + if (field == NULL) + { + radMsgLog (PRI_HIGH, "dbsqliteHiLow: radsqliteFieldGet failed!"); + radsqliteRowDescriptionDelete(row); + return ERROR; + } + else + { + radsqliteFieldSetDoubleValue(field, (double)value); + } + + field = radsqliteFieldGet(row, "timeLow"); + if (field == NULL) + { + radMsgLog (PRI_HIGH, "dbsqliteHiLow: radsqliteFieldGet failed!"); + radsqliteRowDescriptionDelete(row); + return ERROR; + } + else + { + radsqliteFieldSetBigIntValue(field, (uint64_t)timestamp); + } + + field = radsqliteFieldGet(row, "high"); + if (field == NULL) + { + radMsgLog (PRI_HIGH, "dbsqliteHiLow: radsqliteFieldGet failed!"); + radsqliteRowDescriptionDelete(row); + return ERROR; + } + else + { + radsqliteFieldSetDoubleValue(field, (double)value); + } + + field = radsqliteFieldGet(row, "timeHigh"); + if (field == NULL) + { + radMsgLog (PRI_HIGH, "dbsqliteHiLow: radsqliteFieldGet failed!"); + radsqliteRowDescriptionDelete(row); + return ERROR; + } + else + { + radsqliteFieldSetBigIntValue(field, (uint64_t)timestamp); + } + + field = radsqliteFieldGet(row, "whenHigh"); + if (field == NULL) + { + radMsgLog (PRI_HIGH, "dbsqliteHiLow: radsqliteFieldGet failed!"); + radsqliteRowDescriptionDelete(row); + return ERROR; + } + else + { + radsqliteFieldSetDoubleValue(field, (double)whenHigh); + } + + field = radsqliteFieldGet(row, "cumulative"); + if (field == NULL) + { + radMsgLog (PRI_HIGH, "dbsqliteHiLow: radsqliteFieldGet failed!"); + radsqliteRowDescriptionDelete(row); + return ERROR; + } + else + { + radsqliteFieldSetDoubleValue(field, (double)value); + } + + field = radsqliteFieldGet(row, "samples"); + if (field == NULL) + { + radMsgLog (PRI_HIGH, "dbsqliteHiLow: radsqliteFieldGet failed!"); + radsqliteRowDescriptionDelete(row); + return ERROR; + } + else + { + radsqliteFieldSetBigIntValue(field, (uint64_t)1); + } + + // insert the row: + if (radsqliteTableInsertRow(hilowDB, sensorTables[type], row) == ERROR) + { + radMsgLog (PRI_HIGH, "dbsqlite: radsqliteTableInsertRow (hilow) failed!"); + radsqliteRowDescriptionDelete(row); + return ERROR; + } + + radsqliteRowDescriptionDelete(row); + } + + return OK; +} + +static int hilowInsertWindDir (time_t timestamp, int value) +{ + SQLITE_ROW_ID row, newrow; + SQLITE_RESULT_SET_ID results; + SQLITE_FIELD_ID field; + time_t hilowTime; + struct tm bknTime; + char query[DB_SQLITE_QUERY_LENGTH_MAX]; + int i, binIndex, binValue; + char binName[16]; + + if (hilowDB == NULL) + { + radMsgLog (PRI_HIGH, "hilowInsertWindDir: failed to open %s!", hilowGetDBFilename()); + return ERROR; + } + + hilowTime = timestamp; + localtime_r(&hilowTime, &bknTime); + bknTime.tm_min = 0; + bknTime.tm_sec = 0; + bknTime.tm_isdst = -1; + hilowTime = mktime(&bknTime); + + if (value < 0) + binIndex = 0; + else + binIndex = value; + + binIndex += (WAVG_BIN_SIZE/2); + binIndex /= WAVG_BIN_SIZE; + binIndex %= WAVG_NUM_BINS; + + // First see if the record exists: + binValue = hilowGetWindRecord(hilowTime, binIndex); + if (binValue == ERROR) + { + // Create the row: + newrow = radsqliteTableDescriptionGet(hilowDB, WVIEW_HILOW_WINDDIR_TABLE); + if (newrow == NULL) + { + printf ("hilowInsertWindDir: databaseTableDescriptionGet failed!"); + return ERROR; + } + + field = radsqliteFieldGet(newrow, "dateTime"); + if (field == NULL) + { + radMsgLog (PRI_MEDIUM, "hilowInsertWindDir: radsqliteFieldGet failed!"); + radsqliteRowDescriptionDelete(newrow); + return ERROR; + } + else + { + radsqliteFieldSetBigIntValue(field, (uint64_t)hilowTime); + } + + for (i = 0; i < WAVG_NUM_BINS; i ++) + { + sprintf(binName, "bin%d", i); + field = radsqliteFieldGet(newrow, binName); + if (field == NULL) + { + radMsgLog (PRI_MEDIUM, "hilowInsertWindDir: radsqliteFieldGet failed!"); + radsqliteRowDescriptionDelete(newrow); + return ERROR; + } + else + { + if (i == binIndex) + { + // This is our guy: + radsqliteFieldSetBigIntValue(field, (uint64_t)1); + } + else + { + radsqliteFieldSetBigIntValue(field, (uint64_t)0); + } + } + } + + // insert the row: + if (radsqliteTableInsertRow(hilowDB, WVIEW_HILOW_WINDDIR_TABLE, newrow) == ERROR) + { + radMsgLog (PRI_HIGH, "hilowInsertWindDir: radsqliteTableInsertRow (hilow) failed!"); + radsqliteRowDescriptionDelete(newrow); + return ERROR; + } + + radsqliteRowDescriptionDelete(newrow); + } + else + { + sprintf(binName, "bin%d", binIndex); + binValue ++; + + sprintf(query, "UPDATE %s SET %s = '%d' WHERE dateTime = '%d'", + WVIEW_HILOW_WINDDIR_TABLE, + binName, binValue, (int)hilowTime); + + + // Execute the query: + if (radsqliteQuery(hilowDB, query, FALSE) == ERROR) + { + return ERROR; + } + } + + return OK; +} + +static int hilowUpdateTableWithArchive (SENSOR_TYPES type, ARCHIVE_PKT* pkt) +{ + switch(type) + { + case SENSOR_INTEMP: + if (pkt->value[DATA_INDEX_inTemp] > -500 && pkt->value[DATA_INDEX_inTemp] < 500) + hilowInsertData(pkt->dateTime - (60*pkt->interval), type, pkt->value[DATA_INDEX_inTemp], 0); + break; + case SENSOR_OUTTEMP: + hilowInsertData(pkt->dateTime - (60*pkt->interval), type, pkt->value[DATA_INDEX_outTemp], 0); + break; + case SENSOR_INHUMID: + if (pkt->value[DATA_INDEX_inHumidity] >= 0 && pkt->value[DATA_INDEX_inHumidity] <= 100) + hilowInsertData(pkt->dateTime - (60*pkt->interval), type, pkt->value[DATA_INDEX_inHumidity], 0); + break; + case SENSOR_OUTHUMID: + hilowInsertData(pkt->dateTime - (60*pkt->interval), type, pkt->value[DATA_INDEX_outHumidity], 0); + break; + case SENSOR_BP: + hilowInsertData(pkt->dateTime - (60*pkt->interval), type, pkt->value[DATA_INDEX_barometer], 0); + break; + case SENSOR_WSPEED: + hilowInsertData(pkt->dateTime - (60*pkt->interval), type, pkt->value[DATA_INDEX_windSpeed], 0); + break; + case SENSOR_WGUST: + hilowInsertData(pkt->dateTime - (60*pkt->interval), type, pkt->value[DATA_INDEX_windGust], + pkt->value[DATA_INDEX_windGustDir]); + break; + case SENSOR_DEWPOINT: + hilowInsertData(pkt->dateTime - (60*pkt->interval), type, pkt->value[DATA_INDEX_dewpoint], 0); + break; + case SENSOR_RAIN: + hilowInsertData(pkt->dateTime - (60*pkt->interval), type, pkt->value[DATA_INDEX_rain], 0); + break; + case SENSOR_RAINRATE: + hilowInsertData(pkt->dateTime - (60*pkt->interval), type, pkt->value[DATA_INDEX_rainRate], 0); + break; + case SENSOR_WCHILL: + hilowInsertData(pkt->dateTime - (60*pkt->interval), type, pkt->value[DATA_INDEX_windchill], 0); + break; + case SENSOR_HINDEX: + hilowInsertData(pkt->dateTime - (60*pkt->interval), type, pkt->value[DATA_INDEX_heatindex], 0); + break; + case SENSOR_ET: + if (pkt->value[DATA_INDEX_ET] >= 0 && pkt->value[DATA_INDEX_ET] < 100) + hilowInsertData(pkt->dateTime - (60*pkt->interval), type, pkt->value[DATA_INDEX_ET], 0); + break; + case SENSOR_UV: + if (pkt->value[DATA_INDEX_UV] >= 0 && pkt->value[DATA_INDEX_UV] < 100) + hilowInsertData(pkt->dateTime - (60*pkt->interval), type, pkt->value[DATA_INDEX_UV], 0); + break; + case SENSOR_SOLRAD: + if (pkt->value[DATA_INDEX_radiation] >= 0 && pkt->value[DATA_INDEX_radiation] < 10000) + hilowInsertData(pkt->dateTime - (60*pkt->interval), type, pkt->value[DATA_INDEX_radiation], 0); + break; + case SENSOR_HAIL: + if (pkt->value[DATA_INDEX_hail] >= 0 && pkt->value[DATA_INDEX_hail] < 100) + hilowInsertData(pkt->dateTime - (60*pkt->interval), type, pkt->value[DATA_INDEX_hail], 0); + break; + case SENSOR_HAILRATE: + if (pkt->value[DATA_INDEX_hailrate] >= 0 && pkt->value[DATA_INDEX_hailrate] < 100) + hilowInsertData(pkt->dateTime - (60*pkt->interval), type, pkt->value[DATA_INDEX_hailrate], 0); + break; + default: + break; + } + return OK; +} + +static int hilowUpdateTableWithSample (SENSOR_TYPES type, time_t timestamp, LOOP_PKT* pkt) +{ + switch(type) + { + case SENSOR_INTEMP: + if (pkt->inTemp > -500 && pkt->inTemp < 500) + hilowInsertData(timestamp, type, pkt->inTemp, 0); + break; + case SENSOR_OUTTEMP: + hilowInsertData(timestamp, type, pkt->outTemp, 0); + break; + case SENSOR_INHUMID: + if (pkt->inHumidity <= 100) + hilowInsertData(timestamp, type, pkt->inHumidity, 0); + break; + case SENSOR_OUTHUMID: + hilowInsertData(timestamp, type, pkt->outHumidity, 0); + break; + case SENSOR_BP: + hilowInsertData(timestamp, type, pkt->barometer, 0); + break; + case SENSOR_WSPEED: + hilowInsertData(timestamp, type, pkt->windSpeedF, 0); + break; + case SENSOR_WGUST: + hilowInsertData(timestamp, type, pkt->windGustF, pkt->windGustDir); + break; + case SENSOR_DEWPOINT: + hilowInsertData(timestamp, type, pkt->dewpoint, 0); + break; + case SENSOR_RAIN: + hilowInsertData(timestamp, type, pkt->sampleRain, 0); + break; + case SENSOR_RAINRATE: + hilowInsertData(timestamp, type, pkt->rainRate, 0); + break; + case SENSOR_WCHILL: + hilowInsertData(timestamp, type, pkt->windchill, 0); + break; + case SENSOR_HINDEX: + hilowInsertData(timestamp, type, pkt->heatindex, 0); + break; + case SENSOR_ET: + if (pkt->sampleET >= 0 && pkt->sampleET < 100) + hilowInsertData(timestamp, type, pkt->sampleET, 0); + break; + case SENSOR_UV: + if (pkt->UV < 100) + hilowInsertData(timestamp, type, pkt->UV, 0); + break; + case SENSOR_SOLRAD: + if (pkt->radiation < 10000) + hilowInsertData(timestamp, type, pkt->radiation, 0); + break; + case SENSOR_HAIL: + if (pkt->wxt510Hail >= 0 && pkt->wxt510Hail < 100) + hilowInsertData(timestamp, type, pkt->wxt510Hail, 0); + break; + case SENSOR_HAILRATE: + if (pkt->wxt510Hailrate >= 0 && pkt->wxt510Hailrate < 100) + hilowInsertData(timestamp, type, pkt->wxt510Hailrate, 0); + break; + default: + break; + } + return OK; +} + +static int hilowGetDataTimeFrame +( + int32_t first, + int32_t last, + SENSOR_STORE* sensors, + SENSOR_TIMEFRAMES timeFrame +) +{ + SENSOR_TYPES index; + int retVal = 0; + char query[DB_SQLITE_QUERY_LENGTH_MAX]; + SQLITE_DIRECT_ROW rowDescr; + SQLITE_FIELD_ID field; + WV_SENSOR* tempSensor; + WV_SENSOR store; + int i, bins[WAVG_NUM_BINS]; + char binName[16]; + + if (hilowDB == NULL) + { + radMsgLog (PRI_HIGH, "dbsqliteHiLowGet: failed to open %s!", hilowGetDBFilename()); + return ERROR; + } + + // Loop through the sensor types: + for (index = SENSOR_INTEMP; index < SENSOR_MAX; index ++) + { + // grab the rows: + sprintf (query, "SELECT * FROM %s WHERE dateTime >= '%d' AND dateTime < '%d' ORDER BY dateTime ASC", + sensorTables[index], first, last); + + // Execute the query: + if (radsqlitedirectQuery(hilowDB, query, TRUE) == ERROR) + { + return ERROR; + } + + for (rowDescr = radsqlitedirectGetRow(hilowDB); + rowDescr != NULL; + rowDescr = radsqlitedirectGetRow(hilowDB)) + { + // copy it to the internal store: + tempSensor = &sensors->sensor[timeFrame][index]; + + if (hilowExtractValues(rowDescr, &store) == ERROR) + { + radsqlitedirectReleaseResults(hilowDB); + radMsgLog (PRI_HIGH, "dbsqliteHiLowGetDay: failed to extract data"); + return ERROR; + } + + tempSensor->cumulative += store.cumulative; + tempSensor->samples += store.samples; + if (tempSensor->low > store.low) + { + tempSensor->low = store.low; + tempSensor->time_low = store.time_low; + } + if (tempSensor->high < store.high) + { + tempSensor->high = store.high; + tempSensor->time_high = store.time_high; + tempSensor->when_high = store.when_high; + } + } + + radsqlitedirectReleaseResults(hilowDB); + } + + // Do the wind dir here: + sprintf (query, "SELECT * FROM %s WHERE dateTime >= '%d' AND dateTime < '%d' ORDER BY dateTime ASC", + WVIEW_HILOW_WINDDIR_TABLE, (int)first, (int)last); + + // Execute the query: + if (radsqlitedirectQuery(hilowDB, query, TRUE) == ERROR) + { + return ERROR; + } + + retVal = 0; + for (rowDescr = radsqlitedirectGetRow(hilowDB); + rowDescr != NULL; + rowDescr = radsqlitedirectGetRow(hilowDB)) + { + retVal ++; + for (i = 0; i < WAVG_NUM_BINS; i ++) + { + bins[i] = 0; + sprintf(binName, "bin%d", i); + field = radsqlitedirectFieldGet(rowDescr, binName); + if (field == NULL) + { + radsqlitedirectReleaseResults(hilowDB); + return ERROR; + } + else + { + bins[i] = (int)radsqliteFieldGetBigIntValue(field); + } + } + + windAverageAddBins(&sensors->wind[timeFrame], bins); + } + + radsqlitedirectReleaseResults(hilowDB); + return retVal; +} + +static time_t hilowGetLastUpdateTime (void) +{ + char query[DB_SQLITE_QUERY_LENGTH_MAX]; + SQLITE_DIRECT_ROW rowDescr; + SQLITE_FIELD_ID field; + time_t retVal; + char tempstr[256]; + + if (hilowDB == NULL) + { + radMsgLog (PRI_HIGH, "hilowGetLastUpdateTime: failed to open %s!", hilowGetDBFilename()); + return (time_t)(-1); + } + + // grab the row: + sprintf (query, "SELECT * FROM %s WHERE name = 'lastUpdate'", + WVIEW_HILOW_META_TABLE); + + // Execute the query: + if (radsqlitedirectQuery(hilowDB, query, TRUE) == ERROR) + { + return (time_t)(-1); + } + + rowDescr = radsqlitedirectGetRow(hilowDB); + if (rowDescr == NULL) + { + radsqlitedirectReleaseResults(hilowDB); + return (time_t)(-1); + } + + field = radsqlitedirectFieldGet(rowDescr, "value"); + if (field == NULL) + { + radsqlitedirectReleaseResults(hilowDB); + return (time_t)(-1); + } + + strncpy(tempstr, radsqliteFieldGetCharValue(field), radsqliteFieldGetCharLength(field)); + retVal = (time_t)atoi(tempstr); + radsqlitedirectReleaseResults(hilowDB); + return retVal; +} + +static int hilowSetLastUpdateTime (time_t newtime) +{ + char query[DB_SQLITE_QUERY_LENGTH_MAX]; + + if (hilowDB == NULL) + { + radMsgLog (PRI_HIGH, "hilowSetLastUpdateTime: failed to open %s!", hilowGetDBFilename()); + return ERROR; + } + + // update the row: + sprintf (query, "UPDATE %s SET value = '%d' WHERE name = 'lastUpdate'", + WVIEW_HILOW_META_TABLE, (int)newtime); + + // Execute the query: + if (radsqliteQuery(hilowDB, query, FALSE) == ERROR) + { + return ERROR; + } + + return OK; +} + +// Make these static so we can use the dbsqliteArchiveExecutePerRecord call to +// efficiently populate the HILOW tables when created: +static int IfTablesCreated = FALSE, IfWindTableExists = FALSE, IfTableExists[SENSOR_MAX]; +static time_t LastHiLowUpdate, LastArchiveTime; + +// Callback method for dbsqliteArchiveExecutePerRecord: +static void hilowInitPerRecord (ARCHIVE_PKT* rec, void* data) +{ + struct tm bknTime; + SENSOR_TYPES index; + time_t Time = (time_t)rec->dateTime; + + localtime_r(&Time, &bknTime); + if (bknTime.tm_hour == 0 && + bknTime.tm_min == rec->interval) + { + // Beginning of day, log it: + radMsgLog(PRI_STATUS, "HILOW: adding records for %4.4d%2.2d%2.2d", + bknTime.tm_year + 1900, + bknTime.tm_mon + 1, + bknTime.tm_mday); + } + + // Did we just create the wind dir table -OR- is this record newer than + // our last update? + if (IfWindTableExists == FALSE || LastHiLowUpdate < rec->dateTime) + { + // Insert wind direction data: + hilowInsertWindDir(rec->dateTime - (60*rec->interval), + (int)rec->value[DATA_INDEX_windDir]); + } + + // Now loop through the sensor types, only updating ones we just created: + for (index = SENSOR_INTEMP; index < SENSOR_MAX; index ++) + { + // If this table was not just created -AND- this is not a new record: + if (IfTableExists[index] && LastHiLowUpdate > rec->dateTime) + { + // Nothing to do here: + continue; + } + + // Update table[index] with this record: + hilowUpdateTableWithArchive(index, rec); + } + + LastArchiveTime = rec->dateTime; + hilowSetLastUpdateTime(LastArchiveTime); +} + +//////////////////////////////////////////////////////////////////////////////// + +// Initialize the HILOW database (returns OK or ERROR): +int dbsqliteHiLowInit(int update) +{ + SQLITE_ROW_ID rowDesc, newrow; + SQLITE_FIELD_ID field; + SENSOR_TYPES index; + int retVal; + int i, done = FALSE; + int weeks=0; + char tableName[64]; + ARCHIVE_PKT archiveRec; + time_t archiveTime, startTime, stopTime, runStartTime = time(NULL), diffTime; + char binName[16]; + struct tm bknTime; + + hilowDB = radsqliteOpen(hilowGetDBFilename()); + if (hilowDB == NULL) + { + radMsgLog (PRI_HIGH, "dbsqliteHiLowInit: failed to open %s!", hilowGetDBFilename()); + return ERROR; + } + + if (! update) + { + //dbsqliteHiLowPragmaSet("journal_mode", "DELETE"); + dbsqliteHiLowPragmaSet("synchronous", "NORMAL"); + + radMsgLog(PRI_STATUS, "HILOW: OK"); + return OK; + } + + // Make writes faster (and less safe) by avoiding fsyncs: + dbsqliteHiLowPragmaSet("synchronous", "OFF"); + + // Make things quicker but less safe by turning off journalling: + //dbsqliteHiLowPragmaSet("journal_mode", "OFF"); + + + // Do the meta table first: + if (! radsqliteTableIfExists(hilowDB, WVIEW_HILOW_META_TABLE)) + { + // Create it: + rowDesc = radsqliteRowDescriptionCreate(); + if (rowDesc == NULL) + { + radsqliteClose(hilowDB); + radMsgLog(PRI_HIGH, "dbsqliteHiLowInit: radsqliteRowDescriptionCreate failed!"); + return ERROR; + } + + // Populate the table: + retVal = radsqliteRowDescriptionAddField(rowDesc, + "name", + SQLITE_FIELD_STRING | SQLITE_FIELD_PRI_KEY, + 64); + if (retVal == ERROR) + { + radsqliteClose(hilowDB); + radMsgLog(PRI_HIGH, "dbsqliteHiLowInit: databaseRowDescriptionAddField failed!"); + radsqliteRowDescriptionDelete(rowDesc); + return ERROR; + } + retVal = radsqliteRowDescriptionAddField(rowDesc, + "value", + SQLITE_FIELD_STRING, + 64); + if (retVal == ERROR) + { + radsqliteClose(hilowDB); + radMsgLog(PRI_HIGH, "dbsqliteHiLowInit: databaseRowDescriptionAddField failed!"); + radsqliteRowDescriptionDelete(rowDesc); + return ERROR; + } + + // Now create the table: + if (radsqliteTableCreate(hilowDB, WVIEW_HILOW_META_TABLE, rowDesc) == ERROR) + { + radsqliteClose(hilowDB); + radMsgLog(PRI_HIGH, "dbsqliteHiLowInit: radsqliteTableCreate failed!"); + radsqliteRowDescriptionDelete(rowDesc); + return ERROR; + } + radsqliteRowDescriptionDelete(rowDesc); + + // Create the row: + newrow = radsqliteTableDescriptionGet(hilowDB, WVIEW_HILOW_META_TABLE); + if (newrow == NULL) + { + printf ("hilowInsertWindDir: databaseTableDescriptionGet failed!"); + return ERROR; + } + + field = radsqliteFieldGet(newrow, "name"); + if (field == NULL) + { + radMsgLog (PRI_MEDIUM, "dbsqliteHiLowInit: radsqliteFieldGet failed!"); + radsqliteRowDescriptionDelete(newrow); + return ERROR; + } + else + { + radsqliteFieldSetCharValue(field, "lastUpdate", strlen("lastUpdate")); + } + field = radsqliteFieldGet(newrow, "value"); + if (field == NULL) + { + radMsgLog (PRI_MEDIUM, "dbsqliteHiLowInit: radsqliteFieldGet failed!"); + radsqliteRowDescriptionDelete(newrow); + return ERROR; + } + else + { + radsqliteFieldSetCharValue(field, "0", 1); + } + + // insert the row: + if (radsqliteTableInsertRow(hilowDB, WVIEW_HILOW_META_TABLE, newrow) == ERROR) + { + radMsgLog (PRI_HIGH, "dbsqliteHiLowInit: radsqliteTableInsertRow (hilow) failed!"); + radsqliteRowDescriptionDelete(newrow); + return ERROR; + } + radsqliteRowDescriptionDelete(newrow); + radMsgLog(PRI_STATUS, "HILOW %s table created", WVIEW_HILOW_META_TABLE); + } + + // Do the wind direction bins: + if (radsqliteTableIfExists(hilowDB, WVIEW_HILOW_WINDDIR_TABLE)) + { + IfWindTableExists = TRUE; + } + else + { + IfTablesCreated = TRUE; + + // Create it: + rowDesc = radsqliteRowDescriptionCreate(); + if (rowDesc == NULL) + { + radsqliteClose(hilowDB); + radMsgLog(PRI_HIGH, "dbsqliteHiLowInit: radsqliteRowDescriptionCreate failed!"); + return ERROR; + } + + // Populate the table: + retVal = radsqliteRowDescriptionAddField(rowDesc, + "dateTime", + SQLITE_FIELD_BIGINT | SQLITE_FIELD_PRI_KEY, + 0); + if (retVal == ERROR) + { + radsqliteClose(hilowDB); + radMsgLog(PRI_HIGH, "dbsqliteHiLowInit: databaseRowDescriptionAddField failed!"); + radsqliteRowDescriptionDelete(rowDesc); + return ERROR; + } + + for (i = 0; i < WAVG_NUM_BINS; i ++) + { + sprintf(binName, "bin%d", i); + retVal = radsqliteRowDescriptionAddField(rowDesc, binName, SQLITE_FIELD_BIGINT, 0); + if (retVal == ERROR) + { + radsqliteClose(hilowDB); + radMsgLog(PRI_HIGH, "dbsqliteHiLowInit: databaseRowDescriptionAddField failed!"); + radsqliteRowDescriptionDelete(rowDesc); + return ERROR; + } + } + + // Now create the table: + if (radsqliteTableCreate(hilowDB, WVIEW_HILOW_WINDDIR_TABLE, rowDesc) == ERROR) + { + radsqliteClose(hilowDB); + radMsgLog(PRI_HIGH, "dbsqliteHiLowInit: radsqliteTableCreate failed!"); + radsqliteRowDescriptionDelete(rowDesc); + return ERROR; + } + radsqliteRowDescriptionDelete(rowDesc); + radMsgLog(PRI_STATUS, "HILOW %s table created", WVIEW_HILOW_WINDDIR_TABLE); + } + + + // Loop through all sensor types, creating tables as needed: + for (index = SENSOR_INTEMP; index < SENSOR_MAX; index ++) + { + IfTableExists[index] = FALSE; + + // Does the sensor HILOW table exist? + if (radsqliteTableIfExists(hilowDB, sensorTables[index])) + { + IfTableExists[index] = TRUE; + continue; + } + + IfTablesCreated = TRUE; + + // We need to create the table: + // Define the row first: + rowDesc = radsqliteRowDescriptionCreate(); + if (rowDesc == NULL) + { + radsqliteClose(hilowDB); + radMsgLog(PRI_HIGH, "dbsqliteHiLowInit: radsqliteRowDescriptionCreate failed!"); + return ERROR; + } + + // Populate the table: + retVal = radsqliteRowDescriptionAddField(rowDesc, + "dateTime", + SQLITE_FIELD_BIGINT | SQLITE_FIELD_PRI_KEY, + 0); + if (retVal == ERROR) + { + radsqliteClose(hilowDB); + radMsgLog(PRI_HIGH, "dbsqliteHiLowInit: databaseRowDescriptionAddField failed!"); + radsqliteRowDescriptionDelete(rowDesc); + return ERROR; + } + + retVal = radsqliteRowDescriptionAddField(rowDesc, "low", SQLITE_FIELD_DOUBLE, 0); + if (retVal == ERROR) + { + radsqliteClose(hilowDB); + radMsgLog(PRI_HIGH, "dbsqliteHiLowInit: databaseRowDescriptionAddField failed!"); + radsqliteRowDescriptionDelete(rowDesc); + return ERROR; + } + retVal = radsqliteRowDescriptionAddField(rowDesc, "timeLow", SQLITE_FIELD_BIGINT, 0); + if (retVal == ERROR) + { + radsqliteClose(hilowDB); + radMsgLog(PRI_HIGH, "dbsqliteHiLowInit: databaseRowDescriptionAddField failed!"); + radsqliteRowDescriptionDelete(rowDesc); + return ERROR; + } + retVal = radsqliteRowDescriptionAddField(rowDesc, "high", SQLITE_FIELD_DOUBLE, 0); + if (retVal == ERROR) + { + radsqliteClose(hilowDB); + radMsgLog(PRI_HIGH, "dbsqliteHiLowInit: databaseRowDescriptionAddField failed!"); + radsqliteRowDescriptionDelete(rowDesc); + return ERROR; + } + retVal = radsqliteRowDescriptionAddField(rowDesc, "timeHigh", SQLITE_FIELD_BIGINT, 0); + if (retVal == ERROR) + { + radsqliteClose(hilowDB); + radMsgLog(PRI_HIGH, "dbsqliteHiLowInit: databaseRowDescriptionAddField failed!"); + radsqliteRowDescriptionDelete(rowDesc); + return ERROR; + } + retVal = radsqliteRowDescriptionAddField(rowDesc, "whenHigh", SQLITE_FIELD_DOUBLE, 0); + if (retVal == ERROR) + { + radsqliteClose(hilowDB); + radMsgLog(PRI_HIGH, "dbsqliteHiLowInit: databaseRowDescriptionAddField failed!"); + radsqliteRowDescriptionDelete(rowDesc); + return ERROR; + } + retVal = radsqliteRowDescriptionAddField(rowDesc, "cumulative", SQLITE_FIELD_DOUBLE, 0); + if (retVal == ERROR) + { + radsqliteClose(hilowDB); + radMsgLog(PRI_HIGH, "dbsqliteHiLowInit: databaseRowDescriptionAddField failed!"); + radsqliteRowDescriptionDelete(rowDesc); + return ERROR; + } + retVal = radsqliteRowDescriptionAddField(rowDesc, "samples", SQLITE_FIELD_BIGINT, 0); + if (retVal == ERROR) + { + radsqliteClose(hilowDB); + radMsgLog(PRI_HIGH, "dbsqliteHiLowInit: databaseRowDescriptionAddField failed!"); + radsqliteRowDescriptionDelete(rowDesc); + return ERROR; + } + + + // Now create the table: + if (radsqliteTableCreate(hilowDB, sensorTables[index], rowDesc) == ERROR) + { + radsqliteClose(hilowDB); + radMsgLog(PRI_HIGH, "dbsqliteHiLowInit: radsqliteTableCreate failed!"); + radsqliteRowDescriptionDelete(rowDesc); + return ERROR; + } + + // We're done with this table: + radsqliteRowDescriptionDelete(rowDesc); + + radMsgLog(PRI_STATUS, "HILOW: %s table created", sensorTables[index]); + } + + // OK, if we had to create one or more sensor tables, assume they should + // be completely populated: + if (IfTablesCreated) + { + LastHiLowUpdate = hilowGetLastUpdateTime(); + + radMsgLog(PRI_STATUS, "HILOW: back filling tables with ALL archive data"); + radMsgLog(PRI_STATUS, "HILOW: this is a one time process when tables are created"); + radMsgLog(PRI_STATUS, "HILOW: (this may take a while ...)"); + + // Loop through all archive records, one week at a time: + startTime = dbsqliteArchiveGetNextRecord(0, &archiveRec); + stopTime = startTime + WV_SECONDS_IN_WEEK; + while (! done) + { + // execute per row: + retVal = dbsqliteArchiveExecutePerRecord(hilowInitPerRecord, + NULL, + startTime, + stopTime, + NULL); + if (retVal == ERROR) + { + done = TRUE; + } + else if (retVal == 0) + { + // Make sure we don't have a gap: + startTime = dbsqliteArchiveGetNextRecord(stopTime, &archiveRec); + if ((int)startTime == ERROR) + { + done = TRUE; + } + else + { + stopTime = startTime + WV_SECONDS_IN_WEEK; + } + } + else + { + startTime = stopTime; + stopTime = startTime + WV_SECONDS_IN_WEEK; + + // Output running stats if we had data for the week: + diffTime = time(NULL) - runStartTime; + weeks++; + radMsgLog(PRI_STATUS, "HILOW: Stats:"); + radMsgLog(PRI_STATUS, "HILOW: Time : %2.2d:%2.2d", + (int)diffTime/60, (int)diffTime%60); + radMsgLog(PRI_STATUS, "HILOW: Weeks converted : %d", weeks); + radMsgLog(PRI_STATUS, "HILOW: secs/week : %.2f", + (float)diffTime/(float)weeks); + } + } + + hilowSetLastUpdateTime(LastArchiveTime); + } + else + { + // See if we need to grab any recent archive records since we last ran: + LastHiLowUpdate = hilowGetLastUpdateTime(); + LastArchiveTime = dbsqliteArchiveGetNewestTime(&archiveRec); + + if (LastHiLowUpdate < LastArchiveTime) + { + // We need to grab all records after LastHiLowUpdate: + radMsgLog(PRI_STATUS, "HILOW: adding gap records since last update"); + + // Loop through all archive records: + for (archiveTime = dbsqliteArchiveGetNextRecord(LastHiLowUpdate, &archiveRec); + (int)archiveTime != ERROR; + archiveTime = dbsqliteArchiveGetNextRecord(archiveTime, &archiveRec)) + { + LastArchiveTime = archiveTime; + + time_t Time = (time_t)archiveRec.dateTime; + localtime_r(&Time, &bknTime); + if (bknTime.tm_hour == 0 && + bknTime.tm_min == archiveRec.interval) + { + // Beginning of day, log it: + radMsgLog(PRI_STATUS, "HILOW: adding records for %4.4d%2.2d%2.2d", + bknTime.tm_year + 1900, + bknTime.tm_mon + 1, + bknTime.tm_mday); + } + + // Insert wind direction data: + hilowInsertWindDir(archiveRec.dateTime - (60*archiveRec.interval), + (int)archiveRec.value[DATA_INDEX_windDir]); + + // Now loop through the sensor types: + for (index = SENSOR_INTEMP; index < SENSOR_MAX; index ++) + { + // Update this table with this record: + hilowUpdateTableWithArchive(index, &archiveRec); + } + } + + hilowSetLastUpdateTime(LastArchiveTime); + } + radMsgLog(PRI_STATUS, "HILOW: database OK"); + } + + + // Reinstate journals: + //dbsqliteHiLowPragmaSet("journal_mode", "DELETE"); + + // Restore normal syncing behavior: + dbsqliteHiLowPragmaSet("synchronous", "NORMAL"); + + radMsgLog(PRI_STATUS, "HILOW: beginning normal LOOP operation"); + return OK; +} + +void dbsqliteHiLowExit(void) +{ + if (hilowDB) + radsqliteClose(hilowDB); +} + +// set a PRAGMA to modify the operation of the SQLite library: +// Returns: OK or ERROR +int dbsqliteHiLowPragmaSet(char* pragma, char* setting) +{ + char query[DB_SQLITE_QUERY_LENGTH_MAX]; + + // Check SQLite version if a journalling pragma: + if (!strcmp(pragma, "journal_mode")) + { + if (SQLITE_VERSION_NUMBER < 3005009) + { + // Not supported: + return OK; + } + } + + sprintf (query, "PRAGMA %s = %s", pragma, setting); + + // Execute the query: + if (radsqliteQuery(hilowDB, query, FALSE) == ERROR) + { + return ERROR; + } + + return OK; +} + +// ... Update database with a timestamped LOOP sample: +// ... No duplicate checking is done; +// ... Returns OK or ERROR +int dbsqliteHiLowStoreSample (time_t timestamp, LOOP_PKT* sample) +{ + SENSOR_TYPES index; + char fileName[128]; + + // Update the update time: + hilowSetLastUpdateTime(timestamp); + + // Loop through the sensor types: + for (index = SENSOR_INTEMP; index < SENSOR_MAX; index ++) + { + // Update this table with this record: + hilowUpdateTableWithSample(index, timestamp, sample); + } + + // Update wind direction: + hilowInsertWindDir(timestamp, sample->windDir); + + sprintf (fileName, "%s/export/%s", wvutilsGetConfigPath(), WVIEW_HILOW_MARKER_FILE); + wvutilsWriteMarkerFile(fileName, timestamp); + + return OK; +} + +// ... Update database with an archive record in lieu of LOOP samples: +// ... No duplicate checking is done; +// ... Returns OK or ERROR +int dbsqliteHiLowStoreArchive (ARCHIVE_PKT* record) +{ + SENSOR_TYPES index; + char fileName[128]; + + // Update the update time: + hilowSetLastUpdateTime(record->dateTime); + + // Loop through the sensor types: + for (index = SENSOR_INTEMP; index < SENSOR_MAX; index ++) + { + // Update this table with this record: + hilowUpdateTableWithArchive(index, record); + } + + // Update wind direction: + hilowInsertWindDir(record->dateTime, (int)record->value[DATA_INDEX_windDir]); + + sprintf (fileName, "%s/export/%s", wvutilsGetConfigPath(), WVIEW_HILOW_MARKER_FILE); + wvutilsWriteMarkerFile(fileName, record->dateTime); + + return OK; +} + +// ... Update database with an archive record, ignoring cumulative values: +// ... No duplicate checking is done; +// ... Returns OK or ERROR +int dbsqliteHiLowUpdateArchive (ARCHIVE_PKT* record) +{ + SENSOR_TYPES index; + + // Update the update time: + hilowSetLastUpdateTime(record->dateTime); + + // Loop through the sensor types: + for (index = SENSOR_INTEMP; index < SENSOR_MAX; index ++) + { + // If it is a cumulative sensor, don't store it: + if (index == SENSOR_RAIN || index == SENSOR_ET || index == SENSOR_HAIL) + { + continue; + } + + // Update this table with this record: + hilowUpdateTableWithArchive(index, record); + } + + // Update wind direction: + hilowInsertWindDir(record->dateTime, (int)record->value[DATA_INDEX_windDir]); + + return OK; +} + +// ... Update sensors for the given hour and time frame: +// ... Returns number of records processed or ERROR +int dbsqliteHiLowGetHour +( + time_t hour, + SENSOR_STORE* sensors, + SENSOR_TIMEFRAMES timeFrame +) +{ + time_t first, last; + struct tm bknTime; + + localtime_r(&hour, &bknTime); + first = hour; + bknTime.tm_hour ++; + bknTime.tm_min = 0; + bknTime.tm_sec = 0; + bknTime.tm_isdst = -1; + last = mktime(&bknTime); + + return (hilowGetDataTimeFrame(first, last, sensors, timeFrame)); +} + +int dbsqliteHiLowGetDay +( + time_t day, + SENSOR_STORE* sensors, + SENSOR_TIMEFRAMES timeFrame +) +{ + time_t first, last; + struct tm bknTime; + + localtime_r(&day, &bknTime); + first = day; + bknTime.tm_mday ++; + bknTime.tm_hour = 0; + bknTime.tm_min = 0; + bknTime.tm_sec = 0; + bknTime.tm_isdst = -1; + last = mktime(&bknTime); + + return (hilowGetDataTimeFrame(first, last, sensors, timeFrame)); +} + +int dbsqliteHiLowGetMonth +( + time_t month, + SENSOR_STORE* sensors, + SENSOR_TIMEFRAMES timeFrame, + int yearRainFlag +) +{ + int retVal = 0; + char query[DB_SQLITE_QUERY_LENGTH_MAX]; + SQLITE_DIRECT_ROW rowDescr; + SQLITE_FIELD_ID field; + time_t first, last; + struct tm bknTime; + WV_SENSOR* tempSensor; + WV_SENSOR store; + int i, bins[WAVG_NUM_BINS]; + char binName[16]; + SENSOR_TYPES index; + + localtime_r(&month, &bknTime); + bknTime.tm_mday = 1; + bknTime.tm_hour = 0; + bknTime.tm_min = 0; + bknTime.tm_sec = 0; + bknTime.tm_mon += 1; + bknTime.tm_isdst = -1; + first = month; + last = mktime(&bknTime); + + if (hilowDB == NULL) + { + radMsgLog (PRI_HIGH, "dbsqliteHiLowGetMonth: failed to open %s!", hilowGetDBFilename()); + return ERROR; + } + + // Loop through the sensor types: + for (index = SENSOR_INTEMP; index < SENSOR_MAX; index ++) + { + // grab the rows: + sprintf (query, "SELECT * FROM %s WHERE dateTime >= '%d' AND dateTime < '%d' ORDER BY dateTime ASC", + sensorTables[index], (int)first, (int)last); + + // Execute the query: + if (radsqlitedirectQuery(hilowDB, query, TRUE) == ERROR) + { + return ERROR; + } + + retVal = 0; + for (rowDescr = radsqlitedirectGetRow(hilowDB); + rowDescr != NULL; + rowDescr = radsqlitedirectGetRow(hilowDB)) + { + if (yearRainFlag) + { + tempSensor = &sensors->sensor[STF_YEAR][index]; + if (index == SENSOR_RAIN) + { + retVal ++; + if (hilowExtractValues(rowDescr, &store) == ERROR) + { + radsqlitedirectReleaseResults(hilowDB); + radMsgLog (PRI_HIGH, "dbsqliteHiLowGetMonth: failed to extract data"); + return ERROR; + } + + tempSensor->cumulative += store.cumulative; + tempSensor->samples += store.samples; + } + else if (index == SENSOR_RAINRATE) + { + if (hilowExtractValues(rowDescr, &store) == ERROR) + { + radsqlitedirectReleaseResults(hilowDB); + radMsgLog (PRI_HIGH, "dbsqliteHiLowGetMonth: failed to extract data"); + return ERROR; + } + + tempSensor->cumulative += store.cumulative; + tempSensor->samples += store.samples; + if (tempSensor->high < store.high) + { + tempSensor->high = store.high; + tempSensor->time_high = store.time_high; + tempSensor->when_high = store.when_high; + } + } + else if (index == SENSOR_ET) + { + if (hilowExtractValues(rowDescr, &store) == ERROR) + { + radsqlitedirectReleaseResults(hilowDB); + radMsgLog (PRI_HIGH, "dbsqliteHiLowGetMonth: failed to extract data"); + return ERROR; + } + + tempSensor->cumulative += store.cumulative; + tempSensor->samples += store.samples; + } + continue; + } + + // copy it to the internal store: + tempSensor = &sensors->sensor[timeFrame][index]; + + if (hilowExtractValues(rowDescr, &store) == ERROR) + { + radsqlitedirectReleaseResults(hilowDB); + radMsgLog (PRI_HIGH, "dbsqliteHiLowGetMonth: failed to extract data"); + return ERROR; + } + + tempSensor->cumulative += store.cumulative; + tempSensor->samples += store.samples; + if (tempSensor->low > store.low) + { + tempSensor->low = store.low; + tempSensor->time_low = store.time_low; + } + if (tempSensor->high < store.high) + { + tempSensor->high = store.high; + tempSensor->time_high = store.time_high; + tempSensor->when_high = store.when_high; + } + } + + radsqlitedirectReleaseResults(hilowDB); + } + + if (yearRainFlag) + { + return retVal; + } + + // Do the wind dir here: + sprintf (query, "SELECT * FROM %s WHERE dateTime >= '%d' AND dateTime < '%d' ORDER BY dateTime ASC", + WVIEW_HILOW_WINDDIR_TABLE, (int)first, (int)last); + + // Execute the query: + if (radsqlitedirectQuery(hilowDB, query, TRUE) == ERROR) + { + return ERROR; + } + + retVal = 0; + for (rowDescr = radsqlitedirectGetRow(hilowDB); + rowDescr != NULL; + rowDescr = radsqlitedirectGetRow(hilowDB)) + { + retVal ++; + for (i = 0; i < WAVG_NUM_BINS; i ++) + { + bins[i] = 0; + sprintf(binName, "bin%d", i); + field = radsqlitedirectFieldGet(rowDescr, binName); + if (field == NULL) + { + radsqlitedirectReleaseResults(hilowDB); + return ERROR; + } + else + { + bins[i] = (int)radsqliteFieldGetBigIntValue(field); + } + } + + windAverageAddBins(&sensors->wind[timeFrame], bins); + } + + radsqlitedirectReleaseResults(hilowDB); + return retVal; +} + +// ... Retrieve the last HILOW hour in the database; +// ... Returns time_t or ERROR +time_t dbsqliteHiLowGetLastUpdate(void) +{ + return (hilowGetLastUpdateTime()); +} + + diff --git a/common/dbsqliteHistory.c b/common/dbsqliteHistory.c new file mode 100755 index 0000000..c2d1a4c --- /dev/null +++ b/common/dbsqliteHistory.c @@ -0,0 +1,494 @@ +//---------------------------------------------------------------------------- +// +// FILENAME: +// dbsqliteHistory.c +// +// PURPOSE: +// Provide the weather station historical database utilities. +// +// REVISION HISTORY: +// Date Engineer Revision Remarks +// 08/31/2008 M.S. Teel 0 Original +// +// NOTES: +// +// +// LICENSE: +// Copyright (c) 2008, Mark S. Teel (mark@teel.ws) +// +// This source code is released for free distribution under the terms +// of the GNU General Public License. +// +//---------------------------------------------------------------------------- + +// ... System include files +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// ... Library include files +#include + +// ... Local include files +#include + + +#ifdef BUILD_HTMLGEND + + +// ... local memory: + +static const char* historyName[DATA_INDEX_MAX] = +{ + "barometer", + "pressure", + "altimeter", + "inTemp", + "outTemp", + "inHumidity", + "outHumidity", + "windSpeed", + "windDir", + "windGust", + "windGustDir", + "rainRate", + "rain", + "dewpoint", + "windchill", + "heatindex", + "rxCheckPercent", + "ET", + "radiation", + "UV", + "extraTemp1", + "extraTemp2", + "extraTemp3", + "soilTemp1", + "soilTemp2", + "soilTemp3", + "soilTemp4", + "leafTemp1", + "leafTemp2", + "extraHumid1", + "extraHumid2", + "soilMoist1", + "soilMoist2", + "soilMoist3", + "soilMoist4", + "leafWet1", + "leafWet2", + "txBatteryStatus", + "consBatteryVoltage", + "hail", + "hailRate", + "heatingTemp", + "heatingVoltage", + "supplyVoltage", + "referenceVoltage", + "windBatteryStatus", + "rainBatteryStatus", + "outTempBatteryStatus", + "inTempBatteryStatus" +}; + +static char DefaultArchivePath[_MAX_PATH] = { 0 }; + + + +// ... ----- static (local) methods ----- + +static const char* getHistoryDBFilename(void) +{ + static char dbHistoryFileName[_MAX_PATH]; + + if (strlen(DefaultArchivePath) > 0) + { + sprintf (dbHistoryFileName, "%s/%s", DefaultArchivePath, WVIEW_HISTORY_DATABASE); + } + else + { + sprintf (dbHistoryFileName, "%s/%s", wvutilsGetArchivePath(), WVIEW_HISTORY_DATABASE); + } + + return dbHistoryFileName; +} + +static int getHistoryRecord (SQLITE_DATABASE_ID historyDB, time_t date, HISTORY_DATA* store) +{ + char query[DB_SQLITE_QUERY_LENGTH_MAX]; + SQLITE_DIRECT_ROW rowDescr; + Data_Indices index; + SQLITE_FIELD_ID field; + struct tm locTime; + char dayString[64]; + int RowCount = 0; + + // First let's make sure there aren't 2 records for the same day; + // if there are, delete them both and return ERROR. + localtime_r (&date, &locTime); + snprintf (dayString, 64, "%4.4d-%2.2d-%2.2d", + locTime.tm_year + 1900, + locTime.tm_mon + 1, + locTime.tm_mday); + + // get a count of matching rows: + sprintf (query, + "select count(date) from %s where date(date, 'unixepoch', 'localtime') = '%s'", + WVIEW_DAY_HISTORY_TABLE, dayString); + + // Execute the query: + if (radsqlitedirectQuery(historyDB, query, TRUE) == ERROR) + { + radMsgLog (PRI_MEDIUM, "dbsqliteHistory: row count query failed."); + return ERROR; + } + rowDescr = radsqlitedirectGetRow(historyDB); + if (rowDescr == NULL) + { + radMsgLog (PRI_MEDIUM, "dbsqliteHistory: row count get row failed."); + radsqlitedirectReleaseResults(historyDB); + return ERROR; + } + field = radsqlitedirectFieldGet(rowDescr, "count(date)"); + if ((field == NULL) || ((radsqliteFieldGetType(field) & SQLITE_FIELD_VALUE_IS_NULL) != 0)) + { + radMsgLog (PRI_MEDIUM, "dbsqliteHistory: row count get field failed."); + radsqlitedirectReleaseResults(historyDB); + return ERROR; + } + RowCount = (int)radsqliteFieldGetBigIntValue(field); + radsqlitedirectReleaseResults(historyDB); + + if (RowCount > 1) + { + // Delete all rows, we'll regenerate from archive data: + sprintf (query, + "delete from %s where date(date, 'unixepoch', 'localtime') = '%s'", + WVIEW_DAY_HISTORY_TABLE, dayString); + + // Execute the query: + radsqliteQuery(historyDB, query, FALSE); + + // Return ERROR regardless... + return ERROR; + } + + // Proceed as normal if here. + // grab the entire row: + sprintf (query, "SELECT * FROM %s WHERE date = '%d'", + WVIEW_DAY_HISTORY_TABLE, (int)date); + + // Execute the query: + if (radsqlitedirectQuery(historyDB, query, TRUE) == ERROR) + { + return ERROR; + } + + rowDescr = radsqlitedirectGetRow(historyDB); + if (rowDescr == NULL) + { + radsqlitedirectReleaseResults(historyDB); + return ERROR; + } + + // finally copy it to the internal history: + store->startTime = date; + + for (index = 0; index < DATA_INDEX_MAX; index ++) + { + field = radsqlitedirectFieldGet(rowDescr, historyName[index]); + if ((field == NULL) || ((radsqliteFieldGetType(field) & SQLITE_FIELD_VALUE_IS_NULL) != 0)) + { + store->values[index] = ARCHIVE_VALUE_NULL; + } + else + { + store->samples[index] = 1; + store->values[index] = (float)radsqliteFieldGetDoubleValue(field); + } + } + + radsqlitedirectReleaseResults(historyDB); + return OK; +} + +static int insertDBHistoryData(SQLITE_DATABASE_ID historyDB, HISTORY_DATA* data) +{ + SQLITE_ROW_ID row; + SQLITE_FIELD_ID field; + Data_Indices index; + HISTORY_DATA store; + + // First see if the record exists: + if (getHistoryRecord(historyDB, (time_t)data->startTime, &store) == OK) + { + // Found the guy, delete the old row: + row = radsqliteTableDescriptionGet(historyDB, WVIEW_DAY_HISTORY_TABLE); + if (row == NULL) + { + printf ("dbsqlite: databaseTableDescriptionGet (history) failed!"); + return ERROR; + } + + field = radsqliteFieldGet(row, "date"); + if (field == NULL) + { + radMsgLog (PRI_MEDIUM, "dbsqlite: radsqliteFieldGet failed!"); + radsqliteRowDescriptionDelete(row); + return ERROR; + } + else + { + radsqliteFieldSetBigIntValue(field, (uint64_t)data->startTime); + } + + // Delete him: + if (radsqliteTableDeleteRows(historyDB, WVIEW_DAY_HISTORY_TABLE, row) + == ERROR) + { + radMsgLog (PRI_MEDIUM, "dbsqlite: radsqliteTableDeleteRows failed!"); + radsqliteRowDescriptionDelete(row); + return ERROR; + } + + radsqliteRowDescriptionDelete(row); + } + + // Create the new record: + row = radsqliteTableDescriptionGet(historyDB, WVIEW_DAY_HISTORY_TABLE); + if (row == NULL) + { + printf ("dbsqlite: databaseTableDescriptionGet (history) failed!"); + return ERROR; + } + + field = radsqliteFieldGet(row, "date"); + if (field == NULL) + { + radMsgLog (PRI_MEDIUM, "dbsqlite: radsqliteFieldGet failed!"); + radsqliteRowDescriptionDelete(row); + return ERROR; + } + else + { + radsqliteFieldSetBigIntValue(field, (uint64_t)data->startTime); + } + + for (index = 0; index < DATA_INDEX_MAX; index ++) + { + field = radsqliteFieldGet(row, historyName[index]); + if (field == NULL) + { + radMsgLog (PRI_MEDIUM, "dbsqlite: radsqliteFieldGet %s failed!", historyName[index]); + radsqliteRowDescriptionDelete(row); + return ERROR; + } + else + { + if (data->values[index] <= ARCHIVE_VALUE_NULL || data->samples[index] == 0) + { + radsqliteFieldSetToNull(field); + } + else + { + if (index == DATA_INDEX_rain || index == DATA_INDEX_ET || index == DATA_INDEX_windDir) + { + radsqliteFieldSetDoubleValue(field, (double)data->values[index]); + } + else + { + radsqliteFieldSetDoubleValue(field, (double)data->values[index]/(double)data->samples[index]); + } + } + } + } + + // insert the row: + if (radsqliteTableInsertRow(historyDB, WVIEW_DAY_HISTORY_TABLE, row) == ERROR) + { + radMsgLog (PRI_HIGH, "dbsqlite: radsqliteTableInsertRow (history) failed!"); + radsqliteRowDescriptionDelete(row); + return ERROR; + } + + radsqliteRowDescriptionDelete(row); + return OK; +} + + +// ##################### API Functions ##################### + +void dbsqliteHistoryInit (void) +{ + SQLITE_ROW_ID rowDesc; + Data_Indices index; + int retVal; + SQLITE_DATABASE_ID historyDB = NULL; + + historyDB = radsqliteOpen(getHistoryDBFilename()); + if (historyDB == NULL) + { + radMsgLog (PRI_HIGH, "dbsqliteHistoryInit: failed to open %s!", getHistoryDBFilename()); + return; + } + + // Does the day history table exist? + if (radsqliteTableIfExists(historyDB, WVIEW_DAY_HISTORY_TABLE)) + { + radsqliteClose(historyDB); + return; + } + + // We need to create the table: + // Define the row first: + rowDesc = radsqliteRowDescriptionCreate(); + if (rowDesc == NULL) + { + radsqliteClose(historyDB); + radMsgLog(PRI_HIGH, "dbsqliteHistoryInit: radsqliteRowDescriptionCreate failed!"); + return; + } + + // Populate the table: + retVal = radsqliteRowDescriptionAddField(rowDesc, + "date", + SQLITE_FIELD_BIGINT | SQLITE_FIELD_PRI_KEY, + 0); + if (retVal == ERROR) + { + radsqliteClose(historyDB); + radMsgLog(PRI_HIGH, "dbsqliteHistoryInit: databaseRowDescriptionAddField failed!"); + radsqliteRowDescriptionDelete(rowDesc); + return; + } + + for (index = 0; index < DATA_INDEX_MAX; index ++) + { + retVal = radsqliteRowDescriptionAddField(rowDesc, + historyName[index], + SQLITE_FIELD_DOUBLE, + 0); + if (retVal == ERROR) + { + radsqliteClose(historyDB); + radMsgLog(PRI_HIGH, "dbsqliteHistoryInit: databaseRowDescriptionAddField failed!"); + radsqliteRowDescriptionDelete(rowDesc); + return; + } + } + + // Now create the table: + if (radsqliteTableCreate(historyDB, WVIEW_DAY_HISTORY_TABLE, rowDesc) == ERROR) + { + radsqliteClose(historyDB); + radMsgLog(PRI_HIGH, "dbsqliteHistoryInit: radsqliteTableCreate failed!"); + radsqliteRowDescriptionDelete(rowDesc); + return; + } + + // We're done: + radsqliteRowDescriptionDelete(rowDesc); + + radsqliteClose(historyDB); + return; +} + +// PRAGMA statement to modify the operation of the SQLite library +int dbsqliteHistoryPragmaSet(char *pragma, char *setting) +{ + char query[DB_SQLITE_QUERY_LENGTH_MAX]; + SQLITE_DATABASE_ID historyDB = NULL; + + historyDB = radsqliteOpen(getHistoryDBFilename()); + if (historyDB == NULL) + { + radMsgLog (PRI_HIGH, "dbsqliteHistoryPragmaSet: failed to open %s!", getHistoryDBFilename()); + return ERROR; + } + + // Check SQLite version if a journalling pragma: + if (!strcmp(pragma, "journal_mode")) + { + if (SQLITE_VERSION_NUMBER < 3005009) + { + // Not supported: + radsqliteClose(historyDB); + return OK; + } + } + + sprintf (query, "PRAGMA %s = %s", pragma, setting); + + // Execute the query: + if (radsqliteQuery(historyDB, query, FALSE) == ERROR) + { + return ERROR; + } + + radsqliteClose(historyDB); + return OK; +} + +int dbsqliteHistoryInsertDay (HISTORY_DATA* data) +{ + SQLITE_DATABASE_ID historyDB = NULL; + + historyDB = radsqliteOpen(getHistoryDBFilename()); + if (historyDB == NULL) + { + radMsgLog (PRI_HIGH, "dbsqliteHistoryInsertDay: failed to open %s!", getHistoryDBFilename()); + return ERROR; + } + + // Now do some inserting: + if (insertDBHistoryData(historyDB, data) == ERROR) + { + radsqliteClose(historyDB); + radMsgLog(PRI_HIGH, "dbsqliteHistoryInsertDay: insertDBHistoryData failed!"); + return ERROR; + } + + radsqliteClose(historyDB); + return OK; +} + +int dbsqliteHistoryGetDay (time_t date, HISTORY_DATA* store) +{ + SQLITE_DATABASE_ID historyDB = NULL; + + historyDB = radsqliteOpen(getHistoryDBFilename()); + if (historyDB == NULL) + { + radMsgLog (PRI_HIGH, "dbsqliteHistoryInsertDay: failed to open %s!", getHistoryDBFilename()); + return ERROR; + } + + // First make sure the day history table exists: + if (! radsqliteTableIfExists(historyDB, WVIEW_DAY_HISTORY_TABLE)) + { + radsqliteClose(historyDB); + return ERROR; + } + + // Try to get the day requested: + if (getHistoryRecord(historyDB, date, store) == ERROR) + { + radsqliteClose(historyDB); + return ERROR; + } + + radsqliteClose(historyDB); + return OK; +} + +#endif + diff --git a/common/dbsqliteNOAA.c b/common/dbsqliteNOAA.c new file mode 100755 index 0000000..8d685f1 --- /dev/null +++ b/common/dbsqliteNOAA.c @@ -0,0 +1,1158 @@ +//---------------------------------------------------------------------------- +// +// FILENAME: +// dbsqliteNOAA.c +// +// PURPOSE: +// Provide the weather station NOAA database utilities. +// +// REVISION HISTORY: +// Date Engineer Revision Remarks +// 03/05/2009 M.S. Teel 0 Original +// +// NOTES: +// +// +// LICENSE: +// Copyright (c) 2009, Mark S. Teel (mark@teel.ws) +// +// This source code is released for free distribution under the terms +// of the GNU General Public License. +// +//---------------------------------------------------------------------------- + +// ... System include files +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// ... Library include files +#include + +// ... Local include files +#include + +#if (defined(BUILD_HTMLGEND) || defined(BUILD_UTILITIES)) + + +// ... local memory: + +static SQLITE_DATABASE_ID noaaDB = NULL; +static WAVG noaaWindAvg; + + +// ... ----- static (local) methods ----- + +static const char* noaaGetDBFilename(void) +{ + static char dbNOAAFilename[_MAX_PATH]; + + if (strlen(dbsqliteArchiveGetPath()) > 0) + { + sprintf (dbNOAAFilename, "%s/%s", dbsqliteArchiveGetPath(), WVIEW_NOAA_DATABASE); + } + else + { + sprintf (dbNOAAFilename, "%s/%s", wvutilsGetArchivePath(), WVIEW_NOAA_DATABASE); + } + + return dbNOAAFilename; +} + +static int noaaExtractValues (SQLITE_DIRECT_ROW row, NOAA_DAY_REC* newRec) +{ + SQLITE_FIELD_ID field; + struct tm bknTime; + time_t tempTime; + + field = radsqlitedirectFieldGet(row, "meanTemp"); + if (field == NULL) + { + return ERROR; + } + else + { + newRec->meanTemp = (float)radsqliteFieldGetDoubleValue(field); + } + + field = radsqlitedirectFieldGet(row, "highTemp"); + if (field == NULL) + { + return ERROR; + } + else + { + newRec->highTemp = (float)radsqliteFieldGetDoubleValue(field); + } + + field = radsqlitedirectFieldGet(row, "highTempTime"); + if (field == NULL) + { + return ERROR; + } + else + { + tempTime = (time_t)radsqliteFieldGetBigIntValue(field); + localtime_r(&tempTime, &bknTime); + sprintf(newRec->highTempTime, "%2.2d:%2.2d", + bknTime.tm_hour, + bknTime.tm_min); + } + + field = radsqlitedirectFieldGet(row, "lowTemp"); + if (field == NULL) + { + return ERROR; + } + else + { + newRec->lowTemp = (float)radsqliteFieldGetDoubleValue(field); + } + + field = radsqlitedirectFieldGet(row, "lowTempTime"); + if (field == NULL) + { + return ERROR; + } + else + { + tempTime = (time_t)radsqliteFieldGetBigIntValue(field); + localtime_r(&tempTime, &bknTime); + sprintf(newRec->lowTempTime, "%2.2d:%2.2d", + bknTime.tm_hour, + bknTime.tm_min); + } + + field = radsqlitedirectFieldGet(row, "heatDegDays"); + if (field == NULL) + { + return ERROR; + } + else + { + newRec->heatDegDays = (float)radsqliteFieldGetDoubleValue(field); + } + + field = radsqlitedirectFieldGet(row, "coolDegDays"); + if (field == NULL) + { + return ERROR; + } + else + { + newRec->coolDegDays = (float)radsqliteFieldGetDoubleValue(field); + } + + field = radsqlitedirectFieldGet(row, "rain"); + if (field == NULL) + { + return ERROR; + } + else + { + newRec->rain = (float)radsqliteFieldGetDoubleValue(field); + } + + field = radsqlitedirectFieldGet(row, "avgWind"); + if (field == NULL) + { + return ERROR; + } + else + { + newRec->avgWind = (float)radsqliteFieldGetDoubleValue(field); + } + + field = radsqlitedirectFieldGet(row, "highWind"); + if (field == NULL) + { + return ERROR; + } + else + { + newRec->highWind = (float)radsqliteFieldGetDoubleValue(field); + } + + field = radsqlitedirectFieldGet(row, "highWindTime"); + if (field == NULL) + { + return ERROR; + } + else + { + tempTime = (time_t)radsqliteFieldGetBigIntValue(field); + localtime_r(&tempTime, &bknTime); + sprintf(newRec->highWindTime, "%2.2d:%2.2d", + bknTime.tm_hour, + bknTime.tm_min); + } + + field = radsqlitedirectFieldGet(row, "domWindDir"); + if (field == NULL) + { + return ERROR; + } + else + { + newRec->domWindDir = (int)radsqliteFieldGetBigIntValue(field); + } + + return OK; +} + +static int noaaGetRecord (time_t dateTime, NOAA_DAY_REC* newRec) +{ + char query[DB_SQLITE_QUERY_LENGTH_MAX]; + SQLITE_DIRECT_ROW row; + + if (noaaDB == NULL) + { + radMsgLog (PRI_HIGH, "noaaGetRecord: failed to open %s!", noaaGetDBFilename()); + return ERROR; + } + + // grab the entire row: + sprintf (query, "SELECT * FROM %s WHERE dateTime = '%d'", + WVIEW_NOAA_TABLE, (int)dateTime); + + // Execute the query: + if (radsqlitedirectQuery(noaaDB, query, TRUE) == ERROR) + { + return ERROR; + } + + row = radsqlitedirectGetRow(noaaDB); + if (row == NULL) + { + radsqlitedirectReleaseResults(noaaDB); + return ERROR; + } + + // copy it to the internal sensor: + if (noaaExtractValues(row, newRec) == ERROR) + { + radsqlitedirectReleaseResults(noaaDB); + radMsgLog (PRI_MEDIUM, "noaaGetRecord: radsqlitedirectFieldGet failed!"); + return ERROR; + } + + radsqlitedirectReleaseResults(noaaDB); + return OK; +} + +static int noaaDeleteRecord (time_t dateTime) +{ + char query[DB_SQLITE_QUERY_LENGTH_MAX]; + + if (noaaDB == NULL) + { + radMsgLog (PRI_HIGH, "noaaGetRecord: failed to open %s!", noaaGetDBFilename()); + return ERROR; + } + + // delete the entire row: + sprintf (query, "DELETE FROM %s WHERE dateTime = '%d'", + WVIEW_NOAA_TABLE, (int)dateTime); + + // Execute the query: + if (radsqlitedirectQuery(noaaDB, query, FALSE) == ERROR) + { + return ERROR; + } + + return OK; +} + +static int noaaInsertData(time_t timestamp, SENSOR_STORE* sensorStore) +{ + SQLITE_ROW_ID row; + SQLITE_FIELD_ID field; + time_t noaaDayTime; + struct tm bknTime; + NOAA_DAY_REC store; + char query[DB_SQLITE_QUERY_LENGTH_MAX]; + double tempd, sum; + int tempint; + WV_SENSOR* sensors = sensorStore->sensor[STF_DAY]; + + noaaDayTime = timestamp; + localtime_r(&noaaDayTime, &bknTime); + bknTime.tm_hour = 0; + bknTime.tm_min = 0; + bknTime.tm_sec = 0; + bknTime.tm_isdst = -1; + noaaDayTime = mktime(&bknTime); + + // First see if the record exists: + if (noaaGetRecord(noaaDayTime, &store) == OK) + { + // Delete so we can replace it: + noaaDeleteRecord(noaaDayTime); + } + + // create a new record: + row = radsqliteTableDescriptionGet(noaaDB, WVIEW_NOAA_TABLE); + if (row == NULL) + { + radMsgLog (PRI_HIGH, "dbsqliteNOAA: databaseTableDescriptionGet failed!"); + return ERROR; + } + + field = radsqliteFieldGet(row, "dateTime"); + if (field == NULL) + { + radMsgLog (PRI_HIGH, "dbsqliteNOAA: radsqliteFieldGet failed!"); + radsqliteRowDescriptionDelete(row); + return ERROR; + } + else + { + radsqliteFieldSetBigIntValue(field, (uint64_t)noaaDayTime); + } + + field = radsqliteFieldGet(row, "meanTemp"); + if (field == NULL) + { + radMsgLog (PRI_HIGH, "dbsqliteNOAA: radsqliteFieldGet failed!"); + radsqliteRowDescriptionDelete(row); + return ERROR; + } + else + { + if (sensors[SENSOR_OUTTEMP].samples > 0) + { + tempd = (double)sensors[SENSOR_OUTTEMP].cumulative; + tempd /= (double)sensors[SENSOR_OUTTEMP].samples; + radsqliteFieldSetDoubleValue(field, tempd); + } + else + { + radsqliteFieldSetToNull(field); + } + } + + field = radsqliteFieldGet(row, "highTemp"); + if (field == NULL) + { + radMsgLog (PRI_HIGH, "dbsqliteNOAA: radsqliteFieldGet failed!"); + radsqliteRowDescriptionDelete(row); + return ERROR; + } + else + { + if (sensors[SENSOR_OUTTEMP].samples > 0) + { + tempd = (double)sensors[SENSOR_OUTTEMP].high; + radsqliteFieldSetDoubleValue(field, tempd); + } + else + { + radsqliteFieldSetToNull(field); + } + } + + field = radsqliteFieldGet(row, "highTempTime"); + if (field == NULL) + { + radMsgLog (PRI_HIGH, "dbsqliteNOAA: radsqliteFieldGet failed!"); + radsqliteRowDescriptionDelete(row); + return ERROR; + } + else + { + if (sensors[SENSOR_OUTTEMP].time_high > 0) + { + radsqliteFieldSetBigIntValue(field, + (uint64_t)sensors[SENSOR_OUTTEMP].time_high); + } + else + { + radsqliteFieldSetToNull(field); + } + } + + field = radsqliteFieldGet(row, "lowTemp"); + if (field == NULL) + { + radMsgLog (PRI_HIGH, "dbsqliteNOAA: radsqliteFieldGet failed!"); + radsqliteRowDescriptionDelete(row); + return ERROR; + } + else + { + if (sensors[SENSOR_OUTTEMP].samples > 0) + { + tempd = (double)sensors[SENSOR_OUTTEMP].low; + radsqliteFieldSetDoubleValue(field, tempd); + } + else + { + radsqliteFieldSetToNull(field); + } + } + + field = radsqliteFieldGet(row, "lowTempTime"); + if (field == NULL) + { + radMsgLog (PRI_HIGH, "dbsqliteNOAA: radsqliteFieldGet failed!"); + radsqliteRowDescriptionDelete(row); + return ERROR; + } + else + { + if (sensors[SENSOR_OUTTEMP].time_low > 0) + { + radsqliteFieldSetBigIntValue(field, + (uint64_t)sensors[SENSOR_OUTTEMP].time_low); + } + else + { + radsqliteFieldSetToNull(field); + } + } + + field = radsqliteFieldGet(row, "heatDegDays"); + if (field == NULL) + { + radMsgLog (PRI_HIGH, "dbsqliteNOAA: radsqliteFieldGet failed!"); + radsqliteRowDescriptionDelete(row); + return ERROR; + } + else + { + if (sensors[SENSOR_OUTTEMP].samples > 0) + { + sum = (sensors[SENSOR_OUTTEMP].high + sensors[SENSOR_OUTTEMP].low)/2; + tempd = ((sum < 65) ? 65 - sum : 0); + radsqliteFieldSetDoubleValue(field, tempd); + } + else + { + radsqliteFieldSetToNull(field); + } + } + + field = radsqliteFieldGet(row, "coolDegDays"); + if (field == NULL) + { + radMsgLog (PRI_HIGH, "dbsqliteNOAA: radsqliteFieldGet failed!"); + radsqliteRowDescriptionDelete(row); + return ERROR; + } + else + { + if (sensors[SENSOR_OUTTEMP].samples > 0) + { + sum = (sensors[SENSOR_OUTTEMP].high + sensors[SENSOR_OUTTEMP].low)/2; + tempd = ((sum > 65) ? sum - 65 : 0); + radsqliteFieldSetDoubleValue(field, tempd); + } + else + { + radsqliteFieldSetToNull(field); + } + } + + field = radsqliteFieldGet(row, "rain"); + if (field == NULL) + { + radMsgLog (PRI_HIGH, "dbsqliteNOAA: radsqliteFieldGet failed!"); + radsqliteRowDescriptionDelete(row); + return ERROR; + } + else + { + if (sensors[SENSOR_RAIN].samples > 0) + { + tempd = (double)sensors[SENSOR_RAIN].cumulative; + radsqliteFieldSetDoubleValue(field, tempd); + } + else + { + radsqliteFieldSetToNull(field); + } + } + + field = radsqliteFieldGet(row, "avgWind"); + if (field == NULL) + { + radMsgLog (PRI_HIGH, "dbsqliteNOAA: radsqliteFieldGet failed!"); + radsqliteRowDescriptionDelete(row); + return ERROR; + } + else + { + if (sensors[SENSOR_WSPEED].samples > 0) + { + tempd = (double)sensors[SENSOR_WSPEED].cumulative/sensors[SENSOR_WSPEED].samples; + radsqliteFieldSetDoubleValue(field, tempd); + } + else + { + radsqliteFieldSetToNull(field); + } + } + + field = radsqliteFieldGet(row, "highWind"); + if (field == NULL) + { + radMsgLog (PRI_HIGH, "dbsqliteNOAA: radsqliteFieldGet failed!"); + radsqliteRowDescriptionDelete(row); + return ERROR; + } + else + { + if (sensors[SENSOR_WGUST].samples > 0) + { + tempd = (double)sensors[SENSOR_WGUST].high; + radsqliteFieldSetDoubleValue(field, tempd); + } + else + { + radsqliteFieldSetToNull(field); + } + } + + field = radsqliteFieldGet(row, "highWindTime"); + if (field == NULL) + { + radMsgLog (PRI_HIGH, "dbsqliteNOAA: radsqliteFieldGet failed!"); + radsqliteRowDescriptionDelete(row); + return ERROR; + } + else + { + if (sensors[SENSOR_WGUST].time_high > 0) + { + radsqliteFieldSetBigIntValue(field, + (uint64_t)sensors[SENSOR_WGUST].time_high); + } + else + { + radsqliteFieldSetToNull(field); + } + } + + field = radsqliteFieldGet(row, "domWindDir"); + if (field == NULL) + { + radMsgLog (PRI_HIGH, "dbsqliteNOAA: radsqliteFieldGet failed!"); + radsqliteRowDescriptionDelete(row); + return ERROR; + } + else + { + if (sensors[SENSOR_WSPEED].samples > 0) + { + tempint = windAverageCompute(&sensorStore->wind[STF_DAY]); + radsqliteFieldSetBigIntValue(field, (uint64_t)tempint); + } + else + { + radsqliteFieldSetToNull(field); + } + } + + // insert the row: + if (radsqliteTableInsertRow(noaaDB, WVIEW_NOAA_TABLE, row) == ERROR) + { + radMsgLog (PRI_HIGH, "dbsqlite: radsqliteTableInsertRow (hilow) failed!"); + radsqliteRowDescriptionDelete(row); + return ERROR; + } + + radsqliteRowDescriptionDelete(row); + return OK; +} + +static time_t noaaGetFirstUpdateDay (void) +{ + char query[DB_SQLITE_QUERY_LENGTH_MAX]; + SQLITE_DIRECT_ROW rowDescr; + SQLITE_FIELD_ID field; + time_t retVal; + + if (noaaDB == NULL) + { + radMsgLog (PRI_HIGH, "getNewestDateTime: failed to open %s!", noaaGetDBFilename()); + return ERROR; + } + + // Build the query: + sprintf (query, "SELECT MIN(dateTime) AS 'min' FROM %s", WVIEW_NOAA_TABLE); + + // Execute the query: + if (radsqlitedirectQuery(noaaDB, query, TRUE) == ERROR) + { + radMsgLog (PRI_HIGH, "getNewestDateTime: radsqlitedirectQuery failed!"); + return ERROR; + } + + rowDescr = radsqlitedirectGetRow(noaaDB); + if (rowDescr == NULL) + { + radMsgLog (PRI_MEDIUM, + "getNewestDateTime: radsqlitedirectGetRow failed!"); + radsqlitedirectReleaseResults(noaaDB); + return ERROR; + } + + field = radsqlitedirectFieldGet(rowDescr, "min"); + if (field == NULL) + { + radMsgLog (PRI_MEDIUM, + "getNewestDateTime: radsqlitedirectFieldGet failed!"); + radsqlitedirectReleaseResults(noaaDB); + return ERROR; + } + + retVal = (time_t)radsqliteFieldGetBigIntValue(field); + + // Clean up: + radsqlitedirectReleaseResults(noaaDB); + + return retVal; +} + +static time_t noaaGetLastUpdateDay (void) +{ + char query[DB_SQLITE_QUERY_LENGTH_MAX]; + SQLITE_DIRECT_ROW rowDescr; + SQLITE_FIELD_ID field; + time_t retVal; + + if (noaaDB == NULL) + { + radMsgLog (PRI_HIGH, "noaaGetLastUpdateDay: failed to open %s!", noaaGetDBFilename()); + return ERROR; + } + + // Build the query: + sprintf (query, "SELECT MAX(dateTime) AS 'max' FROM %s", WVIEW_NOAA_TABLE); + + // Execute the query: + if (radsqlitedirectQuery(noaaDB, query, TRUE) == ERROR) + { + radMsgLog (PRI_HIGH, "noaaGetLastUpdateDay: radsqlitedirectQuery failed!"); + return ERROR; + } + + rowDescr = radsqlitedirectGetRow(noaaDB); + if (rowDescr == NULL) + { + radMsgLog (PRI_MEDIUM, + "noaaGetLastUpdateDay: radsqlitedirectGetRow failed!"); + radsqlitedirectReleaseResults(noaaDB); + return ERROR; + } + + field = radsqlitedirectFieldGet(rowDescr, "max"); + if (field == NULL) + { + radMsgLog (PRI_MEDIUM, + "noaaGetLastUpdateDay: radsqlitedirectFieldGet failed!"); + radsqlitedirectReleaseResults(noaaDB); + return ERROR; + } + + retVal = (time_t)radsqliteFieldGetBigIntValue(field); + + // Clean up: + radsqlitedirectReleaseResults(noaaDB); + + return retVal; +} + + +//////////////////////////////////////////////////////////////////////////////// + +// Initialize the NOAA database (returns OK or ERROR): +int dbsqliteNOAAInit(void) +{ + SQLITE_ROW_ID rowDesc, newrow; + SQLITE_FIELD_ID field; + SENSOR_TYPES index; + int retVal, numrecs = 0, numNOAARecs = 0; + int i, done = FALSE; + char tableName[64]; + ARCHIVE_PKT archiveRec; + SENSOR_STORE sensorStore; + time_t archiveTime, startTime, stopTime, hilowTime; + char binName[16]; + struct tm bknTime; + time_t LastNOAAUpdateTime, LastArchiveTime; + + noaaDB = radsqliteOpen(noaaGetDBFilename()); + if (noaaDB == NULL) + { + radMsgLog (PRI_HIGH, "dbsqliteNOAAInit: failed to open %s!", noaaGetDBFilename()); + return ERROR; + } + + + // Does the NOAA table exist? + if (! radsqliteTableIfExists(noaaDB, WVIEW_NOAA_TABLE)) + { + // Make writes faster (and less safe) by avoiding fsyncs: + dbsqliteNOAAPragmaSet("synchronous", "off"); + + // We need to create the table: + // Define the row first: + rowDesc = radsqliteRowDescriptionCreate(); + if (rowDesc == NULL) + { + radsqliteClose(noaaDB); + radMsgLog(PRI_HIGH, "dbsqliteNOAAInit: radsqliteRowDescriptionCreate failed!"); + return ERROR; + } + + // Populate the table: + retVal = radsqliteRowDescriptionAddField(rowDesc, + "dateTime", + SQLITE_FIELD_BIGINT | SQLITE_FIELD_PRI_KEY, + 0); + if (retVal == ERROR) + { + radsqliteClose(noaaDB); + radMsgLog(PRI_HIGH, "dbsqliteNOAAInit: databaseRowDescriptionAddField failed!"); + radsqliteRowDescriptionDelete(rowDesc); + return ERROR; + } + + retVal = radsqliteRowDescriptionAddField(rowDesc, "meanTemp", SQLITE_FIELD_DOUBLE, 0); + if (retVal == ERROR) + { + radsqliteClose(noaaDB); + radMsgLog(PRI_HIGH, "dbsqliteNOAAInit: databaseRowDescriptionAddField failed!"); + radsqliteRowDescriptionDelete(rowDesc); + return ERROR; + } + retVal = radsqliteRowDescriptionAddField(rowDesc, "highTemp", SQLITE_FIELD_DOUBLE, 0); + if (retVal == ERROR) + { + radsqliteClose(noaaDB); + radMsgLog(PRI_HIGH, "dbsqliteNOAAInit: databaseRowDescriptionAddField failed!"); + radsqliteRowDescriptionDelete(rowDesc); + return ERROR; + } + retVal = radsqliteRowDescriptionAddField(rowDesc, "highTempTime", SQLITE_FIELD_BIGINT, 0); + if (retVal == ERROR) + { + radsqliteClose(noaaDB); + radMsgLog(PRI_HIGH, "dbsqliteNOAAInit: databaseRowDescriptionAddField failed!"); + radsqliteRowDescriptionDelete(rowDesc); + return ERROR; + } + retVal = radsqliteRowDescriptionAddField(rowDesc, "lowTemp", SQLITE_FIELD_DOUBLE, 0); + if (retVal == ERROR) + { + radsqliteClose(noaaDB); + radMsgLog(PRI_HIGH, "dbsqliteNOAAInit: databaseRowDescriptionAddField failed!"); + radsqliteRowDescriptionDelete(rowDesc); + return ERROR; + } + retVal = radsqliteRowDescriptionAddField(rowDesc, "lowTempTime", SQLITE_FIELD_BIGINT, 0); + if (retVal == ERROR) + { + radsqliteClose(noaaDB); + radMsgLog(PRI_HIGH, "dbsqliteNOAAInit: databaseRowDescriptionAddField failed!"); + radsqliteRowDescriptionDelete(rowDesc); + return ERROR; + } + retVal = radsqliteRowDescriptionAddField(rowDesc, "heatDegDays", SQLITE_FIELD_DOUBLE, 0); + if (retVal == ERROR) + { + radsqliteClose(noaaDB); + radMsgLog(PRI_HIGH, "dbsqliteNOAAInit: databaseRowDescriptionAddField failed!"); + radsqliteRowDescriptionDelete(rowDesc); + return ERROR; + } + retVal = radsqliteRowDescriptionAddField(rowDesc, "coolDegDays", SQLITE_FIELD_DOUBLE, 0); + if (retVal == ERROR) + { + radsqliteClose(noaaDB); + radMsgLog(PRI_HIGH, "dbsqliteNOAAInit: databaseRowDescriptionAddField failed!"); + radsqliteRowDescriptionDelete(rowDesc); + return ERROR; + } + retVal = radsqliteRowDescriptionAddField(rowDesc, "rain", SQLITE_FIELD_DOUBLE, 0); + if (retVal == ERROR) + { + radsqliteClose(noaaDB); + radMsgLog(PRI_HIGH, "dbsqliteNOAAInit: databaseRowDescriptionAddField failed!"); + radsqliteRowDescriptionDelete(rowDesc); + return ERROR; + } + retVal = radsqliteRowDescriptionAddField(rowDesc, "avgWind", SQLITE_FIELD_DOUBLE, 0); + if (retVal == ERROR) + { + radsqliteClose(noaaDB); + radMsgLog(PRI_HIGH, "dbsqliteNOAAInit: databaseRowDescriptionAddField failed!"); + radsqliteRowDescriptionDelete(rowDesc); + return ERROR; + } + retVal = radsqliteRowDescriptionAddField(rowDesc, "highWind", SQLITE_FIELD_DOUBLE, 0); + if (retVal == ERROR) + { + radsqliteClose(noaaDB); + radMsgLog(PRI_HIGH, "dbsqliteNOAAInit: databaseRowDescriptionAddField failed!"); + radsqliteRowDescriptionDelete(rowDesc); + return ERROR; + } + retVal = radsqliteRowDescriptionAddField(rowDesc, "highWindTime", SQLITE_FIELD_BIGINT, 0); + if (retVal == ERROR) + { + radsqliteClose(noaaDB); + radMsgLog(PRI_HIGH, "dbsqliteNOAAInit: databaseRowDescriptionAddField failed!"); + radsqliteRowDescriptionDelete(rowDesc); + return ERROR; + } + retVal = radsqliteRowDescriptionAddField(rowDesc, "domWindDir", SQLITE_FIELD_BIGINT, 0); + if (retVal == ERROR) + { + radsqliteClose(noaaDB); + radMsgLog(PRI_HIGH, "dbsqliteNOAAInit: databaseRowDescriptionAddField failed!"); + radsqliteRowDescriptionDelete(rowDesc); + return ERROR; + } + + + // Now create the table: + if (radsqliteTableCreate(noaaDB, WVIEW_NOAA_TABLE, rowDesc) == ERROR) + { + radsqliteClose(noaaDB); + radMsgLog(PRI_HIGH, "dbsqliteNOAAInit: radsqliteTableCreate failed!"); + radsqliteRowDescriptionDelete(rowDesc); + return ERROR; + } + + // We're done with this table: + radsqliteRowDescriptionDelete(rowDesc); + + radMsgLog(PRI_STATUS, "NOAA DB: %s table created", WVIEW_NOAA_TABLE); + + + // OK, if we had to create the table, assume it should be completely populated: + startTime = dbsqliteArchiveGetNextRecord(0, &archiveRec); + if (startTime == (time_t)ERROR) + { + radMsgLog(PRI_STATUS, "NOAA DB: Archive table is empty - starting with new station..."); + } + else + { + // Back fill the table: + localtime_r(&startTime, &bknTime); + bknTime.tm_hour = 4; // Avoid DST issues + bknTime.tm_min = 0; + bknTime.tm_sec = 0; + bknTime.tm_isdst = -1; + startTime = mktime(&bknTime); + + stopTime = time(NULL); + localtime_r(&stopTime, &bknTime); + bknTime.tm_hour = 0; + bknTime.tm_min = 0; + bknTime.tm_sec = 0; + bknTime.tm_isdst = -1; + stopTime = mktime(&bknTime); + + radMsgLog(PRI_STATUS, "NOAA DB: back filling tables with ALL HILOW data"); + radMsgLog(PRI_STATUS, "NOAA DB: syncing %4.4d%2.2d%2.2d => %4.4d%2.2d%2.2d", + wvutilsGetYear(startTime), + wvutilsGetMonth(startTime), + wvutilsGetDay(startTime), + wvutilsGetYear(stopTime-1), + wvutilsGetMonth(stopTime-1), + wvutilsGetDay(stopTime-1)); + radMsgLog(PRI_STATUS, "NOAA DB: (this may take a while ...)"); + + // Loop through all days: + while (startTime < stopTime) + { + localtime_r(&startTime, &bknTime); + bknTime.tm_hour = 0; + bknTime.tm_min = 0; + bknTime.tm_sec = 0; + bknTime.tm_isdst = -1; + hilowTime = mktime(&bknTime); + + windAverageReset(&sensorStore.wind[STF_DAY]); + sensorClearSet(sensorStore.sensor[STF_DAY]); + + retVal = dbsqliteHiLowGetDay(hilowTime, &sensorStore, STF_DAY); + if (retVal == ERROR) + { + return ERROR; + } + else if (retVal == 0) + { + // Continue in case there is a gap: + startTime += WV_SECONDS_IN_DAY; + continue; + } + else + { + // Write a record: + numrecs += retVal; + + if (noaaInsertData(startTime, &sensorStore) == OK) + { + numNOAARecs ++; + } + + startTime += WV_SECONDS_IN_DAY; + } + } + + radMsgLog(PRI_STATUS, "NOAA DB: done: %d HILOW records => %d NOAA records", + numrecs, numNOAARecs); + } + } + else + { + radMsgLog(PRI_STATUS, "NOAA DB: OK"); + } + + // Set normal syncing behavior: + dbsqliteNOAAPragmaSet("synchronous", "normal"); + + return OK; +} + +void dbsqliteNOAAExit(void) +{ + if (noaaDB) + radsqliteClose(noaaDB); +} + +// set a PRAGMA to modify the operation of the SQLite library: +// Returns: OK or ERROR +int dbsqliteNOAAPragmaSet(char* pragma, char* setting) +{ + char query[DB_SQLITE_QUERY_LENGTH_MAX]; + + // Check SQLite version if a journalling pragma: + if (!strcmp(pragma, "journal_mode")) + { + if (SQLITE_VERSION_NUMBER < 3005009) + { + // Not supported: + return OK; + } + } + + sprintf (query, "PRAGMA %s = %s", pragma, setting); + + // Execute the query: + if (radsqliteQuery(noaaDB, query, FALSE) == ERROR) + { + return ERROR; + } + + return OK; +} + +// ... dbsqliteNOAAGetDay: summarize day records for NOAA reports; +// ... this will zero out the NOAA_DAY_REC store before beginning; +// ... returns OK or ERROR if day not found in archives +int dbsqliteNOAAGetDay +( + NOAA_DAY_REC *store, + time_t day +) +{ + int retVal, year, month; + struct tm locTime; + time_t ntime; + + localtime_r (&day, &locTime); + locTime.tm_hour = 0; + locTime.tm_sec = 0; + locTime.tm_isdst = -1; + ntime = mktime(&locTime); + + // MUST be done here - noaaGetRecord expects it! + memset (store, 0, sizeof (NOAA_DAY_REC)); + + store->year = locTime.tm_year + 1900; + store->month = locTime.tm_mon + 1; + store->day = locTime.tm_mday; + + retVal = noaaGetRecord(ntime, store); + return retVal; +} + +int dbsqliteNOAAComputeNorms (float *temps, float *rains, float *yearTemp, float *yearRain) +{ + NOAA_DAY_REC record; + int i, numDays[13], numMonths[13]; + float tempSum[13], rainSum[13]; + int thismonth, lastmonth = -1; + time_t ntime, firstDay, lastDay; + struct tm locTime; + + memset (temps, 0, 13 * sizeof (float)); + memset (rains, 0, 13 * sizeof (float)); + memset (numDays, 0, 13 * sizeof (int)); + memset (numMonths, 0, 13 * sizeof (int)); + memset (tempSum, 0, 13 * sizeof (float)); + memset (rainSum, 0, 13 * sizeof (float)); + *yearTemp = *yearRain = 0.0; + + firstDay = noaaGetFirstUpdateDay(); + lastDay = noaaGetLastUpdateDay(); + if ((int)firstDay == ERROR || (int)lastDay == ERROR || + (int)firstDay == 0 || (int)lastDay == 0) + { + return ERROR; + } + + for (ntime = firstDay; ntime <= lastDay; ntime += WV_SECONDS_IN_DAY) + { + if (dbsqliteNOAAGetDay(&record, ntime) == ERROR) + { + continue; + } + + thismonth = wvutilsGetMonth(ntime); + tempSum[thismonth] += record.meanTemp; + rainSum[thismonth] += record.rain; + numDays[thismonth] ++; + if (thismonth != lastmonth) + { + numMonths[thismonth] ++; + } + lastmonth = thismonth; + } + + // Now make sense of it all: + for (i = 1; i < 13; i ++) + { + if (numDays[i] > 0) + { + temps[i] = tempSum[i]/numDays[i]; + rains[i] = rainSum[i]/numMonths[i]; + } + else + { + temps[i] = rains[i] = 0; + } + + *yearTemp += temps[i]; + *yearRain += rains[i]; + } + + *yearTemp /= 12; + + return OK; +} + +void dbsqliteNOAAUpdate (void) +{ + time_t ntime, lastNOAARecTime, nowDay, lastInsertTime = 0; + struct tm locTime; + SENSOR_STORE sensorStore; + int retVal, numrecs = 0, numNOAARecs = 0; + char fileName[128]; + ARCHIVE_PKT archiveRec; + + nowDay = time(NULL); + localtime_r(&nowDay, &locTime); + locTime.tm_hour = 0; + locTime.tm_min = 0; + locTime.tm_sec = 0; + locTime.tm_isdst = -1; + nowDay = mktime(&locTime); + + lastNOAARecTime = noaaGetLastUpdateDay(); + if ((int)lastNOAARecTime == 0) + { + // Use the first archive time: + lastNOAARecTime = dbsqliteArchiveGetNextRecord(0, &archiveRec); + if (lastNOAARecTime == (time_t)ERROR) + { + radMsgLog(PRI_HIGH, "dbsqliteNOAAUpdate: Archive database empty - nothing to do..."); + return; + } + + lastNOAARecTime -= WV_SECONDS_IN_DAY; + localtime_r(&lastNOAARecTime, &locTime); + locTime.tm_hour = 0; + locTime.tm_min = 0; + locTime.tm_sec = 0; + locTime.tm_isdst = -1; + lastNOAARecTime = mktime(&locTime); + } + + ntime = lastNOAARecTime + WV_SECONDS_IN_DAY; + + // Is there any work to do? + if ((ntime + WV_SECONDS_IN_DAY) <= nowDay) + { + // yes! + radMsgLog(PRI_STATUS, "NOAA DB: syncing %4.4d%2.2d%2.2d => %4.4d%2.2d%2.2d", + wvutilsGetYear(ntime), + wvutilsGetMonth(ntime), + wvutilsGetDay(ntime), + wvutilsGetYear(nowDay-WV_SECONDS_IN_DAY), + wvutilsGetMonth(nowDay-WV_SECONDS_IN_DAY), + wvutilsGetDay(nowDay-WV_SECONDS_IN_DAY)); + + // Loop through all days: + while (ntime < nowDay) + { + // Don't create a record if we have advanced to "today": + if (! wvutilsTimeIsToday(ntime)) + { + windAverageReset(&sensorStore.wind[STF_DAY]); + sensorClearSet(sensorStore.sensor[STF_DAY]); + + retVal = dbsqliteHiLowGetDay(ntime, &sensorStore, STF_DAY); + if (retVal > 0) + { + // Write a record: + numrecs += retVal; + + if (noaaInsertData(ntime, &sensorStore) == OK) + { + numNOAARecs ++; + lastInsertTime = ntime; + } + } + } + + ntime += WV_SECONDS_IN_DAY; + } + + if (numrecs > 0) + { + radMsgLog(PRI_STATUS, "NOAA DB: done: %d HILOW records => %d NOAA records", + numrecs, numNOAARecs); + } + + if (lastInsertTime != 0) + { + sprintf (fileName, "%s/export/%s", wvutilsGetConfigPath(), WVIEW_NOAA_MARKER_FILE); + wvutilsWriteMarkerFile(fileName, lastInsertTime); + } + } + else + { + radMsgLog(PRI_STATUS, "NOAA DB: nothing to do: last NOAA %u, today %u", + ntime-WV_SECONDS_IN_DAY, nowDay); + } + + return; +} + +#endif + diff --git a/common/emailAlerts.c b/common/emailAlerts.c new file mode 100755 index 0000000..87a72e1 --- /dev/null +++ b/common/emailAlerts.c @@ -0,0 +1,87 @@ +/*--------------------------------------------------------------------------- + + FILENAME: + emailAlerts.c + + PURPOSE: + Provide the alert email API methods. + + REVISION HISTORY: + Date Engineer Revision Remarks + 03/07/2009 M.S. Teel 0 Original + + NOTES: + + + LICENSE: + Copyright (c) 2009, Mark S. Teel (mark@teel.ws) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +// ... System header files +#include + + +// ... Local header files +#include + +// ... Local memory: + +static ALERT_INFO alertInfo[ALERT_TYPE_MAX] = +{ + { "wview Test Email", "If you received this wview email alerts are working." }, + { "wview File System IO Alert: r/w error", "Failed to read/write file(s) - system disk full or corrupt!" }, + { "wview Station Interface Alert: Wakeup", "Vantage Pro wakeup failed - check cabling, device or interface!" }, + { "wview Station Interface Alert: Loop Error", "Loop data retrieval failed - current conditions may not be updating!" }, + { "wview Station Interface Alert: Archive Error", "Archive data retrieval failed - archive table and graphs may not be updating!" }, + { "wview Station Interface Alert: Interface Failure", "Station device interface failed - check driver, device or interface!" }, + { "wview Station Interface Alert: Corrupt Data", "Bogus or corrupt station data received - check station or cabling" }, + { "wview Station Interface Alert: Flatline Values", "Station data not changing - check station or interface" } +}; + +static time_t lastAlertTime[ALERT_TYPE_MAX] = +{ + 0, + 0, + 0 +}; + + +// ... define methods here + +int emailAlertSend (EmailAlertTypes type) +{ + WVIEW_MSG_ALERT alert; + + if ((time(NULL) - lastAlertTime[type]) < ALERT_NOTIFY_INTERVAL) + { + // Abate: + return OK; + } + + alert.alertType = type; + lastAlertTime[type] = time(NULL); + radMsgRouterMessageSend (WVIEW_MSG_TYPE_ALERT, &alert, sizeof(alert)); + return OK; +} + +char* emailAlertGetSubject (EmailAlertTypes type) +{ + if (type < ALERT_TYPE_MAX) + return alertInfo[type].subject; + else + return NULL; +} + +char* emailAlertGetBody (EmailAlertTypes type) +{ + if (type < ALERT_TYPE_MAX) + return alertInfo[type].body; + else + return NULL; +} + + diff --git a/common/emailAlerts.h b/common/emailAlerts.h new file mode 100644 index 0000000..914a536 --- /dev/null +++ b/common/emailAlerts.h @@ -0,0 +1,79 @@ +#ifndef INC_emailalertsh +#define INC_emailalertsh +/*--------------------------------------------------------------------------- + + FILENAME: + emailAlerts.h + + PURPOSE: + Define the Alert Email API. + Define system alert types. + + REVISION HISTORY: + Date Engineer Revision Remarks + 03/07/2009 M.S. Teel 0 Original + + NOTES: + + + LICENSE: + Copyright (c) 2009, Mark S. Teel (mark@teel.ws) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +// ... includes +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + + +// ... definitions +#define ALERT_NOTIFY_INTERVAL 3600 + +typedef enum +{ + ALERT_TYPE_TEST = 0, + ALERT_TYPE_FILE_IO, + ALERT_TYPE_STATION_VP_WAKEUP, + ALERT_TYPE_STATION_LOOP, + ALERT_TYPE_STATION_ARCHIVE, + ALERT_TYPE_STATION_DEVICE, + ALERT_TYPE_STATION_READ, + ALERT_TYPE_STATION_FLATLINE, + ALERT_TYPE_MAX +} EmailAlertTypes; + + +typedef struct +{ + char* subject; + char* body; +}__attribute__ ((packed)) ALERT_INFO; + + + +// ... API prototypes + +// ... send an alert email with given alert type: +extern int emailAlertSend (EmailAlertTypes type); + +extern char* emailAlertGetSubject (EmailAlertTypes type); + +extern char* emailAlertGetBody (EmailAlertTypes type); + +#endif + + diff --git a/common/lunarCycle.c b/common/lunarCycle.c new file mode 100755 index 0000000..b75bbf7 --- /dev/null +++ b/common/lunarCycle.c @@ -0,0 +1,494 @@ +/*--------------------------------------------------------------------------- + + FILENAME: + lunarCycle.c + + PURPOSE: + Provide lunar cycle computation utility. + + REVISION HISTORY: + Date Engineer Revision Remarks + 08/17/2005 M.S. Teel 0 Original + 12/01/2009 M. Hornsby 1 Add Moon Rise and Set + + NOTES: + + + LICENSE: + Copyright (c) 2005, Mark S. Teel (mark@teel.ws) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +// ... System header files +#include +#include +#include +#include +#include +#include + +#include "sysdefs.h" + + +typedef struct +{ + double rightascension; + double declination; + double parallax; +} +MOONLOCATION; + +typedef struct +{ + int hr; + int min; + double az; + int event; +} +MOONRISESET; + +static double VHz[3], RAn[3], Dec[3]; +static MOONRISESET MoonRise, MoonSet; + +#define PI 3.1415926535897932384626433832795 +#define RAD (PI/180.0) +#define SMALL_FLOAT (1e-12) + + +// Local methods: + +static double GetJulianDate (int year, int month, double day) +{ + int a, b, c, e; + if (month < 3) + { + year--; + month += 12; + } + if ((year > 1582) || + (year == 1582 && month > 10) || + (year == 1582 && month == 10 && day > 15)) + { + a = year/100; + b = 2 - a+a/4; + c = (int)(365.25 * year); + e = (int)(30.6001 * (month + 1)); + return (b + c + e + day + 1720994.5); + } + else + { + return 0; + } +} + +static double GetSunPosition (double j) +{ + double n, x, e, l, dl, v; + int i; + + n = 360/365.2422 * j; + i = (int)(n/360); + n = n - i*360.0; + x = n - 3.762863; + if (x < 0) + x += 360; + x *= RAD; + e = x; + do + { + dl = e - 0.016718 * sin(e) - x; + e = e - dl/(1 - 0.016718 * cos(e)); + } + while (fabs(dl) >= SMALL_FLOAT); + + v = 360/PI * atan(1.01686011182 * tan(e/2)); + l = v + 282.596403; + i = (int)(l/360); + l = l - i*360.0; + return l; +} + +static double GetMoonPosition (double j, double ls) +{ + + double ms, l, mm, n, ev, sms, ae, ec; + int i; + + ms = 0.985647332099*j - 3.762863; + if (ms < 0) + ms += 360.0; + l = 13.176396*j + 64.975464; + i = (int)(l/360); + l = l - i*360.0; + if (l < 0) + l += 360.0; + mm = l-0.1114041*j-349.383063; + i = (int)(mm/360); + mm -= i*360.0; + n = 151.950429 - 0.0529539*j; + i = (int)(n/360); + n -= i*360.0; + ev = 1.2739*sin((2*(l-ls)-mm)*RAD); + sms = sin(ms*RAD); + ae = 0.1858*sms; + mm += ev-ae- 0.37*sms; + ec = 6.2886*sin(mm*RAD); + l += ev+ec-ae+ 0.214*sin(2*mm*RAD); + l= 0.6583*sin(2*(l-ls)*RAD)+l; + return l; +} + +static double GetMoonPhase (int year, int month, int day, int hour) +{ + double j = GetJulianDate(year,month,(double)day+(double)hour/24.0)-2444238.5; + double ls = GetSunPosition(j); + double lm = GetMoonPosition(j, ls); + double t = lm - ls; + double retVal; + + retVal = (1.0 - cos((lm - ls)*RAD))/2; + retVal *= 1000; + retVal += 0.5; + retVal /= 10; + if (t < 0) + t += 360; + if (t > 180) + retVal *= -1; + + return retVal; +} + +// Return the sign of a number. +static int getSign( double num) +{ + if (num < 0) + return(-1); + if (num > 0) + return(1); + return(0); +} + +// Local Sidereal Time for zone in Radians +static double localSiderealTime( double lon, double jd, double tz ) +{ + + double TU, lmst, gmst; + + TU = jd / 36525.0; + gmst = 24110.54841 + TU*(8640184.812866 + TU*(0.093104 + TU*(-6.2E-6))); + lmst = gmst - 86636.6 * tz / 24.0 + WV_SECONDS_IN_DAY * lon / 360.0; + lmst = lmst / WV_SECONDS_IN_DAY; // rotations + lmst = lmst - floor(lmst); // fraction of a circle + + return lmst*2.0*PI; +} + +/* 3-point interpolation */ +static double moonInterpolate( double f0, double f1, double f2, double p ) +{ + double a, b, f; + + a = f1 - f0; + b = f2 - f1 - a; + f = f0 + p*(2*a + b*(2*p - 1)); + + return f; +} + +/* test an hour for an event */ +static double moonTest(int k, double t0, double lat, double plx) +{ + double ha[3]; + double a, b, c, d, e, s, z; + double hr, min, time; + double az, hz, nz, dz; + double K1 = 15 * PI / 180.0 * 1.0027379; + double DR = PI / 180.0; + + if (RAn[2] < RAn[0]) + RAn[2] = RAn[2] + 2.0*PI; + + ha[0] = t0 - RAn[0] + k*K1; + ha[2] = t0 - RAn[2] + k*K1 + K1; + + ha[1] = (ha[2] + ha[0])/2.0; /* hour angle at half hour */ + Dec[1] = (Dec[2] + Dec[0])/2.0; /* declination at half hour */ + + s = sin(DR*lat); + c = cos(DR*lat); + + // refraction + sun semidiameter at horizon + parallax correction + z = cos(DR*(90.567 - 41.685/plx)); + + if (k <= 0) // first call of function + VHz[0] = s * sin(Dec[0]) + c * cos(Dec[0]) * cos(ha[0]) - z; + + VHz[2] = s * sin(Dec[2]) + c * cos(Dec[2]) * cos(ha[2]) - z; + + if (getSign(VHz[0]) == getSign(VHz[2])) + return VHz[2]; // no event this hour + + VHz[1] = s * sin(Dec[1]) + c * cos(Dec[1]) * cos(ha[1]) - z; + + a = 2.0*VHz[2] - 4.0*VHz[1] + 2.0*VHz[0]; + b = 4.0*VHz[1] - 3.0*VHz[0] - VHz[2]; + d = b*b - 4.0*a*VHz[0]; + + if (d < 0.0) + return VHz[2]; // no event this hour + + d = sqrt(d); + e = (-b + d)/(2.0*a); + + if (( e > 1 )||( e < 0.0 )) + e = (-b - d)/(2.0*a); + + time = k + e + 1.0/120.0; // time of an event + round up + hr = floor(time); + min = floor((time - hr)*60.0); + + hz = ha[0] + e * (ha[2] - ha[0]); // azimuth of the moon at the event + nz = -cos(Dec[1]) * sin(hz); + dz = c * sin(Dec[1]) - s * cos(Dec[1]) * cos(hz); + az = atan2(nz, dz)/DR; + if (az < 0.0) + az = az + 360.0; + + if ((VHz[0] < 0.0) && (VHz[2] > 0.0)) + { + MoonRise.hr = (int)hr; + MoonRise.min = (int)min; + MoonRise.az = az; + MoonRise.event = 1; + } + + if ((VHz[0] > 0.0) && (VHz[2] < 0.0)) + { + MoonSet.hr = (int)hr; + MoonSet.min = (int)min; + MoonSet.az = az; + MoonSet.event = 1; + } + + return VHz[2]; +} + + +/* +* moon's position using fundamental arguments +* (Van Flandern & Pulkkinen, 1979) +*/ +static MOONLOCATION GetMoonLocation(double jd) +{ + double d, f, g, h, m, n, s, u, v, w; + MOONLOCATION itshere; + + h = 0.606434 + 0.03660110129 * jd; + m = 0.374897 + 0.03629164709 * jd; + f = 0.259091 + 0.03674819520 * jd; + d = 0.827362 + 0.03386319198 * jd; + n = 0.347343 - 0.00014709391 * jd; + g = 0.993126 + 0.00273777850 * jd; + + h = h - floor(h); + m = m - floor(m); + f = f - floor(f); + d = d - floor(d); + n = n - floor(n); + g = g - floor(g); + + h = h*2*PI; + m = m*2*PI; + f = f*2*PI; + d = d*2*PI; + n = n*2*PI; + g = g*2*PI; + + v = 0.39558 * sin(f + n); + v = v + 0.08200 * sin(f); + v = v + 0.03257 * sin(m - f - n); + v = v + 0.01092 * sin(m + f + n); + v = v + 0.00666 * sin(m - f); + v = v - 0.00644 * sin(m + f - 2*d + n); + v = v - 0.00331 * sin(f - 2*d + n); + v = v - 0.00304 * sin(f - 2*d); + v = v - 0.00240 * sin(m - f - 2*d - n); + v = v + 0.00226 * sin(m + f); + v = v - 0.00108 * sin(m + f - 2*d); + v = v - 0.00079 * sin(f - n); + v = v + 0.00078 * sin(f + 2*d + n); + + u = 1 - 0.10828 * cos(m); + u = u - 0.01880 * cos(m - 2*d); + u = u - 0.01479 * cos(2*d); + u = u + 0.00181 * cos(2*m - 2*d); + u = u - 0.00147 * cos(2*m); + u = u - 0.00105 * cos(2*d - g); + u = u - 0.00075 * cos(m - 2*d + g); + + w = 0.10478 * sin(m); + w = w - 0.04105 * sin(2*f + 2*n); + w = w - 0.02130 * sin(m - 2*d); + w = w - 0.01779 * sin(2*f + n); + w = w + 0.01774 * sin(n); + w = w + 0.00987 * sin(2*d); + w = w - 0.00338 * sin(m - 2*f - 2*n); + w = w - 0.00309 * sin(g); + w = w - 0.00190 * sin(2*f); + w = w - 0.00144 * sin(m + n); + w = w - 0.00144 * sin(m - 2*f - n); + w = w - 0.00113 * sin(m + 2*f + 2*n); + w = w - 0.00094 * sin(m - 2*d + g); + w = w - 0.00092 * sin(2*m - 2*d); + + s = w/sqrt(u - v*v); // compute moon's ... right ascension + itshere.rightascension = h + atan(s/sqrt(1 - s*s)); + + s = v/sqrt(u); // declination ... + itshere.declination = atan(s/sqrt(1 - s*s)); + + itshere.parallax = 60.40974 * sqrt( u ); // and parallax + + return(itshere); +} + + + +// Public methods: +#define PHASE_STR_MAX 128 +char *lunarPhaseGet (char *increase, char *decrease, char *full) +{ + static char phaseStr[PHASE_STR_MAX]; + time_t timeNow = time (NULL); + double phase; + struct tm bknTime; + + localtime_r (&timeNow, &bknTime); + + // compute the period value + phase = GetMoonPhase (bknTime.tm_year+1900, bknTime.tm_mon+1, + bknTime.tm_mday, bknTime.tm_hour); + + if (phase < 0) + snprintf(phaseStr, PHASE_STR_MAX-1, "%s %.0f%c %s", decrease, fabs(phase), '%', full); + else + snprintf(phaseStr, PHASE_STR_MAX-1, "%s %.0f%c %s", increase, phase, '%', full); + + return phaseStr; +} + + +/* +* This is a C language implementation of the Sky and Telscope BASIC +* program 1989 page 78 http://media.skyandtelescope.com/binary/moonup.bas +* +* Its based on the Java implementation by Stephen R. Schmitt +* http://home.att.net/~srschmitt/script_moon_rise_set.html +*/ + + +// calculate MoonRise and MoonSet times +// +// Returns Rise and Set times times returned as packed time (hour*100 + minutes) +// +// packedRise > 0 && packedSet = -1 => the moon rises and never sets +// packedRise = -1 && packSet > 0 => no moon rise and the moon sets +// packedRise = packedSet = -1 => the moon never sets +// packedRise = packedSet = -2 => the moon never rises + +int GetMoonRiseSetTimes +( + int year, + int month, + int day, + double zone, // Timezone offset from UTC/GMT in hours + double lat, // Latitude degress N=> +, S=> - + double lon, // longitude degress E=> +, W=> - + int16_t *packedRise, // returned Moon Rise time + double *riseAz, // return Moon Rise Azimuth + int16_t *packedSet, // returned Moon Set time + double *setAz // return Moon Set Azimuth +) +{ + int k; + MOONLOCATION mp[3]; + double localsidereal; + double ph; + double jd; + + // Julian day relative to Jan 1.5, 2000 + jd = GetJulianDate(year, month, (double)day) - 2451545.0; + + localsidereal = localSiderealTime(lon, jd, zone); // local sidereal time + + jd = jd - zone / 24.0; // get moon position at day start + + for (k = 0; k < 3; k ++) + { + mp[k] = GetMoonLocation(jd); + jd = jd + 0.5; + } + + if (mp[1].rightascension <= mp[0].rightascension) + mp[1].rightascension = mp[1].rightascension + 2*PI; + + if (mp[2].rightascension <= mp[1].rightascension) + mp[2].rightascension = mp[2].rightascension + 2*PI; + + RAn[0] = mp[0].rightascension; + Dec[0] = mp[0].declination; + + MoonRise.event = 0; // initialize + MoonSet.event = 0; + + for (k = 0; k < 24; k++) // check each hour of this day + { + ph = (k + 1.0)/24.0; + + RAn[2] = moonInterpolate(mp[0].rightascension, + mp[1].rightascension, + mp[2].rightascension, + ph); + Dec[2] = moonInterpolate(mp[0].declination, + mp[1].declination, + mp[2].declination, + ph); + + VHz[2] = moonTest(k, localsidereal, lat, mp[1].parallax); + + RAn[0] = RAn[2]; // advance to next hour + Dec[0] = Dec[2]; + VHz[0] = VHz[2]; + } + + *packedRise = (int16_t)(MoonRise.hr * 100 + MoonRise.min); + if (riseAz != NULL) + *riseAz = MoonRise.az; + + *packedSet = (int16_t)(MoonSet.hr * 100 + MoonSet.min); + if (setAz != NULL) + *setAz = MoonSet.az; + + /*check for no MoonRise and/or no MoonSet */ + + if (! MoonRise.event && ! MoonSet.event) // neither MoonRise nor MoonSet + { + if (VHz[2] < 0) + *packedRise = *packedSet = -2; // the moon never sets + else + *packedRise = *packedSet = -1; // the moon never rises + } + else // check for MoonRise or MoonSet + { + if (! MoonRise.event) + *packedRise = -1; // no MoonRise and the moon sets + else if (! MoonSet.event) + *packedSet = -1; // the moon rises and never sets + } + + return OK; +} + diff --git a/common/sensor.c b/common/sensor.c new file mode 100755 index 0000000..930b41f --- /dev/null +++ b/common/sensor.c @@ -0,0 +1,621 @@ +/*--------------------------------------------------------------------------- + + FILENAME: + sensor.c + + PURPOSE: + Provide the sensor API methods. + + REVISION HISTORY: + Date Engineer Revision Remarks + 1/02/2006 M.S. Teel 0 Original + + NOTES: + + + LICENSE: + Copyright (c) 2006, Mark S. Teel (mark@teel.ws) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +// ... System header files +#include +#include + + +// ... Local header files +#include + + +// ... methods + +void sensorInit (WV_SENSOR *sensor) +{ + memset (sensor, 0, sizeof(*sensor)); + sensor->low = 1000000.0; + sensor->high = -1000000.0; + return; +} + +void sensorUpdate (WV_SENSOR *sensor, float value) +{ + if (value <= ARCHIVE_VALUE_NULL) + { + return; + } + + if (sensor->low > value) + { + sensor->low = value; + sensor->time_low = time (NULL); + } + if (sensor->high < value) + { + sensor->high = value; + sensor->time_high = time (NULL); + } + sensor->cumulative += value; + sensor->samples ++; + return; +} + +void sensorUpdateWhen (WV_SENSOR *sensor, float value, float whenVal) +{ + if (value <= ARCHIVE_VALUE_NULL) + { + return; + } + + if (sensor->low > value) + { + sensor->low = value; + sensor->time_low = time (NULL); + } + if (sensor->high < value) + { + sensor->high = value; + sensor->time_high = time (NULL); + sensor->when_high = whenVal; + } + sensor->cumulative += value; + sensor->samples ++; + return; +} + +// Initialize sensor with given values: +void sensorSetValues +( + WV_SENSOR *sensor, + float low, + time_t time_low, + float high, + time_t time_high, + float when_high, + float cumulative, + int samples +) +{ + sensor->low = low; + sensor->time_low = time_low; + sensor->high = high; + sensor->time_high = time_high; + sensor->when_high = when_high; + sensor->cumulative = cumulative; + sensor->samples = samples; +} + +void sensorUpdateLowValue (WV_SENSOR *sensor, float value) +{ + if (value <= ARCHIVE_VALUE_NULL) + { + return; + } + + sensor->low = value; +} + +void sensorUpdateHighValue (WV_SENSOR *sensor, float value) +{ + if (value <= ARCHIVE_VALUE_NULL) + { + return; + } + + sensor->high = value; +} + +void sensorUpdateWhenHighValue (WV_SENSOR *sensor, float value, float whenVal) +{ + if (value <= ARCHIVE_VALUE_NULL) + { + return; + } + + sensor->high = value; + sensor->when_high = whenVal; +} + +void sensorUpdateCumulative (WV_SENSOR *sensor, float value) +{ + if (value <= ARCHIVE_VALUE_NULL) + { + return; + } + + sensor->cumulative = value; +} + +void sensorAddCumulative (WV_SENSOR *sensor, float value) +{ + if (value <= ARCHIVE_VALUE_NULL) + { + return; + } + + sensor->cumulative += value; +} + +void sensorAddSample (WV_SENSOR *sensor, WV_SENSOR *sample, int DoSensorDbg) +{ + if (DoSensorDbg) + { + radMsgLog(PRI_HIGH, "sensorAddSample: Low %f@%2.2d:%2.2d High %f@%2.2d:%2.2d", + sample->low, wvutilsGetHour(sample->time_low), wvutilsGetMin(sample->time_low), + sample->high, wvutilsGetHour(sample->time_high), wvutilsGetMin(sample->time_high)); + } + if (sample->low <= ARCHIVE_VALUE_NULL || sample->high <= ARCHIVE_VALUE_NULL) + { + if (DoSensorDbg) + { + radMsgLog(PRI_HIGH, "sensorAddSample: invalid high or low!"); + } + return; + } + + if (sensor->low > sample->low) + { + if (DoSensorDbg) + { + radMsgLog(PRI_HIGH, "sensorAddSample: New Low: %f@%2.2d:%2.2d", + sample->low, wvutilsGetHour(sample->time_low), wvutilsGetMin(sample->time_low)); + } + sensor->low = sample->low; + sensor->time_low = sample->time_low; + } + if (sensor->high < sample->high) + { + if (DoSensorDbg) + { + radMsgLog(PRI_HIGH, "sensorAddSample: New High: %f@%2.2d:%2.2d", + sample->high, wvutilsGetHour(sample->time_high), wvutilsGetMin(sample->time_high)); + } + sensor->high = sample->high; + sensor->when_high = sample->when_high; + sensor->time_high = sample->time_high; + } + + sensor->cumulative += sample->cumulative; + sensor->samples += sample->samples; + + return; +} + +void sensorPropogateSample (WV_SENSOR *set, WV_SENSOR *sample) +{ + int type; + + for (type = 0; type < SENSOR_MAX; type ++) + { + // Pass TRUE here to debug: + sensorAddSample (&set[type], &sample[type], FALSE); + } + + return; +} + +void sensorClearSet (WV_SENSOR *set) +{ + int type; + + for (type = 0; type < SENSOR_MAX; type ++) + { + sensorInit (&set[type]); + } + + return; +} + +float sensorGetLow (WV_SENSOR *sensor) +{ + return sensor->low; +} + +char *sensorGetLowTime (WV_SENSOR *sensor, char *store) +{ + if (sensor->time_low == (time_t)0) + { + sprintf (store, "-----"); + } + else + { + if (sensor->debug) + { + radMsgLog(PRI_MEDIUM, "SENSOR LOWTIME DBG: " + "low: %.2f, time_low: %u, high: %.2f, time_high: %u," + "samples: %d", + sensor->low, + (unsigned int)sensor->time_low, + sensor->high, + (unsigned int)sensor->time_high, + sensor->samples); + } + + sprintf (store, "%2.2d:%2.2d", wvutilsGetHour(sensor->time_low), wvutilsGetMin(sensor->time_low)); + if (sensor->debug) + { + radMsgLog(PRI_MEDIUM, "SENSOR LOWTIME RESULT DBG: %s", store); + } + } + return store; +} + +char *sensorGetLowDate (WV_SENSOR *sensor, char *store, char *dateFormat) +{ + struct tm locTime; + + if (sensor->time_low == (time_t)0) + { + sprintf (store, "----------"); + } + else + { + if (sensor->debug) + { + radMsgLog(PRI_MEDIUM, "SENSOR LOWDATE DBG: " + "low: %.2f, time_low: %u, high: %.2f, time_high: %u," + "samples: %d", + sensor->low, + (unsigned int)sensor->time_low, + sensor->high, + (unsigned int)sensor->time_high, + sensor->samples); + } + + localtime_r(&sensor->time_low, &locTime); + strftime(store, WV_MAX_DATE_LENGTH-1, dateFormat, &locTime); + if (sensor->debug) + { + radMsgLog(PRI_MEDIUM, "SENSOR LOWDATE RESULT DBG: %s", store); + } + } + return store; +} + +float sensorGetHigh (WV_SENSOR *sensor) +{ + return sensor->high; +} + +float sensorGetWhenHigh (WV_SENSOR *sensor) +{ + return sensor->when_high; +} + +char *sensorGetHighTime (WV_SENSOR *sensor, char *store) +{ + if (sensor->time_high == (time_t)0) + { + sprintf (store, "-----"); + } + else + { + if (sensor->debug) + { + radMsgLog(PRI_MEDIUM, "SENSOR HIGHTIME DBG: " + "low: %.2f, time_low: %u, high: %.2f, time_high: %u," + "samples: %d", + sensor->low, + (unsigned int)sensor->time_low, + sensor->high, + (unsigned int)sensor->time_high, + sensor->samples); + } + + sprintf (store, "%2.2d:%2.2d", wvutilsGetHour(sensor->time_high), wvutilsGetMin(sensor->time_high)); + if (sensor->debug) + { + radMsgLog(PRI_MEDIUM, "SENSOR HIGHTIME RESULT DBG: %s", store); + } + } + return store; +} + +char *sensorGetHighDate (WV_SENSOR *sensor, char *store, char *dateFormat) +{ + struct tm locTime; + + if (sensor->time_high == (time_t)0) + { + sprintf (store, "----------"); + } + else + { + if (sensor->debug) + { + radMsgLog(PRI_MEDIUM, "SENSOR HIGHDATE DBG: " + "low: %.2f, time_low: %u, high: %.2f, time_high: %u," + "samples: %d", + sensor->low, + (unsigned int)sensor->time_low, + sensor->high, + (unsigned int)sensor->time_high, + sensor->samples); + } + + localtime_r(&sensor->time_high, &locTime); + strftime(store, WV_MAX_DATE_LENGTH-1, dateFormat, &locTime); + if (sensor->debug) + { + radMsgLog(PRI_MEDIUM, "SENSOR HIGHDATE RESULT DBG: %s", store); + } + } + return store; +} + +float sensorGetAvg (WV_SENSOR *sensor) +{ + if (sensor->samples == 0) + { + return 0; + } + + return (sensor->cumulative/sensor->samples); +} + +float sensorGetCumulative (WV_SENSOR *sensor) +{ + return sensor->cumulative; +} + +int sensorGetSamples (WV_SENSOR *sensor) +{ + return sensor->samples; +} + +// get a daily low value based on the RTF_INTERVAL and RTF_DAY time frames - +// assumes input of type "set[STF_MAX][SENSOR_MAX]" and "SENSOR_TYPES" +float sensorGetDailyLow (WV_SENSOR set[STF_MAX][SENSOR_MAX], SENSOR_TYPES type) +{ + float intVal, dayVal; + + intVal = sensorGetLow (&set[STF_INTERVAL][type]); + dayVal = sensorGetLow (&set[STF_DAY][type]); + if (intVal < dayVal) + return intVal; + else + return dayVal; +} + +char *sensorGetDailyLowTime (WV_SENSOR set[STF_MAX][SENSOR_MAX], SENSOR_TYPES type, char *store) +{ + float intVal, dayVal; + + intVal = sensorGetLow (&set[STF_INTERVAL][type]); + dayVal = sensorGetLow (&set[STF_DAY][type]); + if (intVal < dayVal) + return sensorGetLowTime (&set[STF_INTERVAL][type], store); + else + return sensorGetLowTime (&set[STF_DAY][type], store); +} + +// get a daily high value based on the RTF_INTERVAL and RTF_DAY time frames - +// assumes input of type "set[STF_MAX][SENSOR_MAX]" and "SENSOR_TYPES" +float sensorGetDailyHigh (WV_SENSOR set[STF_MAX][SENSOR_MAX], SENSOR_TYPES type) +{ + float intVal, dayVal; + + intVal = sensorGetHigh (&set[STF_INTERVAL][type]); + dayVal = sensorGetHigh (&set[STF_DAY][type]); + if (intVal > dayVal) + return intVal; + else + return dayVal; +} + +char *sensorGetDailyHighTime (WV_SENSOR set[STF_MAX][SENSOR_MAX], SENSOR_TYPES type, char *store) +{ + float intVal, dayVal; + + intVal = sensorGetHigh (&set[STF_INTERVAL][type]); + dayVal = sensorGetHigh (&set[STF_DAY][type]); + if (intVal > dayVal) + return sensorGetHighTime (&set[STF_INTERVAL][type], store); + else + return sensorGetHighTime (&set[STF_DAY][type], store); +} + +float sensorGetDailyWhenHigh (WV_SENSOR set[STF_MAX][SENSOR_MAX], SENSOR_TYPES type) +{ + float intVal, dayVal; + + intVal = sensorGetHigh (&set[STF_INTERVAL][type]); + dayVal = sensorGetHigh (&set[STF_DAY][type]); + if (intVal > dayVal) + return sensorGetWhenHigh (&set[STF_INTERVAL][type]); + else + return sensorGetWhenHigh (&set[STF_DAY][type]); +} + +// get a daily cumulative value based on the RTF_INTERVAL and RTF_DAY time frames - +float sensorGetDailyCumulative (WV_SENSOR set[STF_MAX][SENSOR_MAX], SENSOR_TYPES type) +{ + return (sensorGetCumulative(&set[STF_INTERVAL][type]) + sensorGetCumulative(&set[STF_DAY][type])); +} + +// some special purpose utilities +float sensorGetWindRun (SENSOR_TIMEFRAMES frame, WV_SENSOR *sensor) +{ + float retVal, temp; + time_t nowtime, jan1time; + struct tm bknTime, tempTime; + + if (sensor->samples == 0) + { + return 0; + } + + nowtime = time(NULL); + localtime_r(&nowtime, &bknTime); + tempTime = bknTime; + tempTime.tm_sec = 0; + tempTime.tm_min = 0; + tempTime.tm_hour = 0; + tempTime.tm_mday = 0; + tempTime.tm_mon = 0; + jan1time = mktime(&tempTime); + + retVal = sensor->cumulative/sensor->samples; + + switch (frame) + { + case STF_HOUR: + retVal *= (60*bknTime.tm_min + bknTime.tm_sec); + break; + case STF_DAY: + retVal *= (3600*bknTime.tm_hour + 60*bknTime.tm_min + bknTime.tm_sec); + break; + case STF_WEEK: + temp = WV_SECONDS_IN_DAY * 6; + temp += (3600*bknTime.tm_hour + 60*bknTime.tm_min + bknTime.tm_sec); + retVal *= temp; + break; + case STF_MONTH: + retVal *= (86400*bknTime.tm_mday + 3600*bknTime.tm_hour + 60*bknTime.tm_min + bknTime.tm_sec); + break; + case STF_YEAR: + retVal *= (nowtime - jan1time); + break; + default: + break; + } + + retVal /= 3600; + return retVal; +} + +static void AgeAccumulator (WV_ACCUM_ID id) +{ + WV_ACCUM_SAMPLE *nodePtr, *oldPtr; + time_t nowTime = time(NULL); + + for (nodePtr = (WV_ACCUM_SAMPLE*)radListGetLast (&id->samples); + nodePtr != NULL; + nodePtr = (WV_ACCUM_SAMPLE*)radListGetLast (&id->samples)) + { + if ((nowTime - id->secondsInAccumulator) >= nodePtr->sampleTime) + { + // remove and free him + oldPtr = (WV_ACCUM_SAMPLE*)radListRemoveLast (&id->samples); + radBufferRls (oldPtr); + } + else + { + break; + } + } +} + +WV_ACCUM_ID sensorAccumInit (int minutesInAccumulator) +{ + WV_ACCUM_ID newId; + + newId = (WV_ACCUM_ID)radBufferGet (sizeof (WV_ACCUM)); + if (newId == NULL) + { + return NULL; + } + + radListReset (&newId->samples); + newId->secondsInAccumulator = minutesInAccumulator * 60; + newId->sum = 0; + return newId; +} + +void sensorAccumExit (WV_ACCUM_ID id) +{ + WV_ACCUM_SAMPLE* nodePtr; + + for (nodePtr = (WV_ACCUM_SAMPLE*)radListRemoveFirst (&id->samples); + nodePtr != NULL; + nodePtr = (WV_ACCUM_SAMPLE*)radListRemoveFirst (&id->samples)) + { + radBufferRls (nodePtr); + } + radBufferRls (id); +} + +void sensorAccumAddSample (WV_ACCUM_ID id, time_t timeStamp, float value) +{ + WV_ACCUM_SAMPLE* newNode; + + newNode = (WV_ACCUM_SAMPLE*)radBufferGet (sizeof(WV_ACCUM_SAMPLE)); + if (newNode == NULL) + { + return; + } + + newNode->value = value; + newNode->sampleTime = timeStamp; + radListAddToFront (&id->samples, (NODE_PTR)newNode); + + // Do we need to age off any nodes? + AgeAccumulator (id); +} + +float sensorAccumGetTotal (WV_ACCUM_ID id) +{ + WV_ACCUM_SAMPLE* nodePtr; + float sum = 0; + + // Do we need to age off any nodes? + AgeAccumulator (id); + + for (nodePtr = (WV_ACCUM_SAMPLE*)radListGetFirst (&id->samples); + nodePtr != NULL; + nodePtr = (WV_ACCUM_SAMPLE*)radListGetNext (&id->samples, (NODE_PTR)nodePtr)) + { + sum += nodePtr->value; + } + + return sum; +} + +float sensorAccumGetAverage (WV_ACCUM_ID id) +{ + float samples, sum; + + sum = sensorAccumGetTotal(id); + samples = (float)radListGetNumberOfNodes(&id->samples); + if (samples > 0) + { + return (sum/samples); + } + else + { + return 0; + } +} + +static char sensorDebugString[256]; +char* sensorGetString (WV_SENSOR *sensor) +{ + snprintf (sensorDebugString, 256, "Low:%.3f@%2.2d:%2.2d High:%.3f@%2.2d:%2.2d Cum:%.3f Samples:%d", + sensor->low, wvutilsGetHour(sensor->time_low), wvutilsGetMin(sensor->time_low), + sensor->high, wvutilsGetHour(sensor->time_high), wvutilsGetMin(sensor->time_high), + sensor->cumulative, sensor->samples); + return sensorDebugString; +} + diff --git a/common/sensor.h b/common/sensor.h new file mode 100755 index 0000000..b62ae77 --- /dev/null +++ b/common/sensor.h @@ -0,0 +1,158 @@ +#ifndef INC_sensorh +#define INC_sensorh +/*--------------------------------------------------------------------------- + + FILENAME: + sensor.h + + PURPOSE: + Define the sensor API. + + REVISION HISTORY: + Date Engineer Revision Remarks + 01/02/2006 M.S. Teel 0 Original + + NOTES: + + + LICENSE: + Copyright (c) 2006, Mark S. Teel (mark@teel.ws) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +// ... includes +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + + +// ... macro definitions + +#define SENSOR_SAVE_FILENAME "sensorstore.bin" + +// Incrementing this will cause sensorstore.bin to be recreated upon first run: +// Last change version: 5.0.0 +#define SENSOR_SAVE_MAGIC_NUMBER 0x5D372001 + + +// ... typedefs + +// sample accumulator with time decay +typedef struct +{ + NODE node; + float value; + time_t sampleTime; +} WV_ACCUM_SAMPLE; + +typedef struct +{ + RADLIST samples; + int secondsInAccumulator; + float sum; +} WV_ACCUM, *WV_ACCUM_ID; + + +// ... API prototypes + +// Accumulator with time decay +extern WV_ACCUM_ID sensorAccumInit (int minutesInAccumulator); +extern void sensorAccumExit (WV_ACCUM_ID id); +extern void sensorAccumAddSample (WV_ACCUM_ID id, time_t timeStamp, float value); +extern float sensorAccumGetTotal (WV_ACCUM_ID id); +extern float sensorAccumGetAverage (WV_ACCUM_ID id); + + +// -- initialize a sensor -- +extern void sensorInit (WV_SENSOR *sensor); + + +// these insert at the current time +extern void sensorUpdate (WV_SENSOR *sensor, float value); +extern void sensorUpdateWhen (WV_SENSOR *sensor, float value, float whenVal); + +// Initialize sensor with given values: +extern void sensorSetValues +( + WV_SENSOR *sensor, + float low, + time_t time_low, + float high, + time_t time_high, + float when_high, + float cumulative, + int samples +); + +// these just modify high/low/cumulative values +extern void sensorUpdateLowValue (WV_SENSOR *sensor, float value); +extern void sensorUpdateHighValue (WV_SENSOR *sensor, float value); +extern void sensorUpdateWhenHighValue (WV_SENSOR *sensor, float value, float whenVal); +extern void sensorUpdateCumulative (WV_SENSOR *sensor, float value); +extern void sensorAddCumulative (WV_SENSOR *sensor, float value); + +// add a sensor interval to a specific sensor +extern void sensorAddSample (WV_SENSOR *sensor, WV_SENSOR *sample, int doDebug); + +// -- operations on sets of sensors -- +// propogate a new data sample to the current interval +// assumes input of type "set[SENSOR_MAX]" +extern void sensorPropogateSample (WV_SENSOR *set, WV_SENSOR *sample); + +// clear sensors for a SENSOR_MAX array +// assumes input of type "set[SENSOR_MAX]" +extern void sensorClearSet (WV_SENSOR *set); + + +// -- retrieve info from a sensor -- +extern float sensorGetLow (WV_SENSOR *sensor); +extern char *sensorGetLowTime (WV_SENSOR *sensor, char *store); +extern char *sensorGetLowDate (WV_SENSOR *sensor, char *store, char *dateFormat); +extern float sensorGetHigh (WV_SENSOR *sensor); +extern float sensorGetWhenHigh (WV_SENSOR *sensor); +extern char *sensorGetHighTime (WV_SENSOR *sensor, char *store); +extern char *sensorGetHighDate (WV_SENSOR *sensor, char *store, char *dateFormat); +extern float sensorGetAvg (WV_SENSOR *sensor); +extern float sensorGetCumulative (WV_SENSOR *sensor); +extern int sensorGetSamples (WV_SENSOR *sensor); + + +// -- retrieve info from a group of sensors +// get a daily low value based on the RTF_INTERVAL and RTF_DAY time frames - +extern float sensorGetDailyLow (WV_SENSOR set[STF_MAX][SENSOR_MAX], SENSOR_TYPES type); +extern char *sensorGetDailyLowTime (WV_SENSOR set[STF_MAX][SENSOR_MAX], SENSOR_TYPES type, char *store); + +// get a daily high value based on the RTF_INTERVAL and RTF_DAY time frames - +extern float sensorGetDailyHigh (WV_SENSOR set[STF_MAX][SENSOR_MAX], SENSOR_TYPES type); +extern char *sensorGetDailyHighTime (WV_SENSOR set[STF_MAX][SENSOR_MAX], SENSOR_TYPES type, char *store); + +// get a daily "when" high value based on the RTF_INTERVAL and RTF_DAY time frames - +extern float sensorGetDailyWhenHigh (WV_SENSOR set[STF_MAX][SENSOR_MAX], SENSOR_TYPES type); + +// get a daily cumulative value based on the RTF_INTERVAL and RTF_DAY time frames - +extern float sensorGetDailyCumulative (WV_SENSOR set[STF_MAX][SENSOR_MAX], SENSOR_TYPES type); + +// -- special purpose utilities -- +extern float sensorGetWindRun (SENSOR_TIMEFRAMES frame, WV_SENSOR *sensor); +extern char* sensorGetString (WV_SENSOR *sensor); + +#endif + diff --git a/common/services.h b/common/services.h new file mode 100755 index 0000000..76a37e1 --- /dev/null +++ b/common/services.h @@ -0,0 +1,207 @@ +#ifndef INC_servicesh +#define INC_servicesh +/*--------------------------------------------------------------------------- + + FILENAME: + services.h + + PURPOSE: + Provide the wview daemon application sevices definitions. + + REVISION HISTORY: + Date Engineer Revision Remarks + 08/24/03 M.S. Teel 0 Original + + NOTES: + + LICENSE: + Copyright (c) 2004, Mark S. Teel (mark@teel.ws) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +/* ... System include files +*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* ... Library include files +*/ +#include + +/* ... Local include files +*/ +#include + + +/* ... API definitions +*/ + +/* ... request types for the WVIEW_MSG_TYPE_REQUEST message +*/ +typedef enum +{ + WVIEW_RQST_TYPE_STATION_INFO = 1, + WVIEW_RQST_TYPE_HILOW_DATA = 2, + WVIEW_RQST_TYPE_LOOP_DATA = 3 +} WVIEW_RQST_TYPES; + +/* ... message types: + ... currently the wviewd daemon only receives the WVIEW_MSG_TYPE_REQUEST + ... message and sends the other types +*/ +typedef enum +{ + WVIEW_MSG_TYPE_REQUEST = 1, + WVIEW_MSG_TYPE_STATION_INFO = 2, + WVIEW_MSG_TYPE_HILOW_DATA = 3, + WVIEW_MSG_TYPE_LOOP_DATA = 4, + WVIEW_MSG_TYPE_LOOP_DATA_SVC = 5, + WVIEW_MSG_TYPE_ARCHIVE_NOTIFY = 6, + WVIEW_MSG_TYPE_ARCHIVE_DATA = 7, + + // These are outside the "RQST_TYPE" paradigm: + WVIEW_MSG_TYPE_POLL = 8, + WVIEW_MSG_TYPE_POLL_RESPONSE = 9, + WVIEW_MSG_TYPE_ALERT = 10, + WVIEW_MSG_TYPE_SHUTDOWN = 11, + WVIEW_MSG_TYPE_STATION_DATA = 12 // msg structure is station-specific + +} WVIEW_MSG_TYPES; + + +/* ... WVIEW_MSG_TYPE_REQUEST + ... requestType is one of WVIEW_RQST_TYPES; + ... will cause that msg/action to be sent/performed +*/ +typedef struct +{ + int requestType; + int enable; // enable/disable msg reception + // for "SVC" requests +} +__attribute__ ((packed)) WVIEW_MSG_REQUEST; + +/* ... WVIEW_MSG_TYPE_STATION_INFO + ... returns station info +*/ +typedef struct +{ + time_t lastArcTime; + int16_t archiveInterval; + int16_t latitude; + int16_t longitude; + int16_t elevation; + char stationType[_MAX_PATH]; + +} +__attribute__ ((packed)) WVIEW_MSG_STATION_INFO; + +/* ... WVIEW_MSG_TYPE_HILOW_DATA + ... returns the most recent HILOW data +*/ +typedef struct +{ + SENSOR_STORE hilowData; + +} +__attribute__ ((packed)) WVIEW_MSG_HILOW_DATA; + +/* ... WVIEW_MSG_TYPE_LOOP_DATA + ... returns the most recent loop data +*/ +typedef struct +{ + LOOP_PKT loopData; + +} +__attribute__ ((packed)) WVIEW_MSG_LOOP_DATA; + +/* ... WVIEW_MSG_TYPE_ARCHIVE_DATA + ... publishes archive records +*/ +typedef struct +{ + ARCHIVE_PKT archiveData; + +} +__attribute__ ((packed)) WVIEW_MSG_ARCHIVE_DATA; + +/* ... WVIEW_MSG_TYPE_ARCHIVE_NOTIFY + ... notification of the reception of an archive record from the VP console + ... and pertinent data from that record +*/ +typedef struct +{ + time_t dateTime; + int intemp; + int inhumidity; + int temp; + int humidity; + int barom; + int stationPressure; + int altimeter; + int winddir; + float wspeedF; + float hiwspeedF; + float sampleRain; + float rainHour; + float rainDay; + float rainToday; + int dewpoint; + float UV; + float radiation; + int rxPercent; + +} +__attribute__ ((packed)) WVIEW_MSG_ARCHIVE_NOTIFY; + + +// WVIEW_MSG_TYPE_POLL: +// Sent by wvpmond to detect hung processes +typedef struct +{ + int mask; +} +__attribute__ ((packed)) WVIEW_MSG_POLL; + + +// WVIEW_MSG_TYPE_POLL_RESPONSE: +// Sent by wview processes to indicate they are healthy +typedef struct +{ + int pid; +} +__attribute__ ((packed)) WVIEW_MSG_POLL_RESPONSE; + + +// WVIEW_MSG_TYPE_ALERT: +// Sent by wview processes to indicate an alert +typedef struct +{ + int alertType; +} +__attribute__ ((packed)) WVIEW_MSG_ALERT; + + +// WVIEW_MSG_TYPE_SHUTDOWN: +// Sent by wviewd process to indicate he is shutting down: +typedef struct +{ + int placeholder; +} +__attribute__ ((packed)) WVIEW_MSG_SHUTDOWN; + + +#endif + diff --git a/common/status.c b/common/status.c new file mode 100755 index 0000000..718df83 --- /dev/null +++ b/common/status.c @@ -0,0 +1,181 @@ +/*--------------------------------------------------------------------------- + + FILENAME: + status.c + + PURPOSE: + Provide the status reporting API methods. + + REVISION HISTORY: + Date Engineer Revision Remarks + 12/27/2009 M.S. Teel 0 Original + + NOTES: + + + LICENSE: + Copyright (c) 2009, Mark S. Teel (mark@teel.ws) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +// ... System header files +#include + + +// ... Local header files +#include + +// ... Local memory: + +static STATUS_INFO ProcessStatus; + + +static int WriteStatusFile(void) +{ + FILE* statfile; + int index; + + statfile = fopen (ProcessStatus.filePath, "w"); + if (statfile == NULL) + { + radMsgLog (PRI_HIGH, "status file create failed!"); + return ERROR; + } + + fprintf (statfile, "status = %d\n", ProcessStatus.status); + if (strlen(ProcessStatus.lastMessage) > 0) + { + fprintf (statfile, "message = \"%s\"\n", ProcessStatus.lastMessage); + } + + for (index = 0; index < STATUS_STATS_MAX; index ++) + { + if (ProcessStatus.stat[index] == -1 || + ProcessStatus.statLabel[index][0] == 0) + { + continue; + } + + fprintf (statfile, "desc%d = \"%s\"\n", index, ProcessStatus.statLabel[index]); + fprintf (statfile, "stat%d = %d\n", index, ProcessStatus.stat[index]); + } + + fclose (statfile); + return OK; +} + +// ... API methods: + +// ... initialize the status log: +int statusInit(const char* filePath, char* statLabel[4]) +{ + int index; + char temp[256]; + struct stat fileData; + + // create our run directory if it is not there: + sprintf (temp, "%s", "/var/run"); + if (stat (temp, &fileData) != 0) + { + if (mkdir (temp, 0755) != 0) + { + radMsgLog (PRI_CATASTROPHIC, + "Cannot create base run directory: %s - aborting!", + temp); + return ERROR; + } + } + sprintf (temp, "%s", WVIEW_STATUS_DIRECTORY); + if (stat (temp, &fileData) != 0) + { + if (mkdir (temp, 0755) != 0) + { + radMsgLog (PRI_CATASTROPHIC, + "Cannot create status directory: %s - aborting!", + temp); + return ERROR; + } + } + + memset(&ProcessStatus, 0, sizeof(ProcessStatus)); + wvstrncpy(ProcessStatus.filePath, filePath, _MAX_PATH); + + for (index = 0; index < STATUS_STATS_MAX; index ++) + { + if (statLabel == NULL || + statLabel[index] == NULL || statLabel[index][0] == 0) + { + // skip this one: + ProcessStatus.stat[index] = -1; + ProcessStatus.statLabel[index][0] = 0; + } + + ProcessStatus.stat[index] = 0; + wvstrncpy(ProcessStatus.statLabel[index], statLabel[index], 64); + } + + return OK; +} + +// ... send a status update: +int statusUpdate(STATUS_TYPE status) +{ + ProcessStatus.status = status; + WriteStatusFile(); + return OK; +} + +// ... send a status update: +int statusUpdateMessage(const char* message) +{ + wvstrncpy(ProcessStatus.lastMessage, message, _MAX_PATH); + WriteStatusFile(); + return OK; +} + +// ... send a status update: +int statusUpdateStat(int index, int value) +{ + if (0 > index || index >= STATUS_STATS_MAX || + ProcessStatus.stat[index] == -1) + { + return ERROR; + } + + ProcessStatus.stat[index] = value; + WriteStatusFile(); + return OK; +} + +// ... send a status update: +int statusIncrementStat(int index) +{ + if (0 > index || index >= STATUS_STATS_MAX || + ProcessStatus.stat[index] == -1) + { + return ERROR; + } + + ProcessStatus.stat[index] ++; + WriteStatusFile(); + return OK; +} + +// ... send a status update: +// Do not allow the value to be negative: +int statusDecrementStat(int index) +{ + if (0 > index || index >= STATUS_STATS_MAX || + ProcessStatus.stat[index] <= 0) + { + return ERROR; + } + + ProcessStatus.stat[index] --; + WriteStatusFile(); + return OK; +} + diff --git a/common/status.h b/common/status.h new file mode 100644 index 0000000..36e5428 --- /dev/null +++ b/common/status.h @@ -0,0 +1,87 @@ +#ifndef INC_statush +#define INC_statush +/*--------------------------------------------------------------------------- + + FILENAME: + status.h + + PURPOSE: + Define the status reporting API. + + REVISION HISTORY: + Date Engineer Revision Remarks + 12/27/2009 M.S. Teel 0 Original + + NOTES: + + + LICENSE: + Copyright (c) 2009, Mark S. Teel (mark@teel.ws) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +// ... includes +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + + +// ... definitions + +#define STATUS_STATS_MAX 4 + +typedef enum +{ + STATUS_NOT_STARTED = 0, + STATUS_BOOTING = 1, + STATUS_WAITING_FOR_WVIEW, + STATUS_RUNNING, + STATUS_SHUTDOWN, + STATUS_ERROR +} STATUS_TYPE; + +typedef struct +{ + char filePath[_MAX_PATH]; + STATUS_TYPE status; + char lastMessage[_MAX_PATH]; + int stat[STATUS_STATS_MAX]; + char statLabel[STATUS_STATS_MAX][64]; +} STATUS_INFO; + + +// ... API prototypes + +// ... initialize the status log: +extern int statusInit(const char* filePath, char* statLabel[STATUS_STATS_MAX]); + +// ... send a status update: +extern int statusUpdate(STATUS_TYPE status); + +// ... send a status update: +extern int statusUpdateMessage(const char* message); + +// ... send a status update: +extern int statusUpdateStat(int index, int value); + +extern int statusIncrementStat(int index); + +// Does not allow the value to be negative: +extern int statusDecrementStat(int index); + +#endif + + diff --git a/common/sunTimes.c b/common/sunTimes.c new file mode 100644 index 0000000..96d9de7 --- /dev/null +++ b/common/sunTimes.c @@ -0,0 +1,551 @@ +/*--------------------------------------------------------------------------- + + FILENAME: + sunTimes.c + + PURPOSE: + Provide sunrise and sunset computation utilities. + + REVISION HISTORY: + Date Engineer Revision Remarks + 01/07/2006 M.S. Teel 0 Original + + NOTES: + + + LICENSE: + Copyright (c) 2006, Mark S. Teel (mark@teel.ws) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +// ... System header files +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + + +// A macro to compute the number of days elapsed since 1/1/2000 +#define DAYS_THIS_MILLENIUM(y,m,d) \ + (367L*(y)-((7*((y)+(((m)+9)/12)))/4)+((275*(m))/9)+(d)-730530L) + +// Some general conversions + +#ifndef PI +#define PI 3.1415926535897932384 +#endif + +#define RAD_DEG (180.0 / PI) +#define DEG_RAD (PI / 180.0) + +// trig functions in degrees +#define sind(x) sin((x)*DEG_RAD) +#define cosd(x) cos((x)*DEG_RAD) +#define tand(x) tan((x)*DEG_RAD) + +#define atand(x) (RAD_DEG*atan(x)) +#define asind(x) (RAD_DEG*asin(x)) +#define acosd(x) (RAD_DEG*acos(x)) +#define atan2d(y,x) (RAD_DEG*atan2(y,x)) + + +// This macro computes the length of the day, from sunrise to sunset. +// Sunrise/set is considered to occur when the Sun's upper limb is +// 35 arc minutes below the horizon (this accounts for the refraction +// of the Earth's atmosphere). +#define DAY_LENGTH(year,month,day,lon,lat) \ + dayLength(year, month, day, lon, lat, -35.0/60.0, 1) + +// This macro computes the length of the day, including civil twilight. +// Civil twilight starts/ends when the Sun's center is 6 degrees below +// the horizon. +#define DAY_LENGTH_CIVIL(year,month,day,lon,lat) \ + dayLength(year, month, day, lon, lat, -6.0, 0) + +// This macro computes the length of the day, incl. astronomical twilight. +// Astronomical twilight starts/ends when the Sun's center is 18 degrees +// below the horizon. +#define DAY_LENGTH_ASTRONOMICAL(year,month,day,lon,lat) \ + dayLength(year, month, day, lon, lat, -18.0, 0) + + +// This macro computes times for sunrise/sunset. +// Sunrise/set is considered to occur when the Sun's upper limb is +// 35 arc minutes below the horizon (this accounts for the refraction +// of the Earth's atmosphere). +#define SUN_RISE_SET(year,month,day,lon,lat,rise,set) \ + sunRiseSet(year, month, day, lon, lat, -35.0/60.0, 1, rise, set) + +// This macro computes the start and end times of civil twilight. +// Civil twilight starts/ends when the Sun's center is 6 degrees below +// the horizon. +#define CIVIL_RISE_SET(year,month,day,lon,lat,start,end) \ + sunRiseSet(year, month, day, lon, lat, -6.0, 0, start, end) + +// This macro computes the start and end times of astronomical twilight. +// Astronomical twilight starts/ends when the Sun's center is 18 degrees +// below the horizon. +#define ASTRO_RISE_SET(year,month,day,lon,lat,start,end) \ + sunRiseSet(year, month, day, lon, lat, -18.0, 0, start, end) + + +// This function reduces any angle to within the first revolution by subtracting +// or adding even multiples of 360.0 until the result is >= 0.0 and < 360.0 +#define INV360 (1.0 / 360.0) +static double normalize (double x) +{ + return (x - 360.0 * floor(x * INV360)); +} + +static double normalize180 (double x) +{ + return (x - 360.0 * floor(x * INV360 + 0.5)); +} + +// Computes the Sun's ecliptic longitude and distance at an instant given in d, +// number of days since 1/1/2000. The Sun's ecliptic latitude is not computed, +// since it's always very near 0. +static void sunPosition (double d, double *lon, double *r) +{ + double M, /* Mean anomaly of the Sun */ + w, /* Mean longitude of perihelion */ + /* Note: Sun's mean longitude = M + w */ + e, /* Eccentricity of Earth's orbit */ + E, /* Eccentric anomaly */ + x, y, /* x, y coordinates in orbit */ + v; /* True anomaly */ + + /* Compute mean elements */ + M = normalize (356.0470 + 0.9856002585 * d); + w = 282.9404 + 4.70935E-5 * d; + e = 0.016709 - 1.151E-9 * d; + + /* Compute true longitude and radius vector */ + E = M + e * RAD_DEG * sind(M) * ( 1.0 + e * cosd(M) ); + x = cosd(E) - e; + y = sqrt(1.0 - e*e) * sind(E); + *r = sqrt(x*x + y*y); /* Solar distance */ + v = atan2d( y, x ); /* True anomaly */ + *lon = v + w; /* True solar longitude */ + if (*lon >= 360.0) + *lon -= 360.0; /* Make it 0..360 degrees */ +} + +static void getSunRAandDec (double d, double *RA, double *dec, double *r) +{ + double lon, obl_ecl, x, y, z; + + /* Compute Sun's ecliptical coordinates */ + sunPosition (d, &lon, r); + + /* Compute ecliptic rectangular coordinates (z=0) */ + x = *r * cosd(lon); + y = *r * sind(lon); + + /* Compute obliquity of ecliptic (inclination of Earth's axis) */ + obl_ecl = 23.4393 - 3.563E-7 * d; + + /* Convert to equatorial rectangular coordinates - x is uchanged */ + z = y * sind(obl_ecl); + y = y * cosd(obl_ecl); + + /* Convert to spherical coordinates */ + *RA = atan2d( y, x ); + *dec = atan2d( z, sqrt(x*x + y*y) ); +} + + +// This function computes GMST0, the Greenwhich Mean Sidereal Time at UTC. GMST +// is then the sidereal time at Greenwich at any time of the day. +static double GMST0 (double d) +{ + double sidtim0; + + sidtim0 = normalize ((180.0 + 356.0470 + 282.9404) + + (0.9856002585 + 4.70935E-5) * d); + return sidtim0; +} + + +/* Note: year,month,date = calendar date, 1801-2099 only. */ +/* Eastern longitude positive, Western longitude negative */ +/* Northern latitude positive, Southern latitude negative */ +/* The longitude value IS critical in this function! */ +/* altit = the altitude which the Sun should cross */ +/* Set to -35/60 degrees for rise/set, -6 degrees */ +/* for civil, -12 degrees for nautical and -18 */ +/* degrees for astronomical twilight. */ +/* upper_limb: non-zero -> upper limb, zero -> center */ +/* Set to non-zero (e.g. 1) when computing rise/set */ +/* times, and to zero when computing start/end of */ +/* twilight. */ +/* *rise = where to store the rise time */ +/* *set = where to store the set time */ +/* Both times are relative to the specified altitude, */ +/* and thus this function can be used to comupte */ +/* various twilight times, as well as rise/set times */ +/* Return value: 0 = sun rises/sets this day, times stored at */ +/* *trise and *tset. */ +/* +1 = sun above the specified "horizon" 24 hours. */ +/* *trise set to time when the sun is at south, */ +/* minus 12 hours while *tset is set to the south */ +/* time plus 12 hours. "Day" length = 24 hours */ +/* -1 = sun is below the specified "horizon" 24 hours */ +/* "Day" length = 0 hours, *trise and *tset are */ +/* both set to the time when the sun is at south. */ +/* */ +static int sunRiseSet +( + int year, + int month, + int day, + double lon, + double lat, + double altit, + int upper_limb, + double *trise, + double *tset +) +{ + double d, /* Days since 2000 Jan 0.0 (negative before) */ + sr, /* Solar distance, astronomical units */ + sRA, /* Sun's Right Ascension */ + sdec, /* Sun's declination */ + sradius, /* Sun's apparent radius */ + t, /* Diurnal arc */ + tsouth, /* Time when Sun is at south */ + sidtime, /* Local sidereal time */ + cost; + + int rc = 0; /* Return code from function - usually 0 */ + + /* Compute d of 12h local mean solar time */ + d = DAYS_THIS_MILLENIUM(year,month,day) + 0.5 - lon/360.0; + + /* Compute local sideral time of this moment */ + sidtime = normalize (GMST0(d) + 180.0 + lon); + + /* Compute Sun's RA + Decl at this moment */ + getSunRAandDec (d, &sRA, &sdec, &sr); + + /* Compute time when Sun is at south - in hours UT */ + tsouth = 12.0 - normalize180 (sidtime - sRA)/15.0; + + /* Compute the Sun's apparent radius, degrees */ + sradius = 0.2666 / sr; + + /* Do correction to upper limb, if necessary */ + if (upper_limb) + altit -= sradius; + + /* Compute the diurnal arc that the Sun traverses to reach */ + /* the specified altitide altit: */ + cost = (sind(altit) - sind(lat) * sind(sdec)) / (cosd(lat) * cosd(sdec)); + if (cost >= 1.0) + { + rc = -1; + t = 0.0; /* Sun always below altit */ + } + else if (cost <= -1.0) + { + rc = +1; + t = 12.0; /* Sun always above altit */ + } + else + { + t = acosd(cost)/15.0; /* The diurnal arc, hours */ + } + + /* Store rise and set times - in hours UTC */ + *trise = tsouth - t; + *tset = tsouth + t; + + return rc; +} + +/* Note: year,month,date = calendar date, 1801-2099 only. */ +/* Eastern longitude positive, Western longitude negative */ +/* Northern latitude positive, Southern latitude negative */ +/* The longitude value is not critical. Set it to the correct */ +/* longitude if you're picky, otherwise set to to, say, 0.0 */ +/* The latitude however IS critical - be sure to get it correct */ +/* altit = the altitude which the Sun should cross */ +/* Set to -35/60 degrees for rise/set, -6 degrees */ +/* for civil, -12 degrees for nautical and -18 */ +/* degrees for astronomical twilight. */ +/* upper_limb: non-zero -> upper limb, zero -> center */ +/* Set to non-zero (e.g. 1) when computing day length */ +/* and to zero when computing day+twilight length. */ +static double dayLength +( + int year, + int month, + int day, + double lon, + double lat, + double altit, + int upper_limb +) +{ + double d, /* Days since 2000 Jan 0.0 (negative before) */ + obl_ecl, /* Obliquity (inclination) of Earth's axis */ + sr, /* Solar distance, astronomical units */ + slon, /* True solar longitude */ + sin_sdecl, /* Sine of Sun's declination */ + cos_sdecl, /* Cosine of Sun's declination */ + sradius, /* Sun's apparent radius */ + t, /* Diurnal arc */ + cost; + + d = DAYS_THIS_MILLENIUM(year,month,day) + 0.5 - lon/360.0; + + /* Compute obliquity of ecliptic (inclination of Earth's axis) */ + obl_ecl = 23.4393 - 3.563E-7 * d; + + /* Compute Sun's position */ + sunPosition (d, &slon, &sr); + + /* Compute sine and cosine of Sun's declination */ + sin_sdecl = sind(obl_ecl) * sind(slon); + cos_sdecl = sqrt(1.0 - sin_sdecl * sin_sdecl); + + /* Compute the Sun's apparent radius, degrees */ + sradius = 0.2666 / sr; + + /* Do correction to upper limb, if necessary */ + if (upper_limb) + altit -= sradius; + + /* Compute the diurnal arc that the Sun traverses to reach */ + /* the specified altitide altit: */ + cost = (sind(altit) - sind(lat) * sin_sdecl) / + (cosd(lat) * cos_sdecl); + if (cost >= 1.0) + t = 0.0; // Sun always below altit + else if (cost <= -1.0) + t = 24.0; // Sun always above altit + else + t = (2.0/15.0) * acosd(cost); // The diurnal arc, hours + + return t; +} + +static int16_t packAndGMTAlign (double value) +{ + int16_t retVal; + int temp; + time_t nowtime = time (NULL); + struct tm bknTime; + long gmtMinsEast; + + localtime_r (&nowtime, &bknTime); + +#ifdef HAVE_STRUCT_TM_TM_ZONE + gmtMinsEast = bknTime.tm_gmtoff/60; +#else + gmtMinsEast = -(timezone/60); +#endif + + value *= 60; + temp = (int)value; + temp += gmtMinsEast; + temp += 1; + retVal = ((temp/60)*100) + (temp%60); + return retVal; +} + + +// API functions +// all times returned as packed time (hour*100 + minutes) +// lat and long are floats representing degrees: +// N => + +// S => - +// E => + +// W => - + +// packedRise = packedSet = -1 => the sun never sets +// packedRise = packedSet = -2 => the sun never rises +// otherwise the valid times in packed format +void sunTimesGetSunRiseSet +( + int year, + int month, + int day, + float latitude, + float longitude, + int type, // RS_TYPE_SUN, RS_TYPE_CIVIL, RS_TYPE_ASTRO + int16_t *packedRise, + int16_t *packedSet +) +{ + int rv; + double rise, set; + + switch (type) + { + case RS_TYPE_SUN: + rv = SUN_RISE_SET(year, month, day, (double)longitude, + (double)latitude, &rise, &set); + break; + case RS_TYPE_CIVIL: + rv = CIVIL_RISE_SET(year, month, day, (double)longitude, + (double)latitude, &rise, &set); + break; + case RS_TYPE_ASTRO: + rv = ASTRO_RISE_SET(year, month, day, (double)longitude, + (double)latitude, &rise, &set); + break; + case RS_TYPE_MIDDAY: + rv = SUN_RISE_SET(year, month, day, (double)longitude, + (double)latitude, &rise, &set); + break; + default: + rv = -1; + break; + } + if (rv == 1) + { + *packedRise = *packedSet = -1; + } + else if (rv == -1) + { + *packedRise = *packedSet = -2; + } + else + { + if (type == RS_TYPE_MIDDAY) + { + *packedRise = packAndGMTAlign ((rise+set)/2.0); + } + else + { + *packedRise = packAndGMTAlign (rise); + *packedSet = packAndGMTAlign (set); + } + } + + return; +} + +// Get day length in packed format +int16_t sunTimesGetDayLength +( + int year, + int month, + int day, + float latitude, + float longitude +) +{ + int rv; + double daylen; + int16_t packedLength; + + daylen = DAY_LENGTH(year, month, day, (double)longitude, (double)latitude); + daylen *= 60; + daylen += 1; + rv = (int)daylen; + packedLength = ((rv/60) * 100) + (rv % 60); + return packedLength; +} + +// Debug application: +#if 0 +int main (void) +{ + int year, month, day; + double lon, lat; + int16_t packedLen, packedRise, packedSet; + + printf ("Longitude (+ is east) and latitude (+ is north): "); + scanf ("%lf %lf", &lon, &lat); + + for ( ; ; ) + { + printf ("Input date ( yyyy mm dd ) (ctrl-C exits): "); + scanf ("%d %d %d", &year, &month, &day); + + packedLen = sunTimesGetDayLength (year, month, day, (float)lat, (float)lon); + printf ("==> Day length: %2.2d:%2.2d hours\n\n", packedLen/100, packedLen%100); + + sunTimesGetSunRiseSet (year, month, day, (float)lat, (float)lon, + RS_TYPE_MIDDAY, &packedRise, &packedSet); + if (packedRise == -1) + { + printf ("Sun never sets!\n"); + } + else if (packedRise == -2) + { + printf ("Sun never rises!\n"); + } + else + { + printf ("==> Midday: %2.2d:%2.2d Local Time\n\n", + packedRise/100, packedRise%100); + } + + sunTimesGetSunRiseSet (year, month, day, (float)lat, (float)lon, + RS_TYPE_SUN, &packedRise, &packedSet); + if (packedRise == -1) + { + printf ("Sun never sets!\n"); + } + else if (packedRise == -2) + { + printf ("Sun never rises!\n"); + } + else + { + printf ("==> Sunrise: %2.2d:%2.2d, Sunset: %2.2d:%2.2d Local Time\n", + packedRise/100, packedRise%100, packedSet/100, packedSet%100); + } + + sunTimesGetSunRiseSet (year, month, day, (float)lat, (float)lon, + RS_TYPE_CIVIL, &packedRise, &packedSet); + if (packedRise == -1) + { + printf ("Sun never falls below civil twilight!\n"); + } + else if (packedRise == -2) + { + printf ("Sun never rises above civil twilight!\n"); + } + else + { + printf ("==> Civil Twilight Begins: %2.2d:%2.2d, Civil Twilight Ends: %2.2d:%2.2d Local Time\n", + packedRise/100, packedRise%100, packedSet/100, packedSet%100); + } + + sunTimesGetSunRiseSet (year, month, day, (float)lat, (float)lon, + RS_TYPE_ASTRO, &packedRise, &packedSet); + if (packedRise == -1) + { + printf ("Sun never falls below astronomical twilight!\n"); + } + else if (packedRise == -2) + { + printf ("Sun never rises above astronomical twilight!\n"); + } + else + { + printf ("==> Astronomical Twilight Begins: %2.2d:%2.2d, Astronomical Twilight Ends: %2.2d:%2.2d Local Time\n", + packedRise/100, packedRise%100, packedSet/100, packedSet%100); + } + } +} +#endif + diff --git a/common/sysdefs.h b/common/sysdefs.h new file mode 100755 index 0000000..d605422 --- /dev/null +++ b/common/sysdefs.h @@ -0,0 +1,432 @@ +#ifndef INC_sysdefsh +#define INC_sysdefsh +/*--------------------------------------------------------------------------- + + FILENAME: + sysdefs.h + + PURPOSE: + Define system definitions. + + REVISION HISTORY: + Date Engineer Revision Remarks + 08/24/03 M.S. Teel 0 Original + 11/02/2004 K. McGuire 1 ARM Mods and + CONS_ARCHIVE_INTERVAL + 01/10/2006 M.S. Teel 2 Update for station + abstraction + + NOTES: + + + LICENSE: + Copyright (c) 2004, Mark S. Teel (mark@teel.ws) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +#include +#include +#include + +#include "config.h" +#ifdef HAVE_STDINT_H +#include +#endif +#include +#include + + + +// define the GLOBAL version string here +#define globalWviewVersionStr PACKAGE_STRING + + +// define some global debug macros +#define __DEBUG_BUFFERS FALSE + + +// define the radlib system ID for all wview daemons +#define WVIEW_SYSTEM_ID 0x01 + + +// Define the wvconfig semaphore index: +#define WVIEW_CONFIG_SEM_INDEX (SEM_INDEX_USER_START + 1) + +// Define the archive DB semaphore index: +#define WVIEW_ARCHIVE_SEM_INDEX (SEM_INDEX_USER_START + 2) + + +// define the standard system file locations +#define WVIEW_CONFIG_DIR WV_CONFIG_DIR +#define WVIEW_RUN_DIR WV_RUN_DIR + +// Define common string sizes: +#define WVIEW_STRING1_SIZE 128 +#define WVIEW_STRING2_SIZE 256 +#define WVIEW_STRING3_SIZE 512 + + +// Database defs: +#define WVIEW_CONFIG_DATABASE "wview-conf.sdb" + +#define WVIEW_ARCHIVE_DATABASE "wview-archive.sdb" +#define WVIEW_ARCHIVE_MARKER_FILE "archive_marker" + +#define WVIEW_HISTORY_DATABASE "wview-history.sdb" +#define WVIEW_DAY_HISTORY_TABLE "dayHistory" + +#define WVIEW_HILOW_DATABASE "wview-hilow.sdb" +#define WVIEW_HILOW_META_TABLE "metainfo" +#define WVIEW_HILOW_WINDDIR_TABLE "windDir" +#define WVIEW_HILOW_MARKER_FILE "hilow_marker" + +#define WVIEW_NOAA_DATABASE "wview-noaa.sdb" +#define WVIEW_NOAA_TABLE "noaaHistory" +#define WVIEW_NOAA_MARKER_FILE "noaa_marker" + + +// define the process names, etc. + +#define WVD_LOCK_FILE_NAME "wviewd.pid" +#define HTML_LOCK_FILE_NAME "htmlgend.pid" +#define HTML_INDICATE_FILE_NAME "wview-running" +#define FTP_LOCK_FILE_NAME "wviewftpd.pid" +#define SSH_LOCK_FILE_NAME "wviewsshd.pid" +#define ALARMS_LOCK_FILE_NAME "wvalarmd.pid" +#define CWOP_LOCK_FILE_NAME "wvcwopd.pid" +#define HTTP_LOCK_FILE_NAME "wvhttpd.pid" +#define PMON_LOCK_FILE_NAME "wvpmond.pid" + +#define PROC_NAME_DAEMON "wviewd" +#define PROC_NUM_TIMERS_DAEMON 5 + +#define PROC_NAME_HTML "htmlgend" +#define PROC_NUM_TIMERS_HTML 3 +#define HTML_GRAPHICS_CONFIG_FILENAME "graphics.conf" +#define HTML_PRE_GEN_SCRIPT "pre-generate.sh" +#define HTML_POST_GEN_SCRIPT "post-generate.sh" + +#define PROC_NAME_FTP "wviewftpd" +#define PROC_NUM_TIMERS_FTP 1 +#define FTP_MARKER_FILE "ftp_marker" + +#define PROC_NAME_SSH "wviewsshd" +#define PROC_NUM_TIMERS_SSH 1 + +#define PROC_NAME_ALARMS "wvalarmd" +#define PROC_NUM_TIMERS_ALARMS 0 + +#define PROC_NAME_CWOP "wvcwopd" +#define PROC_NUM_TIMERS_CWOP 1 + +#define PROC_NAME_HTTP "wvhttpd" +#define PROC_NUM_TIMERS_HTTP 0 + +#define PROC_NAME_PMON "wvpmond" +#define PROC_NUM_TIMERS_PMON 2 + +#define WVIEW_STATUS_DIRECTORY "/var/run/wview" +#define WVIEW_STATUS_FILE_NAME "wview.sts" +#define HTML_STATUS_FILE_NAME "html.sts" +#define FTP_STATUS_FILE_NAME "ftp.sts" +#define SSH_STATUS_FILE_NAME "ssh.sts" +#define ALARMS_STATUS_FILE_NAME "alarms.sts" +#define CWOP_STATUS_FILE_NAME "cwop.sts" +#define HTTP_STATUS_FILE_NAME "http.sts" +#define PMON_STATUS_FILE_NAME "pmon.sts" + + +// define all wview events here for consistency +#define STATION_INIT_COMPLETE_EVENT 0x00010000 +#define STATION_LOOP_COMPLETE_EVENT 0x00020000 + + +// some general time definitions +#define WV_SECONDS_IN_HOUR 3600 +#define WV_SECONDS_IN_DAY 86400 +#define WV_SECONDS_IN_WEEK (7*WV_SECONDS_IN_DAY) +#define WV_SECONDS_IN_MONTH (28*WV_SECONDS_IN_DAY) +#define WV_SECONDS_IN_YEAR (365*WV_SECONDS_IN_DAY) +#define WV_MINUTES_IN_DAY 1440 +#define WV_MINUTES_IN_YEAR (365*WV_MINUTES_IN_DAY) +#define SECONDS_IN_INTERVAL(x) (x * 60) + + +// packed time utilities +#define EXTRACT_PACKED_YEAR(x) ((((x) >> 9) & 0x3F) + 2000) +#define EXTRACT_PACKED_MONTH(x) (((x) >> 5) & 0xF) +#define EXTRACT_PACKED_DAY(x) ((x) & 0x1F) +#define EXTRACT_PACKED_HOUR(y) ((y) / 100) +#define EXTRACT_PACKED_MINUTE(y) ((y) % 100) + +#define INSERT_PACKED_DATE(y,m,d) (((((y)-2000) & 0x3F) << 9) | (((m) & 0xF) << 5) | ((d) & 0x1F)) + + +#ifndef _MAX_PATH +#define _MAX_PATH 512 +#endif + + +// Define the maximum radMsgLog length: +#define RADMSGLOG_MAX_LENGTH 448 + + +// Define a max for date strings: +#define WV_MAX_DATE_LENGTH 128 + + +// VP Icon bit definitions +#define VP_FCAST_ICON_MAX 0x1F +#define VP_FCAST_ICON_RAIN 0x01 +#define VP_FCAST_ICON_CLOUD 0x02 +#define VP_FCAST_ICON_PART_SUN 0x04 +#define VP_FCAST_ICON_SUNNY 0x08 +#define VP_FCAST_ICON_SNOW 0x10 + + +// Process Monitor process types +typedef enum +{ + PMON_PROCESS_WVIEWD = 0, + PMON_PROCESS_HTMLGEND, + PMON_PROCESS_WVALARMD, + PMON_PROCESS_WVCWOPD, + PMON_PROCESS_WVHTTPD, + PMON_PROCESS_MAX +} PMON_PROCESS_TYPES; + +#define PMON_PROCESS_SET(mask,type) (mask | (0x01 << type)) +#define PMON_PROCESS_ISSET(mask,type) ((mask >> type) & 0x01) + + +// utility prototypes + +// define the verbosity bit definitions +enum _verbosity_bits +{ + WV_VERBOSE_WVIEWD = 0x01, + WV_VERBOSE_HTMLGEND = 0x02, + WV_VERBOSE_WVALARMD = 0x04, + WV_VERBOSE_WVIEWFTPD = 0x08, + WV_VERBOSE_WVIEWSSHD = 0x10, + WV_VERBOSE_WVCWOPD = 0x20, + WV_VERBOSE_WVWUNDERD = 0x40, + WV_VERBOSE_ALL = 0x7F +}; + +// Define wind units enumerations: +typedef enum +{ + HTML_WINDUNITS_MPH = 0, + HTML_WINDUNITS_MS, + HTML_WINDUNITS_KNOTS, + HTML_WINDUNITS_KMH +} HTML_WUNITS; + +extern void wvutilsSetWindUnits(HTML_WUNITS units); +extern char* wvutilsGetWindUnitLabel(void); +extern float wvutilsGetWindSpeed(float mph); +extern float wvutilsGetWindSpeedMetric(float kph); + +// wvconfigInit() must have been called before calling this: +extern int wvutilsSetVerbosity (uint16_t daemonBitMask); + +extern int wvutilsToggleVerbosity (void); +extern void wvutilsLogEvent (int priority, char *format, ...); + +extern float wvutilsCalculateHeatIndex (float temp, float humidity); +extern float wvutilsCalculateWindChill (float temp, float windspeed); +extern float wvutilsCalculateDewpoint (float temp, float humidity); + +/**************************************************************************** + * Converters + ***************************************************************************/ +extern void wvutilsSetRainIsMM(int setValue); +extern int wvutilsGetRainIsMM(void); +extern float wvutilsConvertFToC (float fahrenValue); +extern float wvutilsConvertCToF (float celsiusValue); +extern float wvutilsConvertDeltaFToC (float fahrenValue); +extern float wvutilsConvertMPHToKPH (float mph); +extern float wvutilsConvertMPHToMPS (float mph); +extern float wvutilsConvertMPHToKnots (float mph); +extern float wvutilsConvertKPHToMPH (float kph); +extern float wvutilsConvertKPHToMPS (float kph); +extern float wvutilsConvertKPHToKnots (float kph); +extern float wvutilsConvertMPSToKPH (float mps); +extern float wvutilsConvertMPSToMPH (float mps); +extern float wvutilsConvertMPSToKnots (float mps); +extern float wvutilsConvertKnotsToKPH (float mps); +extern float wvutilsConvertKnotsToMPH (float mps); +extern float wvutilsConvertKnotsToMPS (float mps); +extern float wvutilsConvertINHGToHPA (float inches); +extern float wvutilsConvertHPAToINHG (float mb); +extern float wvutilsConvertRainINToMetric (float inches); +extern float wvutilsConvertRainMetricToIN (float inches); +extern float wvutilsConvertINToCM (float inches); +extern float wvutilsConvertCMToIN (float cm); +extern float wvutilsConvertINToMM (float inches); +extern float wvutilsConvertMMToIN (float mm); + +extern float wvutilsConvertFeetToMeters (float feet); +extern float wvutilsConvertKilometersToMiles (float km); +extern float wvutilsConvertMilesToKilometers (float miles); + +extern char *wvutilsConvertToBeaufortScale (int windSpeed); + + +// calculate the air density in kg/m^3 +extern float wvutilsCalculateAirDensity (float tempF, float bp, float dp); + +// calculate sea level pressure from station pressure +extern float wvutilsConvertSPToSLP (float SP, float tempF, float elevationFT); + +// calculate station pressure from sea level pressure +extern float wvutilsConvertSLPToSP (float SLP, float tempF, float elevationFT); + +// calculate altimeter pressure from station pressure +extern float wvutilsConvertSPToAltimeter (float SPInches, float elevationFT); + +// Calculate the apparent temperature: +extern float wvutilsCalculateApparentTemp(float temp, float windspeed, float humidity); + +// Calculate wet bulb temperature +extern float wvutilsCalculateWetBulbTemp( float temp, float humidity, float pressure); + +// convert packed date/time to time_t +extern time_t wvutilsPackedTimeToTimeT (uint16_t packedDate, uint16_t packedTime); + +// calculate the time difference in packed date/time format +// does not consider leap years +// returns the delta in minutes +extern int wvutilsCalculatePackedTimeDelta +( + uint16_t newDate, + uint16_t newTime, + uint16_t oldDate, + uint16_t oldTime +); + +// increment a packed time value by the given minutes, rolls over at 24:00 +extern uint16_t wvutilsIncrementPackedTime (uint16_t pTime, int minutes); + + +// define sun rise and set types +enum _rise_set_types +{ + RS_TYPE_SUN = 1, + RS_TYPE_CIVIL, + RS_TYPE_ASTRO, + RS_TYPE_MIDDAY +}; + +// all times returned as packed time (hour*100 + minutes) +// lat and long are floats representing degrees: +// N => + +// S => - +// E => + +// W => - + +// Return Values: +// packedRise = packedSet = -1 => the sun never sets +// packedRise = packedSet = -2 => the sun never rises +// otherwise the valid times in packed format +extern void sunTimesGetSunRiseSet +( + int year, + int month, + int day, + float latitude, + float longitude, + int type, // RS_TYPE_SUN, RS_TYPE_CIVIL, RS_TYPE_ASTRO, RS_TYPE_MIDDAY + int16_t *packedRise, + int16_t *packedSet +); + +// Get day length in packed format +extern int16_t sunTimesGetDayLength +( + int year, + int month, + int day, + float latitude, + float longitude +); + +// produce a float string fixing the truncation annoyance +extern char *wvutilsPrintFloat (float value, int decPlaces); + +// Define a NULL-terminating wvstrncpy: +extern int wvstrncpy(char *d, const char *s, size_t bufsize); + +// Right-trim a string: +extern void wvstrtrim(char *d); + +// Respond to PMON poll messages +extern void wvutilsSendPMONPollResponse (int mask, PMON_PROCESS_TYPES process); + +// History start time utilities: +extern int wvutilsGetDayStartTime (int archiveInterval); +extern time_t wvutilsGetWeekStartTime (int archiveInterval); +extern time_t wvutilsGetMonthStartTime (int archiveInterval); +extern time_t wvutilsGetYearStartTime (int archiveInterval); + +// Time utilities: +extern int wvutilsGetYear (time_t ntime); +extern int wvutilsGetMonth (time_t ntime); +extern int wvutilsGetDay (time_t ntime); +extern int wvutilsGetHour (time_t ntime); +extern int wvutilsGetMin (time_t ntime); +extern int wvutilsGetSec (time_t ntime); + +extern int wvutilsTimeIsToday(time_t checkTime); + +// Determine if it is day or night +// Returns TRUE or FALSE +extern int wvutilsIsDayTime (int16_t timeSunrise, int16_t timeSunset); + + +// return a string indicating the moon phase: +extern char *lunarPhaseGet (char *increase, char *decrease, char *full); + + +// Path utilities: +extern char* wvutilsGetArchivePath(void); +extern char* wvutilsGetConfigPath(void); + +// Marker file utilities: +extern int wvutilsWriteMarkerFile(const char* filePath, time_t marker); +extern time_t wvutilsReadMarkerFile(const char* filePath); + + +// Define a SIGCHLD handler to wait for child processes to exit: +// Should only be called from process signal handler. +extern void wvutilsWaitForChildren(void); + + +// Initialize and detect a DST state change: +// Uses the system clock and the tm struct value "tm_isdst" returned by the +// system call "localtime" to make the determination +extern int wvutilsDetectDSTInit(void); + +typedef enum +{ + WVUTILS_DST_NO_CHANGE = 0, // no change + WVUTILS_DST_FALL_BACK = -1, // End of DST + WVUTILS_DST_SPRING_FORWARD = 1 // Start od DST +} WVUTILS_DST_TYPE; + +// Returns: WVUTILS_DST_TYPE +extern int wvutilsDetectDSTChange(void); + + +// Assign a degree value to received string representation for wind direction: +// Returns degree equivalent or -1 if ERROR: +extern int wvutilsConvertWindStrToDegrees(const char* windStr); + +// Create the CWOP version string: +extern char* wvutilsCreateCWOPVersion(char* wviewStr); + +#endif diff --git a/common/windAverage.c b/common/windAverage.c new file mode 100755 index 0000000..1fa82b4 --- /dev/null +++ b/common/windAverage.c @@ -0,0 +1,116 @@ +/*--------------------------------------------------------------------------- + + FILENAME: + windAverage.c + + PURPOSE: + Provide the consensus wind averaging API methods. + + REVISION HISTORY: + Date Engineer Revision Remarks + 1/31/2004 M.S. Teel 0 Original + + NOTES: + + + LICENSE: + Copyright (c) 2004, Mark S. Teel (mark@teel.ws) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +// ... System header files +#include + + +// ... Local header files +#include + + + +// ... define methods here + +void windAverageReset (WAVG_ID id) +{ + memset (id, 0, sizeof (*id)); +} + +// ... add a data point (wind observation) to the data set +void windAverageAddValue (WAVG_ID id, int value) +{ + if (value < 0) + value = 0; + + value += (WAVG_BIN_SIZE/2); + value /= WAVG_BIN_SIZE; + value %= WAVG_NUM_BINS; + + id->bins[value] ++; + + return; +} + +// ... add a set of wind direction bins: +void windAverageAddBins (WAVG_ID id, int* bins) +{ + int i; + + for (i = 0; i < WAVG_NUM_BINS; i ++) + { + id->bins[i] += bins[i]; + } +} + +// ... use consensus averaging to compute the average wind direction +int windAverageCompute (WAVG_ID id) +{ + int i, j, retVal, maxIndex = 0; + uint64_t sum, maxSum = 0; + + // ... first, fill in the "wrap-around" bins + for (i = WAVG_NUM_BINS; i < WAVG_TOTAL_BINS; i ++) + { + id->bins[i] = id->bins[i - WAVG_NUM_BINS]; + } + + // ... now, find our best consensus + for (i = 0; i < WAVG_NUM_BINS; i ++) + { + sum = 0; + for (j = 0; j <= WAVG_CONSENSUS_BINS; j ++) + { + sum += id->bins[i + j]; + } + + if (sum > maxSum) + { + maxSum = sum; + maxIndex = i; + } + } + + if (maxSum == 0) + { + return ARCHIVE_VALUE_NULL; + } + + // ... now we know where the best "id->consBins" is, + // ... compute the weighted average value + sum = 0; + for (i = 0; i <= WAVG_CONSENSUS_BINS; i ++) + { + sum += (i * id->bins[maxIndex+i]); + } + sum *= WAVG_INTERVAL; + sum /= maxSum; + sum += (maxIndex * WAVG_INTERVAL); + + retVal = (int)sum; + retVal %= 720; + retVal /= 2; + + return retVal; +} + diff --git a/common/windAverage.h b/common/windAverage.h new file mode 100644 index 0000000..3cebd7b --- /dev/null +++ b/common/windAverage.h @@ -0,0 +1,64 @@ +#ifndef INC_windaverageh +#define INC_windaverageh +/*--------------------------------------------------------------------------- + + FILENAME: + windAverage.h + + PURPOSE: + Define the windAverage API. + This utilizes consensus averaging to avoid the problems associated + with values around North (i.e., 359 and 1 averaging to 180, + or South, instead of 0, or North). + + REVISION HISTORY: + Date Engineer Revision Remarks + 01/31/2004 M.S. Teel 0 Original + + NOTES: + + + LICENSE: + Copyright (c) 2004, Mark S. Teel (mark@teel.ws) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +// ... includes +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + + +// ... macro definitions + + +// ... API prototypes + +extern void windAverageReset (WAVG_ID id); + +// ... add a data point (wind observation) to the data set; +// ... direction MUST be an integer in the range [0,359] +extern void windAverageAddValue (WAVG_ID id, int direction); + +// ... add a set of wind direction bins: +extern void windAverageAddBins (WAVG_ID id, int* bins); + +// ... use consensus averaging to compute the average wind dir +extern int windAverageCompute (WAVG_ID id); + +#endif + + diff --git a/common/wvconfig.c b/common/wvconfig.c new file mode 100644 index 0000000..a0fead5 --- /dev/null +++ b/common/wvconfig.c @@ -0,0 +1,237 @@ +/*--------------------------------------------------------------------------- + + FILENAME: + wvconfig.c + + PURPOSE: + Provide the configuration API methods. + + REVISION HISTORY: + Date Engineer Revision Remarks + 7/05/2008 M.S. Teel 0 Original + + NOTES: + + + LICENSE: + Copyright (c) 2008, Mark S. Teel (mark@teel.ws) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +// ... System header files +#include +#include + + +// ... Local header files +#include + + +// ... Local memory: +static SQLITE_DATABASE_ID sqliteID; + +// ... Define a semaphore for access control: +static SEM_ID wvconfigMutex; + + +// ... Local methods + +// Query for a parameter value - it is converted to the proper format later: +static int queryParmValue (const char* configItem, char* valueStore) +{ + char query[DB_SQLITE_QUERY_LENGTH_MAX]; + SQLITE_RESULT_SET_ID result; + SQLITE_ROW_ID rowDescr; + SQLITE_FIELD_ID field; + + if (sqliteID == NULL) + { + radMsgLog (PRI_HIGH, "queryParmValue: sqliteID is NULL!"); + return ERROR; + } + + sprintf (query, "SELECT value FROM config WHERE name = '%s'", configItem); + + if (radsqliteQuery(sqliteID, query, TRUE) == ERROR) + { + radMsgLog (PRI_MEDIUM, + "queryParmValue: radsqliteQuery %s failed!", configItem); + return ERROR; + } + + result = radsqliteGetResults (sqliteID); + if (result == NULL) + { + radMsgLog (PRI_MEDIUM, + "queryParmValue: radsqliteGetResults failed!"); + return ERROR; + } + + // We have a result, return it to the caller: + rowDescr = radsqliteResultsGetFirst (result); + if (rowDescr == NULL) + { + radMsgLog (PRI_MEDIUM, + "queryParmValue: radsqliteResultsGetFirst failed!"); + radsqliteReleaseResults (sqliteID, result); + return ERROR; + } + + field = radsqliteFieldGet (rowDescr, configCOLUMN_VALUE); + if (field == NULL) + { + radMsgLog (PRI_MEDIUM, + "queryParmValue: radsqliteFieldGet failed!"); + radsqliteReleaseResults (sqliteID, result); + return ERROR; + } + + memcpy (valueStore, + radsqliteFieldGetCharValue(field), + radsqliteFieldGetCharLength(field)); + valueStore[radsqliteFieldGetCharLength(field)] = 0; + + // Clean up: + radsqliteReleaseResults (sqliteID, result); + + return OK; +} + + +// ... API (public) methods + +// wvconfigInit: Open the configuration database for this process: +int wvconfigInit (int firstProcess) +{ + char buffer[_MAX_PATH]; + struct stat fileData; + + // Make sure our config db is there: + sprintf (buffer, "%s/%s", WVIEW_CONFIG_DIR, WVIEW_CONFIG_DATABASE); + if (stat (buffer, &fileData) != 0) + { + radMsgLog (PRI_CATASTROPHIC, + "Cannot locate config database %s - aborting!", + buffer); + return ERROR; + } + + if (firstProcess == TRUE) + { + wvconfigMutex = radSemCreate(WVIEW_CONFIG_SEM_INDEX, 1); + } + else + { + wvconfigMutex = radSemCreate(WVIEW_CONFIG_SEM_INDEX, -1); + } + + if (wvconfigMutex == NULL) + { + radMsgLog (PRI_CATASTROPHIC, + "Cannot create/attach config database semaphore - aborting!"); + return ERROR; + } + + // Lock for serial access: + radSemTake(wvconfigMutex); + + sqliteID = radsqliteOpen ((const char*)buffer); + if (sqliteID == NULL) + { + radMsgLog (PRI_CATASTROPHIC, "wvconfigInit: radsqliteOpen %s failed!", + buffer); + radSemGive(wvconfigMutex); + radSemDelete(wvconfigMutex); + return ERROR; + } + + return OK; +} + +// wvconfigExit: clean up and detach from the wview configuration API +void wvconfigExit (void) +{ + if (sqliteID != NULL) + { + radsqliteClose (sqliteID); + sqliteID = NULL; + } + + radSemGive(wvconfigMutex); + radSemDelete(wvconfigMutex); +} + +// wvconfigGetINTValue: retrieve the integer value for this parameter; +// Returns: integer value or 0 +int wvconfigGetINTValue (const char* configItem) +{ + char buffer[_MAX_PATH]; + + if (queryParmValue (configItem, buffer) == ERROR) + { + return 0; + } + + return (atoi(buffer)); +} + +// wvconfigGetDOUBLEValue: retrieve the double value for this parameter; +// Returns: double value +double wvconfigGetDOUBLEValue (const char* configItem) +{ + char buffer[_MAX_PATH]; + + if (queryParmValue (configItem, buffer) == ERROR) + { + return 0.0; + } + + return ((double)atof(buffer)); +} + +// wvconfigGetStringValue: retrieve the string value for this parameter +// Returns: const static string reference +const char* wvconfigGetStringValue (const char* configItem) +{ + static char buffer[_MAX_PATH]; + + if (queryParmValue (configItem, buffer) == ERROR) + { + return NULL; + } + + return buffer; +} + +// wvconfigGetBooleanValue: retrieve the bool value for this parameter +// Returns: TRUE or FALSE +int wvconfigGetBooleanValue (const char* configItem) +{ + char buffer[_MAX_PATH]; + + if (queryParmValue (configItem, buffer) == ERROR) + { + return ERROR; + } + + if (!strcmp(buffer, "yes")) + { + return TRUE; + } + else if (!strcmp(buffer, "1")) + { + return TRUE; + } + else if (!strcmp(buffer, "TRUE")) + { + return TRUE; + } + else + { + return FALSE; + } +} + diff --git a/common/wvconfig.h b/common/wvconfig.h new file mode 100644 index 0000000..0465f6b --- /dev/null +++ b/common/wvconfig.h @@ -0,0 +1,298 @@ +#ifndef INC_wvconfigh +#define INC_wvconfigh +/*--------------------------------------------------------------------------- + + FILENAME: + wvconfig.h + + PURPOSE: + Define the wview configuration API. + + REVISION HISTORY: + Date Engineer Revision Remarks + 07/05/2008 M.S. Teel 0 Original + + NOTES: + + + LICENSE: + Copyright (c) 2008, Mark S. Teel (mark@teel.ws) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +// ... includes +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + + +// ... macro definitions + + +// ... typedefs + +// wview configuration item IDs: + +#define configItem_ENABLE_HTMLGEN "ENABLE_HTMLGEN" +#define configItem_ENABLE_ALARMS "ENABLE_ALARMS" +#define configItem_ENABLE_CWOP "ENABLE_CWOP" +#define configItem_ENABLE_HTTP "ENABLE_HTTP" +#define configItem_ENABLE_FTP "ENABLE_FTP" +#define configItem_ENABLE_SSH "ENABLE_SSH" +#define configItem_ENABLE_PROCMON "ENABLE_PROCMON" +#define configItem_ENABLE_SQL "ENABLE_SQL" + +#define configItem_ENABLE_EMAIL "ENABLE_EMAIL_ALERTS" +#define configItem_TO_EMAIL_ADDRESS "EMAIL_ADDRESS" +#define configItem_FROM_EMAIL_ADDRESS "FROM_EMAIL_ADDRESS" +#define configItem_SEND_TEST_EMAIL "SEND_TEST_EMAIL" + +#define configItem_STATION_STATION_TYPE "STATION_TYPE" +#define configItem_STATION_STATION_INTERFACE "STATION_INTERFACE" +#define configItem_STATION_STATION_DEV "STATION_DEV" +#define configItem_STATION_STATION_HOST "STATION_HOST" +#define configItem_STATION_STATION_PORT "STATION_PORT" +#define configItem_STATION_STATION_WLIP "STATION_WLIP" +#define configItem_STATION_STATION_RETRIEVE_ARCHIVE "STATION_RETRIEVE_ARCHIVE" +#define configItem_STATION_STATION_DTR "STATION_DTR" +#define configItem_STATION_STATION_RAIN_SEASON_START "STATION_RAIN_SEASON_START" +#define configItem_STATION_STATION_RAIN_STORM_TRIGGER_START "STATION_RAIN_STORM_TRIGGER_START" +#define configItem_STATION_STATION_RAIN_STORM_IDLE_STOP "STATION_RAIN_STORM_IDLE_STOP" +#define configItem_STATION_STATION_RAIN_YTD "STATION_RAIN_YTD" +#define configItem_STATION_STATION_ET_YTD "STATION_ET_YTD" +#define configItem_STATION_STATION_RAIN_ET_YTD_YEAR "STATION_RAIN_ET_YTD_YEAR" +#define configItem_STATION_STATION_ELEVATION "STATION_ELEVATION" +#define configItem_STATION_STATION_LATITUDE "STATION_LATITUDE" +#define configItem_STATION_STATION_LONGITUDE "STATION_LONGITUDE" +#define configItem_STATION_STATION_ARCHIVE_INTERVAL "STATION_ARCHIVE_INTERVAL" +#define configItem_STATION_ARCHIVE_PATH "STATION_ARCHIVE_PATH" +#define configItem_STATION_POLL_INTERVAL "STATION_POLL_INTERVAL" +#define configItem_STATION_PUSH_INTERVAL "STATION_PUSH_INTERVAL" +#define configItem_STATION_VERBOSE_MSGS "STATION_VERBOSE_MSGS" +#define configItem_STATION_DO_RXCHECK "STATION_DO_RCHECK" +#define configItem_STATION_OUTSIDE_CHANNEL "STATION_OUTSIDE_CHANNEL" + +#define configItem_HTMLGEN_STATION_NAME "HTMLGEN_STATION_NAME" +#define configItem_HTMLGEN_STATION_CITY "HTMLGEN_STATION_CITY" +#define configItem_HTMLGEN_STATION_STATE "HTMLGEN_STATION_STATE" +#define configItem_HTMLGEN_STATION_SHOW_IF "HTMLGEN_STATION_SHOW_IF" +#define configItem_HTMLGEN_IMAGE_PATH "HTMLGEN_IMAGE_PATH" +#define configItem_HTMLGEN_HTML_PATH "HTMLGEN_HTML_PATH" +#define configItem_HTMLGEN_START_OFFSET "HTMLGEN_START_OFFSET" +#define configItem_HTMLGEN_GENERATE_INTERVAL "HTMLGEN_GENERATE_INTERVAL" +#define configItem_HTMLGEN_METRIC_UNITS "HTMLGEN_METRIC_UNITS" +#define configItem_HTMLGEN_METRIC_USE_RAIN_MM "HTMLGEN_METRIC_USE_RAIN_MM" +#define configItem_HTMLGEN_WIND_UNITS "HTMLGEN_WIND_UNITS" +#define configItem_HTMLGEN_DUAL_UNITS "HTMLGEN_DUAL_UNITS" +#define configItem_HTMLGEN_EXTENDED_DATA "HTMLGEN_EXTENDED_DATA" +#define configItem_HTMLGEN_ARCHIVE_BROWSER_FILES_TO_KEEP "HTMLGEN_ARCHIVE_BROWSER_FILES_TO_KEEP" +#define configItem_HTMLGEN_MPHASE_INCREASE "HTMLGEN_MPHASE_INCREASE" +#define configItem_HTMLGEN_MPHASE_DECREASE "HTMLGEN_MPHASE_DECREASE" +#define configItem_HTMLGEN_MPHASE_FULL "HTMLGEN_MPHASE_FULL" +#define configItem_HTMLGEN_LOCAL_RADAR_URL "HTMLGEN_LOCAL_RADAR_URL" +#define configItem_HTMLGEN_LOCAL_FORECAST_URL "HTMLGEN_LOCAL_FORECAST_URL" +#define configItem_HTMLGEN_DATE_FORMAT "HTMLGEN_DATE_FORMAT" + +#define configItem_ALARMS_STATION_METRIC "ALARMS_STATION_METRIC" +#define configItem_ALARMS_DO_TEST "ALARMS_DO_TEST" +#define configItem_ALARMS_DO_TEST_NUMBER "ALARMS_DO_TEST_NUMBER" +#define configItem_ALARMS_1_TYPE "ALARMS_1_TYPE" +#define configItem_ALARMS_1_MAX "ALARMS_1_MAX" +#define configItem_ALARMS_1_THRESHOLD "ALARMS_1_THRESHOLD" +#define configItem_ALARMS_1_ABATEMENT "ALARMS_1_ABATEMENT" +#define configItem_ALARMS_1_EXECUTE "ALARMS_1_EXECUTE" +#define configItem_ALARMS_2_TYPE "ALARMS_2_TYPE" +#define configItem_ALARMS_2_MAX "ALARMS_2_MAX" +#define configItem_ALARMS_2_THRESHOLD "ALARMS_2_THRESHOLD" +#define configItem_ALARMS_2_ABATEMENT "ALARMS_2_ABATEMENT" +#define configItem_ALARMS_2_EXECUTE "ALARMS_2_EXECUTE" +#define configItem_ALARMS_3_TYPE "ALARMS_3_TYPE" +#define configItem_ALARMS_3_MAX "ALARMS_3_MAX" +#define configItem_ALARMS_3_THRESHOLD "ALARMS_3_THRESHOLD" +#define configItem_ALARMS_3_ABATEMENT "ALARMS_3_ABATEMENT" +#define configItem_ALARMS_3_EXECUTE "ALARMS_3_EXECUTE" +#define configItem_ALARMS_4_TYPE "ALARMS_4_TYPE" +#define configItem_ALARMS_4_MAX "ALARMS_4_MAX" +#define configItem_ALARMS_4_THRESHOLD "ALARMS_4_THRESHOLD" +#define configItem_ALARMS_4_ABATEMENT "ALARMS_4_ABATEMENT" +#define configItem_ALARMS_4_EXECUTE "ALARMS_4_EXECUTE" +#define configItem_ALARMS_5_TYPE "ALARMS_5_TYPE" +#define configItem_ALARMS_5_MAX "ALARMS_5_MAX" +#define configItem_ALARMS_5_THRESHOLD "ALARMS_5_THRESHOLD" +#define configItem_ALARMS_5_ABATEMENT "ALARMS_5_ABATEMENT" +#define configItem_ALARMS_5_EXECUTE "ALARMS_5_EXECUTE" +#define configItem_ALARMS_6_TYPE "ALARMS_6_TYPE" +#define configItem_ALARMS_6_MAX "ALARMS_6_MAX" +#define configItem_ALARMS_6_THRESHOLD "ALARMS_6_THRESHOLD" +#define configItem_ALARMS_6_ABATEMENT "ALARMS_6_ABATEMENT" +#define configItem_ALARMS_6_EXECUTE "ALARMS_6_EXECUTE" +#define configItem_ALARMS_7_TYPE "ALARMS_7_TYPE" +#define configItem_ALARMS_7_MAX "ALARMS_7_MAX" +#define configItem_ALARMS_7_THRESHOLD "ALARMS_7_THRESHOLD" +#define configItem_ALARMS_7_ABATEMENT "ALARMS_7_ABATEMENT" +#define configItem_ALARMS_7_EXECUTE "ALARMS_7_EXECUTE" +#define configItem_ALARMS_8_TYPE "ALARMS_8_TYPE" +#define configItem_ALARMS_8_MAX "ALARMS_8_MAX" +#define configItem_ALARMS_8_THRESHOLD "ALARMS_8_THRESHOLD" +#define configItem_ALARMS_8_ABATEMENT "ALARMS_8_ABATEMENT" +#define configItem_ALARMS_8_EXECUTE "ALARMS_8_EXECUTE" +#define configItem_ALARMS_9_TYPE "ALARMS_9_TYPE" +#define configItem_ALARMS_9_MAX "ALARMS_9_MAX" +#define configItem_ALARMS_9_THRESHOLD "ALARMS_9_THRESHOLD" +#define configItem_ALARMS_9_ABATEMENT "ALARMS_9_ABATEMENT" +#define configItem_ALARMS_9_EXECUTE "ALARMS_9_EXECUTE" +#define configItem_ALARMS_10_TYPE "ALARMS_10_TYPE" +#define configItem_ALARMS_10_MAX "ALARMS_10_MAX" +#define configItem_ALARMS_10_THRESHOLD "ALARMS_10_THRESHOLD" +#define configItem_ALARMS_10_ABATEMENT "ALARMS_10_ABATEMENT" +#define configItem_ALARMS_10_EXECUTE "ALARMS_10_EXECUTE" + +#define configItemFTP_HOST "FTP_HOST" +#define configItemFTP_USERNAME "FTP_USERNAME" +#define configItemFTP_PASSWD "FTP_PASSWD" +#define configItemFTP_REMOTE_DIRECTORY "FTP_REMOTE_DIRECTORY" +#define configItemFTP_USE_PASSIVE "FTP_USE_PASSIVE" +#define configItemFTP_INTERVAL "FTP_INTERVAL" +#define configItemFTP_RULE_1_SOURCE "FTP_RULE_1_SOURCE" +#define configItemFTP_RULE_2_SOURCE "FTP_RULE_2_SOURCE" +#define configItemFTP_RULE_3_SOURCE "FTP_RULE_3_SOURCE" +#define configItemFTP_RULE_4_SOURCE "FTP_RULE_4_SOURCE" +#define configItemFTP_RULE_5_SOURCE "FTP_RULE_5_SOURCE" +#define configItemFTP_RULE_6_SOURCE "FTP_RULE_6_SOURCE" +#define configItemFTP_RULE_7_SOURCE "FTP_RULE_7_SOURCE" +#define configItemFTP_RULE_8_SOURCE "FTP_RULE_8_SOURCE" +#define configItemFTP_RULE_9_SOURCE "FTP_RULE_9_SOURCE" +#define configItemFTP_RULE_10_SOURCE "FTP_RULE_10_SOURCE" + +#define configItemSSH_1_INTERVAL "SSH_1_INTERVAL" +#define configItemSSH_1_SOURCE "SSH_1_SOURCE" +#define configItemSSH_1_HOST "SSH_1_HOST" +#define configItemSSH_1_PORT "SSH_1_PORT" +#define configItemSSH_1_USERNAME "SSH_1_USERNAME" +#define configItemSSH_1_DESTINATION "SSH_1_DESTINATION" +#define configItemSSH_2_INTERVAL "SSH_2_INTERVAL" +#define configItemSSH_2_SOURCE "SSH_2_SOURCE" +#define configItemSSH_2_HOST "SSH_2_HOST" +#define configItemSSH_2_PORT "SSH_2_PORT" +#define configItemSSH_2_USERNAME "SSH_2_USERNAME" +#define configItemSSH_2_DESTINATION "SSH_2_DESTINATION" +#define configItemSSH_3_INTERVAL "SSH_3_INTERVAL" +#define configItemSSH_3_SOURCE "SSH_3_SOURCE" +#define configItemSSH_3_HOST "SSH_3_HOST" +#define configItemSSH_3_PORT "SSH_3_PORT" +#define configItemSSH_3_USERNAME "SSH_3_USERNAME" +#define configItemSSH_3_DESTINATION "SSH_3_DESTINATION" +#define configItemSSH_4_INTERVAL "SSH_4_INTERVAL" +#define configItemSSH_4_SOURCE "SSH_4_SOURCE" +#define configItemSSH_4_HOST "SSH_4_HOST" +#define configItemSSH_4_PORT "SSH_4_PORT" +#define configItemSSH_4_USERNAME "SSH_4_USERNAME" +#define configItemSSH_4_DESTINATION "SSH_4_DESTINATION" +#define configItemSSH_5_INTERVAL "SSH_5_INTERVAL" +#define configItemSSH_5_SOURCE "SSH_5_SOURCE" +#define configItemSSH_5_HOST "SSH_5_HOST" +#define configItemSSH_5_PORT "SSH_5_PORT" +#define configItemSSH_5_USERNAME "SSH_5_USERNAME" +#define configItemSSH_5_DESTINATION "SSH_5_DESTINATION" + +#define configItemCWOP_APRS_CALL_SIGN "CWOP_APRS_CALL_SIGN" +#define configItemCWOP_APRS_SERVER1 "CWOP_APRS_SERVER1" +#define configItemCWOP_APRS_PORTNO1 "CWOP_APRS_PORTNO1" +#define configItemCWOP_APRS_SERVER2 "CWOP_APRS_SERVER2" +#define configItemCWOP_APRS_PORTNO2 "CWOP_APRS_PORTNO2" +#define configItemCWOP_APRS_SERVER3 "CWOP_APRS_SERVER3" +#define configItemCWOP_APRS_PORTNO3 "CWOP_APRS_PORTNO3" +#define configItemCWOP_LATITUDE "CWOP_LATITUDE" +#define configItemCWOP_LONGITUDE "CWOP_LONGITUDE" +#define configItemCWOP_LOG_WX_PACKET "CWOP_LOG_WX_PACKET" + +#define configItemHTTP_WUSTATIONID "HTTP_WUSTATIONID" +#define configItemHTTP_WUPASSWD "HTTP_WUPASSWD" +#define configItemHTTP_YOUSTATIONID "HTTP_YOUSTATIONID" +#define configItemHTTP_YOUPASSWD "HTTP_YOUPASSWD" + +#define configItemCAL_MULT_BAROMETER "CAL_MULT_BAROMETER" +#define configItemCAL_CONST_BAROMETER "CAL_CONST_BAROMETER" +#define configItemCAL_MULT_PRESSURE "CAL_MULT_PRESSURE" +#define configItemCAL_CONST_PRESSURE "CAL_CONST_PRESSURE" +#define configItemCAL_MULT_ALTIMETER "CAL_MULT_ALTIMETER" +#define configItemCAL_CONST_ALTIMETER "CAL_CONST_ALTIMETER" +#define configItemCAL_MULT_INTEMP "CAL_MULT_INTEMP" +#define configItemCAL_CONST_INTEMP "CAL_CONST_INTEMP" +#define configItemCAL_MULT_OUTTEMP "CAL_MULT_OUTTEMP" +#define configItemCAL_CONST_OUTTEMP "CAL_CONST_OUTTEMP" +#define configItemCAL_MULT_INHUMIDITY "CAL_MULT_INHUMIDITY" +#define configItemCAL_CONST_INHUMIDITY "CAL_CONST_INHUMIDITY" +#define configItemCAL_MULT_OUTHUMIDITY "CAL_MULT_OUTHUMIDITY" +#define configItemCAL_CONST_OUTHUMIDITY "CAL_CONST_OUTHUMIDITY" +#define configItemCAL_MULT_WINDSPEED "CAL_MULT_WINDSPEED" +#define configItemCAL_CONST_WINDSPEED "CAL_CONST_WINDSPEED" +#define configItemCAL_MULT_WINDDIR "CAL_MULT_WINDDIR" +#define configItemCAL_CONST_WINDDIR "CAL_CONST_WINDDIR" +#define configItemCAL_MULT_RAIN "CAL_MULT_RAIN" +#define configItemCAL_CONST_RAIN "CAL_CONST_RAIN" +#define configItemCAL_MULT_RAINRATE "CAL_MULT_RAINRATE" +#define configItemCAL_CONST_RAINRATE "CAL_CONST_RAINRATE" + +#define configItemPROCMON_wviewd "PROCMON_wviewd" +#define configItemPROCMON_htmlgend "PROCMON_htmlgend" +#define configItemPROCMON_wvalarmd "PROCMON_wvalarmd" +#define configItemPROCMON_wvcwopd "PROCMON_wvcwopd" +#define configItemPROCMON_wvhttpd "PROCMON_wvhttpd" +#define configItemPROCMON_wviewsqld "PROCMON_wviewsqld" + +// Define the column names for wview-conf.sdb: +#define configCOLUMN_NAME "name" +#define configCOLUMN_VALUE "value" +#define configCOLUMN_DESCRIPTION "description" + + +// ... API prototypes + +// wvconfigInit: Initialize/Attach to the wview configuration API: +// "firstProcess" is a BOOL to indicate if this is the first process to call init; +// Returns: OK or ERROR +extern int wvconfigInit (int firstProcess); + +// wvconfigExit: clean up and detach from the wview configuration API +extern void wvconfigExit (void); + +// wvconfigGetINTValue: retrieve the integer value for this parameter; +// Returns: integer value +extern int wvconfigGetINTValue (const char* configItem); + +// wvconfigGetDOUBLEValue: retrieve the double value for this parameter; +// Returns: double value +extern double wvconfigGetDOUBLEValue (const char* configItem); + +// wvconfigGetStringValue: retrieve the string value for this parameter +// Returns: const static string reference or NULL +extern const char* wvconfigGetStringValue (const char* configItem); + +// wvconfigGetBooleanValue: retrieve the bool value for this parameter: +// Assumption: anything other than lowercase "yes" or "1" or "TRUE" +// is considered FALSE +// Returns: TRUE or FALSE or ERROR +extern int wvconfigGetBooleanValue (const char* configItem); + +#endif + diff --git a/common/wvutils.c b/common/wvutils.c new file mode 100755 index 0000000..10561e0 --- /dev/null +++ b/common/wvutils.c @@ -0,0 +1,1406 @@ +/*--------------------------------------------------------------------------- + + FILENAME: + wvutils.c + + PURPOSE: + Provide some global utility API methods. + + REVISION HISTORY: + Date Engineer Revision Remarks + 05/29/2005 M.S. Teel 0 Original + + NOTES: + + + LICENSE: + Copyright (c) 2004, Mark S. Teel (mark@teel.ws) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +// ... System header files +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +// ... Local header files +#include +#include +#include + +#ifndef BUILD_UTILITIES +#include +static uint16_t VerboseMask, DaemonMask; +#endif + +static int IsMetricRainMM = 1; + +// Define a validity test macro: +#define WV_UTILS_VALID(x) ((x < -300 || x > 300 || !isfinite(x)) ? FALSE : TRUE) + + +// ... define methods here + +#ifndef BUILD_UTILITIES +#ifndef _WXT510_CONFIG_ONLY +// *************************************************************************** +// * Output Controlled General Event Logging +// *************************************************************************** +int wvutilsSetVerbosity (uint16_t daemonBitMask) +{ + const char* sValue; + int i; + + sValue = wvconfigGetStringValue(configItem_STATION_VERBOSE_MSGS); + if (sValue == NULL) + { + return ERROR; + } + + DaemonMask = daemonBitMask; + + // be backwards compatible + if (strlen(sValue) < 8) + { + i = atoi (sValue); + if (i) + VerboseMask = WV_VERBOSE_ALL; + else + VerboseMask = 0x0000; + } + else + { + // process bit-by-bit + VerboseMask = 0x0000; + for (i = 0; i < 8; i ++) + { + if (sValue[i] == '1') + VerboseMask |= (1 << (7-i)); + } + } + + return OK; +} + +int wvutilsToggleVerbosity (void) +{ + int retVal; + + if (DaemonMask & VerboseMask) + { + retVal = 0; + VerboseMask &= ~DaemonMask; + } + else + { + retVal = 1; + VerboseMask |= DaemonMask; + } + + return retVal; +} + +void wvutilsLogEvent (int priority, char *format, ...) +{ + va_list argList; + char temp1[RADMSGLOG_MAX_LENGTH+64]; + + if ((DaemonMask & VerboseMask) == 0) + { + return; + } + else + { + // print the var arg stuff to the buffer + va_start (argList, format); + vsprintf (temp1, format, argList); + va_end (argList); + + radMsgLog (priority, temp1); + } + + return; +} +#endif +#endif + +// *************************************************************************** +// * Calculated Weather Data +// *************************************************************************** +// calculate the heat index +float wvutilsCalculateHeatIndex (float temp, float humidity) +{ + double T1, T2, T3, T4, T5, T6, T7, T8, T9, result; + + if (temp <= ARCHIVE_VALUE_NULL || humidity <= ARCHIVE_VALUE_NULL) + return ARCHIVE_VALUE_NULL; + + if (temp < 75.0) + { + return temp; + } + + T1 = -42.379; + T2 = 2.04901523 * temp; + T3 = 10.14333127 * humidity; + T4 = -0.22475541 * temp * humidity; + T5 = -6.83783E-3 * (temp * temp); + T6 = -5.481717E-2 * (humidity * humidity); + T7 = 1.22874E-3 * (temp * temp) * humidity; + T8 = 8.5282E-4 * temp * (humidity * humidity); + T9 = -1.99E-6 * (temp * temp) * (humidity * humidity); + result = T1 + T2 + T3 + T4 + T5 + T6 + T7 + T8 + T9; + + if (! WV_UTILS_VALID(result)) + { + return ARCHIVE_VALUE_NULL; + } + + return (float)result; +} + +// calculate the wind chill +float wvutilsCalculateWindChill (float temp, float windspeed) +{ + double T1, T2, T3, T4; + double result; + + if (temp <= ARCHIVE_VALUE_NULL || windspeed <= ARCHIVE_VALUE_NULL) + return ARCHIVE_VALUE_NULL; + + if (temp >= 50.0 || windspeed <= 3.0) + { + return temp; + } + + T1 = 35.74; + T2 = 0.6215 * temp; + T3 = -1.0 * (35.75 * pow (windspeed, 0.16)); + T4 = 0.4275 * temp * pow (windspeed, 0.16); + result = T1 + T2 + T3 + T4; + + if (! WV_UTILS_VALID(result)) + { + return ARCHIVE_VALUE_NULL; + } + + return (float)result; +} + +// calculate the dewpoint +float wvutilsCalculateDewpoint (float temp, float humidity) +{ + float Tc, Es, E, Tdc; + float result; + + if (temp <= ARCHIVE_VALUE_NULL || humidity <= ARCHIVE_VALUE_NULL) + return ARCHIVE_VALUE_NULL; + + Tc = (5.0/9.0)*(temp - 32.0); + Es = 6.11 * pow ((double)10.0, (double)(7.5 * (Tc/(237.7 + Tc)))); + E = (humidity * Es)/100; + Tdc = (-430.22 + 237.7 * log(E))/(-log(E)+19.08); + result = ((9.0/5.0) * Tdc) + 32; + + if (! WV_UTILS_VALID(result)) + { + return ARCHIVE_VALUE_NULL; + } + + return result; +} + +// Calculate the "e ^ (-mgh/RT)" term for pressure conversions: +static double calculatePressureTerm (float tempF, float elevationFT) +{ + double exponent; + double elevMeters = (double)wvutilsConvertFeetToMeters(elevationFT); + double tempKelvin = (double)wvutilsConvertFToC(tempF) + 273.15; + + // e ^ -elevMeters/(tempK * 29.263) + exponent = (-elevMeters); + + // degrees Kelvin (T) + exponent /= (tempKelvin * 29.263); + + // e ^ (-mgh/RT) + exponent = exp(exponent); + + return exponent; +} + +// calculate sea level pressure from station pressure +float wvutilsConvertSPToSLP (float SP, float tempF, float elevationFT) +{ + double SLP, PT; + + // Formula used: SLP = SP / PressureTerm + // compute SLP: + PT = calculatePressureTerm (tempF, elevationFT); + if (PT != 0.0) + SLP = SP / PT; + else + SLP = 0.0; + + return (float)SLP; +} + +// calculate station pressure from sea level pressure +float wvutilsConvertSLPToSP (float SLP, float tempF, float elevationFT) +{ + double SP, PT; + + // Formula used: SP = SLP * PressureTerm + // compute PressureTerm: + PT = calculatePressureTerm (tempF, elevationFT); + SP = SLP * PT; + + return (float)SP; +} + +// calculate altimeter pressure from station pressure +float wvutilsConvertSPToAltimeter (float SPInches, float elevationFT) +{ + double magicEXP = 0.190284; + double inverseMagicEXP = 1 / magicEXP; + double elevMeters = (double)wvutilsConvertFeetToMeters(elevationFT); + double stationPressureMB = (double)wvutilsConvertINHGToHPA(SPInches); + double constantTerm; + double variableTerm; + double tempdouble; + double altimeter; + + // Formula used: http://www.wrh.noaa.gov/slc/projects/wxcalc/formulas/altimeterSetting.pdf + + // calculate the constant term + constantTerm = pow (1013.25, magicEXP); + constantTerm *= 0.0065; + constantTerm /= 288; + + // calculate the variable term + tempdouble = stationPressureMB - 0.3; + tempdouble = pow (tempdouble, magicEXP); + variableTerm = elevMeters / tempdouble; + + // compute main term + tempdouble = constantTerm * variableTerm; + tempdouble += 1; + tempdouble = pow (tempdouble, inverseMagicEXP); + + // compute altimeter + altimeter = stationPressureMB - 0.3; + altimeter *= tempdouble; + + // finally, convert back to inches + altimeter *= 0.0295299; + + return (float)altimeter; +} + +// *************************************************************************** +// * Converters +// *************************************************************************** + +// Configurable wind units: +static HTML_WUNITS WVU_WindUnits; +void wvutilsSetWindUnits(HTML_WUNITS units) +{ + WVU_WindUnits = units; +} + +char* wvutilsGetWindUnitLabel(void) +{ + static char W_Units_Label[16]; + + switch (WVU_WindUnits) + { + case HTML_WINDUNITS_MPH: + strncpy(W_Units_Label, "mph", 3); + break; + case HTML_WINDUNITS_MS: + strncpy(W_Units_Label, "m/s", 3); + break; + case HTML_WINDUNITS_KNOTS: + strncpy(W_Units_Label, "knots", 5); + break; + case HTML_WINDUNITS_KMH: + strncpy(W_Units_Label, "km/h", 4); + break; + } + + return W_Units_Label; +} + +float wvutilsGetWindSpeed(float mph) +{ + float RetVal = -1; + + switch (WVU_WindUnits) + { + case HTML_WINDUNITS_MPH: + RetVal = mph; + break; + case HTML_WINDUNITS_MS: + RetVal = wvutilsConvertMPHToMPS(mph); + break; + case HTML_WINDUNITS_KNOTS: + RetVal = wvutilsConvertMPHToKnots(mph); + break; + case HTML_WINDUNITS_KMH: + RetVal = wvutilsConvertMPHToKPH(mph); + break; + } + + return RetVal; +} + +float wvutilsGetWindSpeedMetric(float kmh) +{ + float RetVal = -1; + + switch (WVU_WindUnits) + { + case HTML_WINDUNITS_MPH: + RetVal = wvutilsConvertKPHToMPH(kmh); + break; + case HTML_WINDUNITS_MS: + RetVal = wvutilsConvertKPHToMPS(kmh); + break; + case HTML_WINDUNITS_KNOTS: + RetVal = wvutilsConvertKPHToKnots(kmh); + break; + case HTML_WINDUNITS_KMH: + RetVal = kmh; + break; + } + + return RetVal; +} + +void wvutilsSetRainIsMM(int setValue) +{ + IsMetricRainMM = setValue; +} + +int wvutilsGetRainIsMM(void) +{ + return IsMetricRainMM; +} + +float wvutilsConvertFToC (float fahrenValue) +{ + float retVal; + + if (fahrenValue <= ARCHIVE_VALUE_NULL) + return ARCHIVE_VALUE_NULL; + + retVal = fahrenValue - 32.0; + retVal *= 5.0; + retVal /= 9.0; + + return retVal; +} + +float wvutilsConvertCToF (float celsiusValue) +{ + float retVal; + + if (celsiusValue <= ARCHIVE_VALUE_NULL) + return ARCHIVE_VALUE_NULL; + + retVal = celsiusValue * 9.0; + retVal /= 5.0; + retVal += 32.0; + + return retVal; +} + +float wvutilsConvertDeltaFToC (float fahrenValue) +{ + float retVal; + + if (fahrenValue <= ARCHIVE_VALUE_NULL) + return ARCHIVE_VALUE_NULL; + + retVal = fahrenValue * 5.0; + retVal /= 9.0; + + return retVal; +} + +float wvutilsConvertINHGToHPA (float inches) +{ + float retVal; + + if (inches <= ARCHIVE_VALUE_NULL) + return ARCHIVE_VALUE_NULL; + + retVal = inches / 0.0295299; + + return retVal; +} + +float wvutilsConvertHPAToINHG (float mb) +{ + float retVal; + + if (mb <= ARCHIVE_VALUE_NULL) + return ARCHIVE_VALUE_NULL; + + retVal = mb * 0.0295299; + + return retVal; +} + +float wvutilsConvertINToCM (float inches) +{ + float retVal; + + if (inches <= ARCHIVE_VALUE_NULL) + return ARCHIVE_VALUE_NULL; + + retVal = inches * 2.54; + + return retVal; +} + +float wvutilsConvertCMToIN (float cm) +{ + float retVal; + + if (cm <= ARCHIVE_VALUE_NULL) + return ARCHIVE_VALUE_NULL; + + retVal = cm / 2.54; + + return retVal; +} + +float wvutilsConvertINToMM (float inches) +{ + float retVal; + + if (inches <= ARCHIVE_VALUE_NULL) + return ARCHIVE_VALUE_NULL; + + retVal = inches * 25.4; + + return retVal; +} + +float wvutilsConvertMMToIN (float mm) +{ + float retVal; + + if (mm <= ARCHIVE_VALUE_NULL) + return ARCHIVE_VALUE_NULL; + + retVal = mm / 25.4; + + return retVal; +} + +float wvutilsConvertRainINToMetric (float inches) +{ + if (IsMetricRainMM) + { + return wvutilsConvertINToMM(inches); + } + else + { + return wvutilsConvertINToCM(inches); + } +} + +float wvutilsConvertRainMetricToIN (float inches) +{ + if (IsMetricRainMM) + { + return wvutilsConvertMMToIN(inches); + } + else + { + return wvutilsConvertCMToIN(inches); + } +} + +float wvutilsConvertMilesToKilometers (float miles) +{ + float retVal; + + if (miles <= ARCHIVE_VALUE_NULL) + return ARCHIVE_VALUE_NULL; + + retVal = miles * 1.609; + + return retVal; +} + +float wvutilsConvertMPHToKPH (float mph) +{ + float retVal; + + if (mph <= ARCHIVE_VALUE_NULL) + return ARCHIVE_VALUE_NULL; + + retVal = 1.609344 * mph; + + return retVal; +} + +float wvutilsConvertKPHToMPH (float kph) +{ + float retVal; + + if (kph <= ARCHIVE_VALUE_NULL) + return ARCHIVE_VALUE_NULL; + + retVal = kph / 1.609344; + + return retVal; +} + +float wvutilsConvertKPHToMPS (float kph) +{ + float retVal; + + if (kph <= ARCHIVE_VALUE_NULL) + return ARCHIVE_VALUE_NULL; + + retVal = kph * 0.2777697; + + return retVal; +} + +float wvutilsConvertKPHToKnots (float kph) +{ + float retVal; + + if (kph <= ARCHIVE_VALUE_NULL) + return ARCHIVE_VALUE_NULL; + + retVal = kph * 0.5399417; + + return retVal; +} +float wvutilsConvertMPHToMPS (float mph) +{ + float retVal; + + if (mph <= ARCHIVE_VALUE_NULL) + return ARCHIVE_VALUE_NULL; + + retVal = 0.447027 * mph; + + return retVal; +} + +float wvutilsConvertMPHToKnots (float mph) +{ + float retVal; + + if (mph <= ARCHIVE_VALUE_NULL) + return ARCHIVE_VALUE_NULL; + + retVal = 0.8689762 * mph; + + return retVal; +} + +float wvutilsConvertMPSToKPH (float mps) +{ + float retVal; + + if (mps <= ARCHIVE_VALUE_NULL) + return ARCHIVE_VALUE_NULL; + + retVal = mps / 3.6; + + return retVal; +} + +float wvutilsConvertMPSToMPH (float mps) +{ + float retVal; + + if (mps <= ARCHIVE_VALUE_NULL) + return ARCHIVE_VALUE_NULL; + + retVal = mps * 2.2369; + + return retVal; +} + +float wvutilsConvertMPSToKnots (float mps) +{ + float retVal; + + if (mps <= ARCHIVE_VALUE_NULL) + return ARCHIVE_VALUE_NULL; + + retVal = mps * 1.943759; + + return retVal; +} + +float wvutilsConvertKnotsToKPH (float mps) +{ + float retVal; + + if (mps <= ARCHIVE_VALUE_NULL) + return ARCHIVE_VALUE_NULL; + + retVal = mps / 1.852; + + return retVal; +} + +float wvutilsConvertKnotsToMPS (float mps) +{ + float retVal; + + if (mps <= ARCHIVE_VALUE_NULL) + return ARCHIVE_VALUE_NULL; + + retVal = mps * 0.388445; + + return retVal; +} + +float wvutilsConvertKnotsToMPH (float knots) +{ + float retVal; + + if (knots <= ARCHIVE_VALUE_NULL) + return ARCHIVE_VALUE_NULL; + + retVal = knots * 1.150779; + + return retVal; +} + +float wvutilsConvertKilometersToMiles (float km) +{ + float retVal; + + if (km <= ARCHIVE_VALUE_NULL) + return ARCHIVE_VALUE_NULL; + + retVal = km / 1.609; + + return retVal; +} + +float wvutilsConvertFeetToMeters (float feet) +{ + float retVal; + + if (feet <= ARCHIVE_VALUE_NULL) + return ARCHIVE_VALUE_NULL; + + retVal = feet * 0.3048; + + return retVal; +} + + +// Determine if it is day or night +// Returns TRUE or FALSE +int wvutilsIsDayTime (int16_t sunrise, int16_t sunset) +{ + time_t currTime; + struct tm locTime; + int sunriseHour, sunriseMinute; + int sunsetHour, sunsetMinute; + int currentHour, currentMinute; + int isInverted = FALSE; + + // is it always day or always night? + if (sunrise == -1) + { + return TRUE; + } + else if (sunrise == -2) + { + return FALSE; + } + + // get the current time + currTime = time (NULL); + localtime_r (&currTime, &locTime); + + sunriseHour = EXTRACT_PACKED_HOUR(sunrise); + sunriseMinute = EXTRACT_PACKED_MINUTE(sunrise); + sunsetHour = EXTRACT_PACKED_HOUR(sunset); + sunsetMinute = EXTRACT_PACKED_MINUTE(sunset); + currentHour = locTime.tm_hour; + currentMinute = locTime.tm_min; + + // determine if this is an inverted scenario (Set < Rise) + if (sunriseHour > sunsetHour) + { + isInverted = TRUE; + } + else if (sunriseHour == sunsetHour && sunriseMinute > sunsetMinute) + { + isInverted = TRUE; + } + + // walk through scenarios + if (isInverted) + { + if (currentHour < sunsetHour) + { + return TRUE; + } + else if (currentHour == sunsetHour && currentMinute < sunsetMinute) + { + return TRUE; + } + else + { + if (currentHour < sunriseHour) + { + return FALSE; + } + else if (currentHour == sunriseHour && currentMinute < sunriseMinute) + { + return FALSE; + } + else + { + return TRUE; + } + } + } + else + { + // normal scenario + if (currentHour < sunriseHour) + { + return FALSE; + } + else if (currentHour == sunriseHour && currentMinute < sunriseMinute) + { + return FALSE; + } + else + { + if (currentHour < sunsetHour) + { + return TRUE; + } + else if (currentHour == sunsetHour && currentMinute < sunsetMinute) + { + return TRUE; + } + else + { + return FALSE; + } + } + } +} + +// Air Density Calculation +static float getVaporPressure (float tempC) +{ + float E; + + E = 6.1078 * powf (10, (7.5 * tempC / (237.3 + tempC))); + + return E; +} + +static float calcDensity (float abspressmb, float e, float tc) +{ + float Rv = 461.4964; + float Rd = 287.0531; + float tk = tc + 273.15; + float pv = e * 100; + float pd = (abspressmb - e) * 100; + float d = (pv/(Rv*tk)) + (pd/(Rd*tk)); + return d; +} + +float wvutilsCalculateAirDensity (float tempF, float bp, float dp) +{ + float tempC = wvutilsConvertFToC (tempF); + float bpMB = wvutilsConvertINHGToHPA (bp); + float dewpoint = wvutilsConvertFToC (dp); + float emb = getVaporPressure (dewpoint); + float density = calcDensity (bpMB, emb, tempC); + + return density; +} + +time_t wvutilsPackedTimeToTimeT (uint16_t packedDate, uint16_t packedTime) +{ + time_t timeT; + struct tm locTime; + + // convert everything to epoch time + memset (&locTime, 0, sizeof(locTime)); + locTime.tm_year = EXTRACT_PACKED_YEAR(packedDate) - 1900; + locTime.tm_mon = EXTRACT_PACKED_MONTH(packedDate) - 1; + locTime.tm_mday = EXTRACT_PACKED_DAY(packedDate); + locTime.tm_hour = EXTRACT_PACKED_HOUR(packedTime); + locTime.tm_min = EXTRACT_PACKED_MINUTE(packedTime); + locTime.tm_isdst = -1; + timeT = mktime (&locTime); + return timeT; +} + +// calculate the time difference in packed date/time format +// does not consider leap years +// returns the delta in minutes +int wvutilsCalculatePackedTimeDelta +( + uint16_t newDate, + uint16_t newTime, + uint16_t oldDate, + uint16_t oldTime +) +{ + int diffMinutes; + time_t oldTimeT, newTimeT; + + // convert everything to epoch time + oldTimeT = wvutilsPackedTimeToTimeT (oldDate, oldTime); + newTimeT = wvutilsPackedTimeToTimeT (newDate, newTime); + + diffMinutes = (int)(newTimeT - oldTimeT); + diffMinutes /= 60; + return diffMinutes; +} + +// increment a packed time value by the given minutes, rolls over at 24:00 +uint16_t wvutilsIncrementPackedTime (uint16_t pTime, int minutes) +{ + int tempMin, tempHour; + uint16_t retVal; + + tempMin = EXTRACT_PACKED_MINUTE(pTime) + minutes; + tempHour = EXTRACT_PACKED_HOUR(pTime); + + if (tempMin >= 60) + { + tempHour += tempMin/60; + tempMin %= 60; + } + + if (tempHour > 24 || (tempHour == 24 && tempMin > 0)) + { + // allow 24:00, but nothing greater + tempHour %= 24; + } + + retVal = (tempHour * 100) + tempMin; + return retVal; +} + +// produce a float string fixing the truncation annoyance +char *wvutilsPrintFloat (float value, int decPlaces) +{ + static char flbuffer[32]; + char format[16]; + int intTemp; + + intTemp = (int)(value * pow (10, (decPlaces+1))); + if ((intTemp % 10) > 4) + intTemp += 5; + intTemp /= 10; + sprintf (format, "%s.%1.1d%s", "%", decPlaces, "f"); + sprintf (flbuffer, format, (float)intTemp/(pow (10, decPlaces))); + return flbuffer; +} + +char *wvutilsConvertToBeaufortScale (int windSpeed) +{ + static char beaufortBfr[32]; + + if (windSpeed == Beaufort_Calm) + { + sprintf (beaufortBfr, "Calm"); + } + else if (windSpeed <= Beaufort_LightAir) + { + sprintf (beaufortBfr, "Light Air"); + } + else if (windSpeed <= Beaufort_LightBreeze) + { + sprintf (beaufortBfr, "Light Breeze"); + } + else if (windSpeed <= Beaufort_GentleBreeze) + { + sprintf (beaufortBfr, "Gentle Breeze"); + } + else if (windSpeed <= Beaufort_ModerateBreeze) + { + sprintf (beaufortBfr, "Moderate Breeze"); + } + else if (windSpeed <= Beaufort_FreshBreeze) + { + sprintf (beaufortBfr, "Fresh Breeze"); + } + else if (windSpeed <= Beaufort_StrongBreeze) + { + sprintf (beaufortBfr, "Strong Breeze"); + } + else if (windSpeed <= Beaufort_NearGale) + { + sprintf (beaufortBfr, "Near Gale"); + } + else if (windSpeed <= Beaufort_Gale) + { + sprintf (beaufortBfr, "Gale"); + } + else if (windSpeed <= Beaufort_SevereGale) + { + sprintf (beaufortBfr, "Severe Gale"); + } + else if (windSpeed <= Beaufort_Storm) + { + sprintf (beaufortBfr, "Storm"); + } + else if (windSpeed <= Beaufort_ViolentStorm) + { + sprintf (beaufortBfr, "Violent Storm"); + } + else // if (windSpeed <= Beaufort_Hurricane) + { + sprintf (beaufortBfr, "Hurricane"); + } + + return beaufortBfr; +} + +void wvutilsSendPMONPollResponse(int mask, PMON_PROCESS_TYPES process) +{ + WVIEW_MSG_POLL_RESPONSE response; + + if (! PMON_PROCESS_ISSET(mask,process)) + { + return; + } + + response.pid = getpid(); + radMsgRouterMessageSend (WVIEW_MSG_TYPE_POLL_RESPONSE, &response, sizeof(response)); + return; +} + +int wvutilsGetDayStartTime(int archiveInterval) +{ + time_t ntime = time(NULL); + struct tm locTime; + int retVal; + + localtime_r(&ntime, &locTime); + locTime.tm_min = ((locTime.tm_min/archiveInterval)*archiveInterval); + locTime.tm_sec = 0; + ntime = mktime(&locTime); + ntime -= WV_SECONDS_IN_DAY; + localtime_r(&ntime, &locTime); + retVal = ((60 * locTime.tm_hour) + locTime.tm_min)/archiveInterval; + return retVal; +} + +time_t wvutilsGetWeekStartTime(int archiveInterval) +{ + time_t ntime = time(NULL); + struct tm locTime; + + ntime -= WV_SECONDS_IN_WEEK; + ntime -= WV_SECONDS_IN_HOUR; + localtime_r (&ntime, &locTime); + locTime.tm_min = 0; + ntime = mktime (&locTime); + return ntime; +} + +time_t wvutilsGetMonthStartTime (int archiveInterval) +{ + time_t ntime = time(NULL); + struct tm locTime; + + ntime -= WV_SECONDS_IN_MONTH; + ntime -= WV_SECONDS_IN_HOUR; + localtime_r (&ntime, &locTime); + locTime.tm_min = 0; + ntime = mktime (&locTime); + return ntime; +} + +time_t wvutilsGetYearStartTime (int archiveInterval) +{ + time_t ntime = time(NULL); + struct tm locTime; + + ntime -= WV_SECONDS_IN_YEAR; + ntime -= SECONDS_IN_INTERVAL(archiveInterval); + localtime_r (&ntime, &locTime); + locTime.tm_hour = 0; + locTime.tm_min = archiveInterval; + locTime.tm_sec = 0; + locTime.tm_isdst = -1; + ntime = mktime (&locTime); + return ntime; +} + +int wvutilsGetYear (time_t ntime) +{ + struct tm locTime; + localtime_r (&ntime, &locTime); + return (locTime.tm_year + 1900); +} + +int wvutilsGetMonth (time_t ntime) +{ + struct tm locTime; + localtime_r (&ntime, &locTime); + return (locTime.tm_mon + 1); +} + +int wvutilsGetDay (time_t ntime) +{ + struct tm locTime; + localtime_r (&ntime, &locTime); + return (locTime.tm_mday); +} + +int wvutilsGetHour (time_t ntime) +{ + struct tm locTime; + localtime_r (&ntime, &locTime); + return (locTime.tm_hour); +} + +int wvutilsGetMin (time_t ntime) +{ + struct tm locTime; + localtime_r (&ntime, &locTime); + return (locTime.tm_min); +} + +int wvutilsGetSec (time_t ntime) +{ + struct tm locTime; + localtime_r (&ntime, &locTime); + return (locTime.tm_sec); +} + +int wvutilsTimeIsToday(time_t checkTime) +{ + time_t ntime = time(NULL); + struct tm nowTime, testTime; + + localtime_r (&ntime, &nowTime); + localtime_r (&checkTime, &testTime); + + if (nowTime.tm_year == testTime.tm_year) + { + if (nowTime.tm_mon == testTime.tm_mon) + { + if (nowTime.tm_mday == testTime.tm_mday) + { + return TRUE; + } + } + + } + return FALSE; +} + +char* wvutilsGetArchivePath(void) +{ + static char dbFilePath[128]; + + sprintf (dbFilePath, "%s/archive", WVIEW_RUN_DIR); + return dbFilePath; +} + +char* wvutilsGetConfigPath(void) +{ + static char dbFilePath[128]; + + sprintf (dbFilePath, "%s", WV_CONFIG_DIR); + return dbFilePath; +} + +int wvutilsWriteMarkerFile(const char* filePath, time_t marker) +{ + FILE* pFile; + + pFile = fopen(filePath, "w"); + if (pFile == NULL) + { + return ERROR; + } + + fprintf (pFile, "%u", (uint32_t)marker); + fclose (pFile); + return OK; +} + +time_t wvutilsReadMarkerFile(const char* filePath) +{ + FILE* pFile; + char tempBfr[32]; + uint32_t retVal; + + pFile = fopen(filePath, "r"); + if (pFile == NULL) + { + return (time_t)0; + } + + if (fgets(tempBfr, sizeof(tempBfr), pFile) == NULL) + { + fclose(pFile); + return (time_t)0; + } + + retVal = strtol(tempBfr, NULL, 10); + if (retVal <= 0) + { + fclose(pFile); + return (time_t)0; + } + + fclose (pFile); + return (time_t)retVal; +} + +// Define a SIGCHLD handler to wait for child processes to exit: +// Should only be called from process signal handler. +void wvutilsWaitForChildren(void) +{ + // Wait for any remaining processes: + while (waitpid(-1, NULL, WNOHANG) > 0); + + return; +} + +// Define a NULL-terminating strncpy: +int wvstrncpy(char *d, const char *s, size_t bufsize) +{ + size_t len; + size_t ret; + + if (!d || !s) + return 0; + len = strlen(s); + ret = len; + if (bufsize <= 0) + return 0; + if (len >= bufsize) + len = bufsize-1; + memcpy(d, s, len); + d[len] = 0; + + return ret; +} + +void wvstrtrim(char *d) +{ + char* end = d + strlen(d); + + // find the last non-whitespace character: + while ((end != d) && isspace( *(end-1) )) + { + --end; + } + + *end = '\0'; + return; +} + +static int lastDSTState; +int wvutilsDetectDSTInit(void) +{ + time_t ntime; + struct tm tmtime; + + ntime = time (NULL); + localtime_r (&ntime, &tmtime); + lastDSTState = tmtime.tm_isdst; + return 0; +} + +// returns: +// 0 => no change +// -1 => fall back from DST to non-DST +// 1 => spring forward from non-DST to DST +int wvutilsDetectDSTChange(void) +{ + time_t ntime; + struct tm tmtime; + int retVal; + + ntime = time (NULL); + localtime_r (&ntime, &tmtime); + + if (tmtime.tm_isdst != lastDSTState) + { + if (lastDSTState != 0) + { + // fall back + retVal = WVUTILS_DST_FALL_BACK; + } + else + { + // spring forward + retVal = WVUTILS_DST_SPRING_FORWARD; + } + + lastDSTState = tmtime.tm_isdst; + return retVal; + } + else + { + return WVUTILS_DST_NO_CHANGE; + } +} + +// Assign a degree value to received string representation for wind direction: +// Returns degree equivalent or -1 if ERROR: +static char* directionLabels[16] = +{ + "N", + "NNE", + "NE", + "ENE", + "E", + "ESE", + "SE", + "SSE", + "S", + "SSW", + "SW", + "WSW", + "W", + "WNW", + "NW", + "NNW" +}; +int wvutilsConvertWindStrToDegrees(const char* windStr) +{ + int i; + float tempVal = -1; + + for (i = 0; i < 16; i ++) + { + if (! strcmp(windStr, directionLabels[i])) + { + // found him: + tempVal = 22.5 * (float)i; + } + } + + if (tempVal < 0) + { + return ERROR; + } + + return (int)tempVal; +} + +char* wvutilsCreateCWOPVersion(char* wviewStr) +{ + static char CWOPStr[24]; + int index; + + memset(CWOPStr, 0, 24); + for (index = 0; index < 23 && index < strlen(wviewStr); index ++) + { + if (wviewStr[index] == ' ' || wviewStr[index] == '.') + { + CWOPStr[index] = '_'; + } + else + { + CWOPStr[index] = wviewStr[index]; + } + } + + return CWOPStr; +} + +// Calculate the apparent temperature: +// Australian Bureau of Meteorology http://reg.bom.gov.au/info/thermal_stress/#atapproximation [^] +// Robert G. Steadman. 1994: Norms of apparent temperature in Australia. +float wvutilsCalculateApparentTemp(float temp, float windspeed, float humidity) +{ + double AT,Ta,e,ws; + double result; + + Ta = wvutilsConvertFToC(temp); + e = humidity / 100.0 * 6.105 * exp(17.27 * Ta/( 237.7 + Ta)); + ws = wvutilsConvertMPHToMPS(windspeed); + AT = Ta + 0.33 * e - 0.70 * ws - 4.00; + result = wvutilsConvertCToF(AT); + + return (float)result; +} + +// Calculate the wet bulb temperature: +// Based on NOAA Java Script http://www.srh.noaa.gov/epz/?n=wxcalc_rh +float wvutilsCalculateWetBulbTemp( float temp, float humidity, float pressure) +{ + float tempC = wvutilsConvertFToC(temp); + float pressureMB = wvutilsConvertINHGToHPA(pressure); + float Ewguess, Eguess; + float Es = 6.112 * exp(17.67 * tempC / (tempC + 243.5)); + float Twguess = 0.0; + float incr = 10.0; + int previoussign = 1; + int cursign; + float Edifference = 1; + float E2 = Es * (humidity/100.0); + + while (fabs(Edifference) > 0.05) + { + Ewguess = 6.112 * exp((17.67 * Twguess) / (Twguess + 243.5)); + Eguess = Ewguess - pressureMB * (tempC - Twguess) * 0.00066 * (1.0 + (0.00115 * Twguess)); + Edifference = E2 - Eguess; + if (Edifference == 0) + { + break; + } + else + { + if (Edifference < 0) + cursign = -1; + else cursign = 1; + + if (cursign != previoussign) + { + previoussign = cursign; + incr = incr/10; + } + } + Twguess = Twguess + incr * previoussign; + } + + return wvutilsConvertCToF(Twguess); +} diff --git a/configure.in b/configure.in new file mode 100755 index 0000000..41cc0fc --- /dev/null +++ b/configure.in @@ -0,0 +1,128 @@ +# Process this file with autoconf to produce a configure script. + +AC_PREREQ(2.5) +AC_INIT([wview],[5.21.5],[mteel@users.sourceforge.net]) +AC_CONFIG_SRCDIR([ftp/ftpUtils.c]) +AM_INIT_AUTOMAKE([wview],[5.21.5]) +AM_CONFIG_HEADER([config.h]) + + +# Provide a switch for installing databases: +AC_ARG_ENABLE(install-dpkg, +[ --enable-install-dpkg install debian package environment], +[case "${enableval}" in + yes) install_dpkg=true ;; + no) install_dpkg=false ;; + *) install_dpkg=false ;; +esac],[install_dpkg=false]) +AM_CONDITIONAL(INSTALL_DPKG, test x$install_dpkg = xtrue) + +# grab autoconf version of the host system (not the same as gcc name): +AC_CANONICAL_HOST + +# Check for big endian host: +AC_C_BIGENDIAN() + +# Check where to get timezone (GMT offset) from +AC_STRUCT_TIMEZONE + +# Checks for programs. +AC_PROG_CXX +AC_PROG_CC +AC_PROG_LN_S + +# Set a flag here if we are cross compiling (must be after AC_PROG_CC) +AM_CONDITIONAL(CROSSCOMPILE, test x$cross_compiling = xyes) + +# Determine host system (autoconf version): +case $host_os in + darwin*) + os_darwin=yes + os_linux=no + os_freebsd=no + ;; + freebsd*) + os_darwin=no + os_linux=no + os_freebsd=yes + ;; + *) + os_darwin=no + os_linux=yes + os_freebsd=no + ;; +esac + +AM_CONDITIONAL([DARWIN], [test x$os_darwin = xyes]) +AM_CONDITIONAL([LINUX], [test x$os_linux = xyes]) +AM_CONDITIONAL([FREEBSD], [test x$os_freebsd = xyes]) + + +# Checks for libraries. +AC_CHECK_LIB([c], [strncpy], [], [echo "libc is missing!";exit -1]) +AC_CHECK_LIB([gd], [gdImageCreate], [], [echo "libgd is missing!";exit -1]) +AC_CHECK_LIB([m], [exp], [], [echo "libm is missing!";exit -1]) +AC_CHECK_LIB([png], [png_write_chunk], [], [echo "libpng is missing!";exit -1]) +AC_CHECK_LIB([sqlite3], [sqlite3_open], [], [echo "libsqlite3 is missing!";exit -1]) +AC_CHECK_LIB([pthread], [pthread_create], [], [echo "libpthread is missing!";exit -1]) +AC_CHECK_LIB([rad], [radthreadLock], [], [echo "librad is missing or old version!";exit -1]) +AC_CHECK_LIB([curl], [curl_easy_init], [], [echo "libcurl is missing!";exit -1]) +AC_CHECK_LIB([z], [inflate], [], [echo "libz is missing!";exit -1]) + +# Checks for header files. +AC_HEADER_STDC +AC_HEADER_SYS_WAIT +AC_CHECK_HEADERS([fcntl.h stdlib.h string.h sys/time.h termios.h unistd.h]) + +# Checks for typedefs, structures, and compiler characteristics +AC_C_CONST +AC_TYPE_PID_T +AC_HEADER_TIME +AC_STRUCT_TM + +# Checks for library functions. +AC_FUNC_FORK +AC_FUNC_MKTIME +AC_TYPE_SIGNAL +AC_FUNC_STAT +AC_CHECK_FUNCS([localtime_r memset mkdir pow strrchr]) + + +AC_CONFIG_FILES([Makefile \ + stations/Makefile \ + stations/Simulator/Makefile \ + stations/Virtual/Makefile \ + stations/VantagePro/Makefile \ + stations/VantagePro/vpconfig/Makefile \ + stations/WS-2300/Makefile \ + stations/WMR918/Makefile \ + stations/WMRUSB/Makefile \ + stations/WXT510/Makefile \ + stations/WXT510/wxt510config/Makefile \ + stations/TWI/Makefile \ + stations/WH1080/Makefile \ + stations/TE923/Makefile \ + htmlgenerator/Makefile \ + alarms/Makefile \ + cwop/Makefile \ + http/Makefile \ + ftp/Makefile \ + ssh/Makefile \ + procmon/Makefile \ + wviewconfig/Makefile \ + wviewmgmt/Makefile \ + dbexport/Makefile \ + examples/Makefile \ + examples/Debian/Makefile \ + examples/FedoraCore/Makefile \ + examples/FreeBSD/Makefile \ + examples/MacOSX/wview/Makefile \ + examples/NSLU2/Makefile \ + examples/SuSE/Makefile \ + utilities/Makefile \ + utilities/wlk2sqlite/Makefile \ + utilities/sqlite2wlk/Makefile \ + utilities/archive-be2le/Makefile \ + utilities/archive-le2be/Makefile \ + utilities/hilowcreate/Makefile]) +AC_OUTPUT diff --git a/cross-compile/config-libgd-arm-linux b/cross-compile/config-libgd-arm-linux new file mode 100755 index 0000000..e942321 --- /dev/null +++ b/cross-compile/config-libgd-arm-linux @@ -0,0 +1,39 @@ +#!/bin/bash + +# architecture prefix +ARCH="arm-linux" + +# path to the toolchain and kernel base +TOOL_PATH=/usr/local/arm + +# path to the toolchain base +BASE_PATH=$TOOL_PATH/2.95.3 + +# cumulative toolchain prefix +PREFIX=$BASE_PATH/bin/$ARCH + +export CC=$PREFIX-gcc +export CXX=$PREFIX-g++ +export LD=$PREFIX-ld +export NM="$PREFIX-nm -B" +export AR=$PREFIX-ar +export RANLIB=$PREFIX-ranlib +export STRIP=$PREFIX-strip +export OBJCOPY=$PREFIX-objcopy +export LN_S="ln -s" + +export CFLAGS="-g -O2 -I$BASE_PATH/include" +export CPPFLAGS="-I$BASE_PATH/include" +export CXXFLAGS="" + +export LDFLAGS="-L$BASE_PATH/lib -L$BASE_PATH/$ARCH/lib -L$BASE_PATH/lib/gcc-lib/arm-linux/2.95.3" + +PATH=$BASE_PATH/bin:$PATH +./configure \ + --host=$ARCH \ + --prefix=$BASE_PATH \ + --with-png=$BASE_PATH \ + --with-jpeg=no \ + --with-freetype=no \ + --with-fontconfig=no \ + --with-xpm=no diff --git a/cross-compile/config-libpng-arm-linux b/cross-compile/config-libpng-arm-linux new file mode 100755 index 0000000..d0fe3f6 --- /dev/null +++ b/cross-compile/config-libpng-arm-linux @@ -0,0 +1,31 @@ +#!/bin/bash + +# architecture prefix +ARCH="arm-linux" + +# path to the toolchain and kernel base +TOOL_PATH=/usr/local/arm + +# path to the toolchain base +BASE_PATH=$TOOL_PATH/2.95.3 + +# cumulative toolchain prefix +PREFIX=$BASE_PATH/bin/$ARCH + +export CC=$PREFIX-gcc +export CXX=$PREFIX-g++ +export LD=$PREFIX-ld +export NM="$PREFIX-nm -B" +export AR=$PREFIX-ar +export RANLIB=$PREFIX-ranlib +export STRIP=$PREFIX-strip +export OBJCOPY=$PREFIX-objcopy +export LN_S="ln -s" + +export CFLAGS="-Wall -O3 -funroll-loops -I$BASE_PATH/include" +export CPPFLAGS="-I$BASE_PATH/include" +export CXXFLAGS="" + +export LDFLAGS="-L$BASE_PATH/lib -L$BASE_PATH/$ARCH/lib -L$BASE_PATH/lib/gcc-lib/arm-linux/2.95.3" + +./configure --host=$ARCH --prefix=$BASE_PATH diff --git a/cross-compile/config-libz-arm-linux b/cross-compile/config-libz-arm-linux new file mode 100755 index 0000000..9004089 --- /dev/null +++ b/cross-compile/config-libz-arm-linux @@ -0,0 +1,31 @@ +#!/bin/bash + +# architecture prefix +ARCH="arm-linux" + +# path to the toolchain and kernel base +TOOL_PATH=/usr/local/arm + +# path to the toolchain base +BASE_PATH=$TOOL_PATH/2.95.3 + +# cumulative toolchain prefix +PREFIX=$BASE_PATH/bin/$ARCH + +export CC=$PREFIX-gcc +export CXX=$PREFIX-g++ +export LD=$PREFIX-ld +export NM="$PREFIX-nm -B" +export AR="$PREFIX-ar -r" +export RANLIB=$PREFIX-ranlib +export STRIP=$PREFIX-strip +export OBJCOPY=$PREFIX-objcopy +export LN_S="ln -s" + +export CFLAGS="-g -O2 -I$BASE_PATH/include" +export CPPFLAGS="-I$BASE_PATH/include" +export CXXFLAGS="" + +export LDFLAGS="-L$BASE_PATH/lib -L$BASE_PATH/$ARCH/lib -L$BASE_PATH/lib/gcc-lib/arm-linux/2.95.3" + +./configure --prefix=$BASE_PATH diff --git a/cross-compile/config-radlib-arm-linux b/cross-compile/config-radlib-arm-linux new file mode 100755 index 0000000..4909fe6 --- /dev/null +++ b/cross-compile/config-radlib-arm-linux @@ -0,0 +1,32 @@ +#!/bin/bash + +# architecture prefix +ARCH="arm-linux-uclibc" +VERSION="4.2.4" + +# path to the toolchain and kernel base +TOOL_PATH=/usr/local/arm_9263 + +# path to the toolchain base +BASE_PATH=$TOOL_PATH/usr + +# cumulative toolchain prefix +PREFIX=$BASE_PATH/bin/$ARCH + +export CC=$PREFIX-gcc +export CXX=$PREFIX-g++ +export LD=$PREFIX-ld +export NM="$PREFIX-nm -B" +export AR=$PREFIX-ar +export RANLIB=$PREFIX-ranlib +export STRIP=$PREFIX-strip +export OBJCOPY=$PREFIX-objcopy +export LN_S="ln -s" + +export CFLAGS="-g -O2 -I$BASE_PATH/include" +export CPPFLAGS="-I$BASE_PATH/include" +export CXXFLAGS="" + +export LDFLAGS="-nostdlib -lgcc -L$BASE_PATH/lib -L$BASE_PATH/$ARCH/lib -L$BASE_PATH/lib/gcc/$ARCH/$VERSION" + +./configure --build=`./config.guess` --host=$ARCH --prefix=$BASE_PATH diff --git a/cross-compile/config-wview-arm-linux b/cross-compile/config-wview-arm-linux new file mode 100755 index 0000000..5447d2b --- /dev/null +++ b/cross-compile/config-wview-arm-linux @@ -0,0 +1,33 @@ +#!/bin/bash + +# architecture prefix +ARCH=arm-linux +VERSION="2.95.3" + +# path to the toolchain and kernel base +TOOL_PATH=/usr/local/arm + +# path to the toolchain base +BASE_PATH=$TOOL_PATH/$VERSION + +# cumulative toolchain prefix +PREFIX=$BASE_PATH/bin/$ARCH + +export CC=$PREFIX-gcc +export CXX=$PREFIX-g++ +export LD=$PREFIX-ld +export NM="$PREFIX-nm -B" +export AR=$PREFIX-ar +export RANLIB=$PREFIX-ranlib +export STRIP=$PREFIX-strip +export OBJCOPY=$PREFIX-objcopy +export LN_S="ln -s" + +export CFLAGS="-g -O2 -I$BASE_PATH/include" +export CPPFLAGS="-I$BASE_PATH/include" +export CXXFLAGS="" + +export LDFLAGS="-L$BASE_PATH/lib -L$BASE_PATH/$ARCH/lib -L$BASE_PATH/lib/gcc-lib/$ARCH/$VERSION" + +PATH=$BASE_PATH/bin:$PATH +./configure --build=`./config.guess` --host=$ARCH --prefix=$BASE_PATH $1 $2 $3 diff --git a/cwop/Makefile.am b/cwop/Makefile.am new file mode 100755 index 0000000..2a34de3 --- /dev/null +++ b/cwop/Makefile.am @@ -0,0 +1,40 @@ +# Makefile - wvcwopd + +# files to include in the distro but not build + +#define the executable to be built +bin_PROGRAMS = wvcwopd + +# define include directories +INCLUDES = \ + -I$(top_srcdir)/common \ + -I$(prefix)/include \ + -D_GNU_SOURCE \ + -DWV_CONFIG_DIR=\"$(sysconfdir)/wview\" \ + -DWV_RUN_DIR=\"$(localstatedir)/wview\" \ + -DBUILD_WVCWOPD + +# define the sources +wvcwopd_SOURCES = \ + $(top_srcdir)/common/wvutils.c \ + $(top_srcdir)/common/wvconfig.c \ + $(top_srcdir)/cwop/cwop.c \ + $(top_srcdir)/common/datadefs.h \ + $(top_srcdir)/common/services.h \ + $(top_srcdir)/common/sysdefs.h \ + $(top_srcdir)/common/wvconfig.h \ + $(top_srcdir)/common/status.h \ + $(top_srcdir)/common/status.c \ + $(top_srcdir)/cwop/cwop.h + + +# define libraries +wvcwopd_LDADD = + +# define library directories +wvcwopd_LDFLAGS = -L$(prefix)/lib -L$(prefix)/usr/lib -L/usr/lib + +if CROSSCOMPILE +wvcwopd_LDFLAGS += $(prefix)/lib/crt1.o $(prefix)/lib/crti.o $(prefix)/lib/crtn.o +endif + diff --git a/cwop/cwop.c b/cwop/cwop.c new file mode 100755 index 0000000..3bfcd70 --- /dev/null +++ b/cwop/cwop.c @@ -0,0 +1,952 @@ +/*--------------------------------------------------------------------------- + + FILENAME: + cwop.c + + PURPOSE: + Provide the wview CWOP packet generator entry point. + + REVISION HISTORY: + Date Engineer Revision Remarks + 07/12/2005 M.S. Teel 0 Original + + NOTES: + + + LICENSE: + Copyright (c) 2005, Mark S. Teel (mark@teel.ws) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +/* ... System include files +*/ +#include +#include +#include + +/* ... Library include files +*/ +#include + +/* ... Local include files +*/ +#include + + +/* ... global memory declarations +*/ + +/* ... global memory referenced +*/ + +/* ... static (local) memory declarations +*/ +static WVIEW_CWOP_WORK cwopWork; + +static char* cwopStatusLabels[STATUS_STATS_MAX] = +{ + "Connection errors", + "Packets sent", + NULL, + NULL +}; + +/* ... methods +*/ + +// From Gerry Creager: autogenerate passcode for non-CW/DW stations: +#define kKey 0x73e2 // This is the seed for the key +static int16_t getPasscode (char *callSign) +{ + char rootCall[16]; + char* p1 = rootCall; + int16_t hash; + int16_t i, len; + char* ptr = rootCall; + + while ((*callSign != '-') && (*callSign != '\0')) + { + *p1++ = toupper((int)(*callSign++)); + } + + *p1 = '\0'; + + if (! strncmp (rootCall, "CW", 2) || + ! strncmp (rootCall, "DW", 2) || + ! strncmp (rootCall, "EW", 2) || + ! strncmp (rootCall, "FW", 2)) + { + // Not a HAM, make sure it has been configured: + if (! strncmp (&rootCall[2], "XXXX", 4)) + { + // User has not configured a valid callsign: + return -2; + } + else + { + // Return -1 for passcode: + return -1; + } + } + + hash = kKey; // Initialize with the key value + i = 0; + len = (int16_t)strlen (rootCall); + + while (i < len) + { + // Loop through the string two bytes at a time + hash ^= (unsigned char)(*ptr++) << 8; // xor high byte with accumulated hash + hash ^= (*ptr++); // xor low byte with accumulated hash + i += 2; + } + + return (hash & 0x7fff); // mask off the high bit so + // result is always positive +} + +static void processCWOP () +{ + RADSOCK_ID socket; + time_t ntime; + struct tm *gmTime; + char cwopBuffer[256], login[64]; + int length = 0; + char *serv; + int port; + int passCode; + volatile WVIEW_MSG_ARCHIVE_NOTIFY Notify; + + memcpy ((void*)&Notify, (void*)&cwopWork.ArchiveMsg, sizeof(WVIEW_MSG_ARCHIVE_NOTIFY)); + + // Confirm the callsign is valid: + passCode = (int)getPasscode(cwopWork.callSign); + if (passCode == -2) + { + statusUpdateMessage("CWOP-error: callsign is not valid - aborting transfer."); + radMsgLog (PRI_HIGH, "CWOP-error: callsign %s is not valid - aborting transfer.", + cwopWork.callSign); + return; + } + + // format the CWOP data + ntime = time (NULL); + gmTime = gmtime (&ntime); + length = sprintf (cwopBuffer, "%s>APRS,TCPIP*:@", cwopWork.callSign); + length += sprintf (&cwopBuffer[length], "%2.2d%2.2d%2.2dz", + gmTime->tm_mday, gmTime->tm_hour, gmTime->tm_min); + length += sprintf (&cwopBuffer[length], "%s/%s", + cwopWork.latitude, cwopWork.longitude); + + // check for any wind registered + if (Notify.wspeedF < 0) + { + length += sprintf (&cwopBuffer[length], "_..."); + } + else + { + length += sprintf (&cwopBuffer[length], "_%3.3d", Notify.winddir); + } + + length += sprintf (&cwopBuffer[length], "/%3.3d", (int)Notify.wspeedF); + length += sprintf (&cwopBuffer[length], "g%3.3d", (int)Notify.hiwspeedF); + + if (Notify.temp < 0) + { + if (((Notify.temp * (-1)) % 10) >= 5) + { + Notify.temp -= 10; + } + + length += sprintf (&cwopBuffer[length], "t-%2.2d", + (Notify.temp * (-1))/10); + } + else + { + if ((Notify.temp % 10) >= 5) + { + Notify.temp += 10; + } + + length += sprintf (&cwopBuffer[length], "t%3.3d", Notify.temp/10); + } + + if (Notify.rainHour >= 0) + { + length += sprintf (&cwopBuffer[length], "r%3.3d", (int)(Notify.rainHour*100)); + } + if (Notify.rainDay >= 0) + { + length += sprintf (&cwopBuffer[length], "p%3.3d", (int)(Notify.rainDay*100)); + } + + if (Notify.humidity >= 0 && Notify.humidity <= 100) + { + length += sprintf (&cwopBuffer[length], "h%2.2d", Notify.humidity % 100); + } + + length += sprintf (&cwopBuffer[length], "b%5.5d", + (int)(10 * wvutilsConvertINHGToHPA((float)Notify.altimeter/1000.0))); + + // If there is radiation present, send it: + if (Notify.radiation <= 1800) + { + length += sprintf (&cwopBuffer[length], "L%3.3d", + ((Notify.radiation <= 999) ? (int)Notify.radiation : 999)); + } + + sprintf (&cwopBuffer[length], ".%s", wvutilsCreateCWOPVersion(globalWviewVersionStr)); + + // connect to the CWOP server - try the primary then secondary then tertiary: + socket = radSocketClientCreateAny(cwopWork.server1, cwopWork.portNo1); + if (socket == NULL) + { + // try the secondary server + socket = radSocketClientCreateAny(cwopWork.server2, cwopWork.portNo2); + if (socket == NULL) + { + // try the tertiary server + socket = radSocketClientCreateAny(cwopWork.server3, cwopWork.portNo3); + if (socket == NULL) + { + // we are all out of luck this time! + radMsgLog (PRI_HIGH, + "CWOP-connect: failed to connect to all 3 APRS servers!"); + return; + } + else + { + serv = cwopWork.server3; + port = cwopWork.portNo3; + } + } + else + { + serv = cwopWork.server2; + port = cwopWork.portNo2; + } + } + else + { + serv = cwopWork.server1; + port = cwopWork.portNo1; + } + + // wait 1 second ... + radUtilsSleep (1000); + + // transmit the data + sprintf (login, "user %6s pass %d vers %s", + cwopWork.callSign, passCode, globalWviewVersionStr); + length = strlen (login); + login[length] = 0x0D; // tack on the CR and LF + login[length+1] = 0x0A; + login[length+2] = 0; + + if (radSocketWriteExact (socket, login, length + 2) != (length + 2)) + { + statusUpdateMessage("CWOP-error: failed to login to server"); + radMsgLog (PRI_HIGH, "CWOP-error: %d: failed to login to %s:%d!", + errno, serv, port); + radSocketDestroy (socket); + return; + } + + // wait 3 seconds ... + radUtilsSleep (3000); + + // write the data record + wvutilsLogEvent (PRI_STATUS, "CWOP-send: %s", cwopBuffer); + + length = strlen (cwopBuffer); + cwopBuffer[length] = 0x0D; // tack on the CR and LF + cwopBuffer[length+1] = 0x0A; + cwopBuffer[length+2] = 0; + if (radSocketWriteExact (socket, cwopBuffer, length + 2) != (length + 2)) + { + statusUpdateMessage("CWOP-error: failed to write to server"); + radMsgLog (PRI_HIGH, "CWOP-error: %d: failed to write to %s:%d!", + errno, serv, port); + radSocketDestroy (socket); + return; + } + + statusIncrementStat(CWOP_STATS_PKTS_SENT); + + // wait 3 more seconds ... + radUtilsSleep (3000); + + // close connection and cleanup + radSocketDestroy (socket); + + return; +} + +static int waitForWviewDaemon (void) +{ + WVIEW_MSG_REQUEST msg; + char srcQName[QUEUE_NAME_LENGTH+1]; + UINT msgType; + UINT length; + void *recvBfr; + int retVal, done = FALSE; + + // enable message reception from the radlib router for the archive path + radMsgRouterMessageRegister (WVIEW_MSG_TYPE_STATION_INFO); + + msg.requestType = WVIEW_RQST_TYPE_STATION_INFO; + + if (radMsgRouterMessageSend (WVIEW_MSG_TYPE_REQUEST, &msg, sizeof(msg)) == ERROR) + { + statusUpdateMessage("waitForWviewDaemon: radMsgRouterMessageSend failed!"); + radMsgLog (PRI_HIGH, "waitForWviewDaemon: radMsgRouterMessageSend failed!"); + radMsgRouterMessageDeregister (WVIEW_MSG_TYPE_STATION_INFO); + return ERROR; + } + + statusUpdate(STATUS_WAITING_FOR_WVIEW); + + // now wait for the response here + while (!done) + { + radUtilsSleep (50); + + if ((retVal = radQueueRecv (radProcessQueueGetID (), + srcQName, + &msgType, + &recvBfr, + &length)) + == FALSE) + { + continue; + } + else if (retVal == ERROR) + { + statusUpdateMessage("waitForWviewDaemon: queue is closed!"); + radMsgLog (PRI_STATUS, "waitForWviewDaemon: queue is closed!"); + radMsgRouterMessageDeregister (WVIEW_MSG_TYPE_STATION_INFO); + return ERROR; + } + + // is this what we want? + if (msgType == WVIEW_MSG_TYPE_STATION_INFO) + { + // yes! + done = TRUE; + cwopWork.reportInterval = ((WVIEW_MSG_STATION_INFO*)recvBfr)->archiveInterval; + if (cwopWork.reportInterval < 5) + { + cwopWork.reportInterval = 5; + } + } + else if (msgType == WVIEW_MSG_TYPE_SHUTDOWN) + { + statusUpdateMessage("waitForWviewDaemon: received shutdown from wviewd"); + radMsgLog (PRI_HIGH, "waitForWviewDaemon: received shutdown from wviewd"); + radMsgRouterMessageDeregister (WVIEW_MSG_TYPE_STATION_INFO); + return ERROR; + } + + // release the received buffer + radBufferRls (recvBfr); + } + + // disable message reception from the radlib router for the archive path + radMsgRouterMessageDeregister (WVIEW_MSG_TYPE_STATION_INFO); + + return OK; +} + +/* ... system initialization +*/ +static int cwopSysInit (WVIEW_CWOP_WORK *work) +{ + char devPath[256], temp[64]; + struct stat fileData; + + /* ... check for our daemon's pid file, don't run if it isn't there + */ + sprintf (devPath, "%s/%s", WVIEW_RUN_DIR, WVD_LOCK_FILE_NAME); + if (stat (devPath, &fileData) != 0) + { + radMsgLogInit (PROC_NAME_CWOP, TRUE, TRUE); + radMsgLog (PRI_CATASTROPHIC, + "wviewd process not running - aborting!"); + radMsgLogExit (); + return ERROR; + } + + sprintf (work->pidFile, "%s/%s", WVIEW_RUN_DIR, CWOP_LOCK_FILE_NAME); + sprintf (work->statusFile, "%s/%s", WVIEW_STATUS_DIRECTORY, CWOP_STATUS_FILE_NAME); + sprintf (work->fifoFile, "%s/dev/%s", WVIEW_RUN_DIR, PROC_NAME_CWOP); + sprintf (work->daemonQname, "%s/dev/%s", WVIEW_RUN_DIR, PROC_NAME_DAEMON); + sprintf (work->wviewdir, "%s", WVIEW_RUN_DIR); + + /* ... check for our pid file, don't run if it IS there + */ + if (stat (work->pidFile, &fileData) == 0) + { + radMsgLogInit (PROC_NAME_CWOP, TRUE, TRUE); + radMsgLog (PRI_CATASTROPHIC, + "lock file %s exists, older copy may be running - aborting!", + work->pidFile); + radMsgLogExit (); + return ERROR; + } + + return OK; +} + +/* ... system exit +*/ +static int cwopSysExit (WVIEW_CWOP_WORK *work) +{ + struct stat fileData; + + /* ... delete our pid file + */ + if (stat (work->pidFile, &fileData) == 0) + { + unlink (work->pidFile); + } + + return OK; +} + +static void defaultSigHandler (int signum) +{ + int retVal; + + switch (signum) + { + case SIGHUP: + // user wants us to change the verbosity setting + retVal = wvutilsToggleVerbosity (); + radMsgLog (PRI_STATUS, "wvcwopd: SIGHUP - toggling log verbosity %s", + ((retVal == 0) ? "OFF" : "ON")); + + radProcessSignalCatch(signum, defaultSigHandler); + return; + + case SIGPIPE: + // we have a far end socket disconnection, we'll handle it in the + // "read/write" code + radProcessSignalCatch(signum, defaultSigHandler); + break; + + case SIGILL: + case SIGBUS: + case SIGFPE: + case SIGSEGV: + case SIGXFSZ: + case SIGSYS: + // unrecoverable radProcessSignalCatch- we must exit right now! + radMsgLog (PRI_CATASTROPHIC, + "wvcwopd: recv unrecoverable signal %d: aborting!", + signum); + abort (); + + case SIGCHLD: + wvutilsWaitForChildren(); + radProcessSignalCatch(signum, defaultSigHandler); + return; + + default: + if (cwopWork.exiting) + { + radProcessSignalCatch(signum, defaultSigHandler); + return; + } + + // Exit here in case the socket transaction is hung + statusUpdateMessage("exiting normally"); + statusUpdate(STATUS_SHUTDOWN); + radMsgLog (PRI_HIGH, "wvcwopd: recv sig %d: exiting now!", signum); + radMsgRouterExit (); + cwopSysExit (&cwopWork); + radProcessExit (); + radSystemExit (WVIEW_SYSTEM_ID); + exit (0); + } + + return; +} + +static void msgHandler +( + char *srcQueueName, + UINT msgType, + void *msg, + UINT length, + void *userData +) +{ + WVIEW_MSG_ARCHIVE_NOTIFY *anMsg; + + switch (msgType) + { + case WVIEW_MSG_TYPE_ARCHIVE_NOTIFY: + { + anMsg = (WVIEW_MSG_ARCHIVE_NOTIFY *)msg; + + // Just store the most recent for later submission based on call sign + memcpy ((void*)&cwopWork.ArchiveMsg, anMsg, sizeof(WVIEW_MSG_ARCHIVE_NOTIFY)); + cwopWork.rxFirstArchive = TRUE; + cwopWork.rxArchive = TRUE; + break; + } + case WVIEW_MSG_TYPE_POLL: + { + WVIEW_MSG_POLL* pPoll = (WVIEW_MSG_POLL*)msg; + wvutilsSendPMONPollResponse (pPoll->mask, PMON_PROCESS_WVCWOPD); + break; + } + } + + return; +} + +static void evtHandler +( + UINT eventsRx, + UINT rxData, + void *userData +) +{ + return; +} + +static void timerHandler (void *parm) +{ + time_t ntime; + struct tm locTime; + + radProcessTimerStart (cwopWork.timer, CWOP_MINUTE_INTERVAL); + + ntime = time (NULL); + localtime_r (&ntime, &locTime); + + if ((locTime.tm_min % cwopWork.reportInterval) == cwopWork.callSignOffset) + { + // Time to send a packet if we have any new data: + if (cwopWork.rxArchive) + { + processCWOP (); + cwopWork.rxArchive = FALSE; + } + else + { + if (cwopWork.rxFirstArchive) + { + statusUpdateMessage("no new archive data received since last CWOP submission"); + radMsgLog (PRI_MEDIUM, + "wvcwopd: no new archive data received since last CWOP submission:"); + } + } + } + + return; +} + +/* ... the main entry point for the wvcwopd process +*/ +int main (int argc, char *argv[]) +{ + void (*alarmHandler)(int); + int retVal; + FILE *pidfile; + int iValue; + double dValue; + const char* sValue; + int runAsDaemon = TRUE; + + if (argc > 1) + { + if (!strcmp(argv[1], "-f")) + { + runAsDaemon = FALSE; + } + } + + memset (&cwopWork, 0, sizeof (cwopWork)); + + /* ... initialize some system stuff first + */ + retVal = cwopSysInit (&cwopWork); + if (retVal == ERROR) + { + radMsgLogInit (PROC_NAME_CWOP, FALSE, TRUE); + radMsgLog (PRI_CATASTROPHIC, "wvcwopd init failed!"); + radMsgLogExit (); + exit (1); + } + else if (retVal == ERROR_ABORT) + { + exit (2); + } + + + /* ... call the global radlib system init function + */ + if (radSystemInit (WVIEW_SYSTEM_ID) == ERROR) + { + radMsgLogInit (PROC_NAME_CWOP, TRUE, TRUE); + radMsgLog (PRI_CATASTROPHIC, "radSystemInit failed!"); + radMsgLogExit (); + exit (1); + } + + + /* ... call the radlib process init function + */ + if (radProcessInit (PROC_NAME_CWOP, + cwopWork.fifoFile, + PROC_NUM_TIMERS_CWOP, + runAsDaemon, // TRUE for daemon + msgHandler, + evtHandler, + NULL) + == ERROR) + { + printf ("\nradProcessInit failed: %s\n\n", PROC_NAME_CWOP); + radSystemExit (WVIEW_SYSTEM_ID); + exit (1); + } + + cwopWork.myPid = getpid (); + pidfile = fopen (cwopWork.pidFile, "w"); + if (pidfile == NULL) + { + radMsgLog (PRI_CATASTROPHIC, "lock file create failed!"); + radProcessExit (); + radSystemExit (WVIEW_SYSTEM_ID); + exit (1); + } + fprintf (pidfile, "%d", getpid ()); + fclose (pidfile); + + + alarmHandler = radProcessSignalGetHandler (SIGALRM); + radProcessSignalCatchAll (defaultSigHandler); + radProcessSignalCatch (SIGALRM, alarmHandler); + radProcessSignalRelease(SIGABRT); + + + // get our configuration values + if (wvconfigInit(FALSE) == ERROR) + { + radMsgLog (PRI_CATASTROPHIC, "wvconfigInit failed!"); + cwopSysExit (&cwopWork); + radProcessExit (); + radSystemExit (WVIEW_SYSTEM_ID); + exit (1); + } + + // Is the CWOP daemon enabled? + iValue = wvconfigGetBooleanValue(configItem_ENABLE_CWOP); + if (iValue == ERROR || iValue == 0) + { + wvconfigExit (); + radMsgLog (PRI_CATASTROPHIC, "CWOP daemon is NOT enabled - exiting..."); + cwopSysExit (&cwopWork); + radProcessExit (); + radSystemExit (WVIEW_SYSTEM_ID); + exit (1); + } + + // get the wview verbosity setting + if (wvutilsSetVerbosity (WV_VERBOSE_WVCWOPD) == ERROR) + { + wvconfigExit (); + radMsgLog (PRI_CATASTROPHIC, "wvutilsSetVerbosity failed!"); + cwopSysExit (&cwopWork); + radProcessExit (); + radSystemExit (WVIEW_SYSTEM_ID); + exit (1); + } + + // get the APRS call sign + sValue = wvconfigGetStringValue(configItemCWOP_APRS_CALL_SIGN); + if (sValue == NULL) + { + // we can't do without this! + wvconfigExit (); + radMsgLog (PRI_CATASTROPHIC, "%s failed!", configItemCWOP_APRS_CALL_SIGN); + cwopSysExit (&cwopWork); + radProcessExit (); + radSystemExit (WVIEW_SYSTEM_ID); + exit (1); + } + else + { + wvstrncpy (cwopWork.callSign, sValue, sizeof(cwopWork.callSign)); + // Trim trailing blanks: + wvstrtrim (cwopWork.callSign); + + if (sValue[strlen(sValue)-1] >= '0' && sValue[strlen(sValue)-1] <= '9') + { + cwopWork.callSignOffset = atoi(&sValue[strlen(sValue)-1]); + } + else + { + cwopWork.callSignOffset = (sValue[strlen(sValue)-1] % 5); + } + cwopWork.callSignOffset %= 5; + } + + // get the primary APRS server + sValue = wvconfigGetStringValue(configItemCWOP_APRS_SERVER1); + if (sValue == NULL) + { + // we can't do without this! + wvconfigExit (); + radMsgLog (PRI_CATASTROPHIC, "%s failed!", configItemCWOP_APRS_SERVER1); + cwopSysExit (&cwopWork); + radProcessExit (); + radSystemExit (WVIEW_SYSTEM_ID); + exit (1); + } + else + { + wvstrncpy (cwopWork.server1, sValue, sizeof(cwopWork.server1)); + } + + // get the primary APRS port number + iValue = wvconfigGetINTValue(configItemCWOP_APRS_PORTNO1); + if (iValue < 0) + { + // we can't do without this! + wvconfigExit (); + radMsgLog (PRI_CATASTROPHIC, "%s failed!", configItemCWOP_APRS_PORTNO1); + cwopSysExit (&cwopWork); + radProcessExit (); + radSystemExit (WVIEW_SYSTEM_ID); + exit (1); + } + else + { + cwopWork.portNo1 = iValue; + } + + // get the secondary APRS server + sValue = wvconfigGetStringValue(configItemCWOP_APRS_SERVER2); + if (sValue == NULL) + { + // we can't do without this! + wvconfigExit (); + radMsgLog (PRI_CATASTROPHIC, "%s failed!", configItemCWOP_APRS_SERVER2); + cwopSysExit (&cwopWork); + radProcessExit (); + radSystemExit (WVIEW_SYSTEM_ID); + exit (1); + } + else + { + wvstrncpy (cwopWork.server2, sValue, sizeof(cwopWork.server2)); + } + + // get the primary APRS port number + iValue = wvconfigGetINTValue(configItemCWOP_APRS_PORTNO2); + if (iValue < 0) + { + // we can't do without this! + wvconfigExit (); + radMsgLog (PRI_CATASTROPHIC, "%s failed!", configItemCWOP_APRS_PORTNO2); + cwopSysExit (&cwopWork); + radProcessExit (); + radSystemExit (WVIEW_SYSTEM_ID); + exit (1); + } + else + { + cwopWork.portNo2 = iValue; + } + + // get the tertiary APRS server + sValue = wvconfigGetStringValue(configItemCWOP_APRS_SERVER3); + if (sValue == NULL) + { + // we can't do without this! + wvconfigExit (); + radMsgLog (PRI_CATASTROPHIC, "%s failed!", configItemCWOP_APRS_SERVER3); + cwopSysExit (&cwopWork); + radProcessExit (); + radSystemExit (WVIEW_SYSTEM_ID); + exit (1); + } + else + { + wvstrncpy (cwopWork.server3, sValue, sizeof(cwopWork.server3)); + } + + // get the primary APRS port number + iValue = wvconfigGetINTValue(configItemCWOP_APRS_PORTNO3); + if (iValue < 0) + { + // we can't do without this! + wvconfigExit (); + radMsgLog (PRI_CATASTROPHIC, "%s failed!", configItemCWOP_APRS_PORTNO3); + cwopSysExit (&cwopWork); + radProcessExit (); + radSystemExit (WVIEW_SYSTEM_ID); + exit (1); + } + else + { + cwopWork.portNo3 = iValue; + } + + // get the fine pitch latitude that APRS requires + sValue = wvconfigGetStringValue(configItemCWOP_LATITUDE); + if (sValue == NULL) + { + // we can't do without this! + wvconfigExit (); + radMsgLog (PRI_CATASTROPHIC, "%s failed!", configItemCWOP_LATITUDE); + cwopSysExit (&cwopWork); + radProcessExit (); + radSystemExit (WVIEW_SYSTEM_ID); + exit (1); + } + else + { + wvstrncpy (cwopWork.latitude, sValue, sizeof(cwopWork.latitude)); + } + + // get the fine pitch longitude that APRS requires + sValue = wvconfigGetStringValue(configItemCWOP_LONGITUDE); + if (sValue == NULL) + { + // we can't do without this! + wvconfigExit (); + radMsgLog (PRI_CATASTROPHIC, "%s failed!", configItemCWOP_LONGITUDE); + cwopSysExit (&cwopWork); + radProcessExit (); + radSystemExit (WVIEW_SYSTEM_ID); + exit (1); + } + else + { + wvstrncpy (cwopWork.longitude, sValue, sizeof(cwopWork.longitude)); + } + + // get the WX packet logging preference + iValue = wvconfigGetBooleanValue(configItemCWOP_LOG_WX_PACKET); + if (iValue <= 0) + { + // just disable it + cwopWork.logWXPackets = 0; + } + else + { + cwopWork.logWXPackets = iValue; + } + + wvconfigExit (); + + + if (statusInit(cwopWork.statusFile, cwopStatusLabels) == ERROR) + { + radMsgLog (PRI_HIGH, "statusInit failed - exiting..."); + cwopSysExit (&cwopWork); + radProcessExit (); + radSystemExit (WVIEW_SYSTEM_ID); + exit (1); + } + statusUpdate(STATUS_BOOTING); + + + // wait a bit here before continuing + radUtilsSleep (500); + + + cwopWork.timer = radTimerCreate (NULL, timerHandler, NULL); + if (cwopWork.timer == NULL) + { + statusUpdateMessage("radTimerCreate failed"); + radMsgLog (PRI_HIGH, "radTimerCreate failed"); + statusUpdate(STATUS_ERROR); + cwopSysExit (&cwopWork); + radProcessExit (); + radSystemExit (WVIEW_SYSTEM_ID); + exit (1); + } + radProcessTimerStart (cwopWork.timer, CWOP_MINUTE_INTERVAL); + + + // register with the radlib message router + if (radMsgRouterInit (WVIEW_RUN_DIR) == ERROR) + { + statusUpdateMessage("radMsgRouterInit failed"); + radMsgLog (PRI_HIGH, "radMsgRouterInit failed!"); + statusUpdate(STATUS_ERROR); + cwopSysExit (&cwopWork); + radProcessExit (); + radSystemExit (WVIEW_SYSTEM_ID); + exit (1); + } + + // enable message reception from the radlib router for SHUTDOWN msgs + radMsgRouterMessageRegister (WVIEW_MSG_TYPE_SHUTDOWN); + + + // wait for the wview daemon to be ready + if (waitForWviewDaemon () == ERROR) + { + radMsgLog (PRI_HIGH, "waitForWviewDaemon failed"); + statusUpdate(STATUS_ERROR); + radMsgRouterExit (); + cwopSysExit (&cwopWork); + radProcessExit (); + radSystemExit (WVIEW_SYSTEM_ID); + exit (1); + } + + // enable message reception from the radlib router for archive notifications + radMsgRouterMessageRegister (WVIEW_MSG_TYPE_ARCHIVE_NOTIFY); + + // enable message reception from the radlib router for POLL msgs + radMsgRouterMessageRegister (WVIEW_MSG_TYPE_POLL); + + + radMsgLog (PRI_STATUS, "CWOP: configured to submit station %s data to:", + cwopWork.callSign); + radMsgLog (PRI_STATUS, "CWOP: Primary: %s:%d", cwopWork.server1, cwopWork.portNo1); + radMsgLog (PRI_STATUS, "CWOP: Secondary: %s:%d", cwopWork.server2, cwopWork.portNo2); + radMsgLog (PRI_STATUS, "CWOP: Tertiary: %s:%d", cwopWork.server3, cwopWork.portNo3); + radMsgLog (PRI_STATUS, "CWOP: Submitting every %d minutes at offset minute: %d", + cwopWork.reportInterval, cwopWork.callSignOffset); + + + // enter normal processing + cwopWork.inMainLoop = TRUE; + statusUpdate(STATUS_RUNNING); + statusUpdateMessage("Normal operation"); + radMsgLog (PRI_STATUS, "running..."); + + + while (!cwopWork.exiting) + { + // wait on something interesting + if (radProcessWait (0) == ERROR) + { + cwopWork.exiting = TRUE; + } + } + + + statusUpdateMessage("exiting normally"); + radMsgLog (PRI_STATUS, "exiting normally..."); + statusUpdate(STATUS_SHUTDOWN); + + radMsgRouterExit (); + radTimerDelete (cwopWork.timer); + cwopSysExit (&cwopWork); + radProcessExit (); + radSystemExit (WVIEW_SYSTEM_ID); + exit (0); +} + diff --git a/cwop/cwop.h b/cwop/cwop.h new file mode 100644 index 0000000..d919d42 --- /dev/null +++ b/cwop/cwop.h @@ -0,0 +1,114 @@ +#ifndef INC_cwoph +#define INC_cwoph +/*--------------------------------------------------------------------------- + + FILENAME: + cwop.h + + PURPOSE: + Provide the wview CWOP process definitions. + + REVISION HISTORY: + Date Engineer Revision Remarks + 07/12/2005 M.S. Teel 0 Original + + NOTES: + + + LICENSE: + Copyright (c) 2005, Mark S. Teel (mark@teel.ws) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +/* ... System include files +*/ +#include +#include +#include +#include +#include +#include +#include + +/* ... Library include files +*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* ... Local include files +*/ +#include +#include +#include +#include +#include + + +#define CWOP_MINUTE_INTERVAL 60000 + +/* ... API definitions +*/ + +/* !!!!!!!!!!!!!!!!!! HIDDEN, NOT FOR API USE !!!!!!!!!!!!!!!!!! +*/ + +typedef enum +{ + CWOP_STATS_CONNECT_ERRORS = 0, + CWOP_STATS_PKTS_SENT +} CWOP_STATS; + + +typedef struct +{ + pid_t myPid; + TIMER_ID timer; + char pidFile[128]; + char fifoFile[128]; + char statusFile[128]; + char daemonQname[128]; + char wviewdir[128]; + WVIEW_MSG_ARCHIVE_NOTIFY ArchiveMsg; + char callSign[64]; + int callSignOffset; + int reportInterval; + int rxFirstArchive; + int rxArchive; + char server1[128]; + int portNo1; + char server2[128]; + int portNo2; + char server3[128]; + int portNo3; + char latitude[16]; + char longitude[16]; + int logWXPackets; + int inMainLoop; + int exiting; +} WVIEW_CWOP_WORK; + +/* !!!!!!!!!!!!!!!!!!!! END HIDDEN SECTION !!!!!!!!!!!!!!!!!!!!! +*/ + + + +/* ... API function prototypes +*/ + + + +#endif diff --git a/dbexport/Makefile.am b/dbexport/Makefile.am new file mode 100755 index 0000000..832bbb6 --- /dev/null +++ b/dbexport/Makefile.am @@ -0,0 +1,40 @@ +# Makefile - wview-mysql-export + +EXTRA_DIST = \ + $(srcdir)/wview-mysql-export.sh \ + $(srcdir)/wview-mysql-create.sh \ + $(srcdir)/wview-pgsql-export.sh \ + $(srcdir)/wview-pgsql-create.sh + +# define the scripts to be generated +bin_SCRIPTS = wview-mysql-export wview-mysql-create wview-pgsql-export wview-pgsql-create + +CLEANFILES = $(bin_SCRIPTS) + +wview-mysql-export: $(srcdir)/wview-mysql-export.sh + rm -f wview-mysql-export + echo "#!/bin/sh" > wview-mysql-export + echo "WVIEW_CONF_DIR=$(sysconfdir)/wview" >> wview-mysql-export + echo "WVIEW_DATA_DIR=$(localstatedir)/wview" >> wview-mysql-export + cat $(srcdir)/wview-mysql-export.sh >> wview-mysql-export + chmod ugo+x wview-mysql-export + +wview-mysql-create: $(srcdir)/wview-mysql-create.sh + rm -f wview-mysql-create + echo "#!/bin/sh" > wview-mysql-create + cat $(srcdir)/wview-mysql-create.sh >> wview-mysql-create + chmod ugo+x wview-mysql-create + +wview-pgsql-export: $(srcdir)/wview-pgsql-export.sh + rm -f wview-pgsql-export + echo "#!/bin/sh" > wview-pgsql-export + echo "WVIEW_CONF_DIR=$(sysconfdir)/wview" >> wview-pgsql-export + echo "WVIEW_DATA_DIR=$(localstatedir)/wview" >> wview-pgsql-export + cat $(srcdir)/wview-pgsql-export.sh >> wview-pgsql-export + chmod ugo+x wview-pgsql-export + +wview-pgsql-create: $(srcdir)/wview-pgsql-create.sh + rm -f wview-pgsql-create + echo "#!/bin/sh" > wview-pgsql-create + cat $(srcdir)/wview-pgsql-create.sh >> wview-pgsql-create + chmod ugo+x wview-pgsql-create diff --git a/dbexport/wview-mysql-create.sh b/dbexport/wview-mysql-create.sh new file mode 100644 index 0000000..16f0c63 --- /dev/null +++ b/dbexport/wview-mysql-create.sh @@ -0,0 +1,422 @@ +################################################################################ +# +# File: wview-mysql-create.sh +# +# Description: Provide a script to create the wview database for MySQL. +# +# Usage: wview-mysql-create +# Arguments: +# db_admin MySQL server admin with create and grant +# privileges. +# db_admin_passwd MySQL server admin password. +# db_user MySQL server user with create and grant +# privileges. +# db_passwd MySQL server password. +# db_name MySQL database name to create and grant on. +# +# History: +# Engineer Date Ver Comments +# MS Teel 03/28/09 1 Original +# +################################################################################ + +show_usage() +{ + echo "USAGE:" + echo "wview-mysql-create " + echo "" + echo " db_admin - MySQL server admin with create and grant privileges." + echo "" + echo " db_admin_passwd - MySQL server admin password." + echo "" + echo " db_user - MySQL server user for export acivity (can be same as db_admin)." + echo "" + echo " db_passwd - MySQL server password." + echo "" + echo " db_name - MySQL database name to create and grant privileges for." + echo "" + echo "REQUIRES: mysql CLI utility for database creation." + echo "MUST: be executed on the MySQL server." + echo "" +} + +################################################################################ +################################# M A C R O S ################################ +################################################################################ + +## DO NOT CHANGE ANY DEFS IN THIS BLOCK! + +export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin + +## The defaults match the SQLite table names: +SQL_ARCHIVE_TABLE=archive +SQL_HILOW_TABLE_INTEMP=inTemp +SQL_HILOW_TABLE_OUTTEMP=outTemp +SQL_HILOW_TABLE_INHUMIDITY=inHumidity +SQL_HILOW_TABLE_OUTHUMIDITY=outHumidity +SQL_HILOW_TABLE_BP=baromPressure +SQL_HILOW_TABLE_WINDSPEED=windSpeed +SQL_HILOW_TABLE_WINDGUST=windGust +SQL_HILOW_TABLE_DEWPOINT=dewPoint +SQL_HILOW_TABLE_RAIN=rain +SQL_HILOW_TABLE_RAINRATE=rainRate +SQL_HILOW_TABLE_WINDCHILL=windChill +SQL_HILOW_TABLE_HEATINDEX=heatIndex +SQL_HILOW_TABLE_ET=ET +SQL_HILOW_TABLE_UV=UV +SQL_HILOW_TABLE_SOLARRAD=solarRadiation +SQL_HILOW_TABLE_HAIL=hail +SQL_HILOW_TABLE_HAILRATE=hailRate +SQL_HILOW_TABLE_WINDDIR=windDir +SQL_NOAA_TABLE=noaaHistory + +## Test command line arguments: +if [ "$1" = "help" ]; then + show_usage + exit 0 +fi + +## Check arguments: +if [ x"$1" = x ]; then + echo "You must specify the MySQL admin username to create export database - use 'wview-mysql-create help' for details" + show_usage + exit 1 +elif [ x"$2" = x ]; then + echo "You must specify the MySQL admin password to create export database - use 'wview-mysql-create help' for details" + show_usage + exit 1 +elif [ x"$3" = x ]; then + echo "You must specify the MySQL username to create export database - use 'wview-mysql-create help' for details" + show_usage + exit 1 +elif [ x"$4" = x ]; then + echo "You must specify the MySQL password to create export database - use 'wview-mysql-create help' for details" + show_usage + exit 1 +elif [ x"$5" = x ]; then + echo "You must specify the MySQL database name to create export database - use 'wview-mysql-create help' for details" + show_usage + exit 1 +else + MYSQL_ADMIN_USERNAME=$1 + MYSQL_ADMIN_PASSWORD=$2 + MYSQL_USERNAME=$3 + MYSQL_PASSWORD=$4 + MYSQL_DBNAME=$5 +fi + +################################################################################ +####################### D E F I N E F U N C T I O N S ####################### +################################################################################ +syslog() +{ + logger -t WV_EXPORT $1 $2 +} + +syslog_error() +{ + logger -t WV_EXPORT_ERROR $1 $2 +} + +mysql_create() +{ + echo "create database $MYSQL_DBNAME;" > /tmp/commands.sql + echo "grant all privileges on $MYSQL_DBNAME.* to $MYSQL_USERNAME@\"localhost\" identified by '$MYSQL_PASSWORD';" >> /tmp/commands.sql + echo "grant all privileges on $MYSQL_DBNAME.* to $MYSQL_USERNAME@\"%\" identified by '$MYSQL_PASSWORD';" >> /tmp/commands.sql + echo "use $MYSQL_DBNAME;" >> /tmp/commands.sql + + echo "create table $SQL_ARCHIVE_TABLE (" >> /tmp/commands.sql + echo " dateTime INTEGER NOT NULL PRIMARY KEY," >> /tmp/commands.sql + echo " usUnits INTEGER NOT NULL," >> /tmp/commands.sql + echo " arcInt INTEGER NOT NULL," >> /tmp/commands.sql + echo " barometer REAL," >> /tmp/commands.sql + echo " pressure REAL," >> /tmp/commands.sql + echo " altimeter REAL," >> /tmp/commands.sql + echo " inTemp REAL," >> /tmp/commands.sql + echo " outTemp REAL," >> /tmp/commands.sql + echo " inHumidity REAL," >> /tmp/commands.sql + echo " outHumidity REAL," >> /tmp/commands.sql + echo " windSpeed REAL," >> /tmp/commands.sql + echo " windDir REAL," >> /tmp/commands.sql + echo " windGust REAL," >> /tmp/commands.sql + echo " windGustDir REAL," >> /tmp/commands.sql + echo " rainRate REAL," >> /tmp/commands.sql + echo " rain REAL," >> /tmp/commands.sql + echo " dewpoint REAL," >> /tmp/commands.sql + echo " windchill REAL," >> /tmp/commands.sql + echo " heatindex REAL," >> /tmp/commands.sql + echo " ET REAL," >> /tmp/commands.sql + echo " radiation REAL," >> /tmp/commands.sql + echo " UV REAL," >> /tmp/commands.sql + echo " extraTemp1 REAL," >> /tmp/commands.sql + echo " extraTemp2 REAL," >> /tmp/commands.sql + echo " extraTemp3 REAL," >> /tmp/commands.sql + echo " soilTemp1 REAL," >> /tmp/commands.sql + echo " soilTemp2 REAL," >> /tmp/commands.sql + echo " soilTemp3 REAL," >> /tmp/commands.sql + echo " soilTemp4 REAL," >> /tmp/commands.sql + echo " leafTemp1 REAL," >> /tmp/commands.sql + echo " leafTemp2 REAL," >> /tmp/commands.sql + echo " extraHumid1 REAL," >> /tmp/commands.sql + echo " extraHumid2 REAL," >> /tmp/commands.sql + echo " soilMoist1 REAL," >> /tmp/commands.sql + echo " soilMoist2 REAL," >> /tmp/commands.sql + echo " soilMoist3 REAL," >> /tmp/commands.sql + echo " soilMoist4 REAL," >> /tmp/commands.sql + echo " leafWet1 REAL," >> /tmp/commands.sql + echo " leafWet2 REAL," >> /tmp/commands.sql + echo " rxCheckPercent REAL," >> /tmp/commands.sql + echo " txBatteryStatus REAL," >> /tmp/commands.sql + echo " consBatteryVoltage REAL," >> /tmp/commands.sql + echo " hail REAL," >> /tmp/commands.sql + echo " hailRate REAL," >> /tmp/commands.sql + echo " heatingTemp REAL," >> /tmp/commands.sql + echo " heatingVoltage REAL," >> /tmp/commands.sql + echo " supplyVoltage REAL," >> /tmp/commands.sql + echo " referenceVoltage REAL," >> /tmp/commands.sql + echo " windBatteryStatus REAL," >> /tmp/commands.sql + echo " rainBatteryStatus REAL," >> /tmp/commands.sql + echo " outTempBatteryStatus REAL," >> /tmp/commands.sql + echo " inTempBatteryStatus REAL" >> /tmp/commands.sql + echo ");" >> /tmp/commands.sql + + echo "create table $SQL_HILOW_TABLE_INTEMP (" >> /tmp/commands.sql + echo " dateTime INTEGER NOT NULL PRIMARY KEY," >> /tmp/commands.sql + echo " low REAL," >> /tmp/commands.sql + echo " timeLow INTEGER," >> /tmp/commands.sql + echo " high REAL," >> /tmp/commands.sql + echo " timeHigh INTEGER," >> /tmp/commands.sql + echo " whenHigh REAL," >> /tmp/commands.sql + echo " cumulative REAL," >> /tmp/commands.sql + echo " samples INTEGER" >> /tmp/commands.sql + echo ");" >> /tmp/commands.sql + + echo "create table $SQL_HILOW_TABLE_OUTTEMP (" >> /tmp/commands.sql + echo " dateTime INTEGER NOT NULL PRIMARY KEY," >> /tmp/commands.sql + echo " low REAL," >> /tmp/commands.sql + echo " timeLow INTEGER," >> /tmp/commands.sql + echo " high REAL," >> /tmp/commands.sql + echo " timeHigh INTEGER," >> /tmp/commands.sql + echo " whenHigh REAL," >> /tmp/commands.sql + echo " cumulative REAL," >> /tmp/commands.sql + echo " samples INTEGER" >> /tmp/commands.sql + echo ");" >> /tmp/commands.sql + + echo "create table $SQL_HILOW_TABLE_INHUMIDITY (" >> /tmp/commands.sql + echo " dateTime INTEGER NOT NULL PRIMARY KEY," >> /tmp/commands.sql + echo " low REAL," >> /tmp/commands.sql + echo " timeLow INTEGER," >> /tmp/commands.sql + echo " high REAL," >> /tmp/commands.sql + echo " timeHigh INTEGER," >> /tmp/commands.sql + echo " whenHigh REAL," >> /tmp/commands.sql + echo " cumulative REAL," >> /tmp/commands.sql + echo " samples INTEGER" >> /tmp/commands.sql + echo ");" >> /tmp/commands.sql + + echo "create table $SQL_HILOW_TABLE_OUTHUMIDITY (" >> /tmp/commands.sql + echo " dateTime INTEGER NOT NULL PRIMARY KEY," >> /tmp/commands.sql + echo " low REAL," >> /tmp/commands.sql + echo " timeLow INTEGER," >> /tmp/commands.sql + echo " high REAL," >> /tmp/commands.sql + echo " timeHigh INTEGER," >> /tmp/commands.sql + echo " whenHigh REAL," >> /tmp/commands.sql + echo " cumulative REAL," >> /tmp/commands.sql + echo " samples INTEGER" >> /tmp/commands.sql + echo ");" >> /tmp/commands.sql + + echo "create table $SQL_HILOW_TABLE_BP (" >> /tmp/commands.sql + echo " dateTime INTEGER NOT NULL PRIMARY KEY," >> /tmp/commands.sql + echo " low REAL," >> /tmp/commands.sql + echo " timeLow INTEGER," >> /tmp/commands.sql + echo " high REAL," >> /tmp/commands.sql + echo " timeHigh INTEGER," >> /tmp/commands.sql + echo " whenHigh REAL," >> /tmp/commands.sql + echo " cumulative REAL," >> /tmp/commands.sql + echo " samples INTEGER" >> /tmp/commands.sql + echo ");" >> /tmp/commands.sql + + echo "create table $SQL_HILOW_TABLE_WINDSPEED (" >> /tmp/commands.sql + echo " dateTime INTEGER NOT NULL PRIMARY KEY," >> /tmp/commands.sql + echo " low REAL," >> /tmp/commands.sql + echo " timeLow INTEGER," >> /tmp/commands.sql + echo " high REAL," >> /tmp/commands.sql + echo " timeHigh INTEGER," >> /tmp/commands.sql + echo " whenHigh REAL," >> /tmp/commands.sql + echo " cumulative REAL," >> /tmp/commands.sql + echo " samples INTEGER" >> /tmp/commands.sql + echo ");" >> /tmp/commands.sql + + echo "create table $SQL_HILOW_TABLE_WINDGUST (" >> /tmp/commands.sql + echo " dateTime INTEGER NOT NULL PRIMARY KEY," >> /tmp/commands.sql + echo " low REAL," >> /tmp/commands.sql + echo " timeLow INTEGER," >> /tmp/commands.sql + echo " high REAL," >> /tmp/commands.sql + echo " timeHigh INTEGER," >> /tmp/commands.sql + echo " whenHigh REAL," >> /tmp/commands.sql + echo " cumulative REAL," >> /tmp/commands.sql + echo " samples INTEGER" >> /tmp/commands.sql + echo ");" >> /tmp/commands.sql + + echo "create table $SQL_HILOW_TABLE_DEWPOINT (" >> /tmp/commands.sql + echo " dateTime INTEGER NOT NULL PRIMARY KEY," >> /tmp/commands.sql + echo " low REAL," >> /tmp/commands.sql + echo " timeLow INTEGER," >> /tmp/commands.sql + echo " high REAL," >> /tmp/commands.sql + echo " timeHigh INTEGER," >> /tmp/commands.sql + echo " whenHigh REAL," >> /tmp/commands.sql + echo " cumulative REAL," >> /tmp/commands.sql + echo " samples INTEGER" >> /tmp/commands.sql + echo ");" >> /tmp/commands.sql + + echo "create table $SQL_HILOW_TABLE_RAIN (" >> /tmp/commands.sql + echo " dateTime INTEGER NOT NULL PRIMARY KEY," >> /tmp/commands.sql + echo " low REAL," >> /tmp/commands.sql + echo " timeLow INTEGER," >> /tmp/commands.sql + echo " high REAL," >> /tmp/commands.sql + echo " timeHigh INTEGER," >> /tmp/commands.sql + echo " whenHigh REAL," >> /tmp/commands.sql + echo " cumulative REAL," >> /tmp/commands.sql + echo " samples INTEGER" >> /tmp/commands.sql + echo ");" >> /tmp/commands.sql + + echo "create table $SQL_HILOW_TABLE_RAINRATE (" >> /tmp/commands.sql + echo " dateTime INTEGER NOT NULL PRIMARY KEY," >> /tmp/commands.sql + echo " low REAL," >> /tmp/commands.sql + echo " timeLow INTEGER," >> /tmp/commands.sql + echo " high REAL," >> /tmp/commands.sql + echo " timeHigh INTEGER," >> /tmp/commands.sql + echo " whenHigh REAL," >> /tmp/commands.sql + echo " cumulative REAL," >> /tmp/commands.sql + echo " samples INTEGER" >> /tmp/commands.sql + echo ");" >> /tmp/commands.sql + + echo "create table $SQL_HILOW_TABLE_WINDCHILL (" >> /tmp/commands.sql + echo " dateTime INTEGER NOT NULL PRIMARY KEY," >> /tmp/commands.sql + echo " low REAL," >> /tmp/commands.sql + echo " timeLow INTEGER," >> /tmp/commands.sql + echo " high REAL," >> /tmp/commands.sql + echo " timeHigh INTEGER," >> /tmp/commands.sql + echo " whenHigh REAL," >> /tmp/commands.sql + echo " cumulative REAL," >> /tmp/commands.sql + echo " samples INTEGER" >> /tmp/commands.sql + echo ");" >> /tmp/commands.sql + + echo "create table $SQL_HILOW_TABLE_HEATINDEX (" >> /tmp/commands.sql + echo " dateTime INTEGER NOT NULL PRIMARY KEY," >> /tmp/commands.sql + echo " low REAL," >> /tmp/commands.sql + echo " timeLow INTEGER," >> /tmp/commands.sql + echo " high REAL," >> /tmp/commands.sql + echo " timeHigh INTEGER," >> /tmp/commands.sql + echo " whenHigh REAL," >> /tmp/commands.sql + echo " cumulative REAL," >> /tmp/commands.sql + echo " samples INTEGER" >> /tmp/commands.sql + echo ");" >> /tmp/commands.sql + + echo "create table $SQL_HILOW_TABLE_ET (" >> /tmp/commands.sql + echo " dateTime INTEGER NOT NULL PRIMARY KEY," >> /tmp/commands.sql + echo " low REAL," >> /tmp/commands.sql + echo " timeLow INTEGER," >> /tmp/commands.sql + echo " high REAL," >> /tmp/commands.sql + echo " timeHigh INTEGER," >> /tmp/commands.sql + echo " whenHigh REAL," >> /tmp/commands.sql + echo " cumulative REAL," >> /tmp/commands.sql + echo " samples INTEGER" >> /tmp/commands.sql + echo ");" >> /tmp/commands.sql + + echo "create table $SQL_HILOW_TABLE_UV (" >> /tmp/commands.sql + echo " dateTime INTEGER NOT NULL PRIMARY KEY," >> /tmp/commands.sql + echo " low REAL," >> /tmp/commands.sql + echo " timeLow INTEGER," >> /tmp/commands.sql + echo " high REAL," >> /tmp/commands.sql + echo " timeHigh INTEGER," >> /tmp/commands.sql + echo " whenHigh REAL," >> /tmp/commands.sql + echo " cumulative REAL," >> /tmp/commands.sql + echo " samples INTEGER" >> /tmp/commands.sql + echo ");" >> /tmp/commands.sql + + echo "create table $SQL_HILOW_TABLE_SOLARRAD (" >> /tmp/commands.sql + echo " dateTime INTEGER NOT NULL PRIMARY KEY," >> /tmp/commands.sql + echo " low REAL," >> /tmp/commands.sql + echo " timeLow INTEGER," >> /tmp/commands.sql + echo " high REAL," >> /tmp/commands.sql + echo " timeHigh INTEGER," >> /tmp/commands.sql + echo " whenHigh REAL," >> /tmp/commands.sql + echo " cumulative REAL," >> /tmp/commands.sql + echo " samples INTEGER" >> /tmp/commands.sql + echo ");" >> /tmp/commands.sql + + echo "create table $SQL_HILOW_TABLE_HAIL (" >> /tmp/commands.sql + echo " dateTime INTEGER NOT NULL PRIMARY KEY," >> /tmp/commands.sql + echo " low REAL," >> /tmp/commands.sql + echo " timeLow INTEGER," >> /tmp/commands.sql + echo " high REAL," >> /tmp/commands.sql + echo " timeHigh INTEGER," >> /tmp/commands.sql + echo " whenHigh REAL," >> /tmp/commands.sql + echo " cumulative REAL," >> /tmp/commands.sql + echo " samples INTEGER" >> /tmp/commands.sql + echo ");" >> /tmp/commands.sql + + echo "create table $SQL_HILOW_TABLE_HAILRATE (" >> /tmp/commands.sql + echo " dateTime INTEGER NOT NULL PRIMARY KEY," >> /tmp/commands.sql + echo " low REAL," >> /tmp/commands.sql + echo " timeLow INTEGER," >> /tmp/commands.sql + echo " high REAL," >> /tmp/commands.sql + echo " timeHigh INTEGER," >> /tmp/commands.sql + echo " whenHigh REAL," >> /tmp/commands.sql + echo " cumulative REAL," >> /tmp/commands.sql + echo " samples INTEGER" >> /tmp/commands.sql + echo ");" >> /tmp/commands.sql + + echo "create table $SQL_HILOW_TABLE_WINDDIR (" >> /tmp/commands.sql + echo " dateTime INTEGER NOT NULL PRIMARY KEY," >> /tmp/commands.sql + echo " bin0 INTEGER," >> /tmp/commands.sql + echo " bin1 INTEGER," >> /tmp/commands.sql + echo " bin2 INTEGER," >> /tmp/commands.sql + echo " bin3 INTEGER," >> /tmp/commands.sql + echo " bin4 INTEGER," >> /tmp/commands.sql + echo " bin5 INTEGER," >> /tmp/commands.sql + echo " bin6 INTEGER," >> /tmp/commands.sql + echo " bin7 INTEGER," >> /tmp/commands.sql + echo " bin8 INTEGER," >> /tmp/commands.sql + echo " bin9 INTEGER," >> /tmp/commands.sql + echo " bin10 INTEGER," >> /tmp/commands.sql + echo " bin11 INTEGER," >> /tmp/commands.sql + echo " bin12 INTEGER," >> /tmp/commands.sql + echo " bin13 INTEGER," >> /tmp/commands.sql + echo " bin14 INTEGER," >> /tmp/commands.sql + echo " bin15 INTEGER," >> /tmp/commands.sql + echo " bin16 INTEGER," >> /tmp/commands.sql + echo " bin17 INTEGER," >> /tmp/commands.sql + echo " bin18 INTEGER," >> /tmp/commands.sql + echo " bin19 INTEGER" >> /tmp/commands.sql + echo ");" >> /tmp/commands.sql + + echo "create table $SQL_NOAA_TABLE (" >> /tmp/commands.sql + echo " dateTime INTEGER NOT NULL PRIMARY KEY," >> /tmp/commands.sql + echo " meanTemp REAL," >> /tmp/commands.sql + echo " highTemp REAL," >> /tmp/commands.sql + echo " highTempTime INTEGER," >> /tmp/commands.sql + echo " lowTemp REAL," >> /tmp/commands.sql + echo " lowTempTime INTEGER," >> /tmp/commands.sql + echo " heatDegDays REAL," >> /tmp/commands.sql + echo " coolDegDays REAL," >> /tmp/commands.sql + echo " rain REAL," >> /tmp/commands.sql + echo " avgWind REAL," >> /tmp/commands.sql + echo " highWind REAL," >> /tmp/commands.sql + echo " highWindTime INTEGER," >> /tmp/commands.sql + echo " domWindDir INTEGER" >> /tmp/commands.sql + echo ");" >> /tmp/commands.sql + + mysql --user=$MYSQL_ADMIN_USERNAME --password=$MYSQL_ADMIN_PASSWORD < /tmp/commands.sql > /tmp/mysql_create.log + rm -rf /tmp/commands.sql +} + +################################################################################ +################## S C R I P T E X E C U T I O N S T A R T ################# +################################################################################ + +mysql_create + +## Done! +exit 0 + diff --git a/dbexport/wview-mysql-export.sh b/dbexport/wview-mysql-export.sh new file mode 100644 index 0000000..b59da8f --- /dev/null +++ b/dbexport/wview-mysql-export.sh @@ -0,0 +1,377 @@ +################################################################################ +# +# File: wview-mysql-export.sh +# +# Description: Provide a script to export SQLite3 wview data to a MySQL +# database. Should be run periodically via cron. +# +# Usage: wview-mysql-export +# Arguments: +# debug - indicate to log verbosely and to +# create the MySQL debug log file +# $prefix/etc/wview/export/mysql_import.log +# create - create MySQL user, database and tables based +# on config values in +# $prefix/etc/wview/wview-conf.sdb, then exit +# (should not be given for cron execution) +# +# History: +# Engineer Date Ver Comments +# MS Teel 03/28/09 1 Original +# +# Notes: The marker file approach is inspired by Jerry Fiddler. +# Requires: sqlite3 CLI utility, mysqlimport CLI utility and +# mysql CLI utility for database creation. +# +################################################################################ + +################################################################################ +################################# M A C R O S ################################ +################################################################################ + +## DO NOT CHANGE ANY DEFS IN THIS BLOCK! + +export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin + +SECONDS_IN_DAY=86400 +SECONDS_IN_HOUR=3600 + +ARCHIVE_DB_FILE=$WVIEW_DATA_DIR/archive/wview-archive.sdb +HILOW_DB_FILE=$WVIEW_DATA_DIR/archive/wview-hilow.sdb +NOAA_DB_FILE=$WVIEW_DATA_DIR/archive/wview-noaa.sdb + +## The defaults match the SQLite table names: +SQL_ARCHIVE_TABLE=archive +SQL_HILOW_TABLE_INTEMP=inTemp +SQL_HILOW_TABLE_OUTTEMP=outTemp +SQL_HILOW_TABLE_INHUMIDITY=inHumidity +SQL_HILOW_TABLE_OUTHUMIDITY=outHumidity +SQL_HILOW_TABLE_BP=baromPressure +SQL_HILOW_TABLE_WINDSPEED=windSpeed +SQL_HILOW_TABLE_WINDGUST=windGust +SQL_HILOW_TABLE_DEWPOINT=dewPoint +SQL_HILOW_TABLE_RAIN=rain +SQL_HILOW_TABLE_RAINRATE=rainRate +SQL_HILOW_TABLE_WINDCHILL=windChill +SQL_HILOW_TABLE_HEATINDEX=heatIndex +SQL_HILOW_TABLE_ET=ET +SQL_HILOW_TABLE_UV=UV +SQL_HILOW_TABLE_SOLARRAD=solarRadiation +SQL_HILOW_TABLE_HAIL=hail +SQL_HILOW_TABLE_HAILRATE=hailRate +SQL_HILOW_TABLE_WINDDIR=windDir +SQL_NOAA_TABLE=noaaHistory + +TIME_MARKER_FILE=$WVIEW_CONF_DIR/export/mysql_export_marker +ARCHIVE_MARKER_FILE=$WVIEW_CONF_DIR/export/archive_marker +HILOW_MARKER_FILE=$WVIEW_CONF_DIR/export/hilow_marker +NOAA_MARKER_FILE=$WVIEW_CONF_DIR/export/noaa_marker +ARCHIVE_EXPORT_FILE=$WVIEW_CONF_DIR/export/$SQL_ARCHIVE_TABLE.csv +HILOW_EXPORT_FILE_INTEMP=$WVIEW_CONF_DIR/export/$SQL_HILOW_TABLE_INTEMP.csv +HILOW_EXPORT_FILE_OUTTEMP=$WVIEW_CONF_DIR/export/$SQL_HILOW_TABLE_OUTTEMP.csv +HILOW_EXPORT_FILE_INHUMIDITY=$WVIEW_CONF_DIR/export/$SQL_HILOW_TABLE_INHUMIDITY.csv +HILOW_EXPORT_FILE_OUTHUMIDITY=$WVIEW_CONF_DIR/export/$SQL_HILOW_TABLE_OUTHUMIDITY.csv +HILOW_EXPORT_FILE_BP=$WVIEW_CONF_DIR/export/$SQL_HILOW_TABLE_BP.csv +HILOW_EXPORT_FILE_WINDSPEED=$WVIEW_CONF_DIR/export/$SQL_HILOW_TABLE_WINDSPEED.csv +HILOW_EXPORT_FILE_WINDGUST=$WVIEW_CONF_DIR/export/$SQL_HILOW_TABLE_WINDGUST.csv +HILOW_EXPORT_FILE_DEWPOINT=$WVIEW_CONF_DIR/export/$SQL_HILOW_TABLE_DEWPOINT.csv +HILOW_EXPORT_FILE_RAIN=$WVIEW_CONF_DIR/export/$SQL_HILOW_TABLE_RAIN.csv +HILOW_EXPORT_FILE_RAINRATE=$WVIEW_CONF_DIR/export/$SQL_HILOW_TABLE_RAINRATE.csv +HILOW_EXPORT_FILE_WINDCHILL=$WVIEW_CONF_DIR/export/$SQL_HILOW_TABLE_WINDCHILL.csv +HILOW_EXPORT_FILE_HEATINDEX=$WVIEW_CONF_DIR/export/$SQL_HILOW_TABLE_HEATINDEX.csv +HILOW_EXPORT_FILE_ET=$WVIEW_CONF_DIR/export/$SQL_HILOW_TABLE_ET.csv +HILOW_EXPORT_FILE_UV=$WVIEW_CONF_DIR/export/$SQL_HILOW_TABLE_UV.csv +HILOW_EXPORT_FILE_SOLARRAD=$WVIEW_CONF_DIR/export/$SQL_HILOW_TABLE_SOLARRAD.csv +HILOW_EXPORT_FILE_HAIL=$WVIEW_CONF_DIR/export/$SQL_HILOW_TABLE_HAIL.csv +HILOW_EXPORT_FILE_HAILRATE=$WVIEW_CONF_DIR/export/$SQL_HILOW_TABLE_HAILRATE.csv +HILOW_EXPORT_FILE_WINDDIR=$WVIEW_CONF_DIR/export/$SQL_HILOW_TABLE_WINDDIR.csv +NOAA_EXPORT_FILE=$WVIEW_CONF_DIR/export/$SQL_NOAA_TABLE.csv + +ARCHIVE_ENABLE=1 +HILOW_ENABLE=1 +NOAA_ENABLE=1 +UPDATE_MARKER=0 + +MYSQL_IMPORT_LOG_FILE=$WVIEW_CONF_DIR/export/mysql_import.log + +## Test command line arguments: +DO_CREATION=0 +if [ "$1" = "help" ]; then + echo "USAGE:" + echo "wview-mysql-export [help | debug | create]" + echo " - (default behavior) export SQLite3 data to MySQL, typically executed as a cron job" + echo "" + echo " help - print out usage directions and exit" + echo "" + echo " debug - log verbosely and create the MySQL debug log file $WVIEW_CONF_DIR/export/mysql_import.log" + echo "" + echo " create - create local MySQL user, database and tables based on config values in" + echo " $WVIEW_CONF_DIR/wview-conf.sdb, then exit (no exports done);" + echo " (should not be given for cron execution)" + echo "" + echo "" + echo "REQUIRES: sqlite3 CLI utility, mysqlimport CLI utility and mysql CLI utility for database creation." + echo "" + exit 0 +fi + +if [ "$1" = "debug" ]; then + VERBOSE=1 +else + VERBOSE=0 +fi + +if [ "$1" = "create" ]; then + if [ x"$2" = x ]; then + echo "You must specify the MySQL root user password to create local export database - use 'wview-mysql-export help' for details" + exit 1 + else + MYSQL_ROOT_PASSWORD=$2 + DO_CREATION=1 + fi +fi + +################################################################################ +####################### D E F I N E F U N C T I O N S ####################### +################################################################################ +syslog() +{ + if [ $VERBOSE != 0 ]; then + logger -t WV_EXPORT $1 $2 + fi +} + +syslog_error() +{ + logger -t WV_EXPORT_ERROR $1 $2 +} + +# $1 = DB file (full path) +# $2 = table name +# $3 = dateTime stamp +# $4 = CSV output file +sqlite3_export() +{ + echo ".mode list" > /tmp/commands.sql + echo ".separator ," >> /tmp/commands.sql + echo ".nullvalue '\N'" >> /tmp/commands.sql + echo ".timeout 30000" >> /tmp/commands.sql + echo ".output $4" >> /tmp/commands.sql + echo "select * from $2 where dateTime > '$3' order by dateTime ASC;" >> /tmp/commands.sql + echo ".exit" >> /tmp/commands.sql + + echo ".read /tmp/commands.sql" | sqlite3 $1 + + rm -rf /tmp/commands.sql +} + +# No function args +get_sql_server_config() +{ + echo "select value from config where name='STATION_SQLDB_HOST';" > /tmp/commands.sql + echo ".exit" >> /tmp/commands.sql + echo ".read /tmp/commands.sql" | sqlite3 $WVIEW_CONF_DIR/wview-conf.sdb > /tmp/result.txt + SQL_HOST=`cat /tmp/result.txt` + rm -rf /tmp/commands.sql /tmp/result.txt + + echo "select value from config where name='STATION_SQLDB_USERNAME';" > /tmp/commands.sql + echo ".exit" >> /tmp/commands.sql + echo ".read /tmp/commands.sql" | sqlite3 $WVIEW_CONF_DIR/wview-conf.sdb > /tmp/result.txt + SQL_USERNAME=`cat /tmp/result.txt` + rm -rf /tmp/commands.sql /tmp/result.txt + + echo "select value from config where name='STATION_SQLDB_PASSWORD';" > /tmp/commands.sql + echo ".exit" >> /tmp/commands.sql + echo ".read /tmp/commands.sql" | sqlite3 $WVIEW_CONF_DIR/wview-conf.sdb > /tmp/result.txt + SQL_PASSWORD=`cat /tmp/result.txt` + rm -rf /tmp/commands.sql /tmp/result.txt + + echo "select value from config where name='STATION_SQLDB_DB_NAME';" > /tmp/commands.sql + echo ".exit" >> /tmp/commands.sql + echo ".read /tmp/commands.sql" | sqlite3 $WVIEW_CONF_DIR/wview-conf.sdb > /tmp/result.txt + SQL_DB_NAME=`cat /tmp/result.txt` + rm -rf /tmp/commands.sql /tmp/result.txt +} + +# $1 = import filename +mysql_import() +{ + if [ "$VERBOSE" = "1" ]; then + date >> $MYSQL_IMPORT_LOG_FILE; + mysqlimport --local --compress --replace --fields-terminated-by=',' \ + --host=$SQL_HOST --user=$SQL_USERNAME --password=$SQL_PASSWORD \ + $SQL_DB_NAME $1 >> $MYSQL_IMPORT_LOG_FILE; + else + mysqlimport --local --compress --replace --fields-terminated-by=',' \ + --host=$SQL_HOST --user=$SQL_USERNAME --password=$SQL_PASSWORD \ + $SQL_DB_NAME $1 > /dev/null; + fi +} + +################################################################################ +################## S C R I P T E X E C U T I O N S T A R T ################# +################################################################################ + +## Are we creating the export database? +if [ "$DO_CREATION" = "1" ]; then + get_sql_server_config + if [ x"$SQL_USERNAME" = x ]; then + syslog_error "Failed to retrieve MySQL export parameters from $WVIEW_CONF_DIR/wview-conf.sdb" + exit 1 + else + syslog "Creating with: $SQL_USERNAME::$SQL_DB_NAME" + fi + + wview-mysql-create root $MYSQL_ROOT_PASSWORD $SQL_USERNAME $SQL_PASSWORD $SQL_DB_NAME + exit 0; +fi + + +UPDATE_TIME_SAVE=`date +%s` + +## Check to see if there is new data to export: +if [ ! -e $TIME_MARKER_FILE ]; then + LAST_UPDATE=0 + syslog "First time to export databases" +else + LAST_UPDATE=`cat $TIME_MARKER_FILE` + syslog "Exporting databases after: " $LAST_UPDATE +fi + +if [ -e $ARCHIVE_MARKER_FILE ]; then + if [ $LAST_UPDATE -ge `cat $ARCHIVE_MARKER_FILE` ]; then + ARCHIVE_ENABLE=0 + fi +else + syslog $ARCHIVE_MARKER_FILE " not found" +fi +if [ -e $HILOW_MARKER_FILE ]; then + if [ $LAST_UPDATE -ge `cat $HILOW_MARKER_FILE` ]; then + HILOW_ENABLE=0 + fi +else + syslog $HILOW_MARKER_FILE " not found" +fi +if [ -e $NOAA_MARKER_FILE ]; then + if [ $LAST_UPDATE -ge `cat $NOAA_MARKER_FILE` ]; then + NOAA_ENABLE=0 + fi +else + syslog $NOAA_MARKER_FILE " not found" +fi + + +## Export from the wview SQLite3 databases: +if [ "$ARCHIVE_ENABLE" = "1" ]; then + UPDATE_MARKER=1 + syslog "Exporting to: " $ARCHIVE_EXPORT_FILE + rm -rf $ARCHIVE_EXPORT_FILE + sqlite3_export $ARCHIVE_DB_FILE $SQL_ARCHIVE_TABLE $LAST_UPDATE $ARCHIVE_EXPORT_FILE +fi + +SAVE_UPDATE_TIME=$LAST_UPDATE +LAST_UPDATE=`expr $LAST_UPDATE - $SECONDS_IN_HOUR` + +if [ "$HILOW_ENABLE" = "1" ]; then + UPDATE_MARKER=1 + syslog "Exporting HILOW tables:" + rm -rf $HILOW_EXPORT_FILE_INTEMP + sqlite3_export $HILOW_DB_FILE $SQL_HILOW_TABLE_INTEMP $LAST_UPDATE $HILOW_EXPORT_FILE_INTEMP + rm -rf $HILOW_EXPORT_FILE_OUTTEMP + sqlite3_export $HILOW_DB_FILE $SQL_HILOW_TABLE_OUTTEMP $LAST_UPDATE $HILOW_EXPORT_FILE_OUTTEMP + rm -rf $HILOW_EXPORT_FILE_INHUMIDITY + sqlite3_export $HILOW_DB_FILE $SQL_HILOW_TABLE_INHUMIDITY $LAST_UPDATE $HILOW_EXPORT_FILE_INHUMIDITY + rm -rf $HILOW_EXPORT_FILE_OUTHUMIDITY + sqlite3_export $HILOW_DB_FILE $SQL_HILOW_TABLE_OUTHUMIDITY $LAST_UPDATE $HILOW_EXPORT_FILE_OUTHUMIDITY + rm -rf $HILOW_EXPORT_FILE_BP + sqlite3_export $HILOW_DB_FILE $SQL_HILOW_TABLE_BP $LAST_UPDATE $HILOW_EXPORT_FILE_BP + rm -rf $HILOW_EXPORT_FILE_WINDSPEED + sqlite3_export $HILOW_DB_FILE $SQL_HILOW_TABLE_WINDSPEED $LAST_UPDATE $HILOW_EXPORT_FILE_WINDSPEED + rm -rf $HILOW_EXPORT_FILE_WINDGUST + sqlite3_export $HILOW_DB_FILE $SQL_HILOW_TABLE_WINDGUST $LAST_UPDATE $HILOW_EXPORT_FILE_WINDGUST + rm -rf $HILOW_EXPORT_FILE_DEWPOINT + sqlite3_export $HILOW_DB_FILE $SQL_HILOW_TABLE_DEWPOINT $LAST_UPDATE $HILOW_EXPORT_FILE_DEWPOINT + rm -rf $HILOW_EXPORT_FILE_RAIN + sqlite3_export $HILOW_DB_FILE $SQL_HILOW_TABLE_RAIN $LAST_UPDATE $HILOW_EXPORT_FILE_RAIN + rm -rf $HILOW_EXPORT_FILE_RAINRATE + sqlite3_export $HILOW_DB_FILE $SQL_HILOW_TABLE_RAINRATE $LAST_UPDATE $HILOW_EXPORT_FILE_RAINRATE + rm -rf $HILOW_EXPORT_FILE_WINDCHILL + sqlite3_export $HILOW_DB_FILE $SQL_HILOW_TABLE_WINDCHILL $LAST_UPDATE $HILOW_EXPORT_FILE_WINDCHILL + rm -rf $HILOW_EXPORT_FILE_HEATINDEX + sqlite3_export $HILOW_DB_FILE $SQL_HILOW_TABLE_HEATINDEX $LAST_UPDATE $HILOW_EXPORT_FILE_HEATINDEX + rm -rf $HILOW_EXPORT_FILE_ET + sqlite3_export $HILOW_DB_FILE $SQL_HILOW_TABLE_ET $LAST_UPDATE $HILOW_EXPORT_FILE_ET + rm -rf $HILOW_EXPORT_FILE_UV + sqlite3_export $HILOW_DB_FILE $SQL_HILOW_TABLE_UV $LAST_UPDATE $HILOW_EXPORT_FILE_UV + rm -rf $HILOW_EXPORT_FILE_SOLARRAD + sqlite3_export $HILOW_DB_FILE $SQL_HILOW_TABLE_SOLARRAD $LAST_UPDATE $HILOW_EXPORT_FILE_SOLARRAD + rm -rf $HILOW_EXPORT_FILE_HAIL + sqlite3_export $HILOW_DB_FILE $SQL_HILOW_TABLE_HAIL $LAST_UPDATE $HILOW_EXPORT_FILE_HAIL + rm -rf $HILOW_EXPORT_FILE_HAILRATE + sqlite3_export $HILOW_DB_FILE $SQL_HILOW_TABLE_HAILRATE $LAST_UPDATE $HILOW_EXPORT_FILE_HAILRATE + rm -rf $HILOW_EXPORT_FILE_WINDDIR + sqlite3_export $HILOW_DB_FILE $SQL_HILOW_TABLE_WINDDIR $LAST_UPDATE $HILOW_EXPORT_FILE_WINDDIR +fi + +LAST_UPDATE=`expr $SAVE_UPDATE_TIME - $SECONDS_IN_DAY` + +if [ "$NOAA_ENABLE" = "1" ]; then + UPDATE_MARKER=1 + syslog "Exporting to: " $NOAA_EXPORT_FILE + rm -rf $NOAA_EXPORT_FILE + sqlite3_export $NOAA_DB_FILE $SQL_NOAA_TABLE $LAST_UPDATE $NOAA_EXPORT_FILE +fi + + +## Get MySQL server configuration: +get_sql_server_config +if [ x"$SQL_USERNAME" = x ]; then + syslog_error "Failed to retrieve MySQL export parameters from $WVIEW_CONF_DIR/wview-conf.sdb" + exit 1 +else + syslog "Exporting with: $SQL_HOST:$SQL_USERNAME::$SQL_DB_NAME" +fi + + +## Import to MySQL: +if [ "$ARCHIVE_ENABLE" = "1" ]; then + syslog "Importing: " $ARCHIVE_EXPORT_FILE + mysql_import $ARCHIVE_EXPORT_FILE +fi + +if [ "$HILOW_ENABLE" = "1" ]; then + syslog "Importing HILOW tables:" + mysql_import $HILOW_EXPORT_FILE_INTEMP + mysql_import $HILOW_EXPORT_FILE_OUTTEMP + mysql_import $HILOW_EXPORT_FILE_INHUMIDITY + mysql_import $HILOW_EXPORT_FILE_OUTHUMIDITY + mysql_import $HILOW_EXPORT_FILE_BP + mysql_import $HILOW_EXPORT_FILE_WINDSPEED + mysql_import $HILOW_EXPORT_FILE_WINDGUST + mysql_import $HILOW_EXPORT_FILE_DEWPOINT + mysql_import $HILOW_EXPORT_FILE_RAIN + mysql_import $HILOW_EXPORT_FILE_RAINRATE + mysql_import $HILOW_EXPORT_FILE_WINDCHILL + mysql_import $HILOW_EXPORT_FILE_HEATINDEX + mysql_import $HILOW_EXPORT_FILE_ET + mysql_import $HILOW_EXPORT_FILE_UV + mysql_import $HILOW_EXPORT_FILE_SOLARRAD + mysql_import $HILOW_EXPORT_FILE_HAIL + mysql_import $HILOW_EXPORT_FILE_HAILRATE + mysql_import $HILOW_EXPORT_FILE_WINDDIR +fi + +if [ "$NOAA_ENABLE" = "1" ]; then + syslog "Importing: " $NOAA_EXPORT_FILE + mysql_import $NOAA_EXPORT_FILE +fi + + +## Save the current time marker: +if [ "$UPDATE_MARKER" = "1" ]; then + echo $UPDATE_TIME_SAVE > $TIME_MARKER_FILE +fi + +## Done! +exit 0 + diff --git a/dbexport/wview-pgsql-create.sh b/dbexport/wview-pgsql-create.sh new file mode 100644 index 0000000..4b2153d --- /dev/null +++ b/dbexport/wview-pgsql-create.sh @@ -0,0 +1,429 @@ +################################################################################ +# +# File: wview-pgsql-create.sh +# +# Description: Provide a script to create the wview database for PostgreSQL. +# +# Usage: wview-pgsql-create +# Arguments: +# db_user PostgreSQL server user with create and grant +# privileges. +# db_name PostgreSQL database name to create and grant on. +# +# History: +# Engineer Date Ver Comments +# MS Teel 03/28/09 1 Original +# +################################################################################ + +show_usage() +{ + echo "USAGE:" + echo "wview-pgsql-create " + echo "" + echo " db_name - PostgreSQL database name to create and grant privileges for." + echo "" + echo "REQUIRES: psql CLI utility for database creation." + echo "MUST: be executed on the PostgreSQL server." + echo "" +} + +################################################################################ +################################# M A C R O S ################################ +################################################################################ + +## DO NOT CHANGE ANY DEFS IN THIS BLOCK! + +export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin + +## The defaults match the SQLite table names: +SQL_ARCHIVE_TABLE=archive +SQL_HILOW_TABLE_INTEMP=inTemp +SQL_HILOW_TABLE_OUTTEMP=outTemp +SQL_HILOW_TABLE_INHUMIDITY=inHumidity +SQL_HILOW_TABLE_OUTHUMIDITY=outHumidity +SQL_HILOW_TABLE_BP=baromPressure +SQL_HILOW_TABLE_WINDSPEED=windSpeed +SQL_HILOW_TABLE_WINDGUST=windGust +SQL_HILOW_TABLE_DEWPOINT=dewPoint +SQL_HILOW_TABLE_RAIN=rain +SQL_HILOW_TABLE_RAINRATE=rainRate +SQL_HILOW_TABLE_WINDCHILL=windChill +SQL_HILOW_TABLE_HEATINDEX=heatIndex +SQL_HILOW_TABLE_ET=ET +SQL_HILOW_TABLE_UV=UV +SQL_HILOW_TABLE_SOLARRAD=solarRadiation +SQL_HILOW_TABLE_HAIL=hail +SQL_HILOW_TABLE_HAILRATE=hailRate +SQL_HILOW_TABLE_WINDDIR=windDir +SQL_NOAA_TABLE=noaaHistory + +## Test command line arguments: +if [ "$1" = "help" ]; then + show_usage + exit 0 +fi + +## Check arguments: +if [ x"$1" = x ]; then + echo "You must specify the PostgreSQL database name to create export database - use 'wview-pgsql-create help' for details" + show_usage + exit 1 +else + PGSQL_USERNAME=root + PGSQL_DBNAME=$1 +fi + +################################################################################ +####################### D E F I N E F U N C T I O N S ####################### +################################################################################ +syslog() +{ + logger -t WV_EXPORT $1 $2 +} + +syslog_error() +{ + logger -t WV_EXPORT_ERROR $1 $2 +} + +mysql_create() +{ +# create the plpgsql language; + createlang plpgsql wviewDB + + echo "create table $SQL_ARCHIVE_TABLE (" > /tmp/commands.sql + echo " dateTime INTEGER NOT NULL PRIMARY KEY," >> /tmp/commands.sql + echo " usUnits INTEGER NOT NULL," >> /tmp/commands.sql + echo " arcInt INTEGER NOT NULL," >> /tmp/commands.sql + echo " barometer REAL," >> /tmp/commands.sql + echo " pressure REAL," >> /tmp/commands.sql + echo " altimeter REAL," >> /tmp/commands.sql + echo " inTemp REAL," >> /tmp/commands.sql + echo " outTemp REAL," >> /tmp/commands.sql + echo " inHumidity REAL," >> /tmp/commands.sql + echo " outHumidity REAL," >> /tmp/commands.sql + echo " windSpeed REAL," >> /tmp/commands.sql + echo " windDir REAL," >> /tmp/commands.sql + echo " windGust REAL," >> /tmp/commands.sql + echo " windGustDir REAL," >> /tmp/commands.sql + echo " rainRate REAL," >> /tmp/commands.sql + echo " rain REAL," >> /tmp/commands.sql + echo " dewpoint REAL," >> /tmp/commands.sql + echo " windchill REAL," >> /tmp/commands.sql + echo " heatindex REAL," >> /tmp/commands.sql + echo " ET REAL," >> /tmp/commands.sql + echo " radiation REAL," >> /tmp/commands.sql + echo " UV REAL," >> /tmp/commands.sql + echo " extraTemp1 REAL," >> /tmp/commands.sql + echo " extraTemp2 REAL," >> /tmp/commands.sql + echo " extraTemp3 REAL," >> /tmp/commands.sql + echo " soilTemp1 REAL," >> /tmp/commands.sql + echo " soilTemp2 REAL," >> /tmp/commands.sql + echo " soilTemp3 REAL," >> /tmp/commands.sql + echo " soilTemp4 REAL," >> /tmp/commands.sql + echo " leafTemp1 REAL," >> /tmp/commands.sql + echo " leafTemp2 REAL," >> /tmp/commands.sql + echo " extraHumid1 REAL," >> /tmp/commands.sql + echo " extraHumid2 REAL," >> /tmp/commands.sql + echo " soilMoist1 REAL," >> /tmp/commands.sql + echo " soilMoist2 REAL," >> /tmp/commands.sql + echo " soilMoist3 REAL," >> /tmp/commands.sql + echo " soilMoist4 REAL," >> /tmp/commands.sql + echo " leafWet1 REAL," >> /tmp/commands.sql + echo " leafWet2 REAL," >> /tmp/commands.sql + echo " rxCheckPercent REAL," >> /tmp/commands.sql + echo " txBatteryStatus REAL," >> /tmp/commands.sql + echo " consBatteryVoltage REAL," >> /tmp/commands.sql + echo " hail REAL," >> /tmp/commands.sql + echo " hailRate REAL," >> /tmp/commands.sql + echo " heatingTemp REAL," >> /tmp/commands.sql + echo " heatingVoltage REAL," >> /tmp/commands.sql + echo " supplyVoltage REAL," >> /tmp/commands.sql + echo " referenceVoltage REAL," >> /tmp/commands.sql + echo " windBatteryStatus REAL," >> /tmp/commands.sql + echo " rainBatteryStatus REAL," >> /tmp/commands.sql + echo " outTempBatteryStatus REAL," >> /tmp/commands.sql + echo " inTempBatteryStatus REAL" >> /tmp/commands.sql + echo ");" >> /tmp/commands.sql + + echo "CREATE OR REPLACE FUNCTION repl_archive() RETURNS trigger AS \$BODY\$ BEGIN IF (EXISTS(SELECT 1 FROM $SQL_ARCHIVE_TABLE C WHERE C.dateTime = NEW.dateTime)) THEN UPDATE $SQL_ARCHIVE_TABLE SET usUnits = NEW.usUnits, arcInt = NEW.arcInt, barometer = NEW.barometer, pressure = NEW.pressure, altimeter = NEW.altimeter, inTemp = NEW.inTemp, outTemp = NEW.outTemp, inHumidity = NEW.inHumidity, outHumidity = NEW.outHumidity, windSpeed = NEW.windSpeed, windDir = NEW.windDir, windGust = NEW.windGust, windGustDir = NEW.windGustDir, rainRate = NEW.rainRate, rain = NEW.rain, dewpoint = NEW.dewpoint, windchill = NEW.windchill, heatindex = NEW.heatindex, ET = NEW.ET, radiation = NEW.radiation, UV = NEW.UV, extraTemp1 = NEW.extraTemp1, extraTemp2 = NEW.extraTemp2, extraTemp3 = NEW.extraTemp3, soilTemp1 = NEW.soilTemp1, soilTemp2 = NEW.soilTemp2, soilTemp3 = NEW.soilTemp3, soilTemp4 = NEW.soilTemp4, leafTemp1 = NEW.leafTemp1, leafTemp2 = NEW.leafTemp2, extraHumid1 = NEW.extraHumid1, extraHumid2 = NEW.extraHumid2, soilMoist1 = NEW.soilMoist1, soilMoist2 = NEW.soilMoist2, soilMoist3 = NEW.soilMoist3, soilMoist4 = NEW.soilMoist4, leafWet1 = NEW.leafWet1, leafWet2 = NEW.leafWet2, rxCheckPercent = NEW.rxCheckPercent, txBatteryStatus = NEW.txBatteryStatus, consBatteryVoltage = NEW.consBatteryVoltage, hail = NEW.hail, hailRate = NEW.hailRate, heatingTemp = NEW.heatingTemp, heatingVoltage = NEW.heatingVoltage, supplyVoltage = NEW.supplyVoltage, referenceVoltage = NEW.referenceVoltage, windBatteryStatus = NEW.windBatteryStatus, rainBatteryStatus = NEW.rainBatteryStatus, outTempBatteryStatus = NEW.outTempBatteryStatus, inTempBatteryStatus = NEW.inTempBatteryStatus WHERE dateTime = NEW.dateTime; RETURN OLD; ELSE RETURN NEW; END IF; END; \$BODY\$ LANGUAGE plpgsql VOLATILE COST 100; ALTER FUNCTION repl_archive() OWNER TO postgres; CREATE TRIGGER repl_archive BEFORE INSERT ON $SQL_ARCHIVE_TABLE FOR EACH ROW EXECUTE PROCEDURE repl_archive();" >> /tmp/commands.sql + + echo "create table $SQL_HILOW_TABLE_INTEMP (" >> /tmp/commands.sql + echo " dateTime INTEGER NOT NULL PRIMARY KEY," >> /tmp/commands.sql + echo " low REAL," >> /tmp/commands.sql + echo " timeLow INTEGER," >> /tmp/commands.sql + echo " high REAL," >> /tmp/commands.sql + echo " timeHigh INTEGER," >> /tmp/commands.sql + echo " whenHigh REAL," >> /tmp/commands.sql + echo " cumulative REAL," >> /tmp/commands.sql + echo " samples INTEGER" >> /tmp/commands.sql + echo ");" >> /tmp/commands.sql + + echo "CREATE OR REPLACE FUNCTION repl_intemp() RETURNS trigger AS \$BODY\$ BEGIN IF (EXISTS(SELECT 1 FROM $SQL_HILOW_TABLE_INTEMP C WHERE C.dateTime = NEW.dateTime)) THEN UPDATE $SQL_HILOW_TABLE_INTEMP SET low = NEW.low, timeLow = NEW.timeLow, high = NEW.high, timeHigh = NEW.timeHigh, whenHigh = NEW.whenHigh, cumulative = NEW.cumulative, samples = NEW.samples WHERE dateTime = NEW.dateTime; RETURN OLD; ELSE RETURN NEW; END IF; END; \$BODY\$ LANGUAGE plpgsql VOLATILE COST 100; ALTER FUNCTION repl_intemp() OWNER TO postgres; CREATE TRIGGER repl_intemp BEFORE INSERT ON $SQL_HILOW_TABLE_INTEMP FOR EACH ROW EXECUTE PROCEDURE repl_intemp();" >> /tmp/commands.sql + + echo "create table $SQL_HILOW_TABLE_OUTTEMP (" >> /tmp/commands.sql + echo " dateTime INTEGER NOT NULL PRIMARY KEY," >> /tmp/commands.sql + echo " low REAL," >> /tmp/commands.sql + echo " timeLow INTEGER," >> /tmp/commands.sql + echo " high REAL," >> /tmp/commands.sql + echo " timeHigh INTEGER," >> /tmp/commands.sql + echo " whenHigh REAL," >> /tmp/commands.sql + echo " cumulative REAL," >> /tmp/commands.sql + echo " samples INTEGER" >> /tmp/commands.sql + echo ");" >> /tmp/commands.sql + + echo "CREATE OR REPLACE FUNCTION repl_outtemp() RETURNS trigger AS \$BODY\$ BEGIN IF (EXISTS(SELECT 1 FROM $SQL_HILOW_TABLE_OUTTEMP C WHERE C.dateTime = NEW.dateTime)) THEN UPDATE $SQL_HILOW_TABLE_OUTTEMP SET low = NEW.low, timeLow = NEW.timeLow, high = NEW.high, timeHigh = NEW.timeHigh, whenHigh = NEW.whenHigh, cumulative = NEW.cumulative, samples = NEW.samples WHERE dateTime = NEW.dateTime; RETURN OLD; ELSE RETURN NEW; END IF; END; \$BODY\$ LANGUAGE plpgsql VOLATILE COST 100; ALTER FUNCTION repl_outtemp() OWNER TO postgres; CREATE TRIGGER repl_outtemp BEFORE INSERT ON $SQL_HILOW_TABLE_OUTTEMP FOR EACH ROW EXECUTE PROCEDURE repl_outtemp();" >> /tmp/commands.sql + + echo "create table $SQL_HILOW_TABLE_INHUMIDITY (" >> /tmp/commands.sql + echo " dateTime INTEGER NOT NULL PRIMARY KEY," >> /tmp/commands.sql + echo " low REAL," >> /tmp/commands.sql + echo " timeLow INTEGER," >> /tmp/commands.sql + echo " high REAL," >> /tmp/commands.sql + echo " timeHigh INTEGER," >> /tmp/commands.sql + echo " whenHigh REAL," >> /tmp/commands.sql + echo " cumulative REAL," >> /tmp/commands.sql + echo " samples INTEGER" >> /tmp/commands.sql + echo ");" >> /tmp/commands.sql + + echo "CREATE OR REPLACE FUNCTION repl_inhumid() RETURNS trigger AS \$BODY\$ BEGIN IF (EXISTS(SELECT 1 FROM $SQL_HILOW_TABLE_INHUMIDITY C WHERE C.dateTime = NEW.dateTime)) THEN UPDATE $SQL_HILOW_TABLE_INHUMIDITY SET low = NEW.low, timeLow = NEW.timeLow, high = NEW.high, timeHigh = NEW.timeHigh, whenHigh = NEW.whenHigh, cumulative = NEW.cumulative, samples = NEW.samples WHERE dateTime = NEW.dateTime; RETURN OLD; ELSE RETURN NEW; END IF; END; \$BODY\$ LANGUAGE plpgsql VOLATILE COST 100; ALTER FUNCTION repl_inhumid() OWNER TO postgres; CREATE TRIGGER repl_inhumid BEFORE INSERT ON $SQL_HILOW_TABLE_INHUMIDITY FOR EACH ROW EXECUTE PROCEDURE repl_inhumid();" >> /tmp/commands.sql + + echo "create table $SQL_HILOW_TABLE_OUTHUMIDITY (" >> /tmp/commands.sql + echo " dateTime INTEGER NOT NULL PRIMARY KEY," >> /tmp/commands.sql + echo " low REAL," >> /tmp/commands.sql + echo " timeLow INTEGER," >> /tmp/commands.sql + echo " high REAL," >> /tmp/commands.sql + echo " timeHigh INTEGER," >> /tmp/commands.sql + echo " whenHigh REAL," >> /tmp/commands.sql + echo " cumulative REAL," >> /tmp/commands.sql + echo " samples INTEGER" >> /tmp/commands.sql + echo ");" >> /tmp/commands.sql + + echo "CREATE OR REPLACE FUNCTION repl_outhumid() RETURNS trigger AS \$BODY\$ BEGIN IF (EXISTS(SELECT 1 FROM $SQL_HILOW_TABLE_OUTHUMIDITY C WHERE C.dateTime = NEW.dateTime)) THEN UPDATE $SQL_HILOW_TABLE_OUTHUMIDITY SET low = NEW.low, timeLow = NEW.timeLow, high = NEW.high, timeHigh = NEW.timeHigh, whenHigh = NEW.whenHigh, cumulative = NEW.cumulative, samples = NEW.samples WHERE dateTime = NEW.dateTime; RETURN OLD; ELSE RETURN NEW; END IF; END; \$BODY\$ LANGUAGE plpgsql VOLATILE COST 100; ALTER FUNCTION repl_outhumid() OWNER TO postgres; CREATE TRIGGER repl_outhumid BEFORE INSERT ON $SQL_HILOW_TABLE_OUTHUMIDITY FOR EACH ROW EXECUTE PROCEDURE repl_outhumid();" >> /tmp/commands.sql + + echo "create table $SQL_HILOW_TABLE_BP (" >> /tmp/commands.sql + echo " dateTime INTEGER NOT NULL PRIMARY KEY," >> /tmp/commands.sql + echo " low REAL," >> /tmp/commands.sql + echo " timeLow INTEGER," >> /tmp/commands.sql + echo " high REAL," >> /tmp/commands.sql + echo " timeHigh INTEGER," >> /tmp/commands.sql + echo " whenHigh REAL," >> /tmp/commands.sql + echo " cumulative REAL," >> /tmp/commands.sql + echo " samples INTEGER" >> /tmp/commands.sql + echo ");" >> /tmp/commands.sql + + echo "CREATE OR REPLACE FUNCTION repl_bp() RETURNS trigger AS \$BODY\$ BEGIN IF (EXISTS(SELECT 1 FROM $SQL_HILOW_TABLE_BP C WHERE C.dateTime = NEW.dateTime)) THEN UPDATE $SQL_HILOW_TABLE_BP SET low = NEW.low, timeLow = NEW.timeLow, high = NEW.high, timeHigh = NEW.timeHigh, whenHigh = NEW.whenHigh, cumulative = NEW.cumulative, samples = NEW.samples WHERE dateTime = NEW.dateTime; RETURN OLD; ELSE RETURN NEW; END IF; END; \$BODY\$ LANGUAGE plpgsql VOLATILE COST 100; ALTER FUNCTION repl_bp() OWNER TO postgres; CREATE TRIGGER repl_bp BEFORE INSERT ON $SQL_HILOW_TABLE_BP FOR EACH ROW EXECUTE PROCEDURE repl_bp();" >> /tmp/commands.sql + + echo "create table $SQL_HILOW_TABLE_WINDSPEED (" >> /tmp/commands.sql + echo " dateTime INTEGER NOT NULL PRIMARY KEY," >> /tmp/commands.sql + echo " low REAL," >> /tmp/commands.sql + echo " timeLow INTEGER," >> /tmp/commands.sql + echo " high REAL," >> /tmp/commands.sql + echo " timeHigh INTEGER," >> /tmp/commands.sql + echo " whenHigh REAL," >> /tmp/commands.sql + echo " cumulative REAL," >> /tmp/commands.sql + echo " samples INTEGER" >> /tmp/commands.sql + echo ");" >> /tmp/commands.sql + + echo "CREATE OR REPLACE FUNCTION repl_speed() RETURNS trigger AS \$BODY\$ BEGIN IF (EXISTS(SELECT 1 FROM $SQL_HILOW_TABLE_WINDSPEED C WHERE C.dateTime = NEW.dateTime)) THEN UPDATE $SQL_HILOW_TABLE_WINDSPEED SET low = NEW.low, timeLow = NEW.timeLow, high = NEW.high, timeHigh = NEW.timeHigh, whenHigh = NEW.whenHigh, cumulative = NEW.cumulative, samples = NEW.samples WHERE dateTime = NEW.dateTime; RETURN OLD; ELSE RETURN NEW; END IF; END; \$BODY\$ LANGUAGE plpgsql VOLATILE COST 100; ALTER FUNCTION repl_speed() OWNER TO postgres; CREATE TRIGGER repl_speed BEFORE INSERT ON $SQL_HILOW_TABLE_WINDSPEED FOR EACH ROW EXECUTE PROCEDURE repl_speed();" >> /tmp/commands.sql + + echo "create table $SQL_HILOW_TABLE_WINDGUST (" >> /tmp/commands.sql + echo " dateTime INTEGER NOT NULL PRIMARY KEY," >> /tmp/commands.sql + echo " low REAL," >> /tmp/commands.sql + echo " timeLow INTEGER," >> /tmp/commands.sql + echo " high REAL," >> /tmp/commands.sql + echo " timeHigh INTEGER," >> /tmp/commands.sql + echo " whenHigh REAL," >> /tmp/commands.sql + echo " cumulative REAL," >> /tmp/commands.sql + echo " samples INTEGER" >> /tmp/commands.sql + echo ");" >> /tmp/commands.sql + + echo "CREATE OR REPLACE FUNCTION repl_gust() RETURNS trigger AS \$BODY\$ BEGIN IF (EXISTS(SELECT 1 FROM $SQL_HILOW_TABLE_WINDGUST C WHERE C.dateTime = NEW.dateTime)) THEN UPDATE $SQL_HILOW_TABLE_WINDGUST SET low = NEW.low, timeLow = NEW.timeLow, high = NEW.high, timeHigh = NEW.timeHigh, whenHigh = NEW.whenHigh, cumulative = NEW.cumulative, samples = NEW.samples WHERE dateTime = NEW.dateTime; RETURN OLD; ELSE RETURN NEW; END IF; END; \$BODY\$ LANGUAGE plpgsql VOLATILE COST 100; ALTER FUNCTION repl_gust() OWNER TO postgres; CREATE TRIGGER repl_gust BEFORE INSERT ON $SQL_HILOW_TABLE_WINDGUST FOR EACH ROW EXECUTE PROCEDURE repl_gust();" >> /tmp/commands.sql + + echo "create table $SQL_HILOW_TABLE_DEWPOINT (" >> /tmp/commands.sql + echo " dateTime INTEGER NOT NULL PRIMARY KEY," >> /tmp/commands.sql + echo " low REAL," >> /tmp/commands.sql + echo " timeLow INTEGER," >> /tmp/commands.sql + echo " high REAL," >> /tmp/commands.sql + echo " timeHigh INTEGER," >> /tmp/commands.sql + echo " whenHigh REAL," >> /tmp/commands.sql + echo " cumulative REAL," >> /tmp/commands.sql + echo " samples INTEGER" >> /tmp/commands.sql + echo ");" >> /tmp/commands.sql + + echo "CREATE OR REPLACE FUNCTION repl_dewpt() RETURNS trigger AS \$BODY\$ BEGIN IF (EXISTS(SELECT 1 FROM $SQL_HILOW_TABLE_DEWPOINT C WHERE C.dateTime = NEW.dateTime)) THEN UPDATE $SQL_HILOW_TABLE_DEWPOINT SET low = NEW.low, timeLow = NEW.timeLow, high = NEW.high, timeHigh = NEW.timeHigh, whenHigh = NEW.whenHigh, cumulative = NEW.cumulative, samples = NEW.samples WHERE dateTime = NEW.dateTime; RETURN OLD; ELSE RETURN NEW; END IF; END; \$BODY\$ LANGUAGE plpgsql VOLATILE COST 100; ALTER FUNCTION repl_dewpt() OWNER TO postgres; CREATE TRIGGER repl_dewpt BEFORE INSERT ON $SQL_HILOW_TABLE_DEWPOINT FOR EACH ROW EXECUTE PROCEDURE repl_dewpt();" >> /tmp/commands.sql + + echo "create table $SQL_HILOW_TABLE_RAIN (" >> /tmp/commands.sql + echo " dateTime INTEGER NOT NULL PRIMARY KEY," >> /tmp/commands.sql + echo " low REAL," >> /tmp/commands.sql + echo " timeLow INTEGER," >> /tmp/commands.sql + echo " high REAL," >> /tmp/commands.sql + echo " timeHigh INTEGER," >> /tmp/commands.sql + echo " whenHigh REAL," >> /tmp/commands.sql + echo " cumulative REAL," >> /tmp/commands.sql + echo " samples INTEGER" >> /tmp/commands.sql + echo ");" >> /tmp/commands.sql + + echo "CREATE OR REPLACE FUNCTION repl_rain() RETURNS trigger AS \$BODY\$ BEGIN IF (EXISTS(SELECT 1 FROM $SQL_HILOW_TABLE_RAIN C WHERE C.dateTime = NEW.dateTime)) THEN UPDATE $SQL_HILOW_TABLE_RAIN SET low = NEW.low, timeLow = NEW.timeLow, high = NEW.high, timeHigh = NEW.timeHigh, whenHigh = NEW.whenHigh, cumulative = NEW.cumulative, samples = NEW.samples WHERE dateTime = NEW.dateTime; RETURN OLD; ELSE RETURN NEW; END IF; END; \$BODY\$ LANGUAGE plpgsql VOLATILE COST 100; ALTER FUNCTION repl_rain() OWNER TO postgres; CREATE TRIGGER repl_rain BEFORE INSERT ON $SQL_HILOW_TABLE_RAIN FOR EACH ROW EXECUTE PROCEDURE repl_rain();" >> /tmp/commands.sql + + echo "create table $SQL_HILOW_TABLE_RAINRATE (" >> /tmp/commands.sql + echo " dateTime INTEGER NOT NULL PRIMARY KEY," >> /tmp/commands.sql + echo " low REAL," >> /tmp/commands.sql + echo " timeLow INTEGER," >> /tmp/commands.sql + echo " high REAL," >> /tmp/commands.sql + echo " timeHigh INTEGER," >> /tmp/commands.sql + echo " whenHigh REAL," >> /tmp/commands.sql + echo " cumulative REAL," >> /tmp/commands.sql + echo " samples INTEGER" >> /tmp/commands.sql + echo ");" >> /tmp/commands.sql + + echo "CREATE OR REPLACE FUNCTION repl_rainrate() RETURNS trigger AS \$BODY\$ BEGIN IF (EXISTS(SELECT 1 FROM $SQL_HILOW_TABLE_RAINRATE C WHERE C.dateTime = NEW.dateTime)) THEN UPDATE $SQL_HILOW_TABLE_RAINRATE SET low = NEW.low, timeLow = NEW.timeLow, high = NEW.high, timeHigh = NEW.timeHigh, whenHigh = NEW.whenHigh, cumulative = NEW.cumulative, samples = NEW.samples WHERE dateTime = NEW.dateTime; RETURN OLD; ELSE RETURN NEW; END IF; END; \$BODY\$ LANGUAGE plpgsql VOLATILE COST 100; ALTER FUNCTION repl_rainrate() OWNER TO postgres; CREATE TRIGGER repl_rainrate BEFORE INSERT ON $SQL_HILOW_TABLE_RAINRATE FOR EACH ROW EXECUTE PROCEDURE repl_rainrate();" >> /tmp/commands.sql + + echo "create table $SQL_HILOW_TABLE_WINDCHILL (" >> /tmp/commands.sql + echo " dateTime INTEGER NOT NULL PRIMARY KEY," >> /tmp/commands.sql + echo " low REAL," >> /tmp/commands.sql + echo " timeLow INTEGER," >> /tmp/commands.sql + echo " high REAL," >> /tmp/commands.sql + echo " timeHigh INTEGER," >> /tmp/commands.sql + echo " whenHigh REAL," >> /tmp/commands.sql + echo " cumulative REAL," >> /tmp/commands.sql + echo " samples INTEGER" >> /tmp/commands.sql + echo ");" >> /tmp/commands.sql + + echo "CREATE OR REPLACE FUNCTION repl_chill() RETURNS trigger AS \$BODY\$ BEGIN IF (EXISTS(SELECT 1 FROM $SQL_HILOW_TABLE_WINDCHILL C WHERE C.dateTime = NEW.dateTime)) THEN UPDATE $SQL_HILOW_TABLE_WINDCHILL SET low = NEW.low, timeLow = NEW.timeLow, high = NEW.high, timeHigh = NEW.timeHigh, whenHigh = NEW.whenHigh, cumulative = NEW.cumulative, samples = NEW.samples WHERE dateTime = NEW.dateTime; RETURN OLD; ELSE RETURN NEW; END IF; END; \$BODY\$ LANGUAGE plpgsql VOLATILE COST 100; ALTER FUNCTION repl_chill() OWNER TO postgres; CREATE TRIGGER repl_chill BEFORE INSERT ON $SQL_HILOW_TABLE_WINDCHILL FOR EACH ROW EXECUTE PROCEDURE repl_chill();" >> /tmp/commands.sql + + echo "create table $SQL_HILOW_TABLE_HEATINDEX (" >> /tmp/commands.sql + echo " dateTime INTEGER NOT NULL PRIMARY KEY," >> /tmp/commands.sql + echo " low REAL," >> /tmp/commands.sql + echo " timeLow INTEGER," >> /tmp/commands.sql + echo " high REAL," >> /tmp/commands.sql + echo " timeHigh INTEGER," >> /tmp/commands.sql + echo " whenHigh REAL," >> /tmp/commands.sql + echo " cumulative REAL," >> /tmp/commands.sql + echo " samples INTEGER" >> /tmp/commands.sql + echo ");" >> /tmp/commands.sql + + echo "CREATE OR REPLACE FUNCTION repl_heat() RETURNS trigger AS \$BODY\$ BEGIN IF (EXISTS(SELECT 1 FROM $SQL_HILOW_TABLE_HEATINDEX C WHERE C.dateTime = NEW.dateTime)) THEN UPDATE $SQL_HILOW_TABLE_HEATINDEX SET low = NEW.low, timeLow = NEW.timeLow, high = NEW.high, timeHigh = NEW.timeHigh, whenHigh = NEW.whenHigh, cumulative = NEW.cumulative, samples = NEW.samples WHERE dateTime = NEW.dateTime; RETURN OLD; ELSE RETURN NEW; END IF; END; \$BODY\$ LANGUAGE plpgsql VOLATILE COST 100; ALTER FUNCTION repl_heat() OWNER TO postgres; CREATE TRIGGER repl_heat BEFORE INSERT ON $SQL_HILOW_TABLE_HEATINDEX FOR EACH ROW EXECUTE PROCEDURE repl_heat();" >> /tmp/commands.sql + + echo "create table $SQL_HILOW_TABLE_ET (" >> /tmp/commands.sql + echo " dateTime INTEGER NOT NULL PRIMARY KEY," >> /tmp/commands.sql + echo " low REAL," >> /tmp/commands.sql + echo " timeLow INTEGER," >> /tmp/commands.sql + echo " high REAL," >> /tmp/commands.sql + echo " timeHigh INTEGER," >> /tmp/commands.sql + echo " whenHigh REAL," >> /tmp/commands.sql + echo " cumulative REAL," >> /tmp/commands.sql + echo " samples INTEGER" >> /tmp/commands.sql + echo ");" >> /tmp/commands.sql + + echo "CREATE OR REPLACE FUNCTION repl_et() RETURNS trigger AS \$BODY\$ BEGIN IF (EXISTS(SELECT 1 FROM $SQL_HILOW_TABLE_ET C WHERE C.dateTime = NEW.dateTime)) THEN UPDATE $SQL_HILOW_TABLE_ET SET low = NEW.low, timeLow = NEW.timeLow, high = NEW.high, timeHigh = NEW.timeHigh, whenHigh = NEW.whenHigh, cumulative = NEW.cumulative, samples = NEW.samples WHERE dateTime = NEW.dateTime; RETURN OLD; ELSE RETURN NEW; END IF; END; \$BODY\$ LANGUAGE plpgsql VOLATILE COST 100; ALTER FUNCTION repl_et() OWNER TO postgres; CREATE TRIGGER repl_et BEFORE INSERT ON $SQL_HILOW_TABLE_ET FOR EACH ROW EXECUTE PROCEDURE repl_et();" >> /tmp/commands.sql + + echo "create table $SQL_HILOW_TABLE_UV (" >> /tmp/commands.sql + echo " dateTime INTEGER NOT NULL PRIMARY KEY," >> /tmp/commands.sql + echo " low REAL," >> /tmp/commands.sql + echo " timeLow INTEGER," >> /tmp/commands.sql + echo " high REAL," >> /tmp/commands.sql + echo " timeHigh INTEGER," >> /tmp/commands.sql + echo " whenHigh REAL," >> /tmp/commands.sql + echo " cumulative REAL," >> /tmp/commands.sql + echo " samples INTEGER" >> /tmp/commands.sql + echo ");" >> /tmp/commands.sql + + echo "CREATE OR REPLACE FUNCTION repl_uv() RETURNS trigger AS \$BODY\$ BEGIN IF (EXISTS(SELECT 1 FROM $SQL_HILOW_TABLE_UV C WHERE C.dateTime = NEW.dateTime)) THEN UPDATE $SQL_HILOW_TABLE_UV SET low = NEW.low, timeLow = NEW.timeLow, high = NEW.high, timeHigh = NEW.timeHigh, whenHigh = NEW.whenHigh, cumulative = NEW.cumulative, samples = NEW.samples WHERE dateTime = NEW.dateTime; RETURN OLD; ELSE RETURN NEW; END IF; END; \$BODY\$ LANGUAGE plpgsql VOLATILE COST 100; ALTER FUNCTION repl_uv() OWNER TO postgres; CREATE TRIGGER repl_uv BEFORE INSERT ON $SQL_HILOW_TABLE_UV FOR EACH ROW EXECUTE PROCEDURE repl_uv();" >> /tmp/commands.sql + + echo "create table $SQL_HILOW_TABLE_SOLARRAD (" >> /tmp/commands.sql + echo " dateTime INTEGER NOT NULL PRIMARY KEY," >> /tmp/commands.sql + echo " low REAL," >> /tmp/commands.sql + echo " timeLow INTEGER," >> /tmp/commands.sql + echo " high REAL," >> /tmp/commands.sql + echo " timeHigh INTEGER," >> /tmp/commands.sql + echo " whenHigh REAL," >> /tmp/commands.sql + echo " cumulative REAL," >> /tmp/commands.sql + echo " samples INTEGER" >> /tmp/commands.sql + echo ");" >> /tmp/commands.sql + + echo "CREATE OR REPLACE FUNCTION repl_solar() RETURNS trigger AS \$BODY\$ BEGIN IF (EXISTS(SELECT 1 FROM $SQL_HILOW_TABLE_SOLARRAD C WHERE C.dateTime = NEW.dateTime)) THEN UPDATE $SQL_HILOW_TABLE_SOLARRAD SET low = NEW.low, timeLow = NEW.timeLow, high = NEW.high, timeHigh = NEW.timeHigh, whenHigh = NEW.whenHigh, cumulative = NEW.cumulative, samples = NEW.samples WHERE dateTime = NEW.dateTime; RETURN OLD; ELSE RETURN NEW; END IF; END; \$BODY\$ LANGUAGE plpgsql VOLATILE COST 100; ALTER FUNCTION repl_solar() OWNER TO postgres; CREATE TRIGGER repl_solar BEFORE INSERT ON $SQL_HILOW_TABLE_SOLARRAD FOR EACH ROW EXECUTE PROCEDURE repl_solar();" >> /tmp/commands.sql + + echo "create table $SQL_HILOW_TABLE_HAIL (" >> /tmp/commands.sql + echo " dateTime INTEGER NOT NULL PRIMARY KEY," >> /tmp/commands.sql + echo " low REAL," >> /tmp/commands.sql + echo " timeLow INTEGER," >> /tmp/commands.sql + echo " high REAL," >> /tmp/commands.sql + echo " timeHigh INTEGER," >> /tmp/commands.sql + echo " whenHigh REAL," >> /tmp/commands.sql + echo " cumulative REAL," >> /tmp/commands.sql + echo " samples INTEGER" >> /tmp/commands.sql + echo ");" >> /tmp/commands.sql + + echo "CREATE OR REPLACE FUNCTION repl_hail() RETURNS trigger AS \$BODY\$ BEGIN IF (EXISTS(SELECT 1 FROM $SQL_HILOW_TABLE_HAIL C WHERE C.dateTime = NEW.dateTime)) THEN UPDATE $SQL_HILOW_TABLE_HAIL SET low = NEW.low, timeLow = NEW.timeLow, high = NEW.high, timeHigh = NEW.timeHigh, whenHigh = NEW.whenHigh, cumulative = NEW.cumulative, samples = NEW.samples WHERE dateTime = NEW.dateTime; RETURN OLD; ELSE RETURN NEW; END IF; END; \$BODY\$ LANGUAGE plpgsql VOLATILE COST 100; ALTER FUNCTION repl_hail() OWNER TO postgres; CREATE TRIGGER repl_hail BEFORE INSERT ON $SQL_HILOW_TABLE_HAIL FOR EACH ROW EXECUTE PROCEDURE repl_hail();" >> /tmp/commands.sql + + echo "create table $SQL_HILOW_TABLE_HAILRATE (" >> /tmp/commands.sql + echo " dateTime INTEGER NOT NULL PRIMARY KEY," >> /tmp/commands.sql + echo " low REAL," >> /tmp/commands.sql + echo " timeLow INTEGER," >> /tmp/commands.sql + echo " high REAL," >> /tmp/commands.sql + echo " timeHigh INTEGER," >> /tmp/commands.sql + echo " whenHigh REAL," >> /tmp/commands.sql + echo " cumulative REAL," >> /tmp/commands.sql + echo " samples INTEGER" >> /tmp/commands.sql + echo ");" >> /tmp/commands.sql + + echo "CREATE OR REPLACE FUNCTION repl_hailrate() RETURNS trigger AS \$BODY\$ BEGIN IF (EXISTS(SELECT 1 FROM $SQL_HILOW_TABLE_HAILRATE C WHERE C.dateTime = NEW.dateTime)) THEN UPDATE $SQL_HILOW_TABLE_HAILRATE SET low = NEW.low, timeLow = NEW.timeLow, high = NEW.high, timeHigh = NEW.timeHigh, whenHigh = NEW.whenHigh, cumulative = NEW.cumulative, samples = NEW.samples WHERE dateTime = NEW.dateTime; RETURN OLD; ELSE RETURN NEW; END IF; END; \$BODY\$ LANGUAGE plpgsql VOLATILE COST 100; ALTER FUNCTION repl_hailrate() OWNER TO postgres; CREATE TRIGGER repl_hailrate BEFORE INSERT ON $SQL_HILOW_TABLE_HAILRATE FOR EACH ROW EXECUTE PROCEDURE repl_hailrate();" >> /tmp/commands.sql + + echo "create table $SQL_HILOW_TABLE_WINDDIR (" >> /tmp/commands.sql + echo " dateTime INTEGER NOT NULL PRIMARY KEY," >> /tmp/commands.sql + echo " bin0 INTEGER," >> /tmp/commands.sql + echo " bin1 INTEGER," >> /tmp/commands.sql + echo " bin2 INTEGER," >> /tmp/commands.sql + echo " bin3 INTEGER," >> /tmp/commands.sql + echo " bin4 INTEGER," >> /tmp/commands.sql + echo " bin5 INTEGER," >> /tmp/commands.sql + echo " bin6 INTEGER," >> /tmp/commands.sql + echo " bin7 INTEGER," >> /tmp/commands.sql + echo " bin8 INTEGER," >> /tmp/commands.sql + echo " bin9 INTEGER," >> /tmp/commands.sql + echo " bin10 INTEGER," >> /tmp/commands.sql + echo " bin11 INTEGER," >> /tmp/commands.sql + echo " bin12 INTEGER," >> /tmp/commands.sql + echo " bin13 INTEGER," >> /tmp/commands.sql + echo " bin14 INTEGER," >> /tmp/commands.sql + echo " bin15 INTEGER," >> /tmp/commands.sql + echo " bin16 INTEGER," >> /tmp/commands.sql + echo " bin17 INTEGER," >> /tmp/commands.sql + echo " bin18 INTEGER," >> /tmp/commands.sql + echo " bin19 INTEGER" >> /tmp/commands.sql + echo ");" >> /tmp/commands.sql + + echo "CREATE OR REPLACE FUNCTION repl_winddir() RETURNS trigger AS \$BODY\$ BEGIN IF (EXISTS(SELECT 1 FROM $SQL_HILOW_TABLE_WINDDIR C WHERE C.dateTime = NEW.dateTime)) THEN UPDATE $SQL_HILOW_TABLE_WINDDIR SET bin0 = NEW.bin0, bin1 = NEW.bin1, bin2 = NEW.bin2, bin3 = NEW.bin3, bin4 = NEW.bin4, bin5 = NEW.bin5, bin6 = NEW.bin6, bin7 = NEW.bin7, bin8 = NEW.bin8, bin9 = NEW.bin9, bin10 = NEW.bin10, bin11 = NEW.bin11, bin12 = NEW.bin12, bin13 = NEW.bin13, bin14 = NEW.bin14, bin15 = NEW.bin15, bin16 = NEW.bin16, bin17 = NEW.bin17, bin18 = NEW.bin18, bin19 = NEW.bin19 WHERE dateTime = NEW.dateTime; RETURN OLD; ELSE RETURN NEW; END IF; END; \$BODY\$ LANGUAGE plpgsql VOLATILE COST 100; ALTER FUNCTION repl_winddir() OWNER TO postgres; CREATE TRIGGER repl_winddir BEFORE INSERT ON $SQL_HILOW_TABLE_WINDDIR FOR EACH ROW EXECUTE PROCEDURE repl_winddir();" >> /tmp/commands.sql + + echo "create table $SQL_NOAA_TABLE (" >> /tmp/commands.sql + echo " dateTime INTEGER NOT NULL PRIMARY KEY," >> /tmp/commands.sql + echo " meanTemp REAL," >> /tmp/commands.sql + echo " highTemp REAL," >> /tmp/commands.sql + echo " highTempTime INTEGER," >> /tmp/commands.sql + echo " lowTemp REAL," >> /tmp/commands.sql + echo " lowTempTime INTEGER," >> /tmp/commands.sql + echo " heatDegDays REAL," >> /tmp/commands.sql + echo " coolDegDays REAL," >> /tmp/commands.sql + echo " rain REAL," >> /tmp/commands.sql + echo " avgWind REAL," >> /tmp/commands.sql + echo " highWind REAL," >> /tmp/commands.sql + echo " highWindTime INTEGER," >> /tmp/commands.sql + echo " domWindDir INTEGER" >> /tmp/commands.sql + echo ");" >> /tmp/commands.sql + + echo "CREATE OR REPLACE FUNCTION repl_noaa() RETURNS trigger AS \$BODY\$ BEGIN IF (EXISTS(SELECT 1 FROM $SQL_NOAA_TABLE C WHERE C.dateTime = NEW.dateTime)) THEN UPDATE $SQL_NOAA_TABLE SET meanTemp = NEW.meanTemp, highTemp = NEW.highTemp, highTempTime = NEW.highTempTime, lowTemp = NEW.lowTemp, lowTempTime = NEW.lowTempTime, heatDegDays = NEW.heatDegDays, coolDegDays = NEW.coolDegDays, rain = NEW.rain, avgWind = NEW.avgWind, highWind = NEW.highWind, highWindTime = NEW.highWindTime, domWindDir = NEW.domWindDir WHERE dateTime = NEW.dateTime; RETURN OLD; ELSE RETURN NEW; END IF; END; \$BODY\$ LANGUAGE plpgsql VOLATILE COST 100; ALTER FUNCTION repl_noaa() OWNER TO postgres; CREATE TRIGGER repl_noaa BEFORE INSERT ON $SQL_NOAA_TABLE FOR EACH ROW EXECUTE PROCEDURE repl_noaa();" >> /tmp/commands.sql + + psql $PGSQL_DBNAME < /tmp/commands.sql > /tmp/pgsql_create.log + rm -rf /tmp/commands.sql +} + +################################################################################ +################## S C R I P T E X E C U T I O N S T A R T ################# +################################################################################ + +mysql_create + +## Done! +exit 0 + diff --git a/dbexport/wview-pgsql-export.sh b/dbexport/wview-pgsql-export.sh new file mode 100644 index 0000000..3a0ffd2 --- /dev/null +++ b/dbexport/wview-pgsql-export.sh @@ -0,0 +1,333 @@ +################################################################################ +# +# File: wview-pgsql-export.sh +# +# Description: Provide a script to export SQLite3 wview data to a PostgreSQL +# database. Should be run periodically via cron. +# +# Usage: wview-pgsql-export +# Arguments: +# debug - indicate to log verbosely and to +# create the PostgreSQL debug log file +# $prefix/etc/wview/export/pgsql_import.log +# +# History: +# Engineer Date Ver Comments +# MS Teel 04/04/09 1 Original +# +# Notes: The marker file approach is inspired by Jerry Fiddler. +# Requires: sqlite3 CLI utility and pgsql CLI utility. +# +################################################################################ + +################################################################################ +################################# M A C R O S ################################ +################################################################################ + +## DO NOT CHANGE ANY DEFS IN THIS BLOCK! + +export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin + +SECONDS_IN_DAY=86400 +SECONDS_IN_HOUR=3600 + +ARCHIVE_DB_FILE=$WVIEW_DATA_DIR/archive/wview-archive.sdb +HILOW_DB_FILE=$WVIEW_DATA_DIR/archive/wview-hilow.sdb +NOAA_DB_FILE=$WVIEW_DATA_DIR/archive/wview-noaa.sdb + +## The defaults match the SQLite table names: +SQL_ARCHIVE_TABLE=archive +SQL_HILOW_TABLE_INTEMP=inTemp +SQL_HILOW_TABLE_OUTTEMP=outTemp +SQL_HILOW_TABLE_INHUMIDITY=inHumidity +SQL_HILOW_TABLE_OUTHUMIDITY=outHumidity +SQL_HILOW_TABLE_BP=baromPressure +SQL_HILOW_TABLE_WINDSPEED=windSpeed +SQL_HILOW_TABLE_WINDGUST=windGust +SQL_HILOW_TABLE_DEWPOINT=dewPoint +SQL_HILOW_TABLE_RAIN=rain +SQL_HILOW_TABLE_RAINRATE=rainRate +SQL_HILOW_TABLE_WINDCHILL=windChill +SQL_HILOW_TABLE_HEATINDEX=heatIndex +SQL_HILOW_TABLE_ET=ET +SQL_HILOW_TABLE_UV=UV +SQL_HILOW_TABLE_SOLARRAD=solarRadiation +SQL_HILOW_TABLE_HAIL=hail +SQL_HILOW_TABLE_HAILRATE=hailRate +SQL_HILOW_TABLE_WINDDIR=windDir +SQL_NOAA_TABLE=noaaHistory + +TIME_MARKER_FILE=$WVIEW_CONF_DIR/export/pgsql_export_marker +ARCHIVE_MARKER_FILE=$WVIEW_CONF_DIR/export/archive_marker +HILOW_MARKER_FILE=$WVIEW_CONF_DIR/export/hilow_marker +NOAA_MARKER_FILE=$WVIEW_CONF_DIR/export/noaa_marker +ARCHIVE_EXPORT_FILE=$WVIEW_CONF_DIR/export/$SQL_ARCHIVE_TABLE.csv +HILOW_EXPORT_FILE_INTEMP=$WVIEW_CONF_DIR/export/$SQL_HILOW_TABLE_INTEMP.csv +HILOW_EXPORT_FILE_OUTTEMP=$WVIEW_CONF_DIR/export/$SQL_HILOW_TABLE_OUTTEMP.csv +HILOW_EXPORT_FILE_INHUMIDITY=$WVIEW_CONF_DIR/export/$SQL_HILOW_TABLE_INHUMIDITY.csv +HILOW_EXPORT_FILE_OUTHUMIDITY=$WVIEW_CONF_DIR/export/$SQL_HILOW_TABLE_OUTHUMIDITY.csv +HILOW_EXPORT_FILE_BP=$WVIEW_CONF_DIR/export/$SQL_HILOW_TABLE_BP.csv +HILOW_EXPORT_FILE_WINDSPEED=$WVIEW_CONF_DIR/export/$SQL_HILOW_TABLE_WINDSPEED.csv +HILOW_EXPORT_FILE_WINDGUST=$WVIEW_CONF_DIR/export/$SQL_HILOW_TABLE_WINDGUST.csv +HILOW_EXPORT_FILE_DEWPOINT=$WVIEW_CONF_DIR/export/$SQL_HILOW_TABLE_DEWPOINT.csv +HILOW_EXPORT_FILE_RAIN=$WVIEW_CONF_DIR/export/$SQL_HILOW_TABLE_RAIN.csv +HILOW_EXPORT_FILE_RAINRATE=$WVIEW_CONF_DIR/export/$SQL_HILOW_TABLE_RAINRATE.csv +HILOW_EXPORT_FILE_WINDCHILL=$WVIEW_CONF_DIR/export/$SQL_HILOW_TABLE_WINDCHILL.csv +HILOW_EXPORT_FILE_HEATINDEX=$WVIEW_CONF_DIR/export/$SQL_HILOW_TABLE_HEATINDEX.csv +HILOW_EXPORT_FILE_ET=$WVIEW_CONF_DIR/export/$SQL_HILOW_TABLE_ET.csv +HILOW_EXPORT_FILE_UV=$WVIEW_CONF_DIR/export/$SQL_HILOW_TABLE_UV.csv +HILOW_EXPORT_FILE_SOLARRAD=$WVIEW_CONF_DIR/export/$SQL_HILOW_TABLE_SOLARRAD.csv +HILOW_EXPORT_FILE_HAIL=$WVIEW_CONF_DIR/export/$SQL_HILOW_TABLE_HAIL.csv +HILOW_EXPORT_FILE_HAILRATE=$WVIEW_CONF_DIR/export/$SQL_HILOW_TABLE_HAILRATE.csv +HILOW_EXPORT_FILE_WINDDIR=$WVIEW_CONF_DIR/export/$SQL_HILOW_TABLE_WINDDIR.csv +NOAA_EXPORT_FILE=$WVIEW_CONF_DIR/export/$SQL_NOAA_TABLE.csv + +ARCHIVE_ENABLE=1 +HILOW_ENABLE=1 +NOAA_ENABLE=1 +UPDATE_MARKER=0 + +PGSQL_IMPORT_LOG_FILE=$WVIEW_CONF_DIR/export/pgsql_import.log + +## Test command line arguments: +if [ "$1" = "help" ]; then + echo "USAGE:" + echo "wview-pgsql-export [help | debug]" + echo " - (default behavior) export SQLite3 data to PostgreSQL, typically executed as a cron job" + echo "" + echo " help - print out usage directions and exit" + echo "" + echo " debug - log verbosely and create the PostgreSQL debug log file $WVIEW_CONF_DIR/export/pgsql_import.log" + echo "" + echo "" + echo "REQUIRES: sqlite3 CLI utility and pgsql CLI utility." + echo "" + exit 0 +fi + +if [ "$1" = "debug" ]; then + VERBOSE=1 +else + VERBOSE=0 +fi + + +################################################################################ +####################### D E F I N E F U N C T I O N S ####################### +################################################################################ +syslog() +{ + if [ $VERBOSE != 0 ]; then + logger -t WV_EXPORT $1 $2 + fi +} + +syslog_error() +{ + logger -t WV_EXPORT_ERROR $1 $2 +} + +# $1 = DB file (full path) +# $2 = table name +# $3 = dateTime stamp +# $4 = CSV output file +sqlite3_export() +{ + echo ".mode list" > /tmp/commands.sql + echo ".separator ," >> /tmp/commands.sql + echo ".nullvalue '\N'" >> /tmp/commands.sql + echo ".timeout 30000" >> /tmp/commands.sql + echo ".output $4" >> /tmp/commands.sql + echo "select * from $2 where dateTime > '$3' order by dateTime ASC;" >> /tmp/commands.sql + echo ".exit" >> /tmp/commands.sql + + echo ".read /tmp/commands.sql" | sqlite3 $1 + + rm -rf /tmp/commands.sql +} + +# No function args +get_sql_server_config() +{ + echo "select value from config where name='STATION_SQLDB_HOST';" > /tmp/commands.sql + echo ".exit" >> /tmp/commands.sql + echo ".read /tmp/commands.sql" | sqlite3 $WVIEW_CONF_DIR/wview-conf.sdb > /tmp/result.txt + SQL_HOST=`cat /tmp/result.txt` + rm -rf /tmp/commands.sql /tmp/result.txt + + echo "select value from config where name='STATION_SQLDB_DB_NAME';" > /tmp/commands.sql + echo ".exit" >> /tmp/commands.sql + echo ".read /tmp/commands.sql" | sqlite3 $WVIEW_CONF_DIR/wview-conf.sdb > /tmp/result.txt + SQL_DB_NAME=`cat /tmp/result.txt` + rm -rf /tmp/commands.sql /tmp/result.txt +} + +# $1 = import table name +# $2 = import filename +pgsql_import() +{ + echo -n "\\" > /tmp/commands.sql + echo "copy $1 from $2 delimiter as ','" >> /tmp/commands.sql + + if [ "$VERBOSE" = "1" ]; then + date >> $PGSQL_IMPORT_LOG_FILE; + psql --host $SQL_HOST $SQL_DB_NAME < /tmp/commands.sql > $PGSQL_IMPORT_LOG_FILE + else + psql --host $SQL_HOST $SQL_DB_NAME < /tmp/commands.sql > /dev/null; + fi + + rm -rf /tmp/commands.sql +} + +################################################################################ +################## S C R I P T E X E C U T I O N S T A R T ################# +################################################################################ + +UPDATE_TIME_SAVE=`date +%s` + +## Check to see if there is new data to export: +if [ ! -e $TIME_MARKER_FILE ]; then + LAST_UPDATE=0 + syslog "First time to export databases" +else + LAST_UPDATE=`cat $TIME_MARKER_FILE` + syslog "Exporting databases after: " $LAST_UPDATE +fi + +if [ -e $ARCHIVE_MARKER_FILE ]; then + if [ $LAST_UPDATE -ge `cat $ARCHIVE_MARKER_FILE` ]; then + ARCHIVE_ENABLE=0 + fi +else + syslog $ARCHIVE_MARKER_FILE " not found" +fi +if [ -e $HILOW_MARKER_FILE ]; then + if [ $LAST_UPDATE -ge `cat $HILOW_MARKER_FILE` ]; then + HILOW_ENABLE=0 + fi +else + syslog $HILOW_MARKER_FILE " not found" +fi +if [ -e $NOAA_MARKER_FILE ]; then + if [ $LAST_UPDATE -ge `cat $NOAA_MARKER_FILE` ]; then + NOAA_ENABLE=0 + fi +else + syslog $NOAA_MARKER_FILE " not found" +fi + + +## Export from the wview SQLite3 databases: +if [ "$ARCHIVE_ENABLE" = "1" ]; then + UPDATE_MARKER=1 + syslog "Exporting to: " $ARCHIVE_EXPORT_FILE + rm -rf $ARCHIVE_EXPORT_FILE + sqlite3_export $ARCHIVE_DB_FILE $SQL_ARCHIVE_TABLE $LAST_UPDATE $ARCHIVE_EXPORT_FILE +fi + +SAVE_UPDATE_TIME=$LAST_UPDATE +LAST_UPDATE=`expr $LAST_UPDATE - $SECONDS_IN_HOUR` + +if [ "$HILOW_ENABLE" = "1" ]; then + UPDATE_MARKER=1 + syslog "Exporting HILOW tables:" + rm -rf $HILOW_EXPORT_FILE_INTEMP + sqlite3_export $HILOW_DB_FILE $SQL_HILOW_TABLE_INTEMP $LAST_UPDATE $HILOW_EXPORT_FILE_INTEMP + rm -rf $HILOW_EXPORT_FILE_OUTTEMP + sqlite3_export $HILOW_DB_FILE $SQL_HILOW_TABLE_OUTTEMP $LAST_UPDATE $HILOW_EXPORT_FILE_OUTTEMP + rm -rf $HILOW_EXPORT_FILE_INHUMIDITY + sqlite3_export $HILOW_DB_FILE $SQL_HILOW_TABLE_INHUMIDITY $LAST_UPDATE $HILOW_EXPORT_FILE_INHUMIDITY + rm -rf $HILOW_EXPORT_FILE_OUTHUMIDITY + sqlite3_export $HILOW_DB_FILE $SQL_HILOW_TABLE_OUTHUMIDITY $LAST_UPDATE $HILOW_EXPORT_FILE_OUTHUMIDITY + rm -rf $HILOW_EXPORT_FILE_BP + sqlite3_export $HILOW_DB_FILE $SQL_HILOW_TABLE_BP $LAST_UPDATE $HILOW_EXPORT_FILE_BP + rm -rf $HILOW_EXPORT_FILE_WINDSPEED + sqlite3_export $HILOW_DB_FILE $SQL_HILOW_TABLE_WINDSPEED $LAST_UPDATE $HILOW_EXPORT_FILE_WINDSPEED + rm -rf $HILOW_EXPORT_FILE_WINDGUST + sqlite3_export $HILOW_DB_FILE $SQL_HILOW_TABLE_WINDGUST $LAST_UPDATE $HILOW_EXPORT_FILE_WINDGUST + rm -rf $HILOW_EXPORT_FILE_DEWPOINT + sqlite3_export $HILOW_DB_FILE $SQL_HILOW_TABLE_DEWPOINT $LAST_UPDATE $HILOW_EXPORT_FILE_DEWPOINT + rm -rf $HILOW_EXPORT_FILE_RAIN + sqlite3_export $HILOW_DB_FILE $SQL_HILOW_TABLE_RAIN $LAST_UPDATE $HILOW_EXPORT_FILE_RAIN + rm -rf $HILOW_EXPORT_FILE_RAINRATE + sqlite3_export $HILOW_DB_FILE $SQL_HILOW_TABLE_RAINRATE $LAST_UPDATE $HILOW_EXPORT_FILE_RAINRATE + rm -rf $HILOW_EXPORT_FILE_WINDCHILL + sqlite3_export $HILOW_DB_FILE $SQL_HILOW_TABLE_WINDCHILL $LAST_UPDATE $HILOW_EXPORT_FILE_WINDCHILL + rm -rf $HILOW_EXPORT_FILE_HEATINDEX + sqlite3_export $HILOW_DB_FILE $SQL_HILOW_TABLE_HEATINDEX $LAST_UPDATE $HILOW_EXPORT_FILE_HEATINDEX + rm -rf $HILOW_EXPORT_FILE_ET + sqlite3_export $HILOW_DB_FILE $SQL_HILOW_TABLE_ET $LAST_UPDATE $HILOW_EXPORT_FILE_ET + rm -rf $HILOW_EXPORT_FILE_UV + sqlite3_export $HILOW_DB_FILE $SQL_HILOW_TABLE_UV $LAST_UPDATE $HILOW_EXPORT_FILE_UV + rm -rf $HILOW_EXPORT_FILE_SOLARRAD + sqlite3_export $HILOW_DB_FILE $SQL_HILOW_TABLE_SOLARRAD $LAST_UPDATE $HILOW_EXPORT_FILE_SOLARRAD + rm -rf $HILOW_EXPORT_FILE_HAIL + sqlite3_export $HILOW_DB_FILE $SQL_HILOW_TABLE_HAIL $LAST_UPDATE $HILOW_EXPORT_FILE_HAIL + rm -rf $HILOW_EXPORT_FILE_HAILRATE + sqlite3_export $HILOW_DB_FILE $SQL_HILOW_TABLE_HAILRATE $LAST_UPDATE $HILOW_EXPORT_FILE_HAILRATE + rm -rf $HILOW_EXPORT_FILE_WINDDIR + sqlite3_export $HILOW_DB_FILE $SQL_HILOW_TABLE_WINDDIR $LAST_UPDATE $HILOW_EXPORT_FILE_WINDDIR +fi + +LAST_UPDATE=`expr $SAVE_UPDATE_TIME - $SECONDS_IN_DAY` + +if [ "$NOAA_ENABLE" = "1" ]; then + UPDATE_MARKER=1 + syslog "Exporting to: " $NOAA_EXPORT_FILE + rm -rf $NOAA_EXPORT_FILE + sqlite3_export $NOAA_DB_FILE $SQL_NOAA_TABLE $LAST_UPDATE $NOAA_EXPORT_FILE +fi + + +## Get MySQL server configuration: +get_sql_server_config +if [ x"$SQL_HOST" = x ]; then + syslog_error "Failed to retrieve PostgreSQL export parameters from $WVIEW_CONF_DIR/wview-conf.sdb" + exit 1 +else + syslog "Exporting with: $SQL_HOST:$SQL_USERNAME::$SQL_DB_NAME" +fi + + +## Import to PostgreSQL: +if [ "$ARCHIVE_ENABLE" = "1" ]; then + syslog "Importing: " $ARCHIVE_EXPORT_FILE + pgsql_import $SQL_ARCHIVE_TABLE $ARCHIVE_EXPORT_FILE +fi + +if [ "$HILOW_ENABLE" = "1" ]; then + syslog "Importing HILOW tables:" + pgsql_import $SQL_HILOW_TABLE_INTEMP $HILOW_EXPORT_FILE_INTEMP + pgsql_import $SQL_HILOW_TABLE_OUTTEMP $HILOW_EXPORT_FILE_OUTTEMP + pgsql_import $SQL_HILOW_TABLE_INHUMIDITY $HILOW_EXPORT_FILE_INHUMIDITY + pgsql_import $SQL_HILOW_TABLE_OUTHUMIDITY $HILOW_EXPORT_FILE_OUTHUMIDITY + pgsql_import $SQL_HILOW_TABLE_BP $HILOW_EXPORT_FILE_BP + pgsql_import $SQL_HILOW_TABLE_WINDSPEED $HILOW_EXPORT_FILE_WINDSPEED + pgsql_import $SQL_HILOW_TABLE_WINDGUST $HILOW_EXPORT_FILE_WINDGUST + pgsql_import $SQL_HILOW_TABLE_DEWPOINT $HILOW_EXPORT_FILE_DEWPOINT + pgsql_import $SQL_HILOW_TABLE_RAIN $HILOW_EXPORT_FILE_RAIN + pgsql_import $SQL_HILOW_TABLE_RAINRATE $HILOW_EXPORT_FILE_RAINRATE + pgsql_import $SQL_HILOW_TABLE_WINDCHILL $HILOW_EXPORT_FILE_WINDCHILL + pgsql_import $SQL_HILOW_TABLE_HEATINDEX $HILOW_EXPORT_FILE_HEATINDEX + pgsql_import $SQL_HILOW_TABLE_ET $HILOW_EXPORT_FILE_ET + pgsql_import $SQL_HILOW_TABLE_UV $HILOW_EXPORT_FILE_UV + pgsql_import $SQL_HILOW_TABLE_SOLARRAD $HILOW_EXPORT_FILE_SOLARRAD + pgsql_import $SQL_HILOW_TABLE_HAIL $HILOW_EXPORT_FILE_HAIL + pgsql_import $SQL_HILOW_TABLE_HAILRATE $HILOW_EXPORT_FILE_HAILRATE + pgsql_import $SQL_HILOW_TABLE_WINDDIR $HILOW_EXPORT_FILE_WINDDIR +fi + +if [ "$NOAA_ENABLE" = "1" ]; then + syslog "Importing: " $NOAA_EXPORT_FILE + pgsql_import $SQL_NOAA_TABLE $NOAA_EXPORT_FILE +fi + + +## Save the current time marker: +if [ "$UPDATE_MARKER" = "1" ]; then + echo $UPDATE_TIME_SAVE > $TIME_MARKER_FILE +fi + +## Done! +exit 0 + diff --git a/debian/99-wview.conf b/debian/99-wview.conf new file mode 100644 index 0000000..fd651f1 --- /dev/null +++ b/debian/99-wview.conf @@ -0,0 +1,6 @@ +:programname,isequal,"radmrouted" /var/log/wview.log +:programname,isequal,"radmrouted" ~ +:programname,isequal,"htmlgend" /var/log/wview.log +:programname,isequal,"htmlgend" ~ +:programname,startswith,"wv" /var/log/wview.log +:programname,startswith,"wv" ~ diff --git a/debian/arc_be2le.1 b/debian/arc_be2le.1 new file mode 100644 index 0000000..f53fa46 --- /dev/null +++ b/debian/arc_be2le.1 @@ -0,0 +1,43 @@ +.\" Hey, EMACS: -*- nroff -*- +.\" First parameter, NAME, should be all caps +.\" Second parameter, SECTION, should be 1-8, maybe w/ subsection +.\" other parameters are allowed: see man(7), man(1) +.TH ARC_BE2LE 1 "November 19, 2009" +.\" Please adjust this date whenever revising the manpage. +.\" +.\" Some roff macros, for reference: +.\" .nh disable hyphenation +.\" .hy enable hyphenation +.\" .ad l left justify +.\" .ad b justify to both left and right margins +.\" .nf disable filling +.\" .fi enable filling +.\" .br insert line break +.\" .sp insert n+1 empty lines +.\" for manpage-specific macros, see man(7) +.SH NAME +arc_be2le \- program to convert Weatherlink WLK files from big endian to little endian +.SH SYNOPSIS +.B arc_be2le +.RI source_directory dest_directory +.br +.SH DESCRIPTION +This manual page describes the use of +.B arc_be2le +. +It converts wview archive data in source_directory from big endian +to little endian then stores the result in dest_directory. +.SH OPTIONS +There are no options and source_directory and dest_directory are mandatory. +.TP +.SH SEE ALSO +.BR arc_le2be (1), +.BR wlk2sqlite (1), +.BR sqlite2wlk (1). +.BR /usr/share/doc/wview/wview-User-Manual.html +.br +.SH AUTHOR +arc_be2le was written by Mark Teel. +.PP +This manual page was written by Mark Teel , +for the Debian project (and may be used by others). diff --git a/debian/arc_le2be.1 b/debian/arc_le2be.1 new file mode 100644 index 0000000..63b4419 --- /dev/null +++ b/debian/arc_le2be.1 @@ -0,0 +1,43 @@ +.\" Hey, EMACS: -*- nroff -*- +.\" First parameter, NAME, should be all caps +.\" Second parameter, SECTION, should be 1-8, maybe w/ subsection +.\" other parameters are allowed: see man(7), man(1) +.TH ARC_LE2BE 1 "November 19, 2009" +.\" Please adjust this date whenever revising the manpage. +.\" +.\" Some roff macros, for reference: +.\" .nh disable hyphenation +.\" .hy enable hyphenation +.\" .ad l left justify +.\" .ad b justify to both left and right margins +.\" .nf disable filling +.\" .fi enable filling +.\" .br insert line break +.\" .sp insert n+1 empty lines +.\" for manpage-specific macros, see man(7) +.SH NAME +arc_le2be \- program to convert Weatherlink WLK files from little endian to big endian +.SH SYNOPSIS +.B arc_le2be +.RI source_directory dest_directory +.br +.SH DESCRIPTION +This manual page describes the use of +.B arc_le2be +. +It converts wview archive data in source_directory from little endian +to big endian then stores the result in dest_directory. +.SH OPTIONS +There are no options and source_directory and dest_directory are mandatory. +.TP +.SH SEE ALSO +.BR arc_be2le (1), +.BR wlk2sqlite (1), +.BR sqlite2wlk (1). +.BR /usr/share/doc/wview/wview-User-Manual.html +.br +.SH AUTHOR +arc_le2be was written by Mark Teel. +.PP +This manual page was written by Mark Teel , +for the Debian project (and may be used by others). diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 0000000..7108332 --- /dev/null +++ b/debian/changelog @@ -0,0 +1,234 @@ +wview (5.21.5-1) unstable; urgency=low + + * New upstream release + + -- Mark Teel Sun, 06 Apr 2014 11:30:00 -0600 + +wview (5.21.4-1) unstable; urgency=low + + * New upstream release + + -- Mark Teel Sun, 06 Apr 2014 10:30:00 -0600 + +wview (5.21.3-1) unstable; urgency=low + + * New upstream release + + -- Mark Teel Mon, 31 Mar 2014 16:30:00 -0600 + +wview (5.21.2-1) unstable; urgency=low + + * New upstream release + + -- Mark Teel Sun, 30 Mar 2014 10:30:00 -0600 + +wview (5.21.1-1) unstable; urgency=low + + * New upstream release + + -- Mark Teel Thu, 13 Mar 2014 10:30:00 -0600 + +wview (5.21.0-1) unstable; urgency=low + + * New upstream release + + -- Mark Teel Wed, 05 Mar 2014 10:30:00 -0600 + +wview (5.20.2-1) unstable; urgency=low + + * New upstream release + + -- Mark Teel Wed, 21 Mar 2012 10:30:00 -0600 + +wview (5.20.1-1) unstable; urgency=low + + * New upstream release + + -- Mark Teel Tue, 20 Mar 2012 10:30:00 -0600 + +wview (5.20.0-1) unstable; urgency=low + + * New upstream release + + -- Mark Teel Sat, 17 Mar 2012 10:30:00 -0600 + +wview (5.19.0-1) unstable; urgency=low + + * New upstream release + + -- Mark Teel Sun, 22 May 2011 10:30:00 -0600 + +wview (5.18.6-1) unstable; urgency=low + + * New upstream release + + -- Mark Teel Fri, 01 Apr 2011 10:30:00 -0600 + +wview (5.18.5-1) unstable; urgency=low + + * New upstream release + + -- Mark Teel Thu, 31 Mar 2011 18:30:00 -0600 + +wview (5.18.4-1) unstable; urgency=low + + * New upstream release + + -- Mark Teel Sat, 26 Mar 2011 10:30:00 -0600 + +wview (5.18.3-1) unstable; urgency=low + + * New upstream release + + -- Mark Teel Sun, 20 Mar 2011 16:30:00 -0600 + +wview (5.18.2-1) unstable; urgency=low + + * New upstream release + + -- Mark Teel Tue, 15 Mar 2011 6:30:00 -0600 + +wview (5.18.1-1) unstable; urgency=low + + * New upstream release + + -- Mark Teel Mon, 14 Mar 2011 14:30:00 -0600 + +wview (5.18.0-1) unstable; urgency=low + + * New upstream release + + -- Mark Teel Sun, 13 Mar 2011 10:30:00 -0600 + +wview (5.17.3-1) unstable; urgency=low + + * New upstream release + + -- Mark Teel Sun, 16 May 2010 10:30:00 -0600 + +wview (5.17.2-1) unstable; urgency=low + + * New upstream release + + -- Mark Teel Sun, 18 Apr 2010 20:30:00 -0600 + +wview (5.17.1-1) unstable; urgency=low + + * New upstream release + + -- Mark Teel Tue, 07 Apr 2010 18:30:00 -0600 + +wview (5.17.0-1) unstable; urgency=low + + * New upstream release + + -- Mark Teel Sun, 04 Apr 2010 21:30:00 -0600 + +wview (5.16.0-1) unstable; urgency=low + + * New upstream release + + -- Mark Teel Sun, 28 Mar 2010 14:30:00 -0600 + +wview (5.15.0-1) unstable; urgency=low + + * New upstream release + + -- Mark Teel Sun, 21 Mar 2010 19:30:00 -0600 + +wview (5.14.3-1) unstable; urgency=low + + * New upstream release + + -- Mark Teel Tue, 02 Mar 2010 17:30:00 -0600 + +wview (5.14.2-1) unstable; urgency=low + + * New upstream release + + -- Mark Teel Thu, 25 Feb 2010 17:30:00 -0600 + +wview (5.14.1-1) unstable; urgency=low + + * New upstream release + + -- Mark Teel Sun, 21 Feb 2010 22:30:00 -0600 + +wview (5.14.0-1) unstable; urgency=low + + * New upstream release + + -- Mark Teel Fri, 19 Feb 2010 10:30:00 -0600 + +wview (5.12.6-1) unstable; urgency=low + + * New upstream release + + -- Mark Teel Sun, 31 Jan 2010 10:30:00 -0600 + +wview (5.12.5-1) unstable; urgency=low + + * New upstream release + + -- Mark Teel Thu, 28 Jan 2010 10:30:00 -0600 + +wview (5.12.4-1) unstable; urgency=low + + * New upstream release + + -- Mark Teel Sun, 24 Jan 2010 10:30:00 -0600 + +wview (5.12.3-1) unstable; urgency=low + + * New upstream release + + -- Mark Teel Sat, 16 Jan 2010 10:30:00 -0600 + +wview (5.12.2-1) unstable; urgency=low + + * New upstream release + + -- Mark Teel Sat, 16 Jan 2010 10:30:00 -0600 + +wview (5.12.1-1) unstable; urgency=low + + * New upstream release + + -- Mark Teel Thu, 14 Jan 2010 10:30:00 -0600 + +wview (5.12.0-1) unstable; urgency=low + + * New upstream release + + -- Mark Teel Wed, 13 Jan 2010 22:30:00 -0600 + +wview (5.11.0-1) unstable; urgency=low + + * New upstream release + + -- Mark Teel Sat, 09 Jan 2010 10:30:00 -0600 + +wview (5.10.3-1) unstable; urgency=low + + * New upstream release + + -- Mark Teel Tue, 05 Jan 2010 21:30:00 -0600 + +wview (5.10.2-1) unstable; urgency=low + + * New upstream release + + -- Mark Teel Sun, 03 Jan 2010 15:00:00 -0600 + +wview (5.10.1-1) unstable; urgency=low + + * New upstream release + + -- Mark Teel Sun, 29 Dec 2009 06:05:00 -0600 + +wview (5.8.0-1) unstable; urgency=low + +* Initial release + + -- Mark Teel Sun, 06 Dec 2009 09:55:57 -0600 + diff --git a/debian/compat b/debian/compat new file mode 100644 index 0000000..7f8f011 --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +7 diff --git a/debian/conffiles b/debian/conffiles new file mode 100644 index 0000000..e69de29 diff --git a/debian/control b/debian/control new file mode 100644 index 0000000..40016c1 --- /dev/null +++ b/debian/control @@ -0,0 +1,19 @@ +Source: wview +Section: net +Priority: extra +Maintainer: Mark Teel +Build-Depends: debhelper (>= 7), autotools-dev, radlib-dev (>= 2.12), libgd2-noxpm-dev, libssl-dev, libcurl4-openssl-dev, sqlite3, libusb-1.0-0-dev +Standards-Version: 3.8.3 +Homepage: http://www.wviewweather.com + +Package: wview +Architecture: any +Depends: ${shlibs:Depends}, ${misc:Depends}, radlib-dev (>= 2.12), sqlite3, gawk, rsync, libcurl3, libusb-1.0-0 +Recommends: apache2, php5-sqlite +Description: unix weather application that generates web sites + wview is a collection of linux/unix daemons which interface with a supported + weather station to retrieve archive records (if generated by the station) and + current conditions. The archive records are stored in an SQLite3 database. At a + configurable interval, wview will utilize the archive history and current + conditions to generate weather images (buckets, dials and graphs) and HTML web + pages based on user-configurable HTML templates. diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 0000000..f270327 --- /dev/null +++ b/debian/copyright @@ -0,0 +1,18 @@ +This package was debianized by Mark Teel on +Thu, 19 Nov 2009 16:09:27 -0600. + +It was downloaded from http://www.wviewweather.com + +wview was written by Mark Teel + +Copyright: Copyright (C) 2001-2009 Mark Teel. All rights reserved. + +Licensed under GPL version 2, see `/usr/share/common-licenses/GPL-2'. + +The Debian packaging is: + + Copyright (C) 2009 Mark Teel + +and is licensed under the GPL version 3, +see `/usr/share/common-licenses/GPL-3'. + diff --git a/debian/dirs b/debian/dirs new file mode 100644 index 0000000..97db251 --- /dev/null +++ b/debian/dirs @@ -0,0 +1,6 @@ +usr/bin +var/lib +etc +usr/share +usr/share/doc +usr/share/doc/wview/ diff --git a/debian/docs b/debian/docs new file mode 100644 index 0000000..68c911d --- /dev/null +++ b/debian/docs @@ -0,0 +1,8 @@ +NEWS +README +TODO +wview-User-Manual.html +ChangeLog +UPGRADE +debian/copyright +debian/changelog diff --git a/debian/hilowcreate.1 b/debian/hilowcreate.1 new file mode 100644 index 0000000..2b00748 --- /dev/null +++ b/debian/hilowcreate.1 @@ -0,0 +1,39 @@ +.\" Hey, EMACS: -*- nroff -*- +.\" First parameter, NAME, should be all caps +.\" Second parameter, SECTION, should be 1-8, maybe w/ subsection +.\" other parameters are allowed: see man(7), man(1) +.TH HILOWCREATE 1 "November 19, 2009" +.\" Please adjust this date whenever revising the manpage. +.\" +.\" Some roff macros, for reference: +.\" .nh disable hyphenation +.\" .hy enable hyphenation +.\" .ad l left justify +.\" .ad b justify to both left and right margins +.\" .nf disable filling +.\" .fi enable filling +.\" .br insert line break +.\" .sp insert n+1 empty lines +.\" for manpage-specific macros, see man(7) +.SH NAME +hilowcreate \- program to create the wview HILOW database from the Archive database +.SH SYNOPSIS +.B hilowcreate +.RI source_directory +.br +.SH DESCRIPTION +This manual page describes the use of +.B hilowcreate +. +Create a wview HILOW database in source_directory/wview-hilow.sdb +using archive records in source_directory/wview-archive.sdb. +.BR Note: If wview-hilow.sdb already exists in source_directory, it will be overwritten. +.BR source_directory cannot be the live wview archive, copy wview-archive.sdb to a work directory. +.SH OPTIONS +There are no options and source_directory is mandatory. +.TP +.SH AUTHOR +hilowcreate was written by Mark Teel. +.PP +This manual page was written by Mark Teel , +for the Debian project (and may be used by others). diff --git a/debian/htmlgend.1 b/debian/htmlgend.1 new file mode 100644 index 0000000..eb1a613 --- /dev/null +++ b/debian/htmlgend.1 @@ -0,0 +1,42 @@ +.\" Hey, EMACS: -*- nroff -*- +.\" First parameter, NAME, should be all caps +.\" Second parameter, SECTION, should be 1-8, maybe w/ subsection +.\" other parameters are allowed: see man(7), man(1) +.TH HTMLGEND 1 "November 19, 2009" +.\" Please adjust this date whenever revising the manpage. +.\" +.\" Some roff macros, for reference: +.\" .nh disable hyphenation +.\" .hy enable hyphenation +.\" .ad l left justify +.\" .ad b justify to both left and right margins +.\" .nf disable filling +.\" .fi enable filling +.\" .br insert line break +.\" .sp insert n+1 empty lines +.\" for manpage-specific macros, see man(7) +.SH NAME +htmlgend \- wview HTML and file generation daemon +.SH SYNOPSIS +.B htmlgend +.RI +.br +.SH DESCRIPTION +This manual page describes the use of +.B htmlgend +. +htmlgend generates wview websites and other files (php, xml, etc.). It requires the appropriate wview station daemon. +.BR +.BR Note: This daemon should ONLY be launched from the /etc/init.d/wview script (typically started in your default runlevel). +.SH OPTIONS +There are no options. +.TP +.SH SEE ALSO +.BR /etc/init.d/wview, +.BR /usr/share/doc/wview/wview-User-Manual.html +.br +.SH AUTHOR +htmlgend was written by Mark Teel. +.PP +This manual page was written by Mark Teel , +for the Debian project (and may be used by others). diff --git a/debian/init.d b/debian/init.d new file mode 100644 index 0000000..e3e55ec --- /dev/null +++ b/debian/init.d @@ -0,0 +1,181 @@ +#!/bin/bash + +# add to the shared library search path +export LD_LIBRARY_PATH=/lib:/usr/local/lib:/usr/lib +CONF_DIRECTORY=/etc/wview +RUN_DIRECTORY=/var/lib/wview +WVIEW_INSTALL_DIR=/usr/bin +### BEGIN INIT INFO +# Provides: wview +# Required-Start: $local_fs $network $time $syslog +# Required-Stop: $local_fs $network $time $syslog +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: Start wview daemons at boot time +# Description: Start wview daemons at boot time. +### END INIT INFO +# config: $prefix/etc/wview +# pidfiles: $prefix/var/wview/*.pid +################################################################################ + +if [ -f $CONF_DIRECTORY/wview-user ]; then + WVIEW_USER=`cat $CONF_DIRECTORY/wview-user` +fi +: ${WVIEW_USER:=root} + +WVIEWD_FILE=`cat $CONF_DIRECTORY/wview-binary` +WVIEWD_BIN=$WVIEW_INSTALL_DIR/$WVIEWD_FILE +test -x $WVIEWD_BIN || exit 5 + +HTMLD_BIN=$WVIEW_INSTALL_DIR/htmlgend +test -x $HTMLD_BIN || exit 6 + +FTPD_BIN=$WVIEW_INSTALL_DIR/wviewftpd +test -x $FTPD_BIN || exit 7 + +SSHD_BIN=$WVIEW_INSTALL_DIR/wviewsshd +test -x $SSHD_BIN || exit 7 + +ALARMD_BIN=$WVIEW_INSTALL_DIR/wvalarmd +test -x $ALARMD_BIN || exit 8 + +CWOPD_BIN=$WVIEW_INSTALL_DIR/wvcwopd +test -x $CWOPD_BIN || exit 9 + +HTTP_BIN=$WVIEW_INSTALL_DIR/wvhttpd + +RADROUTER_BIN=$WVIEW_INSTALL_DIR/radmrouted + +PMOND_BIN=$WVIEW_INSTALL_DIR/wvpmond +test -x $PMOND_BIN || exit 10 + +RADROUTER_PID=$RUN_DIRECTORY/radmrouted.pid +WVIEWD_PID=$RUN_DIRECTORY/wviewd.pid +HTMLD_PID=$RUN_DIRECTORY/htmlgend.pid +FTPD_PID=$RUN_DIRECTORY/wviewftpd.pid +SSHD_PID=$RUN_DIRECTORY/wviewsshd.pid +ALARMD_PID=$RUN_DIRECTORY/wvalarmd.pid +CWOPD_PID=$RUN_DIRECTORY/wvcwopd.pid +HTTP_PID=$RUN_DIRECTORY/wvhttpd.pid +PMOND_PID=$RUN_DIRECTORY/wvpmond.pid + +wait_for_time_set() { + THOUSAND=1000 + CURRVAL=`date +%s` + while [ "$CURRVAL" -lt "$THOUSAND" ]; do + sleep 1 + CURRVAL=`date +%s` + done +} + +cleanup_pid_files() { + for pidfile in `ls -1 $RUN_DIRECTORY/*.pid 2>/dev/null`; do + testpid=`cat $pidfile`; + if [ -n "$testpid" ]; then + result=`ps --no-headers -o pid $testpid`; + else + result="" + fi; + if [ -z "$result" ]; then + echo "Removing stale PID file $pidfile"; + rm -f $pidfile; + fi; + done; +} + +case "$1" in + start) + cleanup_pid_files + wait_for_time_set + + echo "Starting wview daemons:" + + if [ -x $RADROUTER_BIN ]; then + start-stop-daemon --start --oknodo --pidfile $RADROUTER_PID \ + --chuid $WVIEW_USER --exec $RADROUTER_BIN 1 $RUN_DIRECTORY + + else + echo "Cannot find $RADROUTER_BIN - exiting!" + exit 10 + fi + sleep 1 + start-stop-daemon --start --oknodo --pidfile $WVIEWD_PID \ + --exec $WVIEWD_BIN --chuid $WVIEW_USER + sleep 1 + start-stop-daemon --start --oknodo --pidfile $HTMLD_PID \ + --exec $HTMLD_BIN --chuid $WVIEW_USER + start-stop-daemon --start --oknodo --pidfile $ALARMD_PID \ + --exec $ALARMD_BIN --chuid $WVIEW_USER + start-stop-daemon --start --oknodo --pidfile $CWOPD_PID \ + --exec $CWOPD_BIN --chuid $WVIEW_USER + start-stop-daemon --start --oknodo --pidfile $HTTP_PID \ + --exec $HTTP_BIN --chuid $WVIEW_USER + start-stop-daemon --start --oknodo --pidfile $FTPD_PID \ + --exec $FTPD_BIN --chuid $WVIEW_USER + start-stop-daemon --start --oknodo --pidfile $SSHD_PID \ + --exec $SSHD_BIN --chuid $WVIEW_USER + start-stop-daemon --start --oknodo --pidfile $PMOND_PID \ + --exec $PMOND_BIN --chuid $WVIEW_USER + ;; + start-trace) + echo "Starting wview daemons (tracing to $RUN_DIRECTORY):" + echo "Warning: traced processes run very slowly and may effect performance." + + if [ -x $RADROUTER_BIN ]; then + start-stop-daemon --start --oknodo --pidfile $RADROUTER_PID \ + --chuid $WVIEW_USER --exec $RADROUTER_BIN 1 $RUN_DIRECTORY + + else + echo "Cannot find $RADROUTER_BIN - exiting!" + exit 10 + fi + sleep 1 + strace -o $RUN_DIRECTORY/$WVIEWD_FILE.trace $WVIEWD_BIN -f &> /dev/null & + sleep 1 + strace -o $RUN_DIRECTORY/htmlgend.trace $HTMLD_BIN -f &> /dev/null & + strace -o $RUN_DIRECTORY/wvalarmd.trace $ALARMD_BIN -f &> /dev/null & + strace -o $RUN_DIRECTORY/wvcwopd.trace $CWOPD_BIN -f &> /dev/null & + strace -o $RUN_DIRECTORY/wvhttpd.trace $HTTP_BIN -f &> /dev/null & + strace -o $RUN_DIRECTORY/wviewftpd.trace $FTPD_BIN -f &> /dev/null & + strace -o $RUN_DIRECTORY/wviewsshd.trace $SSHD_BIN -f &> /dev/null & + strace -o $RUN_DIRECTORY/wvpmond.trace $PMOND_BIN -f &> /dev/null & + ;; + stop) + start-stop-daemon --stop --oknodo --pidfile $PMOND_PID \ + --exec $PMOND_BIN --signal 15 --retry 5 + start-stop-daemon --stop --oknodo --pidfile $HTTP_PID \ + --exec $HTTP_BIN --signal 15 --retry 5 + start-stop-daemon --stop --oknodo --pidfile $CWOPD_PID \ + --exec $CWOPD_BIN --signal 15 --retry 5 + start-stop-daemon --stop --oknodo --pidfile $ALARMD_PID \ + --exec $ALARMD_BIN --signal 15 --retry 5 + start-stop-daemon --stop --oknodo --pidfile $SSHD_PID \ + --exec $SSHD_BIN --signal 15 --retry 5 + start-stop-daemon --stop --oknodo --pidfile $FTPD_PID \ + --exec $FTPD_BIN --signal 15 --retry 5 + start-stop-daemon --stop --oknodo --pidfile $HTMLD_PID \ + --exec $HTMLD_BIN --signal 15 --retry 5 + start-stop-daemon --stop --oknodo --pidfile $WVIEWD_PID \ + --exec $WVIEWD_BIN --signal 15 --retry 5 + start-stop-daemon --stop --oknodo --pidfile $RADROUTER_PID \ + --exec $RADROUTER_BIN --signal 15 --retry 5 + ;; + restart) + $0 stop && sleep 2 + $0 start + ;; + force-reload) + $0 stop && sleep 2 + $0 start + ;; + status) + ps aux | grep "wv" + ps aux | grep "htmlgend" + ;; + *) + echo "Usage: $0 {start|start-trace|stop|restart|force-reload|status}" + exit 1 +esac + +exit 0 + diff --git a/debian/install b/debian/install new file mode 100644 index 0000000..e69de29 diff --git a/debian/postinst b/debian/postinst new file mode 100644 index 0000000..45bd019 --- /dev/null +++ b/debian/postinst @@ -0,0 +1,180 @@ +#!/bin/sh +# postinst script for wview +# +# see: dh_installdeb(1) + +set -e + +# summary of how this script can be called: +# * `configure' +# * `abort-upgrade' +# * `abort-remove' `in-favour' +# +# * `abort-remove' +# * `abort-deconfigure' `in-favour' +# `removing' +# +# for details, see http://www.debian.org/doc/debian-policy/ or +# the debian-policy package + + +case "$1" in + configure) + if [ ! -d /var/lib/wview ]; then + mkdir -p /var/lib/wview + mkdir -p /var/lib/wview/noaa + mkdir -p /var/lib/wview/alarms + mkdir -p /var/lib/wview/img + mkdir -p /var/lib/wview/img/Archive + mkdir -p /var/lib/wview/img/NOAA + mkdir -p /var/lib/wview/archive + cp -fp /usr/share/wview/var/wview/img/*.* /var/lib/wview/img + cp -fp /usr/share/wview/var/wview/archive/* /var/lib/wview/archive + /usr/bin/sqlite3 /var/lib/wview/archive/wview-archive.sdb '.read /var/lib/wview/archive/wview-archive.sql' + echo "==> /var/lib/wview has been created with distro examples" + else + echo "==> /var/lib/wview already exists - skipping default data installation" + fi + if [ ! -e /etc/wview/wview-conf.sdb ]; then + mkdir -p /etc/wview + mkdir -p /etc/wview/export + chmod 777 /etc/wview/export + mkdir -p /etc/wview/html + mkdir -p /etc/wview/html/chrome + mkdir -p /etc/wview/html/chrome/plus + mkdir -p /etc/wview/html/chrome/static + mkdir -p /etc/wview/html/chrome/standard + mkdir -p /etc/wview/html/classic + mkdir -p /etc/wview/html/classic/plus + mkdir -p /etc/wview/html/classic/static + mkdir -p /etc/wview/html/classic/standard + mkdir -p /etc/wview/html/exfoliation + mkdir -p /etc/wview/html/exfoliation/plus + mkdir -p /etc/wview/html/exfoliation/static + mkdir -p /etc/wview/html/exfoliation/standard + mkdir -p /etc/wview/alarms + cp -fp /usr/share/wview/etc/wview/*.* /etc/wview + chmod +x /etc/wview/pre-generate.sh + chmod +x /etc/wview/post-generate.sh + cp -fp /usr/share/wview/etc/wview/html/*.* /etc/wview/html + cp -fp /usr/share/wview/etc/wview/html/chrome/plus/*.* /etc/wview/html/chrome/plus + cp -fp /usr/share/wview/etc/wview/html/chrome/static/*.* /etc/wview/html/chrome/static + cp -fp /usr/share/wview/etc/wview/html/chrome/static/*.* /var/lib/wview/img + cp -fp /usr/share/wview/etc/wview/html/chrome/standard/*.* /etc/wview/html/chrome/standard + cp -fp /usr/share/wview/etc/wview/html/classic/plus/*.* /etc/wview/html/classic/plus + cp -fp /usr/share/wview/etc/wview/html/classic/static/*.* /etc/wview/html/classic/static + cp -fp /usr/share/wview/etc/wview/html/classic/standard/*.* /etc/wview/html/classic/standard + cp -fp /usr/share/wview/etc/wview/html/exfoliation/plus/*.* /etc/wview/html/exfoliation/plus + cp -fp /usr/share/wview/etc/wview/html/exfoliation/static/*.* /etc/wview/html/exfoliation/static + cp -fp /usr/share/wview/etc/wview/html/exfoliation/standard/*.* /etc/wview/html/exfoliation/standard + cp -fp /usr/share/wview/etc/wview/alarms/*.* /etc/wview/alarms + cp -fp /usr/share/wview/etc/wview/html/chrome/standard/awekas_wl.htx-us /etc/wview/html/awekas_wl.htx + /usr/bin/sqlite3 /etc/wview/wview-conf.sdb '.read /etc/wview/wview-conf.sql-deb-pkg' + echo "==> /etc/wview has been created with distro examples" + echo "" + echo "This is a first time install so the station type will be set to \"simulator\"." + echo "To change the station type, run \"wviewconfig\" or use the wviewmgmt web interface to change it." + echo "You will need to stop wview and clear the databases of simulator data after you have changed the station type:" + echo "#> wviewcleardata" + echo "" + echo "This is a first time install so the generation templates will be set to \"chrome-standard-US units\"." + echo "To change this run \"wviewhtmlconfig\" to configure the template directory" + echo "after you have configured wview for metric units and-or extended data." + echo "" + echo "If you have problems accessing the wviewmgmt site after installation, try restarting apache2:" + echo "#> sudo apache2ctl restart" + echo "so the PHP5-sqlite module gets loaded - this will never be required again." + echo "" + echo "If you want start/stop control of wview from the Management Web Site" + echo "(and you are comfortable giving the http server user account sudo privileges):" + echo "Add the http user to the sudo group:" + echo "#> sudo adduser www-data sudo" + echo "Set the sudo group to no password required privileges in /etc/sudoers:" + echo "#> sudo visudo" + echo "(Make sure the line [%sudo ALL=NOPASSWD: ALL] is at the bottom of the /etc/sudoers file)" + echo "" +# Assuming php5-sqlite was just installed, restart apache2 so it will load php5-sqlite module: + /usr/sbin/apache2ctl restart + else + echo "==> /etc/wview/wview-conf.sdb already exists - skipping default configuration installation" + cp -fp /usr/share/wview/etc/wview/wview-conf-update.sql /etc/wview + /usr/bin/sqlite3 /etc/wview/wview-conf.sdb '.read /etc/wview/wview-conf-update.sql' + fi + + echo "" + echo "To start/stop wview:" + echo "\"#> sudo /etc/init.d/wview start|stop\"" + echo "" + + if [ -e /var/lib/wviewmgmt/functions.php ]; then + rm -f /var/lib/wviewmgmt/functions.php + fi + ln -s /usr/bin/functions.php /var/lib/wviewmgmt/functions.php + + chmod 666 /etc/wview/wview-binary + chmod 666 /etc/wview/wview-conf.sdb + chmod 777 /etc/wview + +# Always rewrite the version file: + cp -f /usr/share/wview/etc/wview/wview-version /etc/wview + +# Create a few handy links (hard to know what the document root is for the http server): + if [ -e /var/www ]; then + if [ ! -e /var/www/wviewmgmt ]; then + ln -s /var/lib/wviewmgmt /var/www/wviewmgmt + fi + if [ ! -e /var/www/weather ]; then + ln -s /var/lib/wview/img /var/www/weather + fi + fi + if [ -e /srv ]; then + if [ ! -e /srv/wviewmgmt ]; then + ln -s /var/lib/wviewmgmt /srv/wviewmgmt + fi + if [ ! -e /srv/weather ]; then + ln -s /var/lib/wview/img /srv/weather + fi + fi + if [ -e /usr/local/www ]; then + if [ ! -e /usr/local/www/wviewmgmt ]; then + ln -s /var/lib/wviewmgmt /usr/local/www/wviewmgmt + fi + if [ ! -e /usr/local/www/weather ]; then + ln -s /var/lib/wview/img /usr/local/www/weather + fi + fi + +# Copy our rsyslog configuration file if rsyslog is installed: + if [ -e /etc/rsyslog.d ]; then + cp -fp /usr/share/wview/etc/rsyslog.d/99-wview.conf /etc/rsyslog.d + if [ ! -e /var/log/wview.log ]; then + touch /var/log/wview.log + chown syslog:adm /var/log/wview.log + chmod 664 /var/log/wview.log + /etc/init.d/rsyslog restart + fi + echo "wview logs will be found at: /var/log/wview.log" + fi + +# Copy our logrotate configuration file if logrotate is installed: + if [ -e /etc/logrotate.d ]; then + cp -fp /usr/share/wview/etc/logrotate.d/wview-logrotate /etc/logrotate.d/wview + echo "You may want to restart logrotate so wview rotate settings will take effect." + fi + ;; + + abort-upgrade|abort-remove|abort-deconfigure|deconfigure) + ;; + + *) + echo "postinst called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +# dh_installdeb will replace this with shell code automatically +# generated by other debhelper scripts. + +#DEBHELPER# + +exit 0 diff --git a/debian/postrm b/debian/postrm new file mode 100644 index 0000000..4dab846 --- /dev/null +++ b/debian/postrm @@ -0,0 +1,81 @@ +#!/bin/sh +# postrm script for wview +# +# see: dh_installdeb(1) + +set -e + +# summary of how this script can be called: +# * `remove' +# * `purge' +# * `upgrade' +# * `failed-upgrade' +# * `abort-install' +# * `abort-install' +# * `abort-upgrade' +# * `disappear' +# +# for details, see http://www.debian.org/doc/debian-policy/ or +# the debian-policy package + + +case "$1" in + purge|failed-upgrade|abort-install|abort-upgrade|disappear) + if [ -e /var/www/wviewmgmt ]; then + rm -f /var/www/wviewmgmt + fi + if [ -e /var/www/weather ]; then + rm -f /var/www/weather + fi + echo -n "Delete accumulated wview weather data? (Y/N): " + read INVAL + if [ "x" = "x$INVAL" ]; then + ANSWER=N + elif [ "$INVAL" = "Y" ]; then + ANSWER=Y + elif [ "$INVAL" = "y" ]; then + ANSWER=Y + else + ANSWER=N + fi + if [ "$ANSWER" = "Y" ]; then + rm -rf /var/lib/wview + fi + echo -n "Delete wview configuration and possible html customizations? (Y/N): " + read INVAL + if [ "x" = "x$INVAL" ]; then + ANSWER=N + elif [ "$INVAL" = "Y" ]; then + ANSWER=Y + elif [ "$INVAL" = "y" ]; then + ANSWER=Y + else + ANSWER=N + fi + if [ "$ANSWER" = "Y" ]; then + rm -rf /etc/wview + fi + rm -rf /var/lib/wviewmgmt + ;; + + remove) + rm -f /var/www/wviewmgmt + rm -f /var/www/weather + rm -rf /var/lib/wviewmgmt + ;; + + upgrade) + ;; + + *) + echo "postrm called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +# dh_installdeb will replace this with shell code automatically +# generated by other debhelper scripts. + +#DEBHELPER# + +exit 0 diff --git a/debian/preinst b/debian/preinst new file mode 100644 index 0000000..d397534 --- /dev/null +++ b/debian/preinst @@ -0,0 +1,35 @@ +#!/bin/sh +# preinst script for wview +# +# see: dh_installdeb(1) + +set -e + +# summary of how this script can be called: +# * `install' +# * `install' +# * `upgrade' +# * `abort-upgrade' +# for details, see http://www.debian.org/doc/debian-policy/ or +# the debian-policy package + + +case "$1" in + install|upgrade) + ;; + + abort-upgrade) + ;; + + *) + echo "preinst called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +# dh_installdeb will replace this with shell code automatically +# generated by other debhelper scripts. + +#DEBHELPER# + +exit 0 diff --git a/debian/rules b/debian/rules new file mode 100755 index 0000000..6a0dd52 --- /dev/null +++ b/debian/rules @@ -0,0 +1,143 @@ +#!/usr/bin/make -f +# -*- makefile -*- + +# Uncomment this to turn on verbose mode. +#export DH_VERBOSE=1 + +# This has to be exported to make some magic below work. +export DH_OPTIONS + +# These are used for cross-compiling and for saving the configure script +# from having to guess our platform (since we know it already) +DEB_HOST_GNU_TYPE ?= $(shell dpkg-architecture -qDEB_HOST_GNU_TYPE) +DEB_BUILD_GNU_TYPE ?= $(shell dpkg-architecture -qDEB_BUILD_GNU_TYPE) +ifneq ($(DEB_HOST_GNU_TYPE),$(DEB_BUILD_GNU_TYPE)) +CROSS= --build $(DEB_BUILD_GNU_TYPE) --host $(DEB_HOST_GNU_TYPE) +else +CROSS= --build $(DEB_BUILD_GNU_TYPE) +endif + + + +config.status: configure + dh_testdir + # Add here commands to configure the package. +ifneq "$(wildcard /usr/share/misc/config.sub)" "" + cp -f /usr/share/misc/config.sub config.sub +endif +ifneq "$(wildcard /usr/share/misc/config.guess)" "" + cp -f /usr/share/misc/config.guess config.guess +endif + ./configure $(CROSS) \ + --prefix= \ + --exec-prefix=/usr \ + --sysconfdir=/etc \ + --localstatedir=/var/lib \ + --mandir=/usr/share/man \ + --infodir=/usr/share/info \ + --enable-install-dpkg=yes \ + CFLAGS="$(CFLAGS)" LDFLAGS="-Wl,-z,defs" + + +#Architecture +build: build-arch + +build-arch: build-arch-stamp +build-arch-stamp: config.status + + # Add here commands to compile the arch part of the package. + #$(MAKE) + touch $@ + +clean: + dh_testdir + dh_testroot + rm -f build-arch-stamp + + # Add here commands to clean up after the build process. + [ ! -f Makefile ] || $(MAKE) distclean + rm -f config.sub config.guess + + dh_clean + +install: install-arch +install-arch: + dh_testdir + dh_testroot + dh_prep -s + dh_installdirs -s + + # Add here commands to install the arch part of the package into + # debian/tmp. + $(MAKE) DESTDIR=$(CURDIR)/debian/wview install + + dh_install -s +# Must not depend on anything. This is to be called by +# binary-arch +# in another 'make' thread. +binary-common: + dh_testdir + dh_testroot + dh_installchangelogs ChangeLog + dh_installdocs + dh_installexamples +# dh_installmenu +# dh_installdebconf +# dh_installlogrotate +# dh_installemacsen +# dh_installpam +# dh_installmime +# dh_python + dh_installinit --update-rcd-params="defaults 99 10" +# dh_installcron +# dh_installinfo + dh_installman \ + debian/arc_be2le.1 \ + debian/arc_le2be.1 \ + debian/hilowcreate.1 \ + debian/sqlite2wlk.1 \ + debian/wlk2sqlite.1 \ + debian/wview-mysql-create.1 \ + debian/wview-mysql-export.1 \ + debian/wview-pgsql-create.1 \ + debian/wview-pgsql-export.1 \ + debian/wviewconfig.1 \ + debian/wviewhtmlconfig.1 \ + debian/wviewcleardata.1 \ + debian/htmlgend.1 \ + debian/wvalarmd.1 \ + debian/wvcwopd.1 \ + debian/wvhttpd.1 \ + debian/wviewftpd.1 \ + debian/wviewsshd.1 \ + debian/wvpmond.1 \ + debian/wviewd_sim.1 \ + debian/wviewd_vpro.1 \ + debian/wviewd_wmr918.1 \ + debian/wviewd_wxt510.1 \ + debian/wviewd_ws2300.1 \ + debian/vpconfig.1 \ + debian/vpinstall.1 \ + debian/wxt510config.1 + dh_link + dh_strip + dh_compress + dh_fixperms +# dh_perl + dh_makeshlibs + dh_installdeb + dh_shlibdeps -- --ignore-missing-info + dh_gencontrol + dh_md5sums + dh_builddeb + +# Keep this around to avoid lintian warnings: +binary-indep: + +# Build architecture dependant packages using the common target. +binary-arch: build-arch install-arch + $(MAKE) -f debian/rules DH_OPTIONS=-s binary-common + +binary: binary-arch +.PHONY: build clean binary-arch binary install install-arch + diff --git a/debian/sqlite2wlk.1 b/debian/sqlite2wlk.1 new file mode 100644 index 0000000..6e84f0b --- /dev/null +++ b/debian/sqlite2wlk.1 @@ -0,0 +1,43 @@ +.\" Hey, EMACS: -*- nroff -*- +.\" First parameter, NAME, should be all caps +.\" Second parameter, SECTION, should be 1-8, maybe w/ subsection +.\" other parameters are allowed: see man(7), man(1) +.TH SQLITE2WLK 1 "November 19, 2009" +.\" Please adjust this date whenever revising the manpage. +.\" +.\" Some roff macros, for reference: +.\" .nh disable hyphenation +.\" .hy enable hyphenation +.\" .ad l left justify +.\" .ad b justify to both left and right margins +.\" .nf disable filling +.\" .fi enable filling +.\" .br insert line break +.\" .sp insert n+1 empty lines +.\" for manpage-specific macros, see man(7) +.SH NAME +sqlite2wlk \- program to convert wview SQLite3 archive records to the Weatherlink WLK file format +.SH SYNOPSIS +.B sqlite2wlk +.RI source_directory dest_directory +.br +.SH DESCRIPTION +This manual page describes the use of +.B sqlite2wlk +. +Convert wview archive data in source_directory/wview-archive.sdb to Weatherlink WLK files in +dest_directory. +.BR Note: If Weatherlink WLK files exist in dest_directory, they will be overwritten. +.BR If source_directory is the live wview archive, you will need root access. +.SH OPTIONS +There are no options and source_directory and dest_directory are mandatory. +.TP +.SH SEE ALSO +.BR wlk2sqlite (1), +.BR /usr/share/doc/wview/wview-User-Manual.html +.br +.SH AUTHOR +sqlite2wlk was written by Mark Teel. +.PP +This manual page was written by Mark Teel , +for the Debian project (and may be used by others). diff --git a/debian/vpconfig.1 b/debian/vpconfig.1 new file mode 100644 index 0000000..af82416 --- /dev/null +++ b/debian/vpconfig.1 @@ -0,0 +1,78 @@ +.\" Hey, EMACS: -*- nroff -*- +.\" First parameter, NAME, should be all caps +.\" Second parameter, SECTION, should be 1-8, maybe w/ subsection +.\" other parameters are allowed: see man(7), man(1) +.TH VPCONFIG 1 "November 19, 2009" +.\" Please adjust this date whenever revising the manpage. +.\" +.\" Some roff macros, for reference: +.\" .nh disable hyphenation +.\" .hy enable hyphenation +.\" .ad l left justify +.\" .ad b justify to both left and right margins +.\" .nf disable filling +.\" .fi enable filling +.\" .br insert line break +.\" .sp insert n+1 empty lines +.\" for manpage-specific macros, see man(7) +.SH NAME +vpconfig \- wview utility program to configure an attached Vantage Pro weather station +.SH SYNOPSIS +.B vpconfig +.RI station_device command [cmnd_args] +.br +.SH DESCRIPTION +This manual page describes the use of +.B vpconfig +. +Configure an attached Vantage Pro weather station (or display current configuration). +.BR station_device - serial device the VP console is connected to: + FreeBSD: /dev/cuaa0 - /dev/cuaa4 + Linux: /dev/ttyS0 - /dev/ttyS4 + Linux USB: /dev/ttyUSB0 + Ethernet: host:port +.BR command - command to execute, one of: + show + - retrieve and display VP console config + cleararchive + - clear the archive memory without changing the interval + setinterval + - set the archive interval (this clears the archive memory) + setelevation + - set the station elevation (feet) - use this to adjust the barometer + setgain <0 for off, 1 for on> + - sets the gain of the radio receiver + setlatlong + - set the station latitude and longitude in tenths of a degree + setrainseasonstart + - set the month that the yearly rain total begins + setyeartodaterain <0.00 - 200.00> + - set the yearly rain total to date + setyeartodateET <0.00 - 200.00> + - set the yearly Evapotranspiration total to date + setwinddirectioncal <-360 - 360> + - set the station wind direction calibration + setsensor <1 - 8> + - set type of sensor to listen to on the given channel + type is one of: iss temp hum temp_hum + wind rain leaf soil leaf_soil + sensorlink none + (this clears the archive memory) + setretransmit <0 - 8> + - set channel that the station uses to retransmit data (0 is off) + (this clears the archive memory) + setraincollectorsize <0.01in 0.2mm 0.1mm> + - set the amount of rain that tips the collector once + setwindcupsize <0 for small, 1 for large> + - set the size of the anenometer cup (1 is standard) +.SH OPTIONS +station_device and command are mandatory. +.TP +.SH SEE ALSO +.BR /usr/share/doc/wview/wview-User-Manual.html +.br +.SH AUTHOR +vpconfig was written by Mark Teel. +.PP +This manual page was written by Mark Teel , +for the Debian project (and may be used by others). diff --git a/debian/vpinstall.1 b/debian/vpinstall.1 new file mode 100644 index 0000000..78a41e8 --- /dev/null +++ b/debian/vpinstall.1 @@ -0,0 +1,43 @@ +.\" Hey, EMACS: -*- nroff -*- +.\" First parameter, NAME, should be all caps +.\" Second parameter, SECTION, should be 1-8, maybe w/ subsection +.\" other parameters are allowed: see man(7), man(1) +.TH VPINSTALL 1 "November 19, 2009" +.\" Please adjust this date whenever revising the manpage. +.\" +.\" Some roff macros, for reference: +.\" .nh disable hyphenation +.\" .hy enable hyphenation +.\" .ad l left justify +.\" .ad b justify to both left and right margins +.\" .nf disable filling +.\" .fi enable filling +.\" .br insert line break +.\" .sp insert n+1 empty lines +.\" for manpage-specific macros, see man(7) +.SH NAME +vpinstall \- script to configure an attached Vantage Pro for wview installation +.SH SYNOPSIS +.B vpinstall +.RI +.br +.SH DESCRIPTION +This manual page describes the use of +.B vpinstall +. +Installation configuration of a wview Vantage Pro installation. +Utilizes the vpconfig utility to configure the station. +.BR +.BR Note: must be executed on the wview server. +.SH OPTIONS +There are no options. +.TP +.SH SEE ALSO +.BR vpconfig (1), +.BR /usr/share/doc/wview/wview-User-Manual.html +.br +.SH AUTHOR +vpinstall was written by Mark Teel. +.PP +This manual page was written by Mark Teel , +for the Debian project (and may be used by others). diff --git a/debian/wlk2sqlite.1 b/debian/wlk2sqlite.1 new file mode 100644 index 0000000..96df828 --- /dev/null +++ b/debian/wlk2sqlite.1 @@ -0,0 +1,44 @@ +.\" Hey, EMACS: -*- nroff -*- +.\" First parameter, NAME, should be all caps +.\" Second parameter, SECTION, should be 1-8, maybe w/ subsection +.\" other parameters are allowed: see man(7), man(1) +.TH WLK2SQLITE 1 "November 19, 2009" +.\" Please adjust this date whenever revising the manpage. +.\" +.\" Some roff macros, for reference: +.\" .nh disable hyphenation +.\" .hy enable hyphenation +.\" .ad l left justify +.\" .ad b justify to both left and right margins +.\" .nf disable filling +.\" .fi enable filling +.\" .br insert line break +.\" .sp insert n+1 empty lines +.\" for manpage-specific macros, see man(7) +.SH NAME +wlk2sqlite \- program to convert Weatherlink WLK archive files to wview SQLite3 archive records +.SH SYNOPSIS +.B wlk2sqlite +.RI source_directory [dest_directory] +.br +.SH DESCRIPTION +This manual page describes the use of +.B wlk2sqlite +. +Convert WLK archive file data in source_directory to SQLite3 format in +[destination_directory]/wview-archive.sdb or to $localstatedir/var/wview/archive/wview-archive.sdb +if [destination_directory] is not given. Only unique records will be inserted. +.BR Note: Must be run as root if [destination_directory] is not given. +.BR Note: source_directory can be the same as [destination_directory]. +.SH OPTIONS +There are no options and source_directory is mandatory. +.TP +.SH SEE ALSO +.BR sqlite2wlk (1), +.BR /usr/share/doc/wview/wview-User-Manual.html +.br +.SH AUTHOR +wlk2sqlite was written by Mark Teel. +.PP +This manual page was written by Mark Teel , +for the Debian project (and may be used by others). diff --git a/debian/wvalarmd.1 b/debian/wvalarmd.1 new file mode 100644 index 0000000..ff6952f --- /dev/null +++ b/debian/wvalarmd.1 @@ -0,0 +1,43 @@ +.\" Hey, EMACS: -*- nroff -*- +.\" First parameter, NAME, should be all caps +.\" Second parameter, SECTION, should be 1-8, maybe w/ subsection +.\" other parameters are allowed: see man(7), man(1) +.TH WVALARMD 1 "November 19, 2009" +.\" Please adjust this date whenever revising the manpage. +.\" +.\" Some roff macros, for reference: +.\" .nh disable hyphenation +.\" .hy enable hyphenation +.\" .ad l left justify +.\" .ad b justify to both left and right margins +.\" .nf disable filling +.\" .fi enable filling +.\" .br insert line break +.\" .sp insert n+1 empty lines +.\" for manpage-specific macros, see man(7) +.SH NAME +wvalarmd \- wview alarm notification and data feed server daemon +.SH SYNOPSIS +.B wvalarmd +.RI +.br +.SH DESCRIPTION +This manual page describes the use of +.B wvalarmd +. +The daemon wvalarmd processes alarm notifications and serves data feed socket clients. +It requires the appropriate wview station daemon. +.BR +.BR Note: This daemon should ONLY be launched from the /etc/init.d/wview script (typically started in your default runlevel). +.SH OPTIONS +There are no options. +.TP +.SH SEE ALSO +.BR /etc/init.d/wview, +.BR /usr/share/doc/wview/wview-User-Manual.html +.br +.SH AUTHOR +wvalarmd was written by Mark Teel. +.PP +This manual page was written by Mark Teel , +for the Debian project (and may be used by others). diff --git a/debian/wvcwopd.1 b/debian/wvcwopd.1 new file mode 100644 index 0000000..5ccb9dc --- /dev/null +++ b/debian/wvcwopd.1 @@ -0,0 +1,43 @@ +.\" Hey, EMACS: -*- nroff -*- +.\" First parameter, NAME, should be all caps +.\" Second parameter, SECTION, should be 1-8, maybe w/ subsection +.\" other parameters are allowed: see man(7), man(1) +.TH WVCWOPD 1 "November 19, 2009" +.\" Please adjust this date whenever revising the manpage. +.\" +.\" Some roff macros, for reference: +.\" .nh disable hyphenation +.\" .hy enable hyphenation +.\" .ad l left justify +.\" .ad b justify to both left and right margins +.\" .nf disable filling +.\" .fi enable filling +.\" .br insert line break +.\" .sp insert n+1 empty lines +.\" for manpage-specific macros, see man(7) +.SH NAME +wvcwopd \- wview Citizens Weather Observer Program (CWOP) daemon +.SH SYNOPSIS +.B wvcwopd +.RI +.br +.SH DESCRIPTION +This manual page describes the use of +.B wvcwopd +. +The daemon wvcwopd posts wview weather data updates for a registered CWOP station ID. +It requires the appropriate wview station daemon. +.BR +.BR Note: This daemon should ONLY be launched from the /etc/init.d/wview script (typically started in your default runlevel). +.SH OPTIONS +There are no options. +.TP +.SH SEE ALSO +.BR /etc/init.d/wview, +.BR /usr/share/doc/wview/wview-User-Manual.html +.br +.SH AUTHOR +wvcwopd was written by Mark Teel. +.PP +This manual page was written by Mark Teel , +for the Debian project (and may be used by others). diff --git a/debian/wvhttpd.1 b/debian/wvhttpd.1 new file mode 100644 index 0000000..9fda035 --- /dev/null +++ b/debian/wvhttpd.1 @@ -0,0 +1,43 @@ +.\" Hey, EMACS: -*- nroff -*- +.\" First parameter, NAME, should be all caps +.\" Second parameter, SECTION, should be 1-8, maybe w/ subsection +.\" other parameters are allowed: see man(7), man(1) +.TH WVHTTPD 1 "November 19, 2009" +.\" Please adjust this date whenever revising the manpage. +.\" +.\" Some roff macros, for reference: +.\" .nh disable hyphenation +.\" .hy enable hyphenation +.\" .ad l left justify +.\" .ad b justify to both left and right margins +.\" .nf disable filling +.\" .fi enable filling +.\" .br insert line break +.\" .sp insert n+1 empty lines +.\" for manpage-specific macros, see man(7) +.SH NAME +wvhttpd \- wview HTTP services daemon +.SH SYNOPSIS +.B wvhttpd +.RI +.br +.SH DESCRIPTION +This manual page describes the use of +.B wvhttpd +. +wvhttpd posts wview weather data updates for a registered Weather Underground and/or Weather For You station ID. +It requires the appropriate wview station daemon. +.BR +.BR Note: This daemon should ONLY be launched from the /etc/init.d/wview script (typically started in your default runlevel). +.SH OPTIONS +There are no options. +.TP +.SH SEE ALSO +.BR /etc/init.d/wview, +.BR /usr/share/doc/wview/wview-User-Manual.html +.br +.SH AUTHOR +wvhttpd was written by Mark Teel. +.PP +This manual page was written by Mark Teel , +for the Debian project (and may be used by others). diff --git a/debian/wview-logrotate b/debian/wview-logrotate new file mode 100644 index 0000000..f44553d --- /dev/null +++ b/debian/wview-logrotate @@ -0,0 +1,9 @@ +/var/log/wview.log { + weekly + compress + rotate 4 + missingok + notifempty + nocreate + copytruncate +} diff --git a/debian/wview-mysql-create.1 b/debian/wview-mysql-create.1 new file mode 100644 index 0000000..4a095af --- /dev/null +++ b/debian/wview-mysql-create.1 @@ -0,0 +1,48 @@ +.\" Hey, EMACS: -*- nroff -*- +.\" First parameter, NAME, should be all caps +.\" Second parameter, SECTION, should be 1-8, maybe w/ subsection +.\" other parameters are allowed: see man(7), man(1) +.TH WVIEW-MYSQL-CREATE 1 "November 19, 2009" +.\" Please adjust this date whenever revising the manpage. +.\" +.\" Some roff macros, for reference: +.\" .nh disable hyphenation +.\" .hy enable hyphenation +.\" .ad l left justify +.\" .ad b justify to both left and right margins +.\" .nf disable filling +.\" .fi enable filling +.\" .br insert line break +.\" .sp insert n+1 empty lines +.\" for manpage-specific macros, see man(7) +.SH NAME +wview-mysql-create \- script to create the MySQL Database, tables and user on local or remote server for wview export +.SH SYNOPSIS +.B wview-mysql-create +.RI db_admin db_admin_passwd db_user db_passwd db_name +.br +.SH DESCRIPTION +This manual page describes the use of +.B wview-mysql-create +. +Create the MySQL Database, tables and user on local or remote server for wview export. +.BR db_admin - MySQL server admin with create and grant privileges. +.BR db_admin_passwd - MySQL server admin password. +.BR db_user - MySQL server user for export acivity (can be same as db_admin). +.BR db_passwd - MySQL server password. +.BR db_name - MySQL database name to create and grant privileges for. +.BR +.BR Note: REQUIRES: mysql CLI utility for database creation. +.BR Note: must be executed on the target MySQL server. +.SH OPTIONS +There are no options and db_admin, db_admin_passwd, db_user, db_passwd, db_name are all mandatory. +.TP +.SH SEE ALSO +.BR wview-mysql-export (1), +.BR /usr/share/doc/wview/wview-User-Manual.html +.br +.SH AUTHOR +wview-mysql-create was written by Mark Teel. +.PP +This manual page was written by Mark Teel , +for the Debian project (and may be used by others). diff --git a/debian/wview-mysql-export.1 b/debian/wview-mysql-export.1 new file mode 100644 index 0000000..d468a85 --- /dev/null +++ b/debian/wview-mysql-export.1 @@ -0,0 +1,44 @@ +.\" Hey, EMACS: -*- nroff -*- +.\" First parameter, NAME, should be all caps +.\" Second parameter, SECTION, should be 1-8, maybe w/ subsection +.\" other parameters are allowed: see man(7), man(1) +.TH WVIEW-MYSQL-EXPORT 1 "November 19, 2009" +.\" Please adjust this date whenever revising the manpage. +.\" +.\" Some roff macros, for reference: +.\" .nh disable hyphenation +.\" .hy enable hyphenation +.\" .ad l left justify +.\" .ad b justify to both left and right margins +.\" .nf disable filling +.\" .fi enable filling +.\" .br insert line break +.\" .sp insert n+1 empty lines +.\" for manpage-specific macros, see man(7) +.SH NAME +wview-mysql-export \- script to export wview SQLite3 data to a MySQL Database on local or remote server +.SH SYNOPSIS +.B wview-mysql-export +.RI +.br +.SH DESCRIPTION +This manual page describes the use of +.B wview-mysql-export +. +Export wview weather data (archive and HILOW) to a local or remote MySQL server. +.BR +.BR Note: REQUIRES: mysqlimport CLI utility for database export. +.BR Note: must be executed on the source wview server. +.BR Note: Usually executed as a cron job. +.SH OPTIONS +There are no options. +.TP +.SH SEE ALSO +.BR wview-mysql-create (1), +.BR /usr/share/doc/wview/wview-User-Manual.html +.br +.SH AUTHOR +wview-mysql-export was written by Mark Teel. +.PP +This manual page was written by Mark Teel , +for the Debian project (and may be used by others). diff --git a/debian/wview-pgsql-create.1 b/debian/wview-pgsql-create.1 new file mode 100644 index 0000000..58e1f43 --- /dev/null +++ b/debian/wview-pgsql-create.1 @@ -0,0 +1,44 @@ +.\" Hey, EMACS: -*- nroff -*- +.\" First parameter, NAME, should be all caps +.\" Second parameter, SECTION, should be 1-8, maybe w/ subsection +.\" other parameters are allowed: see man(7), man(1) +.TH WVIEW-PGSQL-CREATE 1 "November 19, 2009" +.\" Please adjust this date whenever revising the manpage. +.\" +.\" Some roff macros, for reference: +.\" .nh disable hyphenation +.\" .hy enable hyphenation +.\" .ad l left justify +.\" .ad b justify to both left and right margins +.\" .nf disable filling +.\" .fi enable filling +.\" .br insert line break +.\" .sp insert n+1 empty lines +.\" for manpage-specific macros, see man(7) +.SH NAME +wview-pgsql-create \- script to create the PostGreSQL Database, tables and user on local or remote server for wview export +.SH SYNOPSIS +.B wview-pgsql-create +.RI db_name +.br +.SH DESCRIPTION +This manual page describes the use of +.B wview-pgsql-create +. +Create the PostGreSQL Database, tables and user on local or remote server for wview export. +.BR db_name - PostGreSQL database name to create and grant privileges for. +.BR +.BR Note: REQUIRES: psql CLI utility for database creation. +.BR Note: must be executed on the target PostGreSQL server. +.SH OPTIONS +There are no options and db_name is mandatory. +.TP +.SH SEE ALSO +.BR wview-pgsql-export (1), +.BR /usr/share/doc/wview/wview-User-Manual.html +.br +.SH AUTHOR +wview-pgsql-create was written by Mark Teel. +.PP +This manual page was written by Mark Teel , +for the Debian project (and may be used by others). diff --git a/debian/wview-pgsql-export.1 b/debian/wview-pgsql-export.1 new file mode 100644 index 0000000..4fb80dc --- /dev/null +++ b/debian/wview-pgsql-export.1 @@ -0,0 +1,44 @@ +.\" Hey, EMACS: -*- nroff -*- +.\" First parameter, NAME, should be all caps +.\" Second parameter, SECTION, should be 1-8, maybe w/ subsection +.\" other parameters are allowed: see man(7), man(1) +.TH WVIEW-PGSQL-EXPORT 1 "November 19, 2009" +.\" Please adjust this date whenever revising the manpage. +.\" +.\" Some roff macros, for reference: +.\" .nh disable hyphenation +.\" .hy enable hyphenation +.\" .ad l left justify +.\" .ad b justify to both left and right margins +.\" .nf disable filling +.\" .fi enable filling +.\" .br insert line break +.\" .sp insert n+1 empty lines +.\" for manpage-specific macros, see man(7) +.SH NAME +wview-pgsql-export \- script to export wview SQLite3 data to a PostGreSQL Database on local or remote server +.SH SYNOPSIS +.B wview-pgsql-export +.RI +.br +.SH DESCRIPTION +This manual page describes the use of +.B wview-pgsql-export +. +Export wview weather data (archive and HILOW) to a local or remote PostGreSQL server. +.BR +.BR Note: REQUIRES: psql CLI utility for database export. +.BR Note: must be executed on the source wview server. +.BR Note: Usually executed as a cron job. +.SH OPTIONS +There are no options. +.TP +.SH SEE ALSO +.BR wview-pgsql-create (1), +.BR /usr/share/doc/wview/wview-User-Manual.html +.br +.SH AUTHOR +wview-pgsql-export was written by Mark Teel. +.PP +This manual page was written by Mark Teel , +for the Debian project (and may be used by others). diff --git a/debian/wviewcleardata.1 b/debian/wviewcleardata.1 new file mode 100644 index 0000000..b222111 --- /dev/null +++ b/debian/wviewcleardata.1 @@ -0,0 +1,43 @@ +.\" Hey, EMACS: -*- nroff -*- +.\" First parameter, NAME, should be all caps +.\" Second parameter, SECTION, should be 1-8, maybe w/ subsection +.\" other parameters are allowed: see man(7), man(1) +.TH WVIEWCLEARDATA 1 "January 24, 2010" +.\" Please adjust this date whenever revising the manpage. +.\" +.\" Some roff macros, for reference: +.\" .nh disable hyphenation +.\" .hy enable hyphenation +.\" .ad l left justify +.\" .ad b justify to both left and right margins +.\" .nf disable filling +.\" .fi enable filling +.\" .br insert line break +.\" .sp insert n+1 empty lines +.\" for manpage-specific macros, see man(7) +.SH NAME +wviewcleardata \- script to purge all wview weather data +.SH SYNOPSIS +.B wviewcleardata +.RI +.br +.SH DESCRIPTION +This manual page describes the use of +.B wviewcleardata +. +Purge all weather data stored by a wview installation. +.BR +.BR Note: wview should not be running when running this script. +.BR Note: must be executed on the source wview server. +.SH OPTIONS +There are no options. +.TP +.SH SEE ALSO +.BR wviewconfig (1), +.BR /usr/share/doc/wview/wview-User-Manual.html +.br +.SH AUTHOR +wviewcleardata was written by Mark Teel. +.PP +This manual page was written by Mark Teel , +for the Debian project (and may be used by others). diff --git a/debian/wviewconfig.1 b/debian/wviewconfig.1 new file mode 100644 index 0000000..14f41d0 --- /dev/null +++ b/debian/wviewconfig.1 @@ -0,0 +1,43 @@ +.\" Hey, EMACS: -*- nroff -*- +.\" First parameter, NAME, should be all caps +.\" Second parameter, SECTION, should be 1-8, maybe w/ subsection +.\" other parameters are allowed: see man(7), man(1) +.TH WVIEWCONFIG 1 "November 19, 2009" +.\" Please adjust this date whenever revising the manpage. +.\" +.\" Some roff macros, for reference: +.\" .nh disable hyphenation +.\" .hy enable hyphenation +.\" .ad l left justify +.\" .ad b justify to both left and right margins +.\" .nf disable filling +.\" .fi enable filling +.\" .br insert line break +.\" .sp insert n+1 empty lines +.\" for manpage-specific macros, see man(7) +.SH NAME +wviewconfig \- script to configure a wview installation +.SH SYNOPSIS +.B wviewconfig +.RI +.br +.SH DESCRIPTION +This manual page describes the use of +.B wviewconfig +. +Configure a wview installation. +.BR +.BR Note: wview should not be running when running this script. +.BR Note: must be executed on the source wview server. +.SH OPTIONS +There are no options. +.TP +.SH SEE ALSO +.BR wviewhtmlconfig (1), +.BR /usr/share/doc/wview/wview-User-Manual.html +.br +.SH AUTHOR +wviewconfig was written by Mark Teel. +.PP +This manual page was written by Mark Teel , +for the Debian project (and may be used by others). diff --git a/debian/wviewd_sim.1 b/debian/wviewd_sim.1 new file mode 100644 index 0000000..941f97e --- /dev/null +++ b/debian/wviewd_sim.1 @@ -0,0 +1,42 @@ +.\" Hey, EMACS: -*- nroff -*- +.\" First parameter, NAME, should be all caps +.\" Second parameter, SECTION, should be 1-8, maybe w/ subsection +.\" other parameters are allowed: see man(7), man(1) +.TH WVIEWD_SIM 1 "November 19, 2009" +.\" Please adjust this date whenever revising the manpage. +.\" +.\" Some roff macros, for reference: +.\" .nh disable hyphenation +.\" .hy enable hyphenation +.\" .ad l left justify +.\" .ad b justify to both left and right margins +.\" .nf disable filling +.\" .fi enable filling +.\" .br insert line break +.\" .sp insert n+1 empty lines +.\" for manpage-specific macros, see man(7) +.SH NAME +wviewd_sim \- wview simulated station interface daemon +.SH SYNOPSIS +.B wviewd_sim +.RI +.br +.SH DESCRIPTION +This manual page describes the use of +.B wviewd_sim +. +The daemon wviewd_sim simulates a station interface for testing and demonstration purposes. +.BR +.BR Note: This daemon should ONLY be launched from the /etc/init.d/wview script (typically started in your default runlevel). +.SH OPTIONS +There are no options. +.TP +.SH SEE ALSO +.BR /etc/init.d/wview, +.BR /usr/share/doc/wview/wview-User-Manual.html +.br +.SH AUTHOR +wviewd_sim was written by Mark Teel. +.PP +This manual page was written by Mark Teel , +for the Debian project (and may be used by others). diff --git a/debian/wviewd_vpro.1 b/debian/wviewd_vpro.1 new file mode 100644 index 0000000..720202e --- /dev/null +++ b/debian/wviewd_vpro.1 @@ -0,0 +1,43 @@ +.\" Hey, EMACS: -*- nroff -*- +.\" First parameter, NAME, should be all caps +.\" Second parameter, SECTION, should be 1-8, maybe w/ subsection +.\" other parameters are allowed: see man(7), man(1) +.TH WVIEWD_VPRO 1 "November 19, 2009" +.\" Please adjust this date whenever revising the manpage. +.\" +.\" Some roff macros, for reference: +.\" .nh disable hyphenation +.\" .hy enable hyphenation +.\" .ad l left justify +.\" .ad b justify to both left and right margins +.\" .nf disable filling +.\" .fi enable filling +.\" .br insert line break +.\" .sp insert n+1 empty lines +.\" for manpage-specific macros, see man(7) +.SH NAME +wviewd_vpro \- wview Davis Vantage Pro station interface daemon +.SH SYNOPSIS +.B wviewd_vpro +.RI +.br +.SH DESCRIPTION +This manual page describes the use of +.B wviewd_vpro +. +The daemon wviewd_vpro interfaces a Davis Vantage Pro (2) weather station. +It retrieves weather data and archives it in an SQLite3 database. +.BR +.BR Note: This daemon should ONLY be launched from the /etc/init.d/wview script (typically started in your default runlevel). +.SH OPTIONS +There are no options. +.TP +.SH SEE ALSO +.BR /etc/init.d/wview, +.BR /usr/share/doc/wview/wview-User-Manual.html +.br +.SH AUTHOR +wviewd_vpro was written by Mark Teel. +.PP +This manual page was written by Mark Teel , +for the Debian project (and may be used by others). diff --git a/debian/wviewd_wmr918.1 b/debian/wviewd_wmr918.1 new file mode 100644 index 0000000..cde6131 --- /dev/null +++ b/debian/wviewd_wmr918.1 @@ -0,0 +1,43 @@ +.\" Hey, EMACS: -*- nroff -*- +.\" First parameter, NAME, should be all caps +.\" Second parameter, SECTION, should be 1-8, maybe w/ subsection +.\" other parameters are allowed: see man(7), man(1) +.TH WVIEWD_WMR918 1 "November 19, 2009" +.\" Please adjust this date whenever revising the manpage. +.\" +.\" Some roff macros, for reference: +.\" .nh disable hyphenation +.\" .hy enable hyphenation +.\" .ad l left justify +.\" .ad b justify to both left and right margins +.\" .nf disable filling +.\" .fi enable filling +.\" .br insert line break +.\" .sp insert n+1 empty lines +.\" for manpage-specific macros, see man(7) +.SH NAME +wviewd_wmr918 \- wview Oregon Scientific WMR918/928NX/968 station interface daemon +.SH SYNOPSIS +.B wviewd_wmr918 +.RI +.br +.SH DESCRIPTION +This manual page describes the use of +.B wviewd_wmr918 +. +The daemon wviewd_wmr918 interfaces a Oregon Scientific WMR918/928NX/968 weather station. +It retrieves weather data and archives it in an SQLite3 database. +.BR +.BR Note: This daemon should ONLY be launched from the /etc/init.d/wview script (typically started in your default runlevel). +.SH OPTIONS +There are no options. +.TP +.SH SEE ALSO +.BR /etc/init.d/wview, +.BR /usr/share/doc/wview/wview-User-Manual.html +.br +.SH AUTHOR +wviewd_wmr918 was written by Mark Teel. +.PP +This manual page was written by Mark Teel , +for the Debian project (and may be used by others). diff --git a/debian/wviewd_ws2300.1 b/debian/wviewd_ws2300.1 new file mode 100644 index 0000000..7425dfa --- /dev/null +++ b/debian/wviewd_ws2300.1 @@ -0,0 +1,43 @@ +.\" Hey, EMACS: -*- nroff -*- +.\" First parameter, NAME, should be all caps +.\" Second parameter, SECTION, should be 1-8, maybe w/ subsection +.\" other parameters are allowed: see man(7), man(1) +.TH WVIEWD_WS2300 1 "November 19, 2009" +.\" Please adjust this date whenever revising the manpage. +.\" +.\" Some roff macros, for reference: +.\" .nh disable hyphenation +.\" .hy enable hyphenation +.\" .ad l left justify +.\" .ad b justify to both left and right margins +.\" .nf disable filling +.\" .fi enable filling +.\" .br insert line break +.\" .sp insert n+1 empty lines +.\" for manpage-specific macros, see man(7) +.SH NAME +wviewd_ws2300 \- wview La Crosse WS-2300/2308/2310/2315 station interface daemon +.SH SYNOPSIS +.B wviewd_ws2300 +.RI +.br +.SH DESCRIPTION +This manual page describes the use of +.B wviewd_ws2300 +. +The daemon wviewd_ws2300 interfaces a La Crosse WS-2300/2308/2310/2315 weather station. +It retrieves weather data and archives it in an SQLite3 database. +.BR +.BR Note: This daemon should ONLY be launched from the /etc/init.d/wview script (typically started in your default runlevel). +.SH OPTIONS +There are no options. +.TP +.SH SEE ALSO +.BR /etc/init.d/wview, +.BR /usr/share/doc/wview/wview-User-Manual.html +.br +.SH AUTHOR +wviewd_ws2300 was written by Mark Teel. +.PP +This manual page was written by Mark Teel , +for the Debian project (and may be used by others). diff --git a/debian/wviewd_wxt510.1 b/debian/wviewd_wxt510.1 new file mode 100644 index 0000000..e9b2a75 --- /dev/null +++ b/debian/wviewd_wxt510.1 @@ -0,0 +1,43 @@ +.\" Hey, EMACS: -*- nroff -*- +.\" First parameter, NAME, should be all caps +.\" Second parameter, SECTION, should be 1-8, maybe w/ subsection +.\" other parameters are allowed: see man(7), man(1) +.TH WVIEWD_WXT510 1 "November 19, 2009" +.\" Please adjust this date whenever revising the manpage. +.\" +.\" Some roff macros, for reference: +.\" .nh disable hyphenation +.\" .hy enable hyphenation +.\" .ad l left justify +.\" .ad b justify to both left and right margins +.\" .nf disable filling +.\" .fi enable filling +.\" .br insert line break +.\" .sp insert n+1 empty lines +.\" for manpage-specific macros, see man(7) +.SH NAME +wviewd_wxt510 \- wview Vaisala WXT510 station interface daemon +.SH SYNOPSIS +.B wviewd_wxt510 +.RI +.br +.SH DESCRIPTION +This manual page describes the use of +.B wviewd_wxt510 +. +The daemon wviewd_wxt510 interfaces a Vaisala WXT510 weather station. +It retrieves weather data and archives it in an SQLite3 database. +.BR +.BR Note: This daemon should ONLY be launched from the /etc/init.d/wview script (typically started in your default runlevel). +.SH OPTIONS +There are no options. +.TP +.SH SEE ALSO +.BR /etc/init.d/wview, +.BR /usr/share/doc/wview/wview-User-Manual.html +.br +.SH AUTHOR +wviewd_wxt510 was written by Mark Teel. +.PP +This manual page was written by Mark Teel , +for the Debian project (and may be used by others). diff --git a/debian/wviewftpd.1 b/debian/wviewftpd.1 new file mode 100644 index 0000000..35152d1 --- /dev/null +++ b/debian/wviewftpd.1 @@ -0,0 +1,43 @@ +.\" Hey, EMACS: -*- nroff -*- +.\" First parameter, NAME, should be all caps +.\" Second parameter, SECTION, should be 1-8, maybe w/ subsection +.\" other parameters are allowed: see man(7), man(1) +.TH WVIEWFTPD 1 "November 19, 2009" +.\" Please adjust this date whenever revising the manpage. +.\" +.\" Some roff macros, for reference: +.\" .nh disable hyphenation +.\" .hy enable hyphenation +.\" .ad l left justify +.\" .ad b justify to both left and right margins +.\" .nf disable filling +.\" .fi enable filling +.\" .br insert line break +.\" .sp insert n+1 empty lines +.\" for manpage-specific macros, see man(7) +.SH NAME +wviewftpd \- wview FTP export daemon +.SH SYNOPSIS +.B wviewftpd +.RI +.br +.SH DESCRIPTION +This manual page describes the use of +.B wviewftpd +. +At user-defined intervals wviewftpd will export generated files to a remote FTP server. +It requires the appropriate wview station daemon and htmlgend for generation of files. +.BR +.BR Note: This daemon should ONLY be launched from the /etc/init.d/wview script (typically started in your default runlevel). +.SH OPTIONS +There are no options. +.TP +.SH SEE ALSO +.BR /etc/init.d/wview, +.BR /usr/share/doc/wview/wview-User-Manual.html +.br +.SH AUTHOR +wviewftpd was written by Mark Teel. +.PP +This manual page was written by Mark Teel , +for the Debian project (and may be used by others). diff --git a/debian/wviewhtmlconfig.1 b/debian/wviewhtmlconfig.1 new file mode 100644 index 0000000..b75c49e --- /dev/null +++ b/debian/wviewhtmlconfig.1 @@ -0,0 +1,43 @@ +.\" Hey, EMACS: -*- nroff -*- +.\" First parameter, NAME, should be all caps +.\" Second parameter, SECTION, should be 1-8, maybe w/ subsection +.\" other parameters are allowed: see man(7), man(1) +.TH WVIEWHTMLCONFIG 1 "November 19, 2009" +.\" Please adjust this date whenever revising the manpage. +.\" +.\" Some roff macros, for reference: +.\" .nh disable hyphenation +.\" .hy enable hyphenation +.\" .ad l left justify +.\" .ad b justify to both left and right margins +.\" .nf disable filling +.\" .fi enable filling +.\" .br insert line break +.\" .sp insert n+1 empty lines +.\" for manpage-specific macros, see man(7) +.SH NAME +wviewhtmlconfig \- script to configure the HTML templates for a wview installation +.SH SYNOPSIS +.B wviewhtmlconfig +.RI +.br +.SH DESCRIPTION +This manual page describes the use of +.B wviewhtmlconfig +. +Configure a wview installation's HTML templates. +.BR +.BR Note: wviewconfig MUST be run before this script. +.BR Note: must be executed on the source wview server. +.SH OPTIONS +There are no options. +.TP +.SH SEE ALSO +.BR wviewconfig (1), +.BR /usr/share/doc/wview/wview-User-Manual.html +.br +.SH AUTHOR +wviewhtmlconfig was written by Mark Teel. +.PP +This manual page was written by Mark Teel , +for the Debian project (and may be used by others). diff --git a/debian/wviewsshd.1 b/debian/wviewsshd.1 new file mode 100644 index 0000000..4ea4278 --- /dev/null +++ b/debian/wviewsshd.1 @@ -0,0 +1,43 @@ +.\" Hey, EMACS: -*- nroff -*- +.\" First parameter, NAME, should be all caps +.\" Second parameter, SECTION, should be 1-8, maybe w/ subsection +.\" other parameters are allowed: see man(7), man(1) +.TH WVIEWSSHD 1 "November 19, 2009" +.\" Please adjust this date whenever revising the manpage. +.\" +.\" Some roff macros, for reference: +.\" .nh disable hyphenation +.\" .hy enable hyphenation +.\" .ad l left justify +.\" .ad b justify to both left and right margins +.\" .nf disable filling +.\" .fi enable filling +.\" .br insert line break +.\" .sp insert n+1 empty lines +.\" for manpage-specific macros, see man(7) +.SH NAME +wviewsshd \- wview SSH export daemon +.SH SYNOPSIS +.B wviewsshd +.RI +.br +.SH DESCRIPTION +This manual page describes the use of +.B wviewsshd +. +At user-defined intervals wviewsshd will export generated files to a remote SSH server. +It requires the appropriate wview station daemon and htmlgend for generation of files. +.BR +.BR Note: This daemon should ONLY be launched from the /etc/init.d/wview script (typically started in your default runlevel). +.SH OPTIONS +There are no options. +.TP +.SH SEE ALSO +.BR /etc/init.d/wview, +.BR /usr/share/doc/wview/wview-User-Manual.html +.br +.SH AUTHOR +wviewsshd was written by Mark Teel. +.PP +This manual page was written by Mark Teel , +for the Debian project (and may be used by others). diff --git a/debian/wvpmond.1 b/debian/wvpmond.1 new file mode 100644 index 0000000..adda6da --- /dev/null +++ b/debian/wvpmond.1 @@ -0,0 +1,44 @@ +.\" Hey, EMACS: -*- nroff -*- +.\" First parameter, NAME, should be all caps +.\" Second parameter, SECTION, should be 1-8, maybe w/ subsection +.\" other parameters are allowed: see man(7), man(1) +.TH WVPMOND 1 "November 19, 2009" +.\" Please adjust this date whenever revising the manpage. +.\" +.\" Some roff macros, for reference: +.\" .nh disable hyphenation +.\" .hy enable hyphenation +.\" .ad l left justify +.\" .ad b justify to both left and right margins +.\" .nf disable filling +.\" .fi enable filling +.\" .br insert line break +.\" .sp insert n+1 empty lines +.\" for manpage-specific macros, see man(7) +.SH NAME +wvpmond \- wview process monitoring daemon +.SH SYNOPSIS +.B wvpmond +.RI +.br +.SH DESCRIPTION +This manual page describes the use of +.B wvpmond +. +The daemon wvpmond monitors user-defined wview processes. +If a monitored process becomes non-responsive, wvpmond will restart it. +It requires the appropriate wview station daemon. +.BR +.BR Note: This daemon should ONLY be launched from the /etc/init.d/wview script (typically started in your default runlevel). +.SH OPTIONS +There are no options. +.TP +.SH SEE ALSO +.BR /etc/init.d/wview, +.BR /usr/share/doc/wview/wview-User-Manual.html +.br +.SH AUTHOR +wvpmond was written by Mark Teel. +.PP +This manual page was written by Mark Teel , +for the Debian project (and may be used by others). diff --git a/debian/wxt510config.1 b/debian/wxt510config.1 new file mode 100644 index 0000000..4cfce80 --- /dev/null +++ b/debian/wxt510config.1 @@ -0,0 +1,44 @@ +.\" Hey, EMACS: -*- nroff -*- +.\" First parameter, NAME, should be all caps +.\" Second parameter, SECTION, should be 1-8, maybe w/ subsection +.\" other parameters are allowed: see man(7), man(1) +.TH WXT510CONFIG 1 "November 19, 2009" +.\" Please adjust this date whenever revising the manpage. +.\" +.\" Some roff macros, for reference: +.\" .nh disable hyphenation +.\" .hy enable hyphenation +.\" .ad l left justify +.\" .ad b justify to both left and right margins +.\" .nf disable filling +.\" .fi enable filling +.\" .br insert line break +.\" .sp insert n+1 empty lines +.\" for manpage-specific macros, see man(7) +.SH NAME +wxt510config \- wview utility program to configure an attached Vaisala WXT510 weather station +.SH SYNOPSIS +.B wxt510config +.RI station_device +.br +.SH DESCRIPTION +This manual page describes the use of +.B wxt510config +. +Configure an attached Vaisala WXT510 weather station (autobaud the serial interface). +.BR station_device - serial device the WXT510 is connected to: + FreeBSD: /dev/cuaa0 - /dev/cuaa4 + Linux: /dev/ttyS0 - /dev/ttyS4 + Linux USB: /dev/ttyUSB0 + Ethernet: host:port +.SH OPTIONS +station_device and command are mandatory. +.TP +.SH SEE ALSO +.BR /usr/share/doc/wview/wview-User-Manual.html +.br +.SH AUTHOR +wxt510config was written by Mark Teel. +.PP +This manual page was written by Mark Teel , +for the Debian project (and may be used by others). diff --git a/depcomp b/depcomp new file mode 100755 index 0000000..4ebd5b3 --- /dev/null +++ b/depcomp @@ -0,0 +1,791 @@ +#! /bin/sh +# depcomp - compile a program generating dependencies as side-effects + +scriptversion=2013-05-30.07; # UTC + +# Copyright (C) 1999-2013 Free Software Foundation, Inc. + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. + +# This program 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 General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + +# Originally written by Alexandre Oliva . + +case $1 in + '') + echo "$0: No command. Try '$0 --help' for more information." 1>&2 + exit 1; + ;; + -h | --h*) + cat <<\EOF +Usage: depcomp [--help] [--version] PROGRAM [ARGS] + +Run PROGRAMS ARGS to compile a file, generating dependencies +as side-effects. + +Environment variables: + depmode Dependency tracking mode. + source Source file read by 'PROGRAMS ARGS'. + object Object file output by 'PROGRAMS ARGS'. + DEPDIR directory where to store dependencies. + depfile Dependency file to output. + tmpdepfile Temporary file to use when outputting dependencies. + libtool Whether libtool is used (yes/no). + +Report bugs to . +EOF + exit $? + ;; + -v | --v*) + echo "depcomp $scriptversion" + exit $? + ;; +esac + +# Get the directory component of the given path, and save it in the +# global variables '$dir'. Note that this directory component will +# be either empty or ending with a '/' character. This is deliberate. +set_dir_from () +{ + case $1 in + */*) dir=`echo "$1" | sed -e 's|/[^/]*$|/|'`;; + *) dir=;; + esac +} + +# Get the suffix-stripped basename of the given path, and save it the +# global variable '$base'. +set_base_from () +{ + base=`echo "$1" | sed -e 's|^.*/||' -e 's/\.[^.]*$//'` +} + +# If no dependency file was actually created by the compiler invocation, +# we still have to create a dummy depfile, to avoid errors with the +# Makefile "include basename.Plo" scheme. +make_dummy_depfile () +{ + echo "#dummy" > "$depfile" +} + +# Factor out some common post-processing of the generated depfile. +# Requires the auxiliary global variable '$tmpdepfile' to be set. +aix_post_process_depfile () +{ + # If the compiler actually managed to produce a dependency file, + # post-process it. + if test -f "$tmpdepfile"; then + # Each line is of the form 'foo.o: dependency.h'. + # Do two passes, one to just change these to + # $object: dependency.h + # and one to simply output + # dependency.h: + # which is needed to avoid the deleted-header problem. + { sed -e "s,^.*\.[$lower]*:,$object:," < "$tmpdepfile" + sed -e "s,^.*\.[$lower]*:[$tab ]*,," -e 's,$,:,' < "$tmpdepfile" + } > "$depfile" + rm -f "$tmpdepfile" + else + make_dummy_depfile + fi +} + +# A tabulation character. +tab=' ' +# A newline character. +nl=' +' +# Character ranges might be problematic outside the C locale. +# These definitions help. +upper=ABCDEFGHIJKLMNOPQRSTUVWXYZ +lower=abcdefghijklmnopqrstuvwxyz +digits=0123456789 +alpha=${upper}${lower} + +if test -z "$depmode" || test -z "$source" || test -z "$object"; then + echo "depcomp: Variables source, object and depmode must be set" 1>&2 + exit 1 +fi + +# Dependencies for sub/bar.o or sub/bar.obj go into sub/.deps/bar.Po. +depfile=${depfile-`echo "$object" | + sed 's|[^\\/]*$|'${DEPDIR-.deps}'/&|;s|\.\([^.]*\)$|.P\1|;s|Pobj$|Po|'`} +tmpdepfile=${tmpdepfile-`echo "$depfile" | sed 's/\.\([^.]*\)$/.T\1/'`} + +rm -f "$tmpdepfile" + +# Avoid interferences from the environment. +gccflag= dashmflag= + +# Some modes work just like other modes, but use different flags. We +# parameterize here, but still list the modes in the big case below, +# to make depend.m4 easier to write. Note that we *cannot* use a case +# here, because this file can only contain one case statement. +if test "$depmode" = hp; then + # HP compiler uses -M and no extra arg. + gccflag=-M + depmode=gcc +fi + +if test "$depmode" = dashXmstdout; then + # This is just like dashmstdout with a different argument. + dashmflag=-xM + depmode=dashmstdout +fi + +cygpath_u="cygpath -u -f -" +if test "$depmode" = msvcmsys; then + # This is just like msvisualcpp but w/o cygpath translation. + # Just convert the backslash-escaped backslashes to single forward + # slashes to satisfy depend.m4 + cygpath_u='sed s,\\\\,/,g' + depmode=msvisualcpp +fi + +if test "$depmode" = msvc7msys; then + # This is just like msvc7 but w/o cygpath translation. + # Just convert the backslash-escaped backslashes to single forward + # slashes to satisfy depend.m4 + cygpath_u='sed s,\\\\,/,g' + depmode=msvc7 +fi + +if test "$depmode" = xlc; then + # IBM C/C++ Compilers xlc/xlC can output gcc-like dependency information. + gccflag=-qmakedep=gcc,-MF + depmode=gcc +fi + +case "$depmode" in +gcc3) +## gcc 3 implements dependency tracking that does exactly what +## we want. Yay! Note: for some reason libtool 1.4 doesn't like +## it if -MD -MP comes after the -MF stuff. Hmm. +## Unfortunately, FreeBSD c89 acceptance of flags depends upon +## the command line argument order; so add the flags where they +## appear in depend2.am. Note that the slowdown incurred here +## affects only configure: in makefiles, %FASTDEP% shortcuts this. + for arg + do + case $arg in + -c) set fnord "$@" -MT "$object" -MD -MP -MF "$tmpdepfile" "$arg" ;; + *) set fnord "$@" "$arg" ;; + esac + shift # fnord + shift # $arg + done + "$@" + stat=$? + if test $stat -ne 0; then + rm -f "$tmpdepfile" + exit $stat + fi + mv "$tmpdepfile" "$depfile" + ;; + +gcc) +## Note that this doesn't just cater to obsosete pre-3.x GCC compilers. +## but also to in-use compilers like IMB xlc/xlC and the HP C compiler. +## (see the conditional assignment to $gccflag above). +## There are various ways to get dependency output from gcc. Here's +## why we pick this rather obscure method: +## - Don't want to use -MD because we'd like the dependencies to end +## up in a subdir. Having to rename by hand is ugly. +## (We might end up doing this anyway to support other compilers.) +## - The DEPENDENCIES_OUTPUT environment variable makes gcc act like +## -MM, not -M (despite what the docs say). Also, it might not be +## supported by the other compilers which use the 'gcc' depmode. +## - Using -M directly means running the compiler twice (even worse +## than renaming). + if test -z "$gccflag"; then + gccflag=-MD, + fi + "$@" -Wp,"$gccflag$tmpdepfile" + stat=$? + if test $stat -ne 0; then + rm -f "$tmpdepfile" + exit $stat + fi + rm -f "$depfile" + echo "$object : \\" > "$depfile" + # The second -e expression handles DOS-style file names with drive + # letters. + sed -e 's/^[^:]*: / /' \ + -e 's/^['$alpha']:\/[^:]*: / /' < "$tmpdepfile" >> "$depfile" +## This next piece of magic avoids the "deleted header file" problem. +## The problem is that when a header file which appears in a .P file +## is deleted, the dependency causes make to die (because there is +## typically no way to rebuild the header). We avoid this by adding +## dummy dependencies for each header file. Too bad gcc doesn't do +## this for us directly. +## Some versions of gcc put a space before the ':'. On the theory +## that the space means something, we add a space to the output as +## well. hp depmode also adds that space, but also prefixes the VPATH +## to the object. Take care to not repeat it in the output. +## Some versions of the HPUX 10.20 sed can't process this invocation +## correctly. Breaking it into two sed invocations is a workaround. + tr ' ' "$nl" < "$tmpdepfile" \ + | sed -e 's/^\\$//' -e '/^$/d' -e "s|.*$object$||" -e '/:$/d' \ + | sed -e 's/$/ :/' >> "$depfile" + rm -f "$tmpdepfile" + ;; + +hp) + # This case exists only to let depend.m4 do its work. It works by + # looking at the text of this script. This case will never be run, + # since it is checked for above. + exit 1 + ;; + +sgi) + if test "$libtool" = yes; then + "$@" "-Wp,-MDupdate,$tmpdepfile" + else + "$@" -MDupdate "$tmpdepfile" + fi + stat=$? + if test $stat -ne 0; then + rm -f "$tmpdepfile" + exit $stat + fi + rm -f "$depfile" + + if test -f "$tmpdepfile"; then # yes, the sourcefile depend on other files + echo "$object : \\" > "$depfile" + # Clip off the initial element (the dependent). Don't try to be + # clever and replace this with sed code, as IRIX sed won't handle + # lines with more than a fixed number of characters (4096 in + # IRIX 6.2 sed, 8192 in IRIX 6.5). We also remove comment lines; + # the IRIX cc adds comments like '#:fec' to the end of the + # dependency line. + tr ' ' "$nl" < "$tmpdepfile" \ + | sed -e 's/^.*\.o://' -e 's/#.*$//' -e '/^$/ d' \ + | tr "$nl" ' ' >> "$depfile" + echo >> "$depfile" + # The second pass generates a dummy entry for each header file. + tr ' ' "$nl" < "$tmpdepfile" \ + | sed -e 's/^.*\.o://' -e 's/#.*$//' -e '/^$/ d' -e 's/$/:/' \ + >> "$depfile" + else + make_dummy_depfile + fi + rm -f "$tmpdepfile" + ;; + +xlc) + # This case exists only to let depend.m4 do its work. It works by + # looking at the text of this script. This case will never be run, + # since it is checked for above. + exit 1 + ;; + +aix) + # The C for AIX Compiler uses -M and outputs the dependencies + # in a .u file. In older versions, this file always lives in the + # current directory. Also, the AIX compiler puts '$object:' at the + # start of each line; $object doesn't have directory information. + # Version 6 uses the directory in both cases. + set_dir_from "$object" + set_base_from "$object" + if test "$libtool" = yes; then + tmpdepfile1=$dir$base.u + tmpdepfile2=$base.u + tmpdepfile3=$dir.libs/$base.u + "$@" -Wc,-M + else + tmpdepfile1=$dir$base.u + tmpdepfile2=$dir$base.u + tmpdepfile3=$dir$base.u + "$@" -M + fi + stat=$? + if test $stat -ne 0; then + rm -f "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3" + exit $stat + fi + + for tmpdepfile in "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3" + do + test -f "$tmpdepfile" && break + done + aix_post_process_depfile + ;; + +tcc) + # tcc (Tiny C Compiler) understand '-MD -MF file' since version 0.9.26 + # FIXME: That version still under development at the moment of writing. + # Make that this statement remains true also for stable, released + # versions. + # It will wrap lines (doesn't matter whether long or short) with a + # trailing '\', as in: + # + # foo.o : \ + # foo.c \ + # foo.h \ + # + # It will put a trailing '\' even on the last line, and will use leading + # spaces rather than leading tabs (at least since its commit 0394caf7 + # "Emit spaces for -MD"). + "$@" -MD -MF "$tmpdepfile" + stat=$? + if test $stat -ne 0; then + rm -f "$tmpdepfile" + exit $stat + fi + rm -f "$depfile" + # Each non-empty line is of the form 'foo.o : \' or ' dep.h \'. + # We have to change lines of the first kind to '$object: \'. + sed -e "s|.*:|$object :|" < "$tmpdepfile" > "$depfile" + # And for each line of the second kind, we have to emit a 'dep.h:' + # dummy dependency, to avoid the deleted-header problem. + sed -n -e 's|^ *\(.*\) *\\$|\1:|p' < "$tmpdepfile" >> "$depfile" + rm -f "$tmpdepfile" + ;; + +## The order of this option in the case statement is important, since the +## shell code in configure will try each of these formats in the order +## listed in this file. A plain '-MD' option would be understood by many +## compilers, so we must ensure this comes after the gcc and icc options. +pgcc) + # Portland's C compiler understands '-MD'. + # Will always output deps to 'file.d' where file is the root name of the + # source file under compilation, even if file resides in a subdirectory. + # The object file name does not affect the name of the '.d' file. + # pgcc 10.2 will output + # foo.o: sub/foo.c sub/foo.h + # and will wrap long lines using '\' : + # foo.o: sub/foo.c ... \ + # sub/foo.h ... \ + # ... + set_dir_from "$object" + # Use the source, not the object, to determine the base name, since + # that's sadly what pgcc will do too. + set_base_from "$source" + tmpdepfile=$base.d + + # For projects that build the same source file twice into different object + # files, the pgcc approach of using the *source* file root name can cause + # problems in parallel builds. Use a locking strategy to avoid stomping on + # the same $tmpdepfile. + lockdir=$base.d-lock + trap " + echo '$0: caught signal, cleaning up...' >&2 + rmdir '$lockdir' + exit 1 + " 1 2 13 15 + numtries=100 + i=$numtries + while test $i -gt 0; do + # mkdir is a portable test-and-set. + if mkdir "$lockdir" 2>/dev/null; then + # This process acquired the lock. + "$@" -MD + stat=$? + # Release the lock. + rmdir "$lockdir" + break + else + # If the lock is being held by a different process, wait + # until the winning process is done or we timeout. + while test -d "$lockdir" && test $i -gt 0; do + sleep 1 + i=`expr $i - 1` + done + fi + i=`expr $i - 1` + done + trap - 1 2 13 15 + if test $i -le 0; then + echo "$0: failed to acquire lock after $numtries attempts" >&2 + echo "$0: check lockdir '$lockdir'" >&2 + exit 1 + fi + + if test $stat -ne 0; then + rm -f "$tmpdepfile" + exit $stat + fi + rm -f "$depfile" + # Each line is of the form `foo.o: dependent.h', + # or `foo.o: dep1.h dep2.h \', or ` dep3.h dep4.h \'. + # Do two passes, one to just change these to + # `$object: dependent.h' and one to simply `dependent.h:'. + sed "s,^[^:]*:,$object :," < "$tmpdepfile" > "$depfile" + # Some versions of the HPUX 10.20 sed can't process this invocation + # correctly. Breaking it into two sed invocations is a workaround. + sed 's,^[^:]*: \(.*\)$,\1,;s/^\\$//;/^$/d;/:$/d' < "$tmpdepfile" \ + | sed -e 's/$/ :/' >> "$depfile" + rm -f "$tmpdepfile" + ;; + +hp2) + # The "hp" stanza above does not work with aCC (C++) and HP's ia64 + # compilers, which have integrated preprocessors. The correct option + # to use with these is +Maked; it writes dependencies to a file named + # 'foo.d', which lands next to the object file, wherever that + # happens to be. + # Much of this is similar to the tru64 case; see comments there. + set_dir_from "$object" + set_base_from "$object" + if test "$libtool" = yes; then + tmpdepfile1=$dir$base.d + tmpdepfile2=$dir.libs/$base.d + "$@" -Wc,+Maked + else + tmpdepfile1=$dir$base.d + tmpdepfile2=$dir$base.d + "$@" +Maked + fi + stat=$? + if test $stat -ne 0; then + rm -f "$tmpdepfile1" "$tmpdepfile2" + exit $stat + fi + + for tmpdepfile in "$tmpdepfile1" "$tmpdepfile2" + do + test -f "$tmpdepfile" && break + done + if test -f "$tmpdepfile"; then + sed -e "s,^.*\.[$lower]*:,$object:," "$tmpdepfile" > "$depfile" + # Add 'dependent.h:' lines. + sed -ne '2,${ + s/^ *// + s/ \\*$// + s/$/:/ + p + }' "$tmpdepfile" >> "$depfile" + else + make_dummy_depfile + fi + rm -f "$tmpdepfile" "$tmpdepfile2" + ;; + +tru64) + # The Tru64 compiler uses -MD to generate dependencies as a side + # effect. 'cc -MD -o foo.o ...' puts the dependencies into 'foo.o.d'. + # At least on Alpha/Redhat 6.1, Compaq CCC V6.2-504 seems to put + # dependencies in 'foo.d' instead, so we check for that too. + # Subdirectories are respected. + set_dir_from "$object" + set_base_from "$object" + + if test "$libtool" = yes; then + # Libtool generates 2 separate objects for the 2 libraries. These + # two compilations output dependencies in $dir.libs/$base.o.d and + # in $dir$base.o.d. We have to check for both files, because + # one of the two compilations can be disabled. We should prefer + # $dir$base.o.d over $dir.libs/$base.o.d because the latter is + # automatically cleaned when .libs/ is deleted, while ignoring + # the former would cause a distcleancheck panic. + tmpdepfile1=$dir$base.o.d # libtool 1.5 + tmpdepfile2=$dir.libs/$base.o.d # Likewise. + tmpdepfile3=$dir.libs/$base.d # Compaq CCC V6.2-504 + "$@" -Wc,-MD + else + tmpdepfile1=$dir$base.d + tmpdepfile2=$dir$base.d + tmpdepfile3=$dir$base.d + "$@" -MD + fi + + stat=$? + if test $stat -ne 0; then + rm -f "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3" + exit $stat + fi + + for tmpdepfile in "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3" + do + test -f "$tmpdepfile" && break + done + # Same post-processing that is required for AIX mode. + aix_post_process_depfile + ;; + +msvc7) + if test "$libtool" = yes; then + showIncludes=-Wc,-showIncludes + else + showIncludes=-showIncludes + fi + "$@" $showIncludes > "$tmpdepfile" + stat=$? + grep -v '^Note: including file: ' "$tmpdepfile" + if test $stat -ne 0; then + rm -f "$tmpdepfile" + exit $stat + fi + rm -f "$depfile" + echo "$object : \\" > "$depfile" + # The first sed program below extracts the file names and escapes + # backslashes for cygpath. The second sed program outputs the file + # name when reading, but also accumulates all include files in the + # hold buffer in order to output them again at the end. This only + # works with sed implementations that can handle large buffers. + sed < "$tmpdepfile" -n ' +/^Note: including file: *\(.*\)/ { + s//\1/ + s/\\/\\\\/g + p +}' | $cygpath_u | sort -u | sed -n ' +s/ /\\ /g +s/\(.*\)/'"$tab"'\1 \\/p +s/.\(.*\) \\/\1:/ +H +$ { + s/.*/'"$tab"'/ + G + p +}' >> "$depfile" + echo >> "$depfile" # make sure the fragment doesn't end with a backslash + rm -f "$tmpdepfile" + ;; + +msvc7msys) + # This case exists only to let depend.m4 do its work. It works by + # looking at the text of this script. This case will never be run, + # since it is checked for above. + exit 1 + ;; + +#nosideeffect) + # This comment above is used by automake to tell side-effect + # dependency tracking mechanisms from slower ones. + +dashmstdout) + # Important note: in order to support this mode, a compiler *must* + # always write the preprocessed file to stdout, regardless of -o. + "$@" || exit $? + + # Remove the call to Libtool. + if test "$libtool" = yes; then + while test "X$1" != 'X--mode=compile'; do + shift + done + shift + fi + + # Remove '-o $object'. + IFS=" " + for arg + do + case $arg in + -o) + shift + ;; + $object) + shift + ;; + *) + set fnord "$@" "$arg" + shift # fnord + shift # $arg + ;; + esac + done + + test -z "$dashmflag" && dashmflag=-M + # Require at least two characters before searching for ':' + # in the target name. This is to cope with DOS-style filenames: + # a dependency such as 'c:/foo/bar' could be seen as target 'c' otherwise. + "$@" $dashmflag | + sed "s|^[$tab ]*[^:$tab ][^:][^:]*:[$tab ]*|$object: |" > "$tmpdepfile" + rm -f "$depfile" + cat < "$tmpdepfile" > "$depfile" + # Some versions of the HPUX 10.20 sed can't process this sed invocation + # correctly. Breaking it into two sed invocations is a workaround. + tr ' ' "$nl" < "$tmpdepfile" \ + | sed -e 's/^\\$//' -e '/^$/d' -e '/:$/d' \ + | sed -e 's/$/ :/' >> "$depfile" + rm -f "$tmpdepfile" + ;; + +dashXmstdout) + # This case only exists to satisfy depend.m4. It is never actually + # run, as this mode is specially recognized in the preamble. + exit 1 + ;; + +makedepend) + "$@" || exit $? + # Remove any Libtool call + if test "$libtool" = yes; then + while test "X$1" != 'X--mode=compile'; do + shift + done + shift + fi + # X makedepend + shift + cleared=no eat=no + for arg + do + case $cleared in + no) + set ""; shift + cleared=yes ;; + esac + if test $eat = yes; then + eat=no + continue + fi + case "$arg" in + -D*|-I*) + set fnord "$@" "$arg"; shift ;; + # Strip any option that makedepend may not understand. Remove + # the object too, otherwise makedepend will parse it as a source file. + -arch) + eat=yes ;; + -*|$object) + ;; + *) + set fnord "$@" "$arg"; shift ;; + esac + done + obj_suffix=`echo "$object" | sed 's/^.*\././'` + touch "$tmpdepfile" + ${MAKEDEPEND-makedepend} -o"$obj_suffix" -f"$tmpdepfile" "$@" + rm -f "$depfile" + # makedepend may prepend the VPATH from the source file name to the object. + # No need to regex-escape $object, excess matching of '.' is harmless. + sed "s|^.*\($object *:\)|\1|" "$tmpdepfile" > "$depfile" + # Some versions of the HPUX 10.20 sed can't process the last invocation + # correctly. Breaking it into two sed invocations is a workaround. + sed '1,2d' "$tmpdepfile" \ + | tr ' ' "$nl" \ + | sed -e 's/^\\$//' -e '/^$/d' -e '/:$/d' \ + | sed -e 's/$/ :/' >> "$depfile" + rm -f "$tmpdepfile" "$tmpdepfile".bak + ;; + +cpp) + # Important note: in order to support this mode, a compiler *must* + # always write the preprocessed file to stdout. + "$@" || exit $? + + # Remove the call to Libtool. + if test "$libtool" = yes; then + while test "X$1" != 'X--mode=compile'; do + shift + done + shift + fi + + # Remove '-o $object'. + IFS=" " + for arg + do + case $arg in + -o) + shift + ;; + $object) + shift + ;; + *) + set fnord "$@" "$arg" + shift # fnord + shift # $arg + ;; + esac + done + + "$@" -E \ + | sed -n -e '/^# [0-9][0-9]* "\([^"]*\)".*/ s:: \1 \\:p' \ + -e '/^#line [0-9][0-9]* "\([^"]*\)".*/ s:: \1 \\:p' \ + | sed '$ s: \\$::' > "$tmpdepfile" + rm -f "$depfile" + echo "$object : \\" > "$depfile" + cat < "$tmpdepfile" >> "$depfile" + sed < "$tmpdepfile" '/^$/d;s/^ //;s/ \\$//;s/$/ :/' >> "$depfile" + rm -f "$tmpdepfile" + ;; + +msvisualcpp) + # Important note: in order to support this mode, a compiler *must* + # always write the preprocessed file to stdout. + "$@" || exit $? + + # Remove the call to Libtool. + if test "$libtool" = yes; then + while test "X$1" != 'X--mode=compile'; do + shift + done + shift + fi + + IFS=" " + for arg + do + case "$arg" in + -o) + shift + ;; + $object) + shift + ;; + "-Gm"|"/Gm"|"-Gi"|"/Gi"|"-ZI"|"/ZI") + set fnord "$@" + shift + shift + ;; + *) + set fnord "$@" "$arg" + shift + shift + ;; + esac + done + "$@" -E 2>/dev/null | + sed -n '/^#line [0-9][0-9]* "\([^"]*\)"/ s::\1:p' | $cygpath_u | sort -u > "$tmpdepfile" + rm -f "$depfile" + echo "$object : \\" > "$depfile" + sed < "$tmpdepfile" -n -e 's% %\\ %g' -e '/^\(.*\)$/ s::'"$tab"'\1 \\:p' >> "$depfile" + echo "$tab" >> "$depfile" + sed < "$tmpdepfile" -n -e 's% %\\ %g' -e '/^\(.*\)$/ s::\1\::p' >> "$depfile" + rm -f "$tmpdepfile" + ;; + +msvcmsys) + # This case exists only to let depend.m4 do its work. It works by + # looking at the text of this script. This case will never be run, + # since it is checked for above. + exit 1 + ;; + +none) + exec "$@" + ;; + +*) + echo "Unknown depmode $depmode" 1>&2 + exit 1 + ;; +esac + +exit 0 + +# Local Variables: +# mode: shell-script +# sh-indentation: 2 +# eval: (add-hook 'write-file-hooks 'time-stamp) +# time-stamp-start: "scriptversion=" +# time-stamp-format: "%:y-%02m-%02d.%02H" +# time-stamp-time-zone: "UTC" +# time-stamp-end: "; # UTC" +# End: diff --git a/examples/Archlinux/Makefile.am b/examples/Archlinux/Makefile.am new file mode 100755 index 0000000..7ce8d77 --- /dev/null +++ b/examples/Archlinux/Makefile.am @@ -0,0 +1,19 @@ +# Makefile - FedoraCore + +EXTRA_DIST = $(srcdir)/wview.sh + +# define the scripts to be generated +noinst_SCRIPTS = wview + +CLEANFILES = $(noinst_SCRIPTS) + +wview: $(srcdir)/wview.sh + echo "#!/bin/bash" > wview + echo "" >> wview + echo "# add to the shared library search path" >> wview + echo "export LD_LIBRARY_PATH=$(prefix)/lib:/usr/local/lib:/usr/lib" >> wview + echo "CONF_DIRECTORY=$(sysconfdir)/wview" >> wview + echo "RUN_DIRECTORY=$(localstatedir)/wview" >> wview + echo "WVIEW_INSTALL_DIR=$(exec_prefix)/bin" >> wview + cat $(srcdir)/wview.sh >> wview + chmod ugo+x wview diff --git a/examples/Archlinux/example.sh b/examples/Archlinux/example.sh new file mode 100644 index 0000000..bf18102 --- /dev/null +++ b/examples/Archlinux/example.sh @@ -0,0 +1,40 @@ +#!/bin/bash + +. /etc/rc.conf +. /etc/rc.d/functions + +PID="$(pidof -o %PPID /usr/bin/rpcbind)" +case "$1" in + start) + stat_busy "Starting rpcbind" + [ -z "$PID" ] && /usr/bin/rpcbind &>/dev/null + if [ $? -gt 0 ]; then + stat_fail + else + PID=$(pidof -o %PPID /usr/bin/rpcbind) + echo $PID > /var/run/rpcbind.pid + add_daemon rpcbind + stat_done + fi + ;; + stop) + stat_busy "Stopping rpcbind" + [ ! -z "$PID" ] && kill $PID &> /dev/null + if [ $? -gt 0 ]; then + stat_fail + else + rm /var/run/rpcbind.pid + rm_daemon rpcbind + stat_done + fi + ;; + restart) + $0 stop + sleep 1 + $0 start + ;; + *) + echo "usage: $0 {start|stop|restart}" +esac +exit 0 + diff --git a/examples/Archlinux/wview.sh b/examples/Archlinux/wview.sh new file mode 100755 index 0000000..257480b --- /dev/null +++ b/examples/Archlinux/wview.sh @@ -0,0 +1,187 @@ +# chkconfig: - 89 11 +# description: wview is a unix weather program +# +# wview Start/Stop the wview daemons +# +# processnames: wviewd htmlgend wviewftpd wvalarmd wvcwopd wviewsshd wvhttpd +# config: $prefix/etc/wview +# pidfiles: $prefix/var/wview/*.pid + +# Source function library: +. /etc/init.d/functions + +WVIEWD_FILE=`cat $CONF_DIRECTORY/wview-binary` +WVIEWD_BIN=$WVIEW_INSTALL_DIR/$WVIEWD_FILE +test -x $WVIEWD_BIN || exit 5 + +HTMLD_BIN=$WVIEW_INSTALL_DIR/htmlgend +test -x $HTMLD_BIN || exit 6 + +FTPD_BIN=$WVIEW_INSTALL_DIR/wviewftpd +test -x $FTPD_BIN || exit 7 + +SSHD_BIN=$WVIEW_INSTALL_DIR/wviewsshd +test -x $SSHD_BIN || exit 7 + +ALARMD_BIN=$WVIEW_INSTALL_DIR/wvalarmd +test -x $ALARMD_BIN || exit 8 + +CWOPD_BIN=$WVIEW_INSTALL_DIR/wvcwopd +test -x $CWOPD_BIN || exit 9 + +HTTP_BIN=$WVIEW_INSTALL_DIR/wvhttpd + +SQLD_BIN=$WVIEW_INSTALL_DIR/wviewsqld + +RADROUTER_BIN=$WVIEW_INSTALL_DIR/radmrouted + +PMOND_BIN=$WVIEW_INSTALL_DIR/wvpmond +test -x $PMOND_BIN || exit 10 + +WVIEWD_PID=$RUN_DIRECTORY/wviewd.pid +HTMLD_PID=$RUN_DIRECTORY/htmlgend.pid +FTPD_PID=$RUN_DIRECTORY/wviewftpd.pid +SSHD_PID=$RUN_DIRECTORY/wviewsshd.pid +ALARMD_PID=$RUN_DIRECTORY/wvalarmd.pid +CWOPD_PID=$RUN_DIRECTORY/wvcwopd.pid +HTTP_PID=$RUN_DIRECTORY/wvhttpd.pid +SQLD_PID=$RUN_DIRECTORY/wviewsqld.pid +RADROUTER_PID=$RUN_DIRECTORY/radmrouted.pid +PMOND_PID=$RUN_DIRECTORY/wvpmond.pid + +kill_running_processes() { + if [ -f $RADROUTER_PID ]; then + echo "radlib router pid file $RADROUTER_PID exists - killing existing process" + kill -15 `cat $RADROUTER_PID` + rm -f $RADROUTER_PID + fi + if [ -f $WVIEWD_PID ]; then + echo "wviewd pid file $WVIEWD_PID exists - killing existing process" + kill -15 `cat $WVIEWD_PID` + rm -f $WVIEWD_PID + fi + if [ -f $HTMLD_PID ]; then + echo "htmlgend pid file $HTMLD_PID exists - killing existing process" + kill -15 `cat $HTMLD_PID` + rm -f $HTMLD_PID + fi + if [ -f $FTPD_PID ]; then + echo "wviewftpd pid file $FTPD_PID exists - killing existing process" + kill -15 `cat $FTPD_PID` + rm -f $FTPD_PID + fi + if [ -f $SSHD_PID ]; then + echo "wviewsshd pid file $SSHD_PID exists - killing existing process" + kill -15 `cat $SSHD_PID` + rm -f $SSHD_PID + fi + if [ -f $ALARMD_PID ]; then + echo "wvalarmd pid file $ALARMD_PID exists - killing existing process" + kill -15 `cat $ALARMD_PID` + rm -f $ALARMD_PID + fi + if [ -f $CWOPD_PID ]; then + echo "wvcwopd pid file $CWOPD_PID exists - killing existing process" + kill -15 `cat $CWOPD_PID` + rm -f $CWOPD_PID + fi + if [ -f $HTTP_PID ]; then + echo "wvhttpd pid file $HTTP_PID exists - killing existing process" + kill -15 `cat $HTTP_PID` + rm -f $HTTP_PID + fi + if [ -f $PMOND_PID ]; then + echo "wvpmond pid file $PMOND_PID exists - killing existing process" + kill -15 `cat $PMOND_PID` + rm -f $PMOND_PID + fi +} + +case "$1" in + start) + kill_running_processes + + echo "Starting wview daemons:" + + if [ -x $RADROUTER_BIN ]; then + $RADROUTER_BIN 1 $RUN_DIRECTORY + else + echo "Cannot find $RADROUTER_BIN - exiting!" + exit 10 + fi + sleep 1 + $WVIEWD_BIN + sleep 1 + $HTMLD_BIN + $ALARMD_BIN + $CWOPD_BIN + $HTTP_BIN + $FTPD_BIN + $SSHD_BIN + $PMOND_BIN + ;; + start-trace) + kill_running_processes + + echo "Starting wview daemons (tracing to $RUN_DIRECTORY):" + echo "Warning: traced processes run very slowly and may effect performance." + + if [ -x $RADROUTER_BIN ]; then + $RADROUTER_BIN 1 $RUN_DIRECTORY + else + echo "Cannot find $RADROUTER_BIN - exiting!" + exit 10 + fi + sleep 1 + strace -o $RUN_DIRECTORY/$WVIEWD_FILE.trace $WVIEWD_BIN -f &> /dev/null & + sleep 1 + strace -o $RUN_DIRECTORY/htmlgend.trace $HTMLD_BIN -f &> /dev/null & + strace -o $RUN_DIRECTORY/wvalarmd.trace $ALARMD_BIN -f &> /dev/null & + strace -o $RUN_DIRECTORY/wvcwopd.trace $CWOPD_BIN -f &> /dev/null & + strace -o $RUN_DIRECTORY/wvhttpd.trace $HTTP_BIN -f &> /dev/null & + strace -o $RUN_DIRECTORY/wviewftpd.trace $FTPD_BIN -f &> /dev/null & + strace -o $RUN_DIRECTORY/wviewsshd.trace $SSHD_BIN -f &> /dev/null & + strace -o $RUN_DIRECTORY/wvpmond.trace $PMOND_BIN -f &> /dev/null & + ;; + stop) + echo "Shutting down wview daemons..." + if [ -f $PMOND_PID ]; then + kill -15 `cat $PMOND_PID` + fi + if [ -f $HTTP_PID ]; then + kill -15 `cat $HTTP_PID` + fi + if [ -f $CWOPD_PID ]; then + kill -15 `cat $CWOPD_PID` + fi + if [ -f $ALARMD_PID ]; then + kill -15 `cat $ALARMD_PID` + fi + if [ -f $SSHD_PID ]; then + kill -15 `cat $SSHD_PID` + fi + if [ -f $FTPD_PID ]; then + kill -15 `cat $FTPD_PID` + fi + if [ -f $HTMLD_PID ]; then + kill -15 `cat $HTMLD_PID` + fi + if [ -f $WVIEWD_PID ]; then + kill -15 `cat $WVIEWD_PID` + fi + sleep 1 + if [ -f $RADROUTER_PID ]; then + kill -15 `cat $RADROUTER_PID` + fi + ;; + restart) + $0 stop && sleep 2 + $0 start + ;; + *) + echo "Usage: $0 {start|start-trace|stop|restart}" + exit 1 +esac + +exit 0 + diff --git a/examples/Debian/Makefile.am b/examples/Debian/Makefile.am new file mode 100755 index 0000000..2db00ff --- /dev/null +++ b/examples/Debian/Makefile.am @@ -0,0 +1,19 @@ +# Makefile - Debian + +EXTRA_DIST = $(srcdir)/wview.sh + +# define the scripts to be generated +noinst_SCRIPTS = wview + +CLEANFILES = $(noinst_SCRIPTS) + +wview: $(srcdir)/wview.sh + -echo "#!/bin/bash" > wview + echo "" >> wview + echo "# add to the shared library search path" >> wview + echo "export LD_LIBRARY_PATH=$(prefix)/lib:/usr/local/lib:/usr/lib" >> wview + echo "CONF_DIRECTORY=$(sysconfdir)/wview" >> wview + echo "RUN_DIRECTORY=$(localstatedir)/wview" >> wview + echo "WVIEW_INSTALL_DIR=$(exec_prefix)/bin" >> wview + cat $(srcdir)/wview.sh >> wview + chmod ugo+x wview diff --git a/examples/Debian/wview.sh b/examples/Debian/wview.sh new file mode 100644 index 0000000..584450c --- /dev/null +++ b/examples/Debian/wview.sh @@ -0,0 +1,174 @@ +### BEGIN INIT INFO +# Provides: wview +# Required-Start: $local_fs $network $time $syslog +# Required-Stop: $local_fs $network $time $syslog +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: Start wview daemons at boot time +# Description: Start wview daemons at boot time. +### END INIT INFO +# config: $prefix/etc/wview +# pidfiles: $prefix/var/wview/*.pid +################################################################################ + +if [ -f $CONF_DIRECTORY/wview-user ]; then + WVIEW_USER=`cat $CONF_DIRECTORY/wview-user` +fi +: ${WVIEW_USER:=root} + +WVIEWD_FILE=`cat $CONF_DIRECTORY/wview-binary` +WVIEWD_BIN=$WVIEW_INSTALL_DIR/$WVIEWD_FILE +test -x $WVIEWD_BIN || exit 5 + +HTMLD_BIN=$WVIEW_INSTALL_DIR/htmlgend +test -x $HTMLD_BIN || exit 6 + +FTPD_BIN=$WVIEW_INSTALL_DIR/wviewftpd +test -x $FTPD_BIN || exit 7 + +SSHD_BIN=$WVIEW_INSTALL_DIR/wviewsshd +test -x $SSHD_BIN || exit 7 + +ALARMD_BIN=$WVIEW_INSTALL_DIR/wvalarmd +test -x $ALARMD_BIN || exit 8 + +CWOPD_BIN=$WVIEW_INSTALL_DIR/wvcwopd +test -x $CWOPD_BIN || exit 9 + +HTTP_BIN=$WVIEW_INSTALL_DIR/wvhttpd + +RADROUTER_BIN=$WVIEW_INSTALL_DIR/radmrouted + +PMOND_BIN=$WVIEW_INSTALL_DIR/wvpmond +test -x $PMOND_BIN || exit 10 + +RADROUTER_PID=$RUN_DIRECTORY/radmrouted.pid +WVIEWD_PID=$RUN_DIRECTORY/wviewd.pid +HTMLD_PID=$RUN_DIRECTORY/htmlgend.pid +FTPD_PID=$RUN_DIRECTORY/wviewftpd.pid +SSHD_PID=$RUN_DIRECTORY/wviewsshd.pid +ALARMD_PID=$RUN_DIRECTORY/wvalarmd.pid +CWOPD_PID=$RUN_DIRECTORY/wvcwopd.pid +HTTP_PID=$RUN_DIRECTORY/wvhttpd.pid +PMOND_PID=$RUN_DIRECTORY/wvpmond.pid + +wait_for_time_set() { + THOUSAND=1000 + CURRVAL=`date +%s` + while [ "$CURRVAL" -lt "$THOUSAND" ]; do + sleep 1 + CURRVAL=`date +%s` + done +} + +cleanup_pid_files() { + for pidfile in `ls -1 $RUN_DIRECTORY/*.pid 2>/dev/null`; do + testpid=`cat $pidfile`; + if [ -n "$testpid" ]; then + result=`ps --no-headers -o pid $testpid`; + else + result="" + fi; + if [ -z "$result" ]; then + echo "Removing stale PID file $pidfile"; + rm -f $pidfile; + fi; + done; +} + +case "$1" in + start) + cleanup_pid_files + wait_for_time_set + + echo "Starting wview daemons:" + + if [ -x $RADROUTER_BIN ]; then + start-stop-daemon --start --oknodo --pidfile $RADROUTER_PID \ + --chuid $WVIEW_USER --exec $RADROUTER_BIN 1 $RUN_DIRECTORY + + else + echo "Cannot find $RADROUTER_BIN - exiting!" + exit 10 + fi + sleep 1 + start-stop-daemon --start --oknodo --pidfile $WVIEWD_PID \ + --exec $WVIEWD_BIN --chuid $WVIEW_USER + sleep 1 + start-stop-daemon --start --oknodo --pidfile $HTMLD_PID \ + --exec $HTMLD_BIN --chuid $WVIEW_USER + start-stop-daemon --start --oknodo --pidfile $ALARMD_PID \ + --exec $ALARMD_BIN --chuid $WVIEW_USER + start-stop-daemon --start --oknodo --pidfile $CWOPD_PID \ + --exec $CWOPD_BIN --chuid $WVIEW_USER + start-stop-daemon --start --oknodo --pidfile $HTTP_PID \ + --exec $HTTP_BIN --chuid $WVIEW_USER + start-stop-daemon --start --oknodo --pidfile $FTPD_PID \ + --exec $FTPD_BIN --chuid $WVIEW_USER + start-stop-daemon --start --oknodo --pidfile $SSHD_PID \ + --exec $SSHD_BIN --chuid $WVIEW_USER + start-stop-daemon --start --oknodo --pidfile $PMOND_PID \ + --exec $PMOND_BIN --chuid $WVIEW_USER + ;; + start-trace) + echo "Starting wview daemons (tracing to $RUN_DIRECTORY):" + echo "Warning: traced processes run very slowly and may effect performance." + + if [ -x $RADROUTER_BIN ]; then + start-stop-daemon --start --oknodo --pidfile $RADROUTER_PID \ + --chuid $WVIEW_USER --exec $RADROUTER_BIN 1 $RUN_DIRECTORY + + else + echo "Cannot find $RADROUTER_BIN - exiting!" + exit 10 + fi + sleep 1 + strace -o $RUN_DIRECTORY/$WVIEWD_FILE.trace $WVIEWD_BIN -f &> /dev/null & + sleep 1 + strace -o $RUN_DIRECTORY/htmlgend.trace $HTMLD_BIN -f &> /dev/null & + strace -o $RUN_DIRECTORY/wvalarmd.trace $ALARMD_BIN -f &> /dev/null & + strace -o $RUN_DIRECTORY/wvcwopd.trace $CWOPD_BIN -f &> /dev/null & + strace -o $RUN_DIRECTORY/wvhttpd.trace $HTTP_BIN -f &> /dev/null & + strace -o $RUN_DIRECTORY/wviewftpd.trace $FTPD_BIN -f &> /dev/null & + strace -o $RUN_DIRECTORY/wviewsshd.trace $SSHD_BIN -f &> /dev/null & + strace -o $RUN_DIRECTORY/wvpmond.trace $PMOND_BIN -f &> /dev/null & + ;; + stop) + start-stop-daemon --stop --oknodo --pidfile $PMOND_PID \ + --exec $PMOND_BIN --signal 15 --retry 5 + start-stop-daemon --stop --oknodo --pidfile $HTTP_PID \ + --exec $HTTP_BIN --signal 15 --retry 5 + start-stop-daemon --stop --oknodo --pidfile $CWOPD_PID \ + --exec $CWOPD_BIN --signal 15 --retry 5 + start-stop-daemon --stop --oknodo --pidfile $ALARMD_PID \ + --exec $ALARMD_BIN --signal 15 --retry 5 + start-stop-daemon --stop --oknodo --pidfile $SSHD_PID \ + --exec $SSHD_BIN --signal 15 --retry 5 + start-stop-daemon --stop --oknodo --pidfile $FTPD_PID \ + --exec $FTPD_BIN --signal 15 --retry 5 + start-stop-daemon --stop --oknodo --pidfile $HTMLD_PID \ + --exec $HTMLD_BIN --signal 15 --retry 5 + start-stop-daemon --stop --oknodo --pidfile $WVIEWD_PID \ + --exec $WVIEWD_BIN --signal 15 --retry 5 + start-stop-daemon --stop --oknodo --pidfile $RADROUTER_PID \ + --exec $RADROUTER_BIN --signal 15 --retry 5 + ;; + restart) + $0 stop && sleep 2 + $0 start + ;; + force-reload) + $0 stop && sleep 2 + $0 start + ;; + status) + ps aux | grep "wv" + ps aux | grep "htmlgend" + ;; + *) + echo "Usage: $0 {start|start-trace|stop|restart|force-reload|status}" + exit 1 +esac + +exit 0 + diff --git a/examples/FedoraCore/Makefile.am b/examples/FedoraCore/Makefile.am new file mode 100755 index 0000000..7ce8d77 --- /dev/null +++ b/examples/FedoraCore/Makefile.am @@ -0,0 +1,19 @@ +# Makefile - FedoraCore + +EXTRA_DIST = $(srcdir)/wview.sh + +# define the scripts to be generated +noinst_SCRIPTS = wview + +CLEANFILES = $(noinst_SCRIPTS) + +wview: $(srcdir)/wview.sh + echo "#!/bin/bash" > wview + echo "" >> wview + echo "# add to the shared library search path" >> wview + echo "export LD_LIBRARY_PATH=$(prefix)/lib:/usr/local/lib:/usr/lib" >> wview + echo "CONF_DIRECTORY=$(sysconfdir)/wview" >> wview + echo "RUN_DIRECTORY=$(localstatedir)/wview" >> wview + echo "WVIEW_INSTALL_DIR=$(exec_prefix)/bin" >> wview + cat $(srcdir)/wview.sh >> wview + chmod ugo+x wview diff --git a/examples/FedoraCore/wview.sh b/examples/FedoraCore/wview.sh new file mode 100755 index 0000000..ec5d583 --- /dev/null +++ b/examples/FedoraCore/wview.sh @@ -0,0 +1,198 @@ +# chkconfig: - 89 11 +# description: wview is a unix weather program +# +# wview Start/Stop the wview daemons +# +# processnames: wviewd htmlgend wviewftpd wvalarmd wvcwopd wviewsshd wvhttpd +# config: $prefix/etc/wview +# pidfiles: $prefix/var/wview/*.pid + +# Source function library: +. /etc/init.d/functions + +WVIEWD_FILE=`cat $CONF_DIRECTORY/wview-binary` +WVIEWD_BIN=$WVIEW_INSTALL_DIR/$WVIEWD_FILE +test -x $WVIEWD_BIN || exit 5 + +HTMLD_BIN=$WVIEW_INSTALL_DIR/htmlgend +test -x $HTMLD_BIN || exit 6 + +FTPD_BIN=$WVIEW_INSTALL_DIR/wviewftpd +test -x $FTPD_BIN || exit 7 + +SSHD_BIN=$WVIEW_INSTALL_DIR/wviewsshd +test -x $SSHD_BIN || exit 7 + +ALARMD_BIN=$WVIEW_INSTALL_DIR/wvalarmd +test -x $ALARMD_BIN || exit 8 + +CWOPD_BIN=$WVIEW_INSTALL_DIR/wvcwopd +test -x $CWOPD_BIN || exit 9 + +HTTP_BIN=$WVIEW_INSTALL_DIR/wvhttpd + +SQLD_BIN=$WVIEW_INSTALL_DIR/wviewsqld + +RADROUTER_BIN=$WVIEW_INSTALL_DIR/radmrouted + +PMOND_BIN=$WVIEW_INSTALL_DIR/wvpmond +test -x $PMOND_BIN || exit 10 + +WVIEWD_PID=$RUN_DIRECTORY/wviewd.pid +HTMLD_PID=$RUN_DIRECTORY/htmlgend.pid +FTPD_PID=$RUN_DIRECTORY/wviewftpd.pid +SSHD_PID=$RUN_DIRECTORY/wviewsshd.pid +ALARMD_PID=$RUN_DIRECTORY/wvalarmd.pid +CWOPD_PID=$RUN_DIRECTORY/wvcwopd.pid +HTTP_PID=$RUN_DIRECTORY/wvhttpd.pid +SQLD_PID=$RUN_DIRECTORY/wviewsqld.pid +RADROUTER_PID=$RUN_DIRECTORY/radmrouted.pid +PMOND_PID=$RUN_DIRECTORY/wvpmond.pid + +wait_for_time_set() { + THOUSAND=1000 + CURRVAL=`date +%s` + while [ "$CURRVAL" -lt "$THOUSAND" ]; do + sleep 1 + CURRVAL=`date +%s` + done +} + +kill_running_processes() { + if [ -f $RADROUTER_PID ]; then + echo "radlib router pid file $RADROUTER_PID exists - killing existing process" + kill -15 `cat $RADROUTER_PID` + rm -f $RADROUTER_PID + fi + if [ -f $WVIEWD_PID ]; then + echo "wviewd pid file $WVIEWD_PID exists - killing existing process" + kill -15 `cat $WVIEWD_PID` + rm -f $WVIEWD_PID + fi + if [ -f $HTMLD_PID ]; then + echo "htmlgend pid file $HTMLD_PID exists - killing existing process" + kill -15 `cat $HTMLD_PID` + rm -f $HTMLD_PID + fi + if [ -f $FTPD_PID ]; then + echo "wviewftpd pid file $FTPD_PID exists - killing existing process" + kill -15 `cat $FTPD_PID` + rm -f $FTPD_PID + fi + if [ -f $SSHD_PID ]; then + echo "wviewsshd pid file $SSHD_PID exists - killing existing process" + kill -15 `cat $SSHD_PID` + rm -f $SSHD_PID + fi + if [ -f $ALARMD_PID ]; then + echo "wvalarmd pid file $ALARMD_PID exists - killing existing process" + kill -15 `cat $ALARMD_PID` + rm -f $ALARMD_PID + fi + if [ -f $CWOPD_PID ]; then + echo "wvcwopd pid file $CWOPD_PID exists - killing existing process" + kill -15 `cat $CWOPD_PID` + rm -f $CWOPD_PID + fi + if [ -f $HTTP_PID ]; then + echo "wvhttpd pid file $HTTP_PID exists - killing existing process" + kill -15 `cat $HTTP_PID` + rm -f $HTTP_PID + fi + if [ -f $PMOND_PID ]; then + echo "wvpmond pid file $PMOND_PID exists - killing existing process" + kill -15 `cat $PMOND_PID` + rm -f $PMOND_PID + fi +} + +case "$1" in + start) + kill_running_processes + + wait_for_time_set + + echo "Starting wview daemons:" + + if [ -x $RADROUTER_BIN ]; then + $RADROUTER_BIN 1 $RUN_DIRECTORY + else + echo "Cannot find $RADROUTER_BIN - exiting!" + exit 10 + fi + sleep 1 + $WVIEWD_BIN + sleep 1 + $HTMLD_BIN + $ALARMD_BIN + $CWOPD_BIN + $HTTP_BIN + $FTPD_BIN + $SSHD_BIN + $PMOND_BIN + ;; + start-trace) + kill_running_processes + + echo "Starting wview daemons (tracing to $RUN_DIRECTORY):" + echo "Warning: traced processes run very slowly and may effect performance." + + if [ -x $RADROUTER_BIN ]; then + $RADROUTER_BIN 1 $RUN_DIRECTORY + else + echo "Cannot find $RADROUTER_BIN - exiting!" + exit 10 + fi + sleep 1 + strace -o $RUN_DIRECTORY/$WVIEWD_FILE.trace $WVIEWD_BIN -f &> /dev/null & + sleep 1 + strace -o $RUN_DIRECTORY/htmlgend.trace $HTMLD_BIN -f &> /dev/null & + strace -o $RUN_DIRECTORY/wvalarmd.trace $ALARMD_BIN -f &> /dev/null & + strace -o $RUN_DIRECTORY/wvcwopd.trace $CWOPD_BIN -f &> /dev/null & + strace -o $RUN_DIRECTORY/wvhttpd.trace $HTTP_BIN -f &> /dev/null & + strace -o $RUN_DIRECTORY/wviewftpd.trace $FTPD_BIN -f &> /dev/null & + strace -o $RUN_DIRECTORY/wviewsshd.trace $SSHD_BIN -f &> /dev/null & + strace -o $RUN_DIRECTORY/wvpmond.trace $PMOND_BIN -f &> /dev/null & + ;; + stop) + echo "Shutting down wview daemons..." + if [ -f $PMOND_PID ]; then + kill -15 `cat $PMOND_PID` + fi + if [ -f $HTTP_PID ]; then + kill -15 `cat $HTTP_PID` + fi + if [ -f $CWOPD_PID ]; then + kill -15 `cat $CWOPD_PID` + fi + if [ -f $ALARMD_PID ]; then + kill -15 `cat $ALARMD_PID` + fi + if [ -f $SSHD_PID ]; then + kill -15 `cat $SSHD_PID` + fi + if [ -f $FTPD_PID ]; then + kill -15 `cat $FTPD_PID` + fi + if [ -f $HTMLD_PID ]; then + kill -15 `cat $HTMLD_PID` + fi + if [ -f $WVIEWD_PID ]; then + kill -15 `cat $WVIEWD_PID` + fi + sleep 1 + if [ -f $RADROUTER_PID ]; then + kill -15 `cat $RADROUTER_PID` + fi + ;; + restart) + $0 stop && sleep 2 + $0 start + ;; + *) + echo "Usage: $0 {start|start-trace|stop|restart}" + exit 1 +esac + +exit 0 + diff --git a/examples/FreeBSD/Makefile.am b/examples/FreeBSD/Makefile.am new file mode 100755 index 0000000..c4430e6 --- /dev/null +++ b/examples/FreeBSD/Makefile.am @@ -0,0 +1,19 @@ +# Makefile - FreeBSD + +EXTRA_DIST = $(srcdir)/wview.sh + +# define the scripts to be generated +noinst_SCRIPTS = wview + +CLEANFILES = $(noinst_SCRIPTS) + +wview: $(srcdir)/wview.sh + echo "#!/usr/local/bin/bash" > wview + echo "" >> wview + echo "# add to the shared library search path" >> wview + echo "export LD_LIBRARY_PATH=$(prefix)/lib:/usr/local/lib:/usr/lib" >> wview + echo "CONF_DIRECTORY=$(sysconfdir)/wview" >> wview + echo "RUN_DIRECTORY=$(localstatedir)/wview" >> wview + echo "WVIEW_INSTALL_DIR=$(exec_prefix)/bin" >> wview + cat $(srcdir)/wview.sh >> wview + chmod ugo+x wview diff --git a/examples/FreeBSD/wview.sh b/examples/FreeBSD/wview.sh new file mode 100755 index 0000000..1c9c48f --- /dev/null +++ b/examples/FreeBSD/wview.sh @@ -0,0 +1,214 @@ +# +# wview Start/Stop the wview daemons +# +# processnames: wviewd htmlgend wviewftpd wvalarmd wvcwopd wviewsshd wvhttpd +# +# FreeBSD control script +# + +WVIEWD_FILE=`cat $CONF_DIRECTORY/wview-binary` +WVIEWD_BIN=$WVIEW_INSTALL_DIR/$WVIEWD_FILE +test -x $WVIEWD_BIN || exit 5 + +HTMLD_BIN=$WVIEW_INSTALL_DIR/htmlgend +test -x $HTMLD_BIN || exit 6 + +FTPD_BIN=$WVIEW_INSTALL_DIR/wviewftpd +test -x $FTPD_BIN || exit 7 + +SSHD_BIN=$WVIEW_INSTALL_DIR/wviewsshd +test -x $SSHD_BIN || exit 7 + +ALARMD_BIN=$WVIEW_INSTALL_DIR/wvalarmd +test -x $ALARMD_BIN || exit 8 + +CWOPD_BIN=$WVIEW_INSTALL_DIR/wvcwopd +test -x $CWOPD_BIN || exit 9 + +HTTP_BIN=$WVIEW_INSTALL_DIR/wvhttpd + +SQLD_BIN=$WVIEW_INSTALL_DIR/wviewsqld + +RADROUTER_BIN=$WVIEW_INSTALL_DIR/radmrouted + +PMOND_BIN=$WVIEW_INSTALL_DIR/wvpmond +test -x $PMOND_BIN || exit 10 + +WVIEWD_PID=$RUN_DIRECTORY/wviewd.pid +HTMLD_PID=$RUN_DIRECTORY/htmlgend.pid +FTPD_PID=$RUN_DIRECTORY/wviewftpd.pid +SSHD_PID=$RUN_DIRECTORY/wviewsshd.pid +ALARMD_PID=$RUN_DIRECTORY/wvalarmd.pid +CWOPD_PID=$RUN_DIRECTORY/wvcwopd.pid +HTTP_PID=$RUN_DIRECTORY/wvhttpd.pid +SQLD_PID=$RUN_DIRECTORY/wviewsqld.pid +RADROUTER_PID=$RUN_DIRECTORY/radmrouted.pid +PMOND_PID=$RUN_DIRECTORY/wvpmond.pid + +wait_for_time_set() { + THOUSAND=1000 + CURRVAL=`date +%s` + while [ "$CURRVAL" -lt "$THOUSAND" ]; do + sleep 1 + CURRVAL=`date +%s` + done +} + +kill_running_processes() { + if [ -f $RADROUTER_PID ]; then + echo "radlib router pid file $RADROUTER_PID exists - killing existing process" + kill -15 `cat $RADROUTER_PID` + rm -f $RADROUTER_PID + fi + if [ -f $WVIEWD_PID ]; then + echo "wviewd pid file $WVIEWD_PID exists - killing existing process" + kill -15 `cat $WVIEWD_PID` + rm -f $WVIEWD_PID + fi + if [ -f $HTMLD_PID ]; then + echo "htmlgend pid file $HTMLD_PID exists - killing existing process" + kill -15 `cat $HTMLD_PID` + rm -f $HTMLD_PID + fi + if [ -f $FTPD_PID ]; then + echo "wviewftpd pid file $FTPD_PID exists - killing existing process" + kill -15 `cat $FTPD_PID` + rm -f $FTPD_PID + fi + if [ -f $SSHD_PID ]; then + echo "wviewsshd pid file $SSHD_PID exists - killing existing process" + kill -15 `cat $SSHD_PID` + rm -f $SSHD_PID + fi + if [ -f $ALARMD_PID ]; then + echo "wvalarmd pid file $ALARMD_PID exists - killing existing process" + kill -15 `cat $ALARMD_PID` + rm -f $ALARMD_PID + fi + if [ -f $CWOPD_PID ]; then + echo "wvcwopd pid file $CWOPD_PID exists - killing existing process" + kill -15 `cat $CWOPD_PID` + rm -f $CWOPD_PID + fi + if [ -f $HTTP_PID ]; then + echo "wvhttpd pid file $HTTP_PID exists - killing existing process" + kill -15 `cat $HTTP_PID` + rm -f $HTTP_PID + fi + if [ -f $PMOND_PID ]; then + echo "wvpmond pid file $PMOND_PID exists - killing existing process" + kill -15 `cat $PMOND_PID` + rm -f $PMOND_PID + fi +} + +cleanup_pid_files() { + for pidfile in `ls -1 $RUN_DIRECTORY/*.pid 2>/dev/null`; do + testpid=`cat $pidfile`; + if [ -n "$testpid" ]; then + result=`ps --no-headers -o pid $testpid`; + else + result="" + fi; + if [ -z "$result" ]; then + echo "Removing stale PID file $pidfile"; + rm -f $pidfile; + fi; + done; +} + +case "$1" in + start) + kill_running_processes + cleanup_pid_files + + wait_for_time_set + + echo "Starting wview daemons:" + + if [ -x $RADROUTER_BIN ]; then + $RADROUTER_BIN 1 $RUN_DIRECTORY + else + echo "Cannot find $RADROUTER_BIN - exiting!" + exit 10 + fi + sleep 1 + $WVIEWD_BIN + sleep 1 + $HTMLD_BIN + $ALARMD_BIN + $CWOPD_BIN + $HTTP_BIN + $FTPD_BIN + $SSHD_BIN + $PMOND_BIN + ;; + + start-trace) + kill_running_processes + + echo "Starting wview daemons (tracing to $RUN_DIRECTORY):" + echo "Warning: traced processes run very slowly and may effect performance." + + if [ -x $RADROUTER_BIN ]; then + $RADROUTER_BIN 1 $RUN_DIRECTORY + else + echo "Cannot find $RADROUTER_BIN - exiting!" + exit 10 + fi + sleep 1 + strace -o $RUN_DIRECTORY/$WVIEWD_FILE.trace $WVIEWD_BIN -f &> /dev/null & + sleep 1 + strace -o $RUN_DIRECTORY/htmlgend.trace $HTMLD_BIN -f &> /dev/null & + strace -o $RUN_DIRECTORY/wvalarmd.trace $ALARMD_BIN -f &> /dev/null & + strace -o $RUN_DIRECTORY/wvcwopd.trace $CWOPD_BIN -f &> /dev/null & + strace -o $RUN_DIRECTORY/wvhttpd.trace $HTTP_BIN -f &> /dev/null & + strace -o $RUN_DIRECTORY/wviewftpd.trace $FTPD_BIN -f &> /dev/null & + strace -o $RUN_DIRECTORY/wviewsshd.trace $SSHD_BIN -f &> /dev/null & + strace -o $RUN_DIRECTORY/wvpmond.trace $PMOND_BIN -f &> /dev/null & + ;; + + stop) + echo "Shutting down wview daemons..." + if [ -f $PMOND_PID ]; then + kill -15 `cat $PMOND_PID` + fi + if [ -f $HTTP_PID ]; then + kill -15 `cat $HTTP_PID` + fi + if [ -f $CWOPD_PID ]; then + kill -15 `cat $CWOPD_PID` + fi + if [ -f $ALARMD_PID ]; then + kill -15 `cat $ALARMD_PID` + fi + if [ -f $SSHD_PID ]; then + kill -15 `cat $SSHD_PID` + fi + if [ -f $FTPD_PID ]; then + kill -15 `cat $FTPD_PID` + fi + if [ -f $HTMLD_PID ]; then + kill -15 `cat $HTMLD_PID` + fi + if [ -f $WVIEWD_PID ]; then + kill -15 `cat $WVIEWD_PID` + fi + sleep 1 + if [ -f $RADROUTER_PID ]; then + kill -15 `cat $RADROUTER_PID` + fi + ;; + + restart) + $0 stop && sleep 2 + $0 start + ;; + + *) + echo "Usage: $0 {start|start-trace|stop|restart}" + exit 1 +esac + +exit 0 + diff --git a/examples/MacOSX/wview/Makefile.am b/examples/MacOSX/wview/Makefile.am new file mode 100755 index 0000000..f5592b0 --- /dev/null +++ b/examples/MacOSX/wview/Makefile.am @@ -0,0 +1,19 @@ +# Makefile - Mac OSX + +EXTRA_DIST = $(srcdir)/wview.sh $(srcdir)/StartupParameters.plist + +# define the scripts to be generated +noinst_SCRIPTS = wview + +CLEANFILES = $(noinst_SCRIPTS) + +wview: $(srcdir)/wview.sh + echo "#!/bin/sh" > wview + echo "" >> wview + echo "# add to the shared library search path" >> wview + echo "export LD_LIBRARY_PATH=$(prefix)/lib:/usr/local/lib:/usr/lib" >> wview + echo "CONF_DIRECTORY=$(sysconfdir)/wview" >> wview + echo "RUN_DIRECTORY=$(localstatedir)/wview" >> wview + echo "WVIEW_INSTALL_DIR=$(exec_prefix)/bin" >> wview + cat $(srcdir)/wview.sh >> wview + chmod ugo+x wview diff --git a/examples/MacOSX/wview/StartupParameters.plist b/examples/MacOSX/wview/StartupParameters.plist new file mode 100644 index 0000000..22d77d9 --- /dev/null +++ b/examples/MacOSX/wview/StartupParameters.plist @@ -0,0 +1,7 @@ +{ + Description = "wview daemons"; + Provides = ("wview"); + Requires = ("Disks", "NetInfo", "Resolver"); + Uses = ("Network", "Network Time"); + OrderPreference = "None"; +} diff --git a/examples/MacOSX/wview/wview.sh b/examples/MacOSX/wview/wview.sh new file mode 100755 index 0000000..5f5715b --- /dev/null +++ b/examples/MacOSX/wview/wview.sh @@ -0,0 +1,149 @@ +## +# wview daemons for MacOSX +## + +. /etc/rc.common + + +# fully qualified program names +WVIEWD_FILE=`cat $CONF_DIRECTORY/wview-binary` +WVIEWD_BIN=$WVIEW_INSTALL_DIR/$WVIEWD_FILE +HTMLD_BIN=$WVIEW_INSTALL_DIR/htmlgend +FTPD_BIN=$WVIEW_INSTALL_DIR/wviewftpd +SSHD_BIN=$WVIEW_INSTALL_DIR/wviewsshd +ALARMD_BIN=$WVIEW_INSTALL_DIR/wvalarmd +CWOPD_BIN=$WVIEW_INSTALL_DIR/wvcwopd +HTTP_BIN=$WVIEW_INSTALL_DIR/wvhttpd +SQLD_BIN=$WVIEW_INSTALL_DIR/wviewsqld +RADROUTER_BIN=$WVIEW_INSTALL_DIR/radmrouted +PMOND_BIN=$WVIEW_INSTALL_DIR/wvpmond + +# pid names +WVIEWD_PID=$RUN_DIRECTORY/wviewd.pid +HTMLD_PID=$RUN_DIRECTORY/htmlgend.pid +FTPD_PID=$RUN_DIRECTORY/wviewftpd.pid +SSHD_PID=$RUN_DIRECTORY/wviewsshd.pid +ALARMD_PID=$RUN_DIRECTORY/wvalarmd.pid +CWOPD_PID=$RUN_DIRECTORY/wvcwopd.pid +HTTP_PID=$RUN_DIRECTORY/wvhttpd.pid +SQLD_PID=$RUN_DIRECTORY/wviewsqld.pid +RADROUTER_PID=$RUN_DIRECTORY/radmrouted.pid +PMOND_PID=$RUN_DIRECTORY/wvpmond.pid + +StartService () +{ + ConsoleMessage "Starting wview daemons" + if [ -f $RADROUTER_PID ]; then + echo "radlib router pid file $RADROUTER_PID exists - killing existing process" + kill -15 `cat $RADROUTER_PID` + rm -f $RADROUTER_PID + fi + if [ -f $WVIEWD_PID ]; then + echo "wviewd pid file $WVIEWD_PID exists - killing existing process" + kill -15 `cat $WVIEWD_PID` + rm -f $WVIEWD_PID + fi + if [ -f $HTMLD_PID ]; then + echo "htmlgend pid file $HTMLD_PID exists - killing existing process" + kill -15 `cat $HTMLD_PID` + rm -f $HTMLD_PID + fi + if [ -f $FTPD_PID ]; then + echo "wviewftpd pid file $FTPD_PID exists - killing existing process" + kill -15 `cat $FTPD_PID` + rm -f $FTPD_PID + fi + if [ -f $SSHD_PID ]; then + echo "wviewsshd pid file $SSHD_PID exists - killing existing process" + kill -15 `cat $SSHD_PID` + rm -f $SSHD_PID + fi + if [ -f $ALARMD_PID ]; then + echo "wvalarmd pid file $ALARMD_PID exists - killing existing process" + kill -15 `cat $ALARMD_PID` + rm -f $ALARMD_PID + fi + if [ -f $CWOPD_PID ]; then + echo "wvcwopd pid file $CWOPD_PID exists - killing existing process" + kill -15 `cat $CWOPD_PID` + rm -f $CWOPD_PID + fi + if [ -f $HTTP_PID ]; then + echo "wvhttpd pid file $HTTP_PID exists - killing existing process" + kill -15 `cat $HTTP_PID` + rm -f $HTTP_PID + fi + if [ -f $SQLD_PID ]; then + echo "wviewsqld pid file $SQLD_PID exists - killing existing process" + kill -15 `cat $SQLD_PID` + rm -f $SQLD_PID + fi + if [ -f $PMOND_PID ]; then + echo "wvpmond pid file $PMOND_PID exists - killing existing process" + kill -15 `cat $PMOND_PID` + rm -f $PMOND_PID + fi + + if [ -x $RADROUTER_BIN ]; then + $RADROUTER_BIN 1 $RUN_DIRECTORY + else + echo "Cannot find $RADROUTER_BIN - exiting!" + exit 10 + fi + sleep 1 + $WVIEWD_BIN + sleep 1 + $HTMLD_BIN + $ALARMD_BIN + $CWOPD_BIN + $HTTP_BIN + $FTPD_BIN + $SSHD_BIN + $PMOND_BIN + + ConsoleMessage -S +} + +StopService () +{ + ConsoleMessage "Stopping wview daemons" + if [ -f $PMOND_PID ]; then + kill -15 `cat $PMOND_PID` + fi + if [ -f $HTTP_PID ]; then + kill -15 `cat $HTTP_PID` + fi + if [ -f $CWOPD_PID ]; then + kill -15 `cat $CWOPD_PID` + fi + if [ -f $ALARMD_PID ]; then + kill -15 `cat $ALARMD_PID` + fi + if [ -f $SSHD_PID ]; then + kill -15 `cat $SSHD_PID` + fi + if [ -f $FTPD_PID ]; then + kill -15 `cat $FTPD_PID` + fi + if [ -f $HTMLD_PID ]; then + kill -15 `cat $HTMLD_PID` + fi + if [ -f $WVIEWD_PID ]; then + kill -15 `cat $WVIEWD_PID` + fi + sleep 1 + if [ -f $RADROUTER_PID ]; then + kill -15 `cat $RADROUTER_PID` + fi + + ConsoleMessage -S +} + +RestartService () +{ + StopService + sleep 2 + StartService +} + +RunService "$1" diff --git a/examples/Makefile.am b/examples/Makefile.am new file mode 100755 index 0000000..2bf50e8 --- /dev/null +++ b/examples/Makefile.am @@ -0,0 +1,3 @@ +# Makefile - examples + +SUBDIRS = Debian FedoraCore FreeBSD MacOSX/wview NSLU2 SuSE diff --git a/examples/NSLU2/Makefile.am b/examples/NSLU2/Makefile.am new file mode 100755 index 0000000..4cf04da --- /dev/null +++ b/examples/NSLU2/Makefile.am @@ -0,0 +1,19 @@ +# Makefile - NSLU2 + +EXTRA_DIST = $(srcdir)/wview.sh + +# define the scripts to be generated +noinst_SCRIPTS = wview + +CLEANFILES = $(noinst_SCRIPTS) + +wview: $(srcdir)/wview.sh + echo "#!/bin/sh" > wview + echo "" >> wview + echo "# add to the shared library search path" >> wview + echo "export LD_LIBRARY_PATH=$(prefix)/lib:/usr/lib" >> wview + echo "CONF_DIRECTORY=$(sysconfdir)/wview" >> wview + echo "RUN_DIRECTORY=$(localstatedir)/wview" >> wview + echo "WVIEW_INSTALL_DIR=$(exec_prefix)/bin" >> wview + cat $(srcdir)/wview.sh >> wview + chmod ugo+x wview diff --git a/examples/NSLU2/wview.sh b/examples/NSLU2/wview.sh new file mode 100755 index 0000000..b51a09c --- /dev/null +++ b/examples/NSLU2/wview.sh @@ -0,0 +1,201 @@ +# +# Name: wview - init.d script for busybox wview +# Process Names: radmrouted wviewd htmlgend wviewftpd wvalarmd +# wvcwopd wviewsshd wvhttpd +# Config: /etc/wview/*.conf +# Pidfiles: /var/wview/*.pid +# + +RADROUTER_BIN=$WVIEW_INSTALL_DIR/radmrouted +test -x $RADROUTER_BIN || exit 1 +WVIEWD_FILE=`cat $CONF_DIRECTORY/wview-binary` +WVIEWD_BIN=$WVIEW_INSTALL_DIR/$WVIEWD_FILE +test -x $WVIEWD_BIN || exit 2 +HTMLD_BIN=$WVIEW_INSTALL_DIR/htmlgend +test -x $HTMLD_BIN || exit 3 +FTPD_BIN=$WVIEW_INSTALL_DIR/wviewftpd +test -x $FTPD_BIN || exit 4 +SSHD_BIN=$WVIEW_INSTALL_DIR/wviewsshd +test -x $SSHD_BIN || exit 5 +ALARMD_BIN=$WVIEW_INSTALL_DIR/wvalarmd +test -x $ALARMD_BIN || exit 6 +CWOPD_BIN=$WVIEW_INSTALL_DIR/wvcwopd +test -x $CWOPD_BIN || exit 7 +HTTP_BIN=$WVIEW_INSTALL_DIR/wvhttpd +SQLD_BIN=$WVIEW_INSTALL_DIR/wviewsqld +PMOND_BIN=$WVIEW_INSTALL_DIR/wvpmond +test -x $PMOND_BIN || exit 10 + +WVIEWD_PID=$RUN_DIRECTORY/wviewd.pid +HTMLD_PID=$RUN_DIRECTORY/htmlgend.pid +FTPD_PID=$RUN_DIRECTORY/wviewftpd.pid +SSHD_PID=$RUN_DIRECTORY/wviewsshd.pid +ALARMD_PID=$RUN_DIRECTORY/wvalarmd.pid +CWOPD_PID=$RUN_DIRECTORY/wvcwopd.pid +HTTP_PID=$RUN_DIRECTORY/wvhttpd.pid +SQLD_PID=$RUN_DIRECTORY/wviewsqld.pid +RADROUTER_PID=$RUN_DIRECTORY/radmrouted.pid +PMOND_PID=$RUN_DIRECTORY/wvpmond.pid + +wait_for_time_set() { + THOUSAND=1000 + CURRVAL=`date +%s` + while [ "$CURRVAL" -lt "$THOUSAND" ]; do + sleep 1 + CURRVAL=`date +%s` + done +} + +kill_running_processes() { + if [ -f $RADROUTER_PID ]; then + echo "radlib router pid file $RADROUTER_PID exists - killing existing process" + kill -15 `cat $RADROUTER_PID` + rm -f $RADROUTER_PID + fi + if [ -f $WVIEWD_PID ]; then + echo "wviewd pid file $WVIEWD_PID exists - killing existing process" + kill -15 `cat $WVIEWD_PID` + rm -f $WVIEWD_PID + fi + if [ -f $HTMLD_PID ]; then + echo "htmlgend pid file $HTMLD_PID exists - killing existing process" + kill -15 `cat $HTMLD_PID` + rm -f $HTMLD_PID + fi + if [ -f $FTPD_PID ]; then + echo "wviewftpd pid file $FTPD_PID exists - killing existing process" + kill -15 `cat $FTPD_PID` + rm -f $FTPD_PID + fi + if [ -f $SSHD_PID ]; then + echo "wviewsshd pid file $SSHD_PID exists - killing existing process" + kill -15 `cat $SSHD_PID` + rm -f $SSHD_PID + fi + if [ -f $ALARMD_PID ]; then + echo "wvalarmd pid file $ALARMD_PID exists - killing existing process" + kill -15 `cat $ALARMD_PID` + rm -f $ALARMD_PID + fi + if [ -f $CWOPD_PID ]; then + echo "wvcwopd pid file $CWOPD_PID exists - killing existing process" + kill -15 `cat $CWOPD_PID` + rm -f $CWOPD_PID + fi + if [ -f $HTTP_PID ]; then + echo "wvhttpd pid file $HTTP_PID exists - killing existing process" + kill -15 `cat $HTTP_PID` + rm -f $HTTP_PID + fi + if [ -f $PMOND_PID ]; then + echo "wvpmond pid file $PMOND_PID exists - killing existing process" + kill -15 `cat $PMOND_PID` + rm -f $PMOND_PID + fi +} + +cleanup_pid_files() { + for pidfile in `ls -1 $RUN_DIRECTORY/*.pid 2>/dev/null`; do + testpid=`cat $pidfile`; + if [ -n "$testpid" ]; then + result=`ps --no-headers -o pid $testpid`; + else + result="" + fi; + if [ -z "$result" ]; then + echo "Removing stale PID file $pidfile"; + rm -f $pidfile; + fi; + done; +} + +case "$1" in + start) + kill_running_processes + cleanup_pid_files + + wait_for_time_set + + echo "Starting wview daemons:" + + if [ -x $RADROUTER_BIN ]; then + $RADROUTER_BIN 1 $RUN_DIRECTORY + else + echo "Cannot find $RADROUTER_BIN - exiting!" + exit 10 + fi + sleep 1 + $WVIEWD_BIN + sleep 1 + $HTMLD_BIN + $ALARMD_BIN + $CWOPD_BIN + $HTTP_BIN + $FTPD_BIN + $SSHD_BIN + $PMOND_BIN + ;; + start-trace) + kill_running_processes + + echo "Starting wview daemons (tracing to $RUN_DIRECTORY):" + echo "Warning: traced processes run very slowly and may effect performance." + + if [ -x $RADROUTER_BIN ]; then + $RADROUTER_BIN 1 $RUN_DIRECTORY + else + echo "Cannot find $RADROUTER_BIN - exiting!" + exit 10 + fi + sleep 1 + strace -o $RUN_DIRECTORY/$WVIEWD_FILE.trace $WVIEWD_BIN -f &> /dev/null & + sleep 1 + strace -o $RUN_DIRECTORY/htmlgend.trace $HTMLD_BIN -f &> /dev/null & + strace -o $RUN_DIRECTORY/wvalarmd.trace $ALARMD_BIN -f &> /dev/null & + strace -o $RUN_DIRECTORY/wvcwopd.trace $CWOPD_BIN -f &> /dev/null & + strace -o $RUN_DIRECTORY/wvhttpd.trace $HTTP_BIN -f &> /dev/null & + strace -o $RUN_DIRECTORY/wviewftpd.trace $FTPD_BIN -f &> /dev/null & + strace -o $RUN_DIRECTORY/wviewsshd.trace $SSHD_BIN -f &> /dev/null & + strace -o $RUN_DIRECTORY/wvpmond.trace $PMOND_BIN -f &> /dev/null & + ;; + stop) + echo "Shutting down wview daemons..." + if [ -f $PMOND_PID ]; then + kill -15 `cat $PMOND_PID` + fi + if [ -f $HTTP_PID ]; then + kill -15 `cat $HTTP_PID` + fi + if [ -f $CWOPD_PID ]; then + kill -15 `cat $CWOPD_PID` + fi + if [ -f $ALARMD_PID ]; then + kill -15 `cat $ALARMD_PID` + fi + if [ -f $SSHD_PID ]; then + kill -15 `cat $SSHD_PID` + fi + if [ -f $FTPD_PID ]; then + kill -15 `cat $FTPD_PID` + fi + if [ -f $HTMLD_PID ]; then + kill -15 `cat $HTMLD_PID` + fi + if [ -f $WVIEWD_PID ]; then + kill -15 `cat $WVIEWD_PID` + fi + sleep 1 + if [ -f $RADROUTER_PID ]; then + kill -15 `cat $RADROUTER_PID` + fi + ;; + restart) + $0 stop && sleep 2 + $0 start + ;; + *) + echo "Usage: $0 {start|start-trace|stop|restart}" + exit 1 +esac + +exit 0 diff --git a/examples/SuSE/Makefile.am b/examples/SuSE/Makefile.am new file mode 100755 index 0000000..51b0537 --- /dev/null +++ b/examples/SuSE/Makefile.am @@ -0,0 +1,19 @@ +# Makefile - SuSE + +EXTRA_DIST = $(srcdir)/wview.sh + +# define the scripts to be generated +noinst_SCRIPTS = wview + +CLEANFILES = $(noinst_SCRIPTS) + +wview: $(srcdir)/wview.sh + echo "#!/bin/sh" > wview + echo "" >> wview + echo "# add to the shared library search path" >> wview + echo "export LD_LIBRARY_PATH=$(prefix)/lib:/usr/local/lib:/usr/lib" >> wview + echo "CONF_DIRECTORY=$(sysconfdir)/wview" >> wview + echo "RUN_DIRECTORY=$(localstatedir)/wview" >> wview + echo "WVIEW_INSTALL_DIR=$(exec_prefix)/bin" >> wview + cat $(srcdir)/wview.sh >> wview + chmod ugo+x wview diff --git a/examples/SuSE/wview.sh b/examples/SuSE/wview.sh new file mode 100755 index 0000000..be49f86 --- /dev/null +++ b/examples/SuSE/wview.sh @@ -0,0 +1,242 @@ +# +# /etc/init.d/wview +# +# Start/Stop the wview daemons +# +# processnames: wviewd htmlgend wviewftpd wvalarmd wvcwopd wviewsshd wvhttpd +# +# SuSE control script +# + + +# load the configuration +# +test -s /etc/rc.status && \ + . /etc/rc.status + +WVIEWD_FILE=`cat $CONF_DIRECTORY/wview-binary` +WVIEWD_BIN=$WVIEW_INSTALL_DIR/$WVIEWD_FILE +test -x $WVIEWD_BIN || exit 3 + +HTMLD_BIN=$WVIEW_INSTALL_DIR/htmlgend +test -x $HTMLD_BIN || exit 4 + +FTPD_BIN=$WVIEW_INSTALL_DIR/wviewftpd +test -x $FTPD_BIN || exit 5 + +SSHD_BIN=$WVIEW_INSTALL_DIR/wviewsshd +test -x $SSHD_BIN || exit 6 + +ALARMD_BIN=$WVIEW_INSTALL_DIR/wvalarmd +test -x $ALARMD_BIN || exit 7 + +CWOPD_BIN=$WVIEW_INSTALL_DIR/wvcwopd +test -x $CWOPD_BIN || exit 8 + +HTTP_BIN=$WVIEW_INSTALL_DIR/wvhttpd + +SQLD_BIN=$WVIEW_INSTALL_DIR/wviewsqld + +RADROUTER_BIN=$WVIEW_INSTALL_DIR/radmrouted + +PMOND_BIN=$WVIEW_INSTALL_DIR/wvpmond +test -x $PMOND_BIN || exit 10 + +rc_reset + +WVIEWD_PID=$RUN_DIRECTORY/wviewd.pid +HTMLD_PID=$RUN_DIRECTORY/htmlgend.pid +FTPD_PID=$RUN_DIRECTORY/wviewftpd.pid +SSHD_PID=$RUN_DIRECTORY/wviewsshd.pid +ALARMD_PID=$RUN_DIRECTORY/wvalarmd.pid +CWOPD_PID=$RUN_DIRECTORY/wvcwopd.pid +HTTP_PID=$RUN_DIRECTORY/wvhttpd.pid +SQLD_PID=$RUN_DIRECTORY/wviewsqld.pid +RADROUTER_PID=$RUN_DIRECTORY/radmrouted.pid +PMOND_PID=$RUN_DIRECTORY/wvpmond.pid + +# +# The echo return value for success (defined in /etc/rc.status). +# +return=$rc_done + +wait_for_time_set() { + THOUSAND=1000 + CURRVAL=`date +%s` + while [ "$CURRVAL" -lt "$THOUSAND" ]; do + sleep 1 + CURRVAL=`date +%s` + done +} + + +# +# main part +# +case "$1" in + start) + echo "Starting wview daemons:" + if [ -f $RADROUTER_PID ]; then + echo "radlib router pid file $RADROUTER_PID exists - killing existing process" + /sbin/killproc -G -TERM $RADROUTER_BIN + rm -f $RADROUTER_PID + fi + if [ -f $WVIEWD_PID ]; then + echo "wviewd pid file $WVIEWD_PID exists - killing existing process" + /sbin/killproc -G -TERM $WVIEWD_BIN + rm -f $WVIEWD_PID + fi + if [ -f $HTMLD_PID ]; then + echo "htmlgend pid file $HTMLD_PID exists - killing existing process" + /sbin/killproc -G -TERM $HTMLD_BIN + rm -f $HTMLD_PID + fi + if [ -f $FTPD_PID ]; then + echo "wviewftpd pid file $FTPD_PID exists - killing existing process" + /sbin/killproc -G -TERM $FTPD_BIN + rm -f $FTPD_PID + fi + if [ -f $SSHD_PID ]; then + echo "wviewsshd pid file $SSHD_PID exists - killing existing process" + /sbin/killproc -G -TERM $SSHD_BIN + rm -f $SSHD_PID + fi + if [ -f $ALARMD_PID ]; then + echo "wvalarmd pid file $ALARMD_PID exists - killing existing process" + /sbin/killproc -G -TERM $ALARMD_BIN + rm -f $ALARMD_PID + fi + if [ -f $CWOPD_PID ]; then + echo "wvcwopd pid file $CWOPD_PID exists - killing existing process" + kill -15 `cat $CWOPD_PID` + rm -f $CWOPD_PID + fi + if [ -f $HTTP_PID ]; then + echo "wvhttpd pid file $HTTP_PID exists - killing existing process" + kill -15 `cat $HTTP_PID` + rm -f $HTTP_PID + fi + if [ -f $PMOND_PID ]; then + echo "wvpmond pid file $PMOND_PID exists - killing existing process" + kill -15 `cat $PMOND_PID` + rm -f $PMOND_PID + fi + + wait_for_time_set + + /sbin/startproc -v -t 1 $RADROUTER_BIN 1 $RUN_DIRECTORY + ret=$? + if test $ret != 0; then + echo "$RADROUTER_BIN failed" + rm -f $RADROUTER_PID + rc_failed $ret + rc_status -v + rc_exit + fi + + /sbin/startproc -v -t 1 $WVIEWD_BIN + ret=$? + if test $ret != 0; then + echo "$WVIEWD_BIN failed" + rm -f $WVIEWD_PID + rc_failed $ret + rc_status -v + rc_exit + fi + + /sbin/startproc -v -t 1 $HTMLD_BIN + /sbin/startproc -v $ALARMD_BIN + /sbin/startproc -v $CWOPD_BIN + /sbin/startproc -v $HTTP_BIN + /sbin/startproc -v $FTPD_BIN + /sbin/startproc -v $SSHD_BIN + /sbin/startproc -v $PMOND_BIN + + rc_failed $ret + rc_status -v + ;; + + stop) + echo -n "Shutting down wview daemons" + /sbin/killproc -p $PMOND_PID -TERM $PMOND_BIN + /sbin/killproc -p $HTTP_PID -TERM $HTTP_BIN + /sbin/killproc -p $CWOPD_PID -TERM $CWOPD_BIN + /sbin/killproc -p $ALARMD_PID -TERM $ALARMD_BIN + /sbin/killproc -p $SSHD_PID -TERM $SSHD_BIN + /sbin/killproc -p $FTPD_PID -TERM $FTPD_BIN + /sbin/killproc -p $HTMLD_PID -TERM $HTMLD_BIN + /sbin/killproc -p $WVIEWD_PID -TERM $WVIEWD_BIN + sleep 1 + /sbin/killproc -p $RADROUTER_PID -TERM $RADROUTER_BIN + rc_status -v + ;; + + restart) + $0 stop && sleep 2 + $0 start + # Remember status and be quiet + rc_status + ;; + + status) + echo "Checking for wview daemons:" + /sbin/checkproc $HTTP_BIN + if test $? != 0; then + echo WunderGround daemon not running... + else + echo WunderGround daemon running... + fi + /sbin/checkproc $CWOPD_BIN + if test $? != 0; then + echo CWOP daemon not running... + else + echo CWOP daemon running... + fi + /sbin/checkproc $ALARMD_BIN + if test $? != 0; then + echo alarm daemon not running... + else + echo alarm daemon running... + fi + /sbin/checkproc $SSHD_BIN + if test $? != 0; then + echo ssh daemon not running... + else + echo ssh daemon running... + fi + /sbin/checkproc $FTPD_BIN + if test $? != 0; then + echo FTP daemon not running... + else + echo FTP daemon running... + fi + /sbin/checkproc $HTMLD_BIN + if test $? != 0; then + echo HTML daemon not running... + else + echo HTML daemon running... + fi + /sbin/checkproc $WVIEWD_BIN + if test $? != 0; then + echo station daemon not running... + else + echo station daemon running... + fi + /sbin/checkproc $RADROUTER_BIN + if test $? != 0; then + echo radlib msg router daemon not running... + else + echo radlib msg router daemon running... + fi + rc_status + ;; + + *) + echo "Usage: $0 {start|stop|status|restart}" + exit 1 +esac + + +# Inform the caller verbosely and set an exit status. +rc_exit + diff --git a/examples/alarms/outtempMax.sh b/examples/alarms/outtempMax.sh new file mode 100755 index 0000000..f23fb80 --- /dev/null +++ b/examples/alarms/outtempMax.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +mkdir -p /usr/local/var/wview/alarms +echo "`date`:OutTempMax: type $1, threshold $2, trigger $3" >> /usr/local/var/wview/alarms/alarm.log + diff --git a/examples/alarms/outtempMaxEmail.sh b/examples/alarms/outtempMaxEmail.sh new file mode 100644 index 0000000..a076065 --- /dev/null +++ b/examples/alarms/outtempMaxEmail.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +## Notes: +## - nobody@nobody.org should be changed to the intended recipient address +## - make sure the mail binary is in the root's path or add the full path + +echo "`date`:OutTempMax: threshold $2, trigger $3" | mail -s "`date`: $1 Alarm" nobody@nobody.org + diff --git a/examples/alarms/outtempMin.sh b/examples/alarms/outtempMin.sh new file mode 100755 index 0000000..6994369 --- /dev/null +++ b/examples/alarms/outtempMin.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +mkdir -p /usr/local/var/wview/alarms +echo "`date`:OutTempMin: type $1, threshold $2, trigger $3" >> /usr/local/var/wview/alarms/alarm.log + diff --git a/examples/conf/arcrec-header.conf b/examples/conf/arcrec-header.conf new file mode 100644 index 0000000..0979773 --- /dev/null +++ b/examples/conf/arcrec-header.conf @@ -0,0 +1,2 @@ +--Timestamp--- Temp Chill HIndex Humid Dewpt Wind HiWind WindDir Rain Barom Solar ET UV +-------------- ---- ----- ------ ----- ----- ------ ------ ------- ----- ----- ----- ----- ---- diff --git a/examples/conf/forecast.conf-no-forecast b/examples/conf/forecast.conf-no-forecast new file mode 100644 index 0000000..1864e69 --- /dev/null +++ b/examples/conf/forecast.conf-no-forecast @@ -0,0 +1,285 @@ +# +# forecast.conf - provide the text strings which will be associated with the +# Vantage Pro Forecast Rule and the icon files to be associated +# with the VP Forecast Icon value +# + +# File Format +# +# Icon Image Files +# ---------------- +# +# Each line starting with "ICON" will be parsed for icon file name until +# all 31 are found or EOF. They are assigned in order from 1 through 31. +# Missing assignments in this file are left uninitialized and will appear +# as a zero-length string when the tag is replaced with +# nothing. The text following the keyword "ICON" is ignored by htmlgend and +# is only included for clarity during user configuration. The order cannot +# be changed. +# +# Icon Bit Definitions +# +# VP_FCAST_ICON_RAIN 0x01 +# VP_FCAST_ICON_CLOUD 0x02 +# VP_FCAST_ICON_PART_SUN 0x04 +# VP_FCAST_ICON_SUNNY 0x08 +# VP_FCAST_ICON_SNOW 0x10 +# +# So forecast icon value 0x05 implies PartlySunny and Rain. +# Note that some of the combinations of the bits do not make sense, such as +# "ICON_SNOW_SUN_PARTSUN_CLOUD_RAIN" - I have made judgement calls for these +# to something more reasonable in terms of the icon file used. +# +# +# Forecast Rule Text +# ------------------ +# +# After the first occurence of the string "" in this file, each line +# following from column 0 to the newline is read by htmlgend and assigned +# to the corresponding forecast rule value - IN ORDER. There are 197 entries +# exactly, no more, no less. htmlgend will assign an empty string to any +# forecast rules which are missing entries in this file. Those will always +# be the last entries - htmlgend assumes that each line of text SEQUENTIALLY +# is to be assigned to the forecast rule that equals that relative line +# number. There can be no holes or gaps. +# +# htmlgend begins with the line following the line with the string "". +# +# Text may be converted to any language which can be represented by 8 bit +# characters. +# + +# these are sequntial from 1-31 representing all of the above bit combinations +# +ICON_RAIN fc-icon-rain.gif +ICON_CLOUD fc-icon-cloudy.gif +ICON_CLOUD_RAIN fc-icon-rain.gif +ICON_PARTSUN fc-icon-partlycloudy.gif +ICON_PARTSUN_RAIN fc-icon-partlycloudyandrain.gif +ICON_PARTSUN_CLOUD fc-icon-partlycloudy.gif +ICON_PARTSUN_CLOUD_RAIN fc-icon-partlycloudyandrain.gif +ICON_SUN fc-icon-sunny.gif +ICON_SUN_RAIN fc-icon-partlycloudyandrain.gif +ICON_SUN_CLOUD fc-icon-partlycloudy.gif +ICON_SUN_CLOUD_RAIN fc-icon-partlycloudyandrain.gif +ICON_SUN_PARTSUN fc-icon-partlycloudy.gif +ICON_SUN_PARTSUN_RAIN fc-icon-partlycloudyandrain.gif +ICON_SUN_PARTSUN_CLOUD fc-icon-partlycloudy.gif +ICON_SUN_PARTSUN_CLOUD_RAIN fc-icon-partlycloudyandrain.gif +ICON_SNOW fc-icon-snow.gif +ICON_SNOW_RAIN fc-icon-snowandrain.gif +ICON_SNOW_CLOUD fc-icon-snow.gif +ICON_SNOW_CLOUD_RAIN fc-icon-snowandrain.gif +ICON_SNOW_PARTSUN fc-icon-partlycloudyandsnow.gif +ICON_SNOW_PARTSUN_RAIN fc-icon-snowandrain.gif +ICON_SNOW_PARTSUN_CLOUD fc-icon-partlycloudyandsnow.gif +ICON_SNOW_PARTSUN_CLOUD_RAIN fc-icon-snowandrain.gif +ICON_SNOW_SUN fc-icon-partlycloudyandsnow.gif +ICON_SNOW_SUN_RAIN fc-icon-snowandrain.gif +ICON_SNOW_SUN_CLOUD fc-icon-partlycloudyandsnow.gif +ICON_SNOW_SUN_CLOUD_RAIN fc-icon-snowandrain.gif +ICON_SNOW_SUN_PARTSUN fc-icon-partlycloudyandsnow.gif +ICON_SNOW_SUN_PARTSUN_RAIN fc-icon-snowandrain.gif +ICON_SNOW_SUN_PARTSUN_CLOUD fc-icon-partlycloudyandsnow.gif +ICON_SNOW_SUN_PARTSUN_CLOUD_RAIN fc-icon-snowandrain.gif + + + +Mostly clear and cooler. +Mostly clear with little temperature change. +Mostly clear for 12 hours with little temperature change. +Mostly clear for 12 to 24 hours and cooler. +Mostly clear with little temperature change. +Partly cloudy and cooler. +Partly cloudy with little temperature change. +Partly cloudy with little temperature change. +Mostly clear and warmer. +Partly cloudy with little temperature change. +Partly cloudy with little temperature change. +Mostly clear with little temperature change. +Increasing clouds and warmer. Precipitation possible within 24 to 48 hours. +Partly cloudy with little temperature change. +Mostly clear with little temperature change. +Increasing clouds with little temperature change. Precipitation possible within 24 hours. +Mostly clear with little temperature change. +Partly cloudy with little temperature change. +Mostly clear with little temperature change. +Increasing clouds with little temperature change. Precipitation possible within 12 hours. +Mostly clear with little temperature change. +Partly cloudy with little temperature change. +Mostly clear with little temperature change. +Increasing clouds and warmer. Precipitation possible within 24 hours. +Mostly clear and warmer. Increasing winds. +Partly cloudy with little temperature change. +Mostly clear with little temperature change. +Increasing clouds and warmer. Precipitation possible within 12 hours. Increasing winds. +Mostly clear and warmer. Increasing winds. +Increasing clouds and warmer. +Partly cloudy with little temperature change. +Mostly clear with little temperature change. +Increasing clouds and warmer. Precipitation possible within 12 hours. Increasing winds. +Mostly clear and warmer. Increasing winds. +Increasing clouds and warmer. +Partly cloudy with little temperature change. +Mostly clear with little temperature change. +Increasing clouds and warmer. Precipitation possible within 12 hours. Increasing winds. +Partly cloudy with little temperature change. +Mostly clear with little temperature change. +Mostly clear and warmer. Precipitation possible within 48 hours. +Mostly clear and warmer. +Partly cloudy with little temperature change. +Mostly clear with little temperature change. +Increasing clouds with little temperature change. Precipitation possible within 24 to 48 hours. +Increasing clouds with little temperature change. +Partly cloudy with little temperature change. +Mostly clear with little temperature change. +Increasing clouds and warmer. Precipitation possible within 12 to 24 hours. +Partly cloudy with little temperature change. +Mostly clear with little temperature change. +Increasing clouds and warmer. Precipitation possible within 12 to 24 hours. Windy. +Partly cloudy with little temperature change. +Mostly clear with little temperature change. +Increasing clouds and warmer. Precipitation possible within 12 to 24 hours. Windy. +Partly cloudy with little temperature change. +Mostly clear with little temperature change. +Increasing clouds and warmer. Precipitation possible within 6 to 12 hours. +Partly cloudy with little temperature change. +Mostly clear with little temperature change. +Increasing clouds and warmer. Precipitation possible within 6 to 12 hours. Windy. +Partly cloudy with little temperature change. +Mostly clear with little temperature change. +Increasing clouds and warmer. Precipitation possible within 12 to 24 hours. Windy. +Partly cloudy with little temperature change. +Mostly clear with little temperature change. +Increasing clouds and warmer. Precipitation possible within 12 hours. +Partly cloudy with little temperature change. +Mostly clear with little temperature change. +Increasing clouds and warmer. Precipitation likley. +Clearing and cooler. Precipitation ending within 6 hours. +Partly cloudy with little temperature change. +Clearing and cooler. Precipitation ending within 6 hours. +Mostly clear with little temperature change. +Clearing and cooler. Precipitation ending within 6 hours. +Partly cloudy and cooler. +Partly cloudy with little temperature change. +Mostly clear and cooler. +Clearing and cooler. Precipitation ending within 6 hours. +Mostly clear with little temperature change. +Clearing and cooler. Precipitation ending within 6 hours. +Mostly clear and cooler. +Partly cloudy with little temperature change. +Mostly clear with little temperature change. +Increasing clouds with little temperature change. Precipitation possible within 24 hours. +Mostly cloudy and cooler. Precipitation continuing. +Partly cloudy with little temperature change. +Mostly clear with little temperature change. +Mostly cloudy and cooler. Precipitation likely. +Mostly cloudy with little temperature change. Precipitation continuing. +Mostly cloudy with little temperature change. Precipitation likely. +Partly cloudy with little temperature change. +Mostly clear with little temperature change. +Increasing clouds and cooler. Precipitation possible and windy within 6 hours. +Increasing clouds with little temperature change. Precipitation possible and windy within 6 hours. +Mostly cloudy and cooler. Precipitation continuing. Increasing winds. +Partly cloudy with little temperature change. +Mostly clear with little temperature change. +Mostly cloudy and cooler. Precipitation likely. Increasing winds. +Mostly cloudy with little temperature change. Precipitation continuing. Increasing winds. +Mostly cloudy with little temperature change. Precipitation likely. Increasing winds. +Partly cloudy with little temperature change. +Mostly clear with little temperature change. +Increasing clouds and cooler. Precipitation possible within 12 to 24 hours possible wind shift to the W NW or N. +Increasing clouds with little temperature change. Precipitation possible within 12 to 24 hours possible wind shift to the W NW or N. +Partly cloudy with little temperature change. +Mostly clear with little temperature change. +Increasing clouds and cooler. Precipitation possible within 6 hours possible wind shift to the W NW or N. +Increasing clouds with little temperature change. Precipitation possible within 6 hours possible wind shift to the W NW or N. +Mostly cloudy and cooler. Precipitation ending within 12 hours possible wind shift to the W NW or N. +Mostly cloudy and cooler. Possible wind shift to the W NW or N. +Mostly cloudy with little temperature change. Precipitation ending within 12 hours possible wind shift to the W NW or N. +Mostly cloudy with little temperature change. Possible wind shift to the W NW or N. +Mostly cloudy and cooler. Precipitation ending within 12 hours possible wind shift to the W NW or N. +Partly cloudy with little temperature change. +Mostly clear with little temperature change. +Mostly cloudy and cooler. Precipitation possible within 24 hours possible wind shift to the W NW or N. +Mostly cloudy with little temperature change. Precipitation ending within 12 hours possible wind shift to the W NW or N. +Mostly cloudy with little temperature change. Precipitation possible within 24 hours possible wind shift to the W NW or N. +Clearing cooler and windy. Precipitation ending within 6 hours. +Clearing cooler and windy. +Mostly cloudy and cooler. Precipitation ending within 6 hours. Windy with possible wind shift to the W NW or N. +Mostly cloudy and cooler. Windy with possible wind shift to the W NW or N. +Clearing cooler and windy. +Partly cloudy with little temperature change. +Mostly clear with little temperature change. +Mostly cloudy with little temperature change. Precipitation possible within 12 hours. Windy. +Partly cloudy with little temperature change. +Mostly clear with little temperature change. +Increasing clouds and cooler. Precipitation possible within 12 hours possibly heavy at times. Windy. +Mostly cloudy and cooler. Precipitation ending within 6 hours. Windy. +Partly cloudy with little temperature change. +Mostly clear with little temperature change. +Mostly cloudy and cooler. Precipitation possible within 12 hours. Windy. +Mostly cloudy and cooler. Precipitation ending in 12 to 24 hours. +Mostly cloudy and cooler. +Mostly cloudy and cooler. Precipitation continuing possible heavy at times. Windy. +Partly cloudy with little temperature change. +Mostly clear with little temperature change. +Mostly cloudy and cooler. Precipitation possible within 6 to 12 hours. Windy. +Mostly cloudy with little temperature change. Precipitation continuing possibly heavy at times. Windy. +Partly cloudy with little temperature change. +Mostly clear with little temperature change. +Mostly cloudy with little temperature change. Precipitation possible within 6 to 12 hours. Windy. +Partly cloudy with little temperature change. +Mostly clear with little temperature change. +Increasing clouds with little temperature change. Precipitation possible within 12 hours possibly heavy at times. Windy. +Mostly cloudy and cooler. Windy. +Mostly cloudy and cooler. Precipitation continuing possibly heavy at times. Windy. +Partly cloudy with little temperature change. +Mostly clear with little temperature change. +Mostly cloudy and cooler. Precipitation likely possibly heavy at times. Windy. +Mostly cloudy with little temperature change. Precipitation continuing possibly heavy at times. Windy. +Mostly cloudy with little temperature change. Precipitation likely possibly heavy at times. Windy. +Partly cloudy with little temperature change. +Mostly clear with little temperature change. +Increasing clouds and cooler. Precipitation possible within 6 hours. Windy. +Increasing clouds with little temperature change. Precipitation possible within 6 hours. Windy +Increasing clouds and cooler. Precipitation continuing. Windy with possible wind shift to the W NW or N. +Partly cloudy with little temperature change. +Mostly clear with little temperature change. +Mostly cloudy and cooler. Precipitation likely. Windy with possible wind shift to the W NW or N. +Mostly cloudy with little temperature change. Precipitation continuing. Windy with possible wind shift to the W NW or N. +Mostly cloudy with little temperature change. Precipitation likely. Windy with possible wind shift to the W NW or N. +Increasing clouds and cooler. Precipitation possible within 6 hours. Windy with possible wind shift to the W NW or N. +Partly cloudy with little temperature change. +Mostly clear with little temperature change. +Increasing clouds and cooler. Precipitation possible within 6 hours possible wind shift to the W NW or N. +Increasing clouds with little temperature change. Precipitation possible within 6 hours. Windy with possible wind shift to the W NW or N. +Increasing clouds with little temperature change. Precipitation possible within 6 hours possible wind shift to the W NW or N. +Partly cloudy with little temperature change. +Mostly clear with little temperature change. +Increasing clouds and cooler. Precipitation possible within 6 hours. Windy with possible wind shift to the W NW or N. +Increasing clouds with little temperature change. Precipitation possible within 6 hours. Windy with possible wind shift to the W NW or N. +Partly cloudy with little temperature change. +Mostly clear with little temperature change. +Increasing clouds and cooler. Precipitation possible within 12 to 24 hours. Windy with possible wind shift to the W NW or N. +Increasing clouds with little temperature change. Precipitation possible within 12 to 24 hours. Windy with possible wind shift to the W NW or N. +Mostly cloudy and cooler. Precipitation possibly heavy at times and ending within 12 hours. Windy with possible wind shift to the W NW or N. +Partly cloudy with little temperature change. +Mostly clear with little temperature change. +Mostly cloudy and cooler. Precipitation possible within 6 to 12 hours possibly heavy at times. Windy with possible wind shift to the W NW or N. +Mostly cloudy with little temperature change. Precipitation ending within 12 hours. Windy with possible wind shift to the W NW or N. +Mostly cloudy with little temperature change. Precipitation possible within 6 to 12 hours possibly heavy at times. Windy with possible wind shift to the W NW or N. +Mostly cloudy and cooler. Precipitation continuing. +Partly cloudy with little temperature change. +Mostly clear with little temperature change. +Mostly cloudy and cooler. Precipitation likely. Windy with possible wind shift to the W NW or N. +Mostly cloudy with little temperature change. Precipitation continuing. +Mostly cloudy with little temperature change. Precipitation likely. +Partly cloudy with little temperature change. +Mostly clear with little temperature change. +Mostly cloudy and cooler. Precipitation possible within 12 hours possibly heavy at times. Windy. +FORECAST REQUIRES 3 HOURS OF RECENT DATA +Mostly clear and cooler. +Mostly clear and cooler. +Mostly clear and cooler. + diff --git a/examples/conf/wview-conf-update.sql b/examples/conf/wview-conf-update.sql new file mode 100644 index 0000000..ed5cd6a --- /dev/null +++ b/examples/conf/wview-conf-update.sql @@ -0,0 +1,44 @@ +BEGIN TRANSACTION; +INSERT OR IGNORE INTO "config" VALUES('EMAIL_ADDRESS','address@server.com','Destination address for email alerts:',NULL); +INSERT OR IGNORE INTO "config" VALUES('ENABLE_EMAIL_ALERTS','no','Send system alert emails?',NULL); +INSERT OR IGNORE INTO "config" VALUES('SEND_TEST_EMAIL','no','Send a test email?',NULL); +INSERT OR IGNORE INTO "config" VALUES('SSH_1_PORT','22','SSH Port Number:','ENABLE_SSH'); +INSERT OR IGNORE INTO "config" VALUES('SSH_1_USERNAME','','Remote SSH username:','ENABLE_SSH'); +INSERT OR IGNORE INTO "config" VALUES('SSH_1_TIMEOUT','120','SSH session timeout (secs):','ENABLE_SSH'); +INSERT OR IGNORE INTO "config" VALUES('SSH_2_PORT','22','SSH Port Number:','ENABLE_SSH'); +INSERT OR IGNORE INTO "config" VALUES('SSH_2_USERNAME','','Remote SSH username:','ENABLE_SSH'); +INSERT OR IGNORE INTO "config" VALUES('SSH_2_TIMEOUT','120','SSH session timeout (secs):','ENABLE_SSH'); +INSERT OR IGNORE INTO "config" VALUES('SSH_3_PORT','22','SSH Port Number:','ENABLE_SSH'); +INSERT OR IGNORE INTO "config" VALUES('SSH_3_USERNAME','','Remote SSH username:','ENABLE_SSH'); +INSERT OR IGNORE INTO "config" VALUES('SSH_3_TIMEOUT','120','SSH session timeout (secs):','ENABLE_SSH'); +INSERT OR IGNORE INTO "config" VALUES('SSH_4_PORT','22','SSH Port Number:','ENABLE_SSH'); +INSERT OR IGNORE INTO "config" VALUES('SSH_4_USERNAME','','Remote SSH username:','ENABLE_SSH'); +INSERT OR IGNORE INTO "config" VALUES('SSH_4_TIMEOUT','120','SSH session timeout (secs):','ENABLE_SSH'); +INSERT OR IGNORE INTO "config" VALUES('SSH_5_PORT','22','SSH Port Number:','ENABLE_SSH'); +INSERT OR IGNORE INTO "config" VALUES('SSH_5_USERNAME','','Remote SSH username:','ENABLE_SSH'); +INSERT OR IGNORE INTO "config" VALUES('SSH_5_TIMEOUT','120','SSH session timeout (secs):','ENABLE_SSH'); +DELETE FROM "config" WHERE name = 'FTP_BINARY'; +DELETE FROM "config" WHERE name = 'FTP_ARGS'; +INSERT OR IGNORE INTO "config" VALUES('FTP_USE_PASSIVE','yes','Use EPSV FTP transfer mode (if supported):','ENABLE_FTP'); +INSERT OR IGNORE INTO "config" VALUES('FTP_INTERVAL','5','Global transmit interval (mins):','ENABLE_FTP'); +DELETE FROM "config" WHERE name = 'FTP_RULE_1_INTERVAL'; +DELETE FROM "config" WHERE name = 'FTP_RULE_2_INTERVAL'; +DELETE FROM "config" WHERE name = 'FTP_RULE_3_INTERVAL'; +DELETE FROM "config" WHERE name = 'FTP_RULE_4_INTERVAL'; +DELETE FROM "config" WHERE name = 'FTP_RULE_5_INTERVAL'; +DELETE FROM "config" WHERE name = 'FTP_RULE_6_INTERVAL'; +DELETE FROM "config" WHERE name = 'FTP_RULE_7_INTERVAL'; +DELETE FROM "config" WHERE name = 'FTP_RULE_8_INTERVAL'; +DELETE FROM "config" WHERE name = 'FTP_RULE_9_INTERVAL'; +DELETE FROM "config" WHERE name = 'FTP_RULE_10_INTERVAL'; +INSERT OR IGNORE INTO "config" VALUES('FROM_EMAIL_ADDRESS','from_address@other_server.com','From address for email alerts:',NULL); +INSERT OR IGNORE INTO "config" VALUES('STATION_OUTSIDE_CHANNEL','0','Use extra sensor for outside temperature (pool sensor is not supported) - 0,1,2,3 (Change sensor channel, WMR9XX only)?',0); +INSERT OR IGNORE INTO "config" VALUES('STATION_DTR','yes','Toggle DTR line during serial initialization:',NULL); +INSERT OR IGNORE INTO "config" VALUES('STATION_WLIP','no','Enable WeatherlinkIP interface:',NULL); +INSERT OR IGNORE INTO "config" VALUES('STATION_RETRIEVE_ARCHIVE','yes','Retrieve archive records from console (if no, they will be autogenerated):',NULL); +INSERT OR IGNORE INTO "config" VALUES('ALARMS_DO_TEST','no','Test an alarm by faking it when alarms are initialized?','ENABLE_ALARMS'); +INSERT OR IGNORE INTO "config" VALUES('ALARMS_DO_TEST_NUMBER','1','Which alarm (1 - 10) to test:','ENABLE_ALARMS'); +INSERT OR IGNORE INTO "config" VALUES('HTMLGEN_STATION_SHOW_IF','yes','Show interface details:','ENABLE_HTMLGEN'); +INSERT OR IGNORE INTO "config" VALUES('HTMLGEN_WIND_UNITS','mph','Default Wind Units:','ENABLE_HTMLGEN'); +COMMIT; + diff --git a/examples/conf/wview-conf.sql b/examples/conf/wview-conf.sql new file mode 100644 index 0000000..275369c --- /dev/null +++ b/examples/conf/wview-conf.sql @@ -0,0 +1,207 @@ +BEGIN TRANSACTION; +CREATE TABLE config (name TEXT NOT NULL,value TEXT NOT NULL,description TEXT,dependsOn TEXT,PRIMARY KEY (name)); +INSERT INTO "config" VALUES('ENABLE_HTMLGEN','yes','Run template generator to generate web/other files from templates?',NULL); +INSERT INTO "config" VALUES('ENABLE_ALARMS','no','Run Alarm daemon to provide alarm script/client support?',NULL); +INSERT INTO "config" VALUES('ENABLE_CWOP','no','Run CWOP daemon to report station data to CWOP?',NULL); +INSERT INTO "config" VALUES('ENABLE_HTTP','no','Run HTTP daemon to report station data to Weather Underground/Weather For You?',NULL); +INSERT INTO "config" VALUES('ENABLE_FTP','no','Run FTP daemon to transfer web site/generated files to a remote server?',NULL); +INSERT INTO "config" VALUES('ENABLE_SSH','no','Run SSH daemon to transfer web site/generated files to a remote server?',NULL); +INSERT INTO "config" VALUES('ENABLE_PROCMON','yes','Run process monitor daemon to restart failed/hung daemons?',NULL); +INSERT INTO "config" VALUES('STATION_TYPE','Simulator','Station type - one of Simulator, Virtual, VantagePro, WXT510, TWI, WS-2300, WMR918, WMRUSB, WH1080, TE923:',NULL); +INSERT INTO "config" VALUES('STATION_INTERFACE','serial','Physical interface to the weather station - one of serial or ethernet (usb is considered serial):',NULL); +INSERT INTO "config" VALUES('STATION_DEV','/dev/ttyUSB0','Weather station serial device (/dev/ttyS0, /dev/ttyUSB0, etc.):',NULL); +INSERT INTO "config" VALUES('STATION_HOST','10.10.10.10','Hostname or IP address (ethernet only):',NULL); +INSERT INTO "config" VALUES('STATION_PORT','22222','Host TCP port to the weather station (ethernet only):',NULL); +INSERT INTO "config" VALUES('STATION_WLIP','no','Enable WeatherlinkIP interface:',NULL); +INSERT INTO "config" VALUES('STATION_RETRIEVE_ARCHIVE','yes','Retrieve archive records from console (if no, they will be autogenerated):',NULL); +INSERT INTO "config" VALUES('STATION_DTR','yes','Toggle DTR line during serial initialization:',NULL); +INSERT INTO "config" VALUES('STATION_RAIN_SEASON_START','1','Station rain season start month (1 - 12):',NULL); +INSERT INTO "config" VALUES('STATION_RAIN_STORM_TRIGGER_START','0.05','Station rain storm start trigger (rainfall rate in inches/hour):',NULL); +INSERT INTO "config" VALUES('STATION_RAIN_STORM_IDLE_STOP','12','Station rain storm stop time (hours without any rainfall which will end the storm):',NULL); +INSERT INTO "config" VALUES('STATION_RAIN_YTD','0','Station rain Year-To-Date preset (to include rain not in archive records) (x.yy inches):',NULL); +INSERT INTO "config" VALUES('STATION_ET_YTD','0','Station ET Year-To-Date preset (to include ET not in archive records) (x.yyy inches, 0 disables):',NULL); +INSERT INTO "config" VALUES('STATION_RAIN_ET_YTD_YEAR','0','Station rain/ET preset year (rain season start year that presets should apply to) (2000-present year, 0 disables):',NULL); +INSERT INTO "config" VALUES('STATION_ELEVATION','751','Weather station elevation (feet above sea level):',NULL); +INSERT INTO "config" VALUES('STATION_LATITUDE','33.6','Weather station latitude (decimal degrees, NORTH is positive - SOUTH is negative):',NULL); +INSERT INTO "config" VALUES('STATION_LONGITUDE','-96.9','Weather station longitude (decimal degrees, EAST is positive - WEST is negative):',NULL); +INSERT INTO "config" VALUES('STATION_ARCHIVE_INTERVAL','5','Weather data archive interval (minutes, one of 5, 10, 15, 30):',NULL); +INSERT INTO "config" VALUES('STATION_POLL_INTERVAL','30','Weather station sensor poll interval (seconds) - should be divisor of 60:',NULL); +INSERT INTO "config" VALUES('STATION_PUSH_INTERVAL','60','Current conditions data push interval (seconds) - for wvalarmd and possibly others:',NULL); +INSERT INTO "config" VALUES('STATION_VERBOSE_MSGS','00000011','Daemon Verbose Log Mask: 00000001 - wviewd,00000010 - htmlgend,00000100 - wvalarmd,00001000 - wviewftpd,00010000 - wviewsshd,00100000 - wvcwopd,01000000 - wvhttpd:',NULL); +INSERT INTO "config" VALUES('STATION_DO_RCHECK','no','Generate RX check data (populate rxCheck.png chart, VP only)?',NULL); +INSERT INTO "config" VALUES('STATION_OUTSIDE_CHANNEL','0','Use extra sensor for outside temperature (pool sensor is not supported) - 0,1,2,3 (Change sensor channel, WMR9XX only)?',0); +INSERT INTO "config" VALUES('STATION_SQLDB_HOST','localhost','Database server hostname or IP address:','ENABLE_SQL'); +INSERT INTO "config" VALUES('STATION_SQLDB_USERNAME','wvuser','Database server username:','ENABLE_SQL'); +INSERT INTO "config" VALUES('STATION_SQLDB_PASSWORD','wvpasswd','Database server password:','ENABLE_SQL'); +INSERT INTO "config" VALUES('STATION_SQLDB_DB_NAME','wviewDB','Database name where export data are stored:','ENABLE_SQL'); +INSERT INTO "config" VALUES('HTMLGEN_STATION_NAME','changeme','Station Name:','ENABLE_HTMLGEN'); +INSERT INTO "config" VALUES('HTMLGEN_STATION_CITY','changeme','Station City:','ENABLE_HTMLGEN'); +INSERT INTO "config" VALUES('HTMLGEN_STATION_STATE','changeme','Station State:','ENABLE_HTMLGEN'); +INSERT INTO "config" VALUES('HTMLGEN_STATION_SHOW_IF','yes','Show interface details:','ENABLE_HTMLGEN'); +INSERT INTO "config" VALUES('HTMLGEN_IMAGE_PATH','/usr/local/var/wview/img','Where to store generated html and graphics files:','ENABLE_HTMLGEN'); +INSERT INTO "config" VALUES('HTMLGEN_HTML_PATH','/usr/local/etc/wview/html','Where to find HTML template files:','ENABLE_HTMLGEN'); +INSERT INTO "config" VALUES('HTMLGEN_START_OFFSET','0','Generation start offset (0-4) in minutes:','ENABLE_HTMLGEN'); +INSERT INTO "config" VALUES('HTMLGEN_GENERATE_INTERVAL','1','How often to generate (refresh interval for your site data) in minutes:','ENABLE_HTMLGEN'); +INSERT INTO "config" VALUES('HTMLGEN_METRIC_UNITS','no','Enable metric conversion/units?','ENABLE_HTMLGEN'); +INSERT INTO "config" VALUES('HTMLGEN_METRIC_USE_RAIN_MM','yes','If metric, use mm for rain instead of cm?','ENABLE_HTMLGEN'); +INSERT INTO "config" VALUES('HTMLGEN_WIND_UNITS','mph','Default Wind Units:','ENABLE_HTMLGEN'); +INSERT INTO "config" VALUES('HTMLGEN_DUAL_UNITS','yes','Display both metric and non-metric units on images?','ENABLE_HTMLGEN'); +INSERT INTO "config" VALUES('HTMLGEN_EXTENDED_DATA','no','Store/process extended sensor values?','ENABLE_HTMLGEN'); +INSERT INTO "config" VALUES('HTMLGEN_ARCHIVE_BROWSER_FILES_TO_KEEP','-1','How many daily archive record browser files to keep (-1 disables archive files, 0 indicates keep all, otherwise days to keep):','ENABLE_HTMLGEN'); +INSERT INTO "config" VALUES('HTMLGEN_MPHASE_INCREASE','Waxing','Moon ''increasing'' text:','ENABLE_HTMLGEN'); +INSERT INTO "config" VALUES('HTMLGEN_MPHASE_DECREASE','Waning','Moon ''decreasing'' text:','ENABLE_HTMLGEN'); +INSERT INTO "config" VALUES('HTMLGEN_MPHASE_FULL','Full','Moon ''full'' text:','ENABLE_HTMLGEN'); +INSERT INTO "config" VALUES('HTMLGEN_LOCAL_RADAR_URL','http://radar.weather.gov/ridge/lite/N0R/FWS_0.png','Local Radar Image URL (no spaces):','ENABLE_HTMLGEN'); +INSERT INTO "config" VALUES('HTMLGEN_LOCAL_FORECAST_URL','http://www.wunderground.com/cgi-bin/findweather/getForecast?query=76233','Local Forecast URL (no spaces):','ENABLE_HTMLGEN'); +INSERT INTO "config" VALUES('HTMLGEN_DATE_FORMAT','%x','Date Format - see ''man strftime'' for allowable formats; %D - US format mm/dd/yy, %d/%m/%Y - dd/mm/yyyy, %x - locale''s preferred date representation:','ENABLE_HTMLGEN'); +INSERT INTO "config" VALUES('ALARMS_STATION_METRIC','no','Is station metric?','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_DO_TEST','no','Test an alarm by faking it when alarms are initialized?','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_DO_TEST_NUMBER','1','Which alarm (1 - 10) to test:','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_1_TYPE','0','(1 of 10) Alarm Type (see the User Manual file for valid types):','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_1_MAX','no','Is this an upper bound alarm?','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_1_THRESHOLD','0','Upper/lower bound value (threshold) (float or integer):','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_1_ABATEMENT','600','Number of seconds to suppress alarms after an alarm triggers:','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_1_EXECUTE','/usr/local/etc/wview/alarms/your-alarm.sh','Full path to the shell script or binary to execute when the alarm triggers:','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_2_TYPE','0','(2 of 10) Alarm Type (see the User Manual file for valid types):','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_2_MAX','no','Is this an upper bound alarm?','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_2_THRESHOLD','0','Upper/lower bound value (threshold) (float or integer):','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_2_ABATEMENT','600','Number of seconds to suppress alarms after an alarm triggers:','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_2_EXECUTE','1','Full path to the shell script or binary to execute when the alarm triggers:','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_3_TYPE','0','(3 of 10) Alarm Type (see the User Manual file for valid types):','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_3_MAX','no','Is this an upper bound alarm?','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_3_THRESHOLD','0','Upper/lower bound value (threshold) (float or integer):','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_3_ABATEMENT','600','Number of seconds to suppress alarms after an alarm triggers:','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_3_EXECUTE','1','Full path to the shell script or binary to execute when the alarm triggers:','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_4_TYPE','0','(4 of 10) Alarm Type (see the User Manual file for valid types):','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_4_MAX','no','Is this an upper bound alarm?','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_4_THRESHOLD','0','Upper/lower bound value (threshold) (float or integer):','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_4_ABATEMENT','600','Number of seconds to suppress alarms after an alarm triggers:','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_4_EXECUTE','1','Full path to the shell script or binary to execute when the alarm triggers:','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_5_TYPE','0','(5 of 10) Alarm Type (see the User Manual file for valid types):','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_5_MAX','no','Is this an upper bound alarm?','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_5_THRESHOLD','0','Upper/lower bound value (threshold) (float or integer):','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_5_ABATEMENT','600','Number of seconds to suppress alarms after an alarm triggers:','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_5_EXECUTE','1','Full path to the shell script or binary to execute when the alarm triggers:','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_6_TYPE','0','(6 of 10) Alarm Type (see the User Manual file for valid types):','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_6_MAX','no','Is this an upper bound alarm?','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_6_THRESHOLD','0','Upper/lower bound value (threshold) (float or integer):','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_6_ABATEMENT','600','Number of seconds to suppress alarms after an alarm triggers:','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_6_EXECUTE','1','Full path to the shell script or binary to execute when the alarm triggers:','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_7_TYPE','0','(7 of 10) Alarm Type (see the User Manual file for valid types):','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_7_MAX','no','Is this an upper bound alarm?','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_7_THRESHOLD','0','Upper/lower bound value (threshold) (float or integer):','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_7_ABATEMENT','600','Number of seconds to suppress alarms after an alarm triggers:','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_7_EXECUTE','1','Full path to the shell script or binary to execute when the alarm triggers:','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_8_TYPE','0','(8 of 10) Alarm Type (see the User Manual file for valid types):','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_8_MAX','no','Is this an upper bound alarm?','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_8_THRESHOLD','0','Upper/lower bound value (threshold) (float or integer):','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_8_ABATEMENT','600','Number of seconds to suppress alarms after an alarm triggers:','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_8_EXECUTE','1','Full path to the shell script or binary to execute when the alarm triggers:','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_9_TYPE','0','(9 of 10) Alarm Type (see the User Manual file for valid types):','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_9_MAX','no','Is this an upper bound alarm?','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_9_THRESHOLD','0','Upper/lower bound value (threshold) (float or integer):','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_9_ABATEMENT','600','Number of seconds to suppress alarms after an alarm triggers:','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_9_EXECUTE','1','Full path to the shell script or binary to execute when the alarm triggers:','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_10_TYPE','0','(10 of 10) Alarm Type (see the User Manual file for valid types):','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_10_MAX','no','Is this an upper bound alarm?','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_10_THRESHOLD','0','Upper/lower bound value (threshold) (float or integer):','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_10_ABATEMENT','600','Number of seconds to suppress alarms after an alarm triggers:','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_10_EXECUTE','1','Full path to the shell script or binary to execute when the alarm triggers:','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('FTP_HOST','ftp.yourhost.net','Remote hostname:','ENABLE_FTP'); +INSERT INTO "config" VALUES('FTP_USERNAME','yourusername','Remote username:','ENABLE_FTP'); +INSERT INTO "config" VALUES('FTP_PASSWD','yourpassword','Remote password:','ENABLE_FTP'); +INSERT INTO "config" VALUES('FTP_REMOTE_DIRECTORY','','Optional Remote Transfer Directory (relative to the ftp login directory, no leading ''/''):','ENABLE_FTP'); +INSERT INTO "config" VALUES('FTP_USE_PASSIVE','yes','Use EPSV FTP transfer mode (if supported):','ENABLE_FTP'); +INSERT INTO "config" VALUES('FTP_INTERVAL','5','Global transmit interval (mins):','ENABLE_FTP'); +INSERT INTO "config" VALUES('FTP_RULE_1_SOURCE','*.*','(1 of 10) Source file (relative to $prefix/var/wview/img):','ENABLE_FTP'); +INSERT INTO "config" VALUES('FTP_RULE_2_SOURCE','Archive/*.txt','(2 of 10) Source file (relative to $prefix/var/wview/img):','ENABLE_FTP'); +INSERT INTO "config" VALUES('FTP_RULE_3_SOURCE','NOAA/*.txt','(3 of 10) Source file (relative to $prefix/var/wview/img):','ENABLE_FTP'); +INSERT INTO "config" VALUES('FTP_RULE_4_SOURCE','','(4 of 10) Source file (relative to $prefix/var/wview/img):','ENABLE_FTP'); +INSERT INTO "config" VALUES('FTP_RULE_5_SOURCE','','(5 of 10) Source file (relative to $prefix/var/wview/img):','ENABLE_FTP'); +INSERT INTO "config" VALUES('FTP_RULE_6_SOURCE','','(6 of 10) Source file (relative to $prefix/var/wview/img):','ENABLE_FTP'); +INSERT INTO "config" VALUES('FTP_RULE_7_SOURCE','','(7 of 10) Source file (relative to $prefix/var/wview/img):','ENABLE_FTP'); +INSERT INTO "config" VALUES('FTP_RULE_8_SOURCE','','(8 of 10) Source file (relative to $prefix/var/wview/img):','ENABLE_FTP'); +INSERT INTO "config" VALUES('FTP_RULE_9_SOURCE','','(9 of 10) Source file (relative to $prefix/var/wview/img):','ENABLE_FTP'); +INSERT INTO "config" VALUES('FTP_RULE_10_SOURCE','','(10 of 10) Source file (relative to $prefix/var/wview/img):','ENABLE_FTP'); +INSERT INTO "config" VALUES('SSH_1_SOURCE','/usr/local/var/wview/img','(1 of 5) Local Source directory (normally $prefix/var/wview/img):','ENABLE_SSH'); +INSERT INTO "config" VALUES('SSH_1_INTERVAL','1','How often to synchronize the remote server (minutes):','ENABLE_SSH'); +INSERT INTO "config" VALUES('SSH_1_HOST','remotehost.net','Remote Host (hostname or IP address):','ENABLE_SSH'); +INSERT INTO "config" VALUES('SSH_1_PORT','22','SSH Port Number:','ENABLE_SSH'); +INSERT INTO "config" VALUES('SSH_1_USERNAME','','Remote SSH username:','ENABLE_SSH'); +INSERT INTO "config" VALUES('SSH_1_DESTINATION','http/wview','Remote destination path, relative to the root ssh login directory (no leading ''/''):','ENABLE_SSH'); +INSERT INTO "config" VALUES('SSH_1_TIMEOUT','120','SSH session timeout (secs):','ENABLE_SSH'); +INSERT INTO "config" VALUES('SSH_2_SOURCE','','(2 of 5) Local Source directory (normally $prefix/var/wview/img):','ENABLE_SSH'); +INSERT INTO "config" VALUES('SSH_2_INTERVAL','0','How often to synchronize the remote server (minutes):','ENABLE_SSH'); +INSERT INTO "config" VALUES('SSH_2_HOST','','Remote Host (hostname or IP address):','ENABLE_SSH'); +INSERT INTO "config" VALUES('SSH_2_PORT','22','SSH Port Number:','ENABLE_SSH'); +INSERT INTO "config" VALUES('SSH_2_USERNAME','','Remote SSH username:','ENABLE_SSH'); +INSERT INTO "config" VALUES('SSH_2_DESTINATION','','Remote destination path, relative to the root ssh login directory (no leading ''/''):','ENABLE_SSH'); +INSERT INTO "config" VALUES('SSH_2_TIMEOUT','120','SSH session timeout (secs):','ENABLE_SSH'); +INSERT INTO "config" VALUES('SSH_3_SOURCE','','(3 of 5) Local Source directory (normally $prefix/var/wview/img):','ENABLE_SSH'); +INSERT INTO "config" VALUES('SSH_3_INTERVAL','0','How often to synchronize the remote server (minutes):','ENABLE_SSH'); +INSERT INTO "config" VALUES('SSH_3_HOST','','Remote Host (hostname or IP address):','ENABLE_SSH'); +INSERT INTO "config" VALUES('SSH_3_PORT','22','SSH Port Number:','ENABLE_SSH'); +INSERT INTO "config" VALUES('SSH_3_USERNAME','','Remote SSH username:','ENABLE_SSH'); +INSERT INTO "config" VALUES('SSH_3_DESTINATION','','Remote destination path, relative to the root ssh login directory (no leading ''/''):','ENABLE_SSH'); +INSERT INTO "config" VALUES('SSH_3_TIMEOUT','120','SSH session timeout (secs):','ENABLE_SSH'); +INSERT INTO "config" VALUES('SSH_4_SOURCE','','(4 of 5) Local Source directory (normally $prefix/var/wview/img):','ENABLE_SSH'); +INSERT INTO "config" VALUES('SSH_4_INTERVAL','0','How often to synchronize the remote server (minutes):','ENABLE_SSH'); +INSERT INTO "config" VALUES('SSH_4_HOST','','Remote Host (hostname or IP address):','ENABLE_SSH'); +INSERT INTO "config" VALUES('SSH_4_PORT','22','SSH Port Number:','ENABLE_SSH'); +INSERT INTO "config" VALUES('SSH_4_USERNAME','','Remote SSH username:','ENABLE_SSH'); +INSERT INTO "config" VALUES('SSH_4_DESTINATION','','Remote destination path, relative to the root ssh login directory (no leading ''/''):','ENABLE_SSH'); +INSERT INTO "config" VALUES('SSH_4_TIMEOUT','120','SSH session timeout (secs):','ENABLE_SSH'); +INSERT INTO "config" VALUES('SSH_5_SOURCE','','(5 of 5) Local Source directory (normally $prefix/var/wview/img):','ENABLE_SSH'); +INSERT INTO "config" VALUES('SSH_5_INTERVAL','0','How often to synchronize the remote server (minutes):','ENABLE_SSH'); +INSERT INTO "config" VALUES('SSH_5_HOST','','Remote Host (hostname or IP address):','ENABLE_SSH'); +INSERT INTO "config" VALUES('SSH_5_PORT','22','SSH Port Number:','ENABLE_SSH'); +INSERT INTO "config" VALUES('SSH_5_USERNAME','','Remote SSH username:','ENABLE_SSH'); +INSERT INTO "config" VALUES('SSH_5_DESTINATION','','Remote destination path, relative to the root ssh login directory (no leading ''/''):','ENABLE_SSH'); +INSERT INTO "config" VALUES('SSH_5_TIMEOUT','120','SSH session timeout (secs):','ENABLE_SSH'); +INSERT INTO "config" VALUES('CWOP_APRS_CALL_SIGN','CWXXXX','CWOP call sign (CWXXXX/DWXXXX):','ENABLE_CWOP'); +INSERT INTO "config" VALUES('CWOP_APRS_SERVER1','cwop.aprs.net','CWOP Server name:','ENABLE_CWOP'); +INSERT INTO "config" VALUES('CWOP_APRS_PORTNO1','23','CWOP port number:','ENABLE_CWOP'); +INSERT INTO "config" VALUES('CWOP_APRS_SERVER2','cwop.aprs.net','CWOP Server name:','ENABLE_CWOP'); +INSERT INTO "config" VALUES('CWOP_APRS_PORTNO2','23','CWOP port number:','ENABLE_CWOP'); +INSERT INTO "config" VALUES('CWOP_APRS_SERVER3','cwop.aprs.net','CWOP Server name:','ENABLE_CWOP'); +INSERT INTO "config" VALUES('CWOP_APRS_PORTNO3','23','CWOP port number:','ENABLE_CWOP'); +INSERT INTO "config" VALUES('CWOP_LATITUDE','3333.70N','LATITUDE - MUST be of the form DDMM.hhd (LORAN format): DD - degrees (always positive), MM - minutes (0 - 59), hh - hundredths of minutes, d - hemisphere indicator (N or S):','ENABLE_CWOP'); +INSERT INTO "config" VALUES('CWOP_LONGITUDE','09654.13W','LONGITUDE - MUST be of the form DDDMM.hhd: DDD - degrees (always positive and always 3 digits - use leading zero if necessary), MM - minutes (0 - 59), hh - hundredths of minutes, d - hemisphere indicator (E or W):','ENABLE_CWOP'); +INSERT INTO "config" VALUES('CWOP_LOG_WX_PACKET','no','Log the WX packet when sending (0 or 1)?','ENABLE_CWOP'); +INSERT INTO "config" VALUES('HTTP_WUSTATIONID','','WUNDERGROUND station ID:','ENABLE_HTTP'); +INSERT INTO "config" VALUES('HTTP_WUPASSWD','','WUNDERGROUND password:','ENABLE_HTTP'); +INSERT INTO "config" VALUES('HTTP_YOUSTATIONID','','WEATHERFORYOU station ID:','ENABLE_HTTP'); +INSERT INTO "config" VALUES('HTTP_YOUPASSWD','','WEATHERFORYOU password:','ENABLE_HTTP'); +INSERT INTO "config" VALUES('CAL_MULT_BAROMETER','1.0','Barometer calibration multiplier:',NULL); +INSERT INTO "config" VALUES('CAL_CONST_BAROMETER','0.0','Barometer calibration constant:',NULL); +INSERT INTO "config" VALUES('CAL_MULT_PRESSURE','1.0','Station Pressure calibration multiplier:',NULL); +INSERT INTO "config" VALUES('CAL_CONST_PRESSURE','0.0','Station Pressure calibration constant:',NULL); +INSERT INTO "config" VALUES('CAL_MULT_ALTIMETER','1.0','Altimeter calibration multiplier:',NULL); +INSERT INTO "config" VALUES('CAL_CONST_ALTIMETER','0.0','Altimeter calibration constant:',NULL); +INSERT INTO "config" VALUES('CAL_MULT_INTEMP','1.0','Inside Temp calibration multiplier:',NULL); +INSERT INTO "config" VALUES('CAL_CONST_INTEMP','0.0','Inside Temp calibration constant:',NULL); +INSERT INTO "config" VALUES('CAL_MULT_OUTTEMP','1.0','Outside Temp calibration multiplier:',NULL); +INSERT INTO "config" VALUES('CAL_CONST_OUTTEMP','0.0','Outside Temp calibration constant:',NULL); +INSERT INTO "config" VALUES('CAL_MULT_INHUMIDITY','1.0','Inside Humidity calibration multiplier:',NULL); +INSERT INTO "config" VALUES('CAL_CONST_INHUMIDITY','0.0','Inside Humidity calibration constant:',NULL); +INSERT INTO "config" VALUES('CAL_MULT_OUTHUMIDITY','1.0','Outside Humidity calibration multiplier:',NULL); +INSERT INTO "config" VALUES('CAL_CONST_OUTHUMIDITY','0.0','Outside Humidity calibration constant:',NULL); +INSERT INTO "config" VALUES('CAL_MULT_WINDSPEED','1.0','Wind Speed calibration multiplier:',NULL); +INSERT INTO "config" VALUES('CAL_CONST_WINDSPEED','0.0','Wind Speed calibration constant:',NULL); +INSERT INTO "config" VALUES('CAL_MULT_WINDDIR','1.0','Wind Direction calibration multiplier:',NULL); +INSERT INTO "config" VALUES('CAL_CONST_WINDDIR','0.0','Wind Direction calibration constant:',NULL); +INSERT INTO "config" VALUES('CAL_MULT_RAIN','1.0','Rain calibration multiplier:',NULL); +INSERT INTO "config" VALUES('CAL_CONST_RAIN','0.0','Rain calibration constant:',NULL); +INSERT INTO "config" VALUES('CAL_MULT_RAINRATE','1.0','Rain Rate calibration multiplier:',NULL); +INSERT INTO "config" VALUES('CAL_CONST_RAINRATE','0.0','Rain Rate calibration constant:',NULL); +INSERT INTO "config" VALUES('PROCMON_wviewd','0','Seconds to wait before restarting a non-responsive process (0 disables):','ENABLE_PROCMON'); +INSERT INTO "config" VALUES('PROCMON_htmlgend','0','Seconds to wait before restarting a non-responsive process (0 disables):','ENABLE_PROCMON'); +INSERT INTO "config" VALUES('PROCMON_wvalarmd','120','Seconds to wait before restarting a non-responsive process (0 disables):','ENABLE_PROCMON'); +INSERT INTO "config" VALUES('PROCMON_wvcwopd','120','Seconds to wait before restarting a non-responsive process (0 disables):','ENABLE_PROCMON'); +INSERT INTO "config" VALUES('PROCMON_wvhttpd','120','Seconds to wait before restarting a non-responsive process (0 disables):','ENABLE_PROCMON'); +INSERT INTO "config" VALUES('EMAIL_ADDRESS','address@server.com','Destination address for email alerts:',NULL); +INSERT INTO "config" VALUES('FROM_EMAIL_ADDRESS','from_address@other_server.com','From address for email alerts:',NULL); +INSERT INTO "config" VALUES('ENABLE_EMAIL_ALERTS','no','Send system alert emails?',NULL); +INSERT INTO "config" VALUES('SEND_TEST_EMAIL','no','Send a test email?',NULL); +INSERT INTO "config" VALUES('ADMIN_PASSWORD','9798e81db4d6cc93577169c395b90f66','Admin Password (md5):',''); +COMMIT; diff --git a/examples/conf/wview-conf.sql-deb-pkg b/examples/conf/wview-conf.sql-deb-pkg new file mode 100644 index 0000000..19b9846 --- /dev/null +++ b/examples/conf/wview-conf.sql-deb-pkg @@ -0,0 +1,202 @@ +BEGIN TRANSACTION; +CREATE TABLE config (name TEXT NOT NULL,value TEXT NOT NULL,description TEXT,dependsOn TEXT,PRIMARY KEY (name)); +INSERT INTO "config" VALUES('ENABLE_HTMLGEN','yes','Run template generator to generate web/other files from templates?',NULL); +INSERT INTO "config" VALUES('ENABLE_ALARMS','no','Run Alarm daemon to provide alarm script/client support?',NULL); +INSERT INTO "config" VALUES('ENABLE_CWOP','no','Run CWOP daemon to report station data to CWOP?',NULL); +INSERT INTO "config" VALUES('ENABLE_HTTP','no','Run HTTP daemon to report station data to Weather Underground/Weather For You?',NULL); +INSERT INTO "config" VALUES('ENABLE_FTP','no','Run FTP daemon to transfer web site/generated files to a remote server?',NULL); +INSERT INTO "config" VALUES('ENABLE_SSH','no','Run SSH daemon to transfer web site/generated files to a remote server?',NULL); +INSERT INTO "config" VALUES('ENABLE_PROCMON','yes','Run process monitor daemon to restart failed/hung daemons?',NULL); +INSERT INTO "config" VALUES('STATION_TYPE','Simulator','Station type - one of Simulator, Virtual, VantagePro, WXT510, TWI, WS-2300, WMR918, WMRUSB, WH1080, TE923:',NULL); +INSERT INTO "config" VALUES('STATION_INTERFACE','serial','Physical interface to the weather station - one of serial or ethernet (usb is considered serial):',NULL); +INSERT INTO "config" VALUES('STATION_DEV','/dev/ttyUSB0','Weather station serial device (/dev/ttyS0, /dev/ttyUSB0, etc.):',NULL); +INSERT INTO "config" VALUES('STATION_HOST','10.10.10.10','Hostname or IP address (ethernet only):',NULL); +INSERT INTO "config" VALUES('STATION_PORT','22222','Host TCP port to the weather station (ethernet only):',NULL); +INSERT INTO "config" VALUES('STATION_WLIP','no','Enable WeatherlinkIP interface:',NULL); +INSERT INTO "config" VALUES('STATION_RETRIEVE_ARCHIVE','yes','Retrieve archive records from console (if no, they will be autogenerated):',NULL); +INSERT INTO "config" VALUES('STATION_DTR','yes','Toggle DTR line during serial initialization:',NULL); +INSERT INTO "config" VALUES('STATION_RAIN_SEASON_START','1','Station rain season start month (1 - 12):',NULL); +INSERT INTO "config" VALUES('STATION_RAIN_STORM_TRIGGER_START','0.05','Station rain storm start trigger (rainfall rate in inches/hour):',NULL); +INSERT INTO "config" VALUES('STATION_RAIN_STORM_IDLE_STOP','12','Station rain storm stop time (hours without any rainfall which will end the storm):',NULL); +INSERT INTO "config" VALUES('STATION_RAIN_YTD','0','Station rain Year-To-Date preset (to include rain not in archive records) (x.yy inches):',NULL); +INSERT INTO "config" VALUES('STATION_ET_YTD','0','Station ET Year-To-Date preset (to include ET not in archive records) (x.yyy inches, 0 disables):',NULL); +INSERT INTO "config" VALUES('STATION_RAIN_ET_YTD_YEAR','0','Station rain/ET preset year (rain season start year that presets should apply to) (2000-present year, 0 disables):',NULL); +INSERT INTO "config" VALUES('STATION_ELEVATION','751','Weather station elevation (feet above sea level):',NULL); +INSERT INTO "config" VALUES('STATION_LATITUDE','33.6','Weather station latitude (decimal degrees, NORTH is positive - SOUTH is negative):',NULL); +INSERT INTO "config" VALUES('STATION_LONGITUDE','-96.9','Weather station longitude (decimal degrees, EAST is positive - WEST is negative):',NULL); +INSERT INTO "config" VALUES('STATION_ARCHIVE_INTERVAL','5','Weather data archive interval (minutes, one of 5, 10, 15, 30):',NULL); +INSERT INTO "config" VALUES('STATION_POLL_INTERVAL','30','Weather station sensor poll interval (seconds) - should be divisor of 60:',NULL); +INSERT INTO "config" VALUES('STATION_PUSH_INTERVAL','60','Current conditions data push interval (seconds) - for wvalarmd and possibly others:',NULL); +INSERT INTO "config" VALUES('STATION_VERBOSE_MSGS','00000011','Daemon Verbose Log Mask: 00000001 - wviewd,00000010 - htmlgend,00000100 - wvalarmd,00001000 - wviewftpd,00010000 - wviewsshd,00100000 - wvcwopd,01000000 - wvhttpd:',NULL); +INSERT INTO "config" VALUES('STATION_DO_RCHECK','no','Generate RX check data (populate rxCheck.png chart, VP only)?',NULL); +INSERT INTO "config" VALUES('STATION_OUTSIDE_CHANNEL','0','Use extra sensor for outside temperature (pool sensor is not supported) - 0,1,2,3 (Change sensor channel, WMR9XX only)?',0); +INSERT INTO "config" VALUES('STATION_SQLDB_HOST','localhost','Database server hostname or IP address:','ENABLE_SQL'); +INSERT INTO "config" VALUES('STATION_SQLDB_USERNAME','wvuser','Database server username:','ENABLE_SQL'); +INSERT INTO "config" VALUES('STATION_SQLDB_PASSWORD','wvpasswd','Database server password:','ENABLE_SQL'); +INSERT INTO "config" VALUES('STATION_SQLDB_DB_NAME','wviewDB','Database name where export data are stored:','ENABLE_SQL'); +INSERT INTO "config" VALUES('HTMLGEN_STATION_NAME','changeme','Station Name:','ENABLE_HTMLGEN'); +INSERT INTO "config" VALUES('HTMLGEN_STATION_CITY','changeme','Station City:','ENABLE_HTMLGEN'); +INSERT INTO "config" VALUES('HTMLGEN_STATION_STATE','changeme','Station State:','ENABLE_HTMLGEN'); +INSERT INTO "config" VALUES('HTMLGEN_STATION_SHOW_IF','yes','Show interface details:','ENABLE_HTMLGEN'); +INSERT INTO "config" VALUES('HTMLGEN_IMAGE_PATH','/var/lib/wview/img','Where to store generated html and graphics files:','ENABLE_HTMLGEN'); +INSERT INTO "config" VALUES('HTMLGEN_HTML_PATH','/etc/wview/html','Where to find HTML template files:','ENABLE_HTMLGEN'); +INSERT INTO "config" VALUES('HTMLGEN_START_OFFSET','0','Generation start offset (0-4) in minutes:','ENABLE_HTMLGEN'); +INSERT INTO "config" VALUES('HTMLGEN_GENERATE_INTERVAL','1','How often to generate (refresh interval for your site data) in minutes:','ENABLE_HTMLGEN'); +INSERT INTO "config" VALUES('HTMLGEN_METRIC_UNITS','no','Enable metric conversion/units?','ENABLE_HTMLGEN'); +INSERT INTO "config" VALUES('HTMLGEN_METRIC_USE_RAIN_MM','yes','If metric, use mm for rain instead of cm?','ENABLE_HTMLGEN'); +INSERT INTO "config" VALUES('HTMLGEN_WIND_UNITS','mph','Default Wind Units:','ENABLE_HTMLGEN'); +INSERT INTO "config" VALUES('HTMLGEN_DUAL_UNITS','yes','Display both metric and non-metric units on images?','ENABLE_HTMLGEN'); +INSERT INTO "config" VALUES('HTMLGEN_EXTENDED_DATA','no','Store/process extended sensor values?','ENABLE_HTMLGEN'); +INSERT INTO "config" VALUES('HTMLGEN_ARCHIVE_BROWSER_FILES_TO_KEEP','-1','How many daily archive record browser files to keep (-1 disables archive files, 0 indicates keep all, otherwise days to keep):','ENABLE_HTMLGEN'); +INSERT INTO "config" VALUES('HTMLGEN_MPHASE_INCREASE','Waxing','Moon ''increasing'' text:','ENABLE_HTMLGEN'); +INSERT INTO "config" VALUES('HTMLGEN_MPHASE_DECREASE','Waning','Moon ''decreasing'' text:','ENABLE_HTMLGEN'); +INSERT INTO "config" VALUES('HTMLGEN_MPHASE_FULL','Full','Moon ''full'' text:','ENABLE_HTMLGEN'); +INSERT INTO "config" VALUES('HTMLGEN_LOCAL_RADAR_URL','http://www.srh.noaa.gov/radar/images/DS.p19r0/SI.kfws/latest.gif','Local Radar Image URL (no spaces):','ENABLE_HTMLGEN'); +INSERT INTO "config" VALUES('HTMLGEN_LOCAL_FORECAST_URL','http://www.wunderground.com/cgi-bin/findweather/getForecast?query=76233','Local Forecast URL (no spaces):','ENABLE_HTMLGEN'); +INSERT INTO "config" VALUES('HTMLGEN_DATE_FORMAT','%x','Date Format - see ''man strftime'' for allowable formats; %D - US format mm/dd/yy, %d/%m/%Y - dd/mm/yyyy, %x - locale''s preferred date representation:','ENABLE_HTMLGEN'); +INSERT INTO "config" VALUES('ALARMS_STATION_METRIC','no','Is station metric?','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_DO_TEST','no','Test an alarm by faking it when alarms are initialized?','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_DO_TEST_NUMBER','1','Which alarm (1 - 10) to test:','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_1_TYPE','0','(1 of 10) Alarm Type (see the User Manual file for valid types):','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_1_MAX','no','Is this an upper bound alarm?','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_1_THRESHOLD','0','Upper/lower bound value (threshold) (float or integer):','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_1_ABATEMENT','600','Number of seconds to suppress alarms after an alarm triggers:','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_1_EXECUTE','/etc/wview/alarms/your-alarm.sh','Full path to the shell script or binary to execute when the alarm triggers:','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_2_TYPE','0','(2 of 10) Alarm Type (see the User Manual file for valid types):','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_2_MAX','no','Is this an upper bound alarm?','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_2_THRESHOLD','0','Upper/lower bound value (threshold) (float or integer):','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_2_ABATEMENT','600','Number of seconds to suppress alarms after an alarm triggers:','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_2_EXECUTE','','Full path to the shell script or binary to execute when the alarm triggers:','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_3_TYPE','0','(3 of 10) Alarm Type (see the User Manual file for valid types):','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_3_MAX','no','Is this an upper bound alarm?','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_3_THRESHOLD','0','Upper/lower bound value (threshold) (float or integer):','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_3_ABATEMENT','600','Number of seconds to suppress alarms after an alarm triggers:','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_3_EXECUTE','','Full path to the shell script or binary to execute when the alarm triggers:','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_4_TYPE','0','(4 of 10) Alarm Type (see the User Manual file for valid types):','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_4_MAX','no','Is this an upper bound alarm?','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_4_THRESHOLD','0','Upper/lower bound value (threshold) (float or integer):','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_4_ABATEMENT','600','Number of seconds to suppress alarms after an alarm triggers:','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_4_EXECUTE','','Full path to the shell script or binary to execute when the alarm triggers:','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_5_TYPE','0','(5 of 10) Alarm Type (see the User Manual file for valid types):','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_5_MAX','no','Is this an upper bound alarm?','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_5_THRESHOLD','0','Upper/lower bound value (threshold) (float or integer):','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_5_ABATEMENT','600','Number of seconds to suppress alarms after an alarm triggers:','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_5_EXECUTE','','Full path to the shell script or binary to execute when the alarm triggers:','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_6_TYPE','0','(6 of 10) Alarm Type (see the User Manual file for valid types):','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_6_MAX','no','Is this an upper bound alarm?','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_6_THRESHOLD','0','Upper/lower bound value (threshold) (float or integer):','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_6_ABATEMENT','600','Number of seconds to suppress alarms after an alarm triggers:','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_6_EXECUTE','','Full path to the shell script or binary to execute when the alarm triggers:','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_7_TYPE','0','(7 of 10) Alarm Type (see the User Manual file for valid types):','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_7_MAX','no','Is this an upper bound alarm?','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_7_THRESHOLD','0','Upper/lower bound value (threshold) (float or integer):','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_7_ABATEMENT','600','Number of seconds to suppress alarms after an alarm triggers:','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_7_EXECUTE','','Full path to the shell script or binary to execute when the alarm triggers:','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_8_TYPE','0','(8 of 10) Alarm Type (see the User Manual file for valid types):','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_8_MAX','no','Is this an upper bound alarm?','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_8_THRESHOLD','0','Upper/lower bound value (threshold) (float or integer):','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_8_ABATEMENT','600','Number of seconds to suppress alarms after an alarm triggers:','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_8_EXECUTE','','Full path to the shell script or binary to execute when the alarm triggers:','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_9_TYPE','0','(9 of 10) Alarm Type (see the User Manual file for valid types):','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_9_MAX','no','Is this an upper bound alarm?','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_9_THRESHOLD','0','Upper/lower bound value (threshold) (float or integer):','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_9_ABATEMENT','600','Number of seconds to suppress alarms after an alarm triggers:','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_9_EXECUTE','','Full path to the shell script or binary to execute when the alarm triggers:','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_10_TYPE','0','(10 of 10) Alarm Type (see the User Manual file for valid types):','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_10_MAX','no','Is this an upper bound alarm?','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_10_THRESHOLD','0','Upper/lower bound value (threshold) (float or integer):','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_10_ABATEMENT','600','Number of seconds to suppress alarms after an alarm triggers:','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('ALARMS_10_EXECUTE','','Full path to the shell script or binary to execute when the alarm triggers:','ENABLE_ALARMS'); +INSERT INTO "config" VALUES('FTP_HOST','ftp.yourhost.net','Remote hostname:','ENABLE_FTP'); +INSERT INTO "config" VALUES('FTP_USERNAME','yourusername','Remote username:','ENABLE_FTP'); +INSERT INTO "config" VALUES('FTP_PASSWD','yourpassword','Remote password:','ENABLE_FTP'); +INSERT INTO "config" VALUES('FTP_REMOTE_DIRECTORY','','Optional Remote Transfer Directory (relative to the ftp login directory, no leading ''/''):','ENABLE_FTP'); +INSERT INTO "config" VALUES('FTP_USE_PASSIVE','yes','Use EPSV FTP transfer mode (if supported):','ENABLE_FTP'); +INSERT INTO "config" VALUES('FTP_INTERVAL','5','Global transmit interval (mins):','ENABLE_FTP'); +INSERT INTO "config" VALUES('FTP_RULE_1_SOURCE','*.*','(1 of 10) Source file (relative to $prefix/var/wview/img):','ENABLE_FTP'); +INSERT INTO "config" VALUES('FTP_RULE_2_SOURCE','Archive/*.txt','(2 of 10) Source file (relative to $prefix/var/wview/img):','ENABLE_FTP'); +INSERT INTO "config" VALUES('FTP_RULE_3_SOURCE','NOAA/*.txt','(3 of 10) Source file (relative to $prefix/var/wview/img):','ENABLE_FTP'); +INSERT INTO "config" VALUES('FTP_RULE_4_SOURCE','','(4 of 10) Source file (relative to $prefix/var/wview/img):','ENABLE_FTP'); +INSERT INTO "config" VALUES('FTP_RULE_5_SOURCE','','(5 of 10) Source file (relative to $prefix/var/wview/img):','ENABLE_FTP'); +INSERT INTO "config" VALUES('FTP_RULE_6_SOURCE','','(6 of 10) Source file (relative to $prefix/var/wview/img):','ENABLE_FTP'); +INSERT INTO "config" VALUES('FTP_RULE_7_SOURCE','','(7 of 10) Source file (relative to $prefix/var/wview/img):','ENABLE_FTP'); +INSERT INTO "config" VALUES('FTP_RULE_8_SOURCE','','(8 of 10) Source file (relative to $prefix/var/wview/img):','ENABLE_FTP'); +INSERT INTO "config" VALUES('FTP_RULE_9_SOURCE','','(9 of 10) Source file (relative to $prefix/var/wview/img):','ENABLE_FTP'); +INSERT INTO "config" VALUES('FTP_RULE_10_SOURCE','','(10 of 10) Source file (relative to $prefix/var/wview/img):','ENABLE_FTP'); +INSERT INTO "config" VALUES('SSH_1_SOURCE','/usr/local/var/wview/img','(1 of 5) Local Source directory (normally $prefix/var/wview/img):','ENABLE_SSH'); +INSERT INTO "config" VALUES('SSH_1_INTERVAL','1','How often to synchronize the remote server (minutes):','ENABLE_SSH'); +INSERT INTO "config" VALUES('SSH_1_HOST','remotehost.net','Remote Host (hostname or IP address):','ENABLE_SSH'); +INSERT INTO "config" VALUES('SSH_1_PORT','22','SSH Port Number:','ENABLE_SSH'); +INSERT INTO "config" VALUES('SSH_1_USERNAME','','Remote SSH username:','ENABLE_SSH'); +INSERT INTO "config" VALUES('SSH_1_DESTINATION','http/wview','Remote destination path, relative to the root ssh login directory (no leading ''/''):','ENABLE_SSH'); +INSERT INTO "config" VALUES('SSH_2_SOURCE','','(2 of 5) Local Source directory (normally $prefix/var/wview/img):','ENABLE_SSH'); +INSERT INTO "config" VALUES('SSH_2_INTERVAL','0','How often to synchronize the remote server (minutes):','ENABLE_SSH'); +INSERT INTO "config" VALUES('SSH_2_HOST','','Remote Host (hostname or IP address):','ENABLE_SSH'); +INSERT INTO "config" VALUES('SSH_2_PORT','22','SSH Port Number:','ENABLE_SSH'); +INSERT INTO "config" VALUES('SSH_2_USERNAME','','Remote SSH username:','ENABLE_SSH'); +INSERT INTO "config" VALUES('SSH_2_DESTINATION','','Remote destination path, relative to the root ssh login directory (no leading ''/''):','ENABLE_SSH'); +INSERT INTO "config" VALUES('SSH_3_SOURCE','','(3 of 5) Local Source directory (normally $prefix/var/wview/img):','ENABLE_SSH'); +INSERT INTO "config" VALUES('SSH_3_INTERVAL','0','How often to synchronize the remote server (minutes):','ENABLE_SSH'); +INSERT INTO "config" VALUES('SSH_3_HOST','','Remote Host (hostname or IP address):','ENABLE_SSH'); +INSERT INTO "config" VALUES('SSH_3_PORT','22','SSH Port Number:','ENABLE_SSH'); +INSERT INTO "config" VALUES('SSH_3_USERNAME','','Remote SSH username:','ENABLE_SSH'); +INSERT INTO "config" VALUES('SSH_3_DESTINATION','','Remote destination path, relative to the root ssh login directory (no leading ''/''):','ENABLE_SSH'); +INSERT INTO "config" VALUES('SSH_4_SOURCE','','(4 of 5) Local Source directory (normally $prefix/var/wview/img):','ENABLE_SSH'); +INSERT INTO "config" VALUES('SSH_4_INTERVAL','0','How often to synchronize the remote server (minutes):','ENABLE_SSH'); +INSERT INTO "config" VALUES('SSH_4_HOST','','Remote Host (hostname or IP address):','ENABLE_SSH'); +INSERT INTO "config" VALUES('SSH_4_PORT','22','SSH Port Number:','ENABLE_SSH'); +INSERT INTO "config" VALUES('SSH_4_USERNAME','','Remote SSH username:','ENABLE_SSH'); +INSERT INTO "config" VALUES('SSH_4_DESTINATION','','Remote destination path, relative to the root ssh login directory (no leading ''/''):','ENABLE_SSH'); +INSERT INTO "config" VALUES('SSH_5_SOURCE','','(5 of 5) Local Source directory (normally $prefix/var/wview/img):','ENABLE_SSH'); +INSERT INTO "config" VALUES('SSH_5_INTERVAL','0','How often to synchronize the remote server (minutes):','ENABLE_SSH'); +INSERT INTO "config" VALUES('SSH_5_HOST','','Remote Host (hostname or IP address):','ENABLE_SSH'); +INSERT INTO "config" VALUES('SSH_5_PORT','22','SSH Port Number:','ENABLE_SSH'); +INSERT INTO "config" VALUES('SSH_5_USERNAME','','Remote SSH username:','ENABLE_SSH'); +INSERT INTO "config" VALUES('SSH_5_DESTINATION','','Remote destination path, relative to the root ssh login directory (no leading ''/''):','ENABLE_SSH'); +INSERT INTO "config" VALUES('CWOP_APRS_CALL_SIGN','CWXXXX','CWOP call sign (CWXXXX/DWXXXX):','ENABLE_CWOP'); +INSERT INTO "config" VALUES('CWOP_APRS_SERVER1','cwop.aprs.net','CWOP Server name:','ENABLE_CWOP'); +INSERT INTO "config" VALUES('CWOP_APRS_PORTNO1','23','CWOP port number:','ENABLE_CWOP'); +INSERT INTO "config" VALUES('CWOP_APRS_SERVER2','cwop.aprs.net','CWOP Server name:','ENABLE_CWOP'); +INSERT INTO "config" VALUES('CWOP_APRS_PORTNO2','23','CWOP port number:','ENABLE_CWOP'); +INSERT INTO "config" VALUES('CWOP_APRS_SERVER3','cwop.aprs.net','CWOP Server name:','ENABLE_CWOP'); +INSERT INTO "config" VALUES('CWOP_APRS_PORTNO3','23','CWOP port number:','ENABLE_CWOP'); +INSERT INTO "config" VALUES('CWOP_LATITUDE','3333.70N','LATITUDE - MUST be of the form DDMM.hhd (LORAN format): DD - degrees (always positive), MM - minutes (0 - 59), hh - hundredths of minutes, d - hemisphere indicator (N or S):','ENABLE_CWOP'); +INSERT INTO "config" VALUES('CWOP_LONGITUDE','09654.13W','LONGITUDE - MUST be of the form DDDMM.hhd: DDD - degrees (always positive and always 3 digits - use leading zero if necessary), MM - minutes (0 - 59), hh - hundredths of minutes, d - hemisphere indicator (E or W):','ENABLE_CWOP'); +INSERT INTO "config" VALUES('CWOP_LOG_WX_PACKET','no','Log the WX packet when sending (0 or 1)?','ENABLE_CWOP'); +INSERT INTO "config" VALUES('HTTP_WUSTATIONID','','WUNDERGROUND station ID:','ENABLE_HTTP'); +INSERT INTO "config" VALUES('HTTP_WUPASSWD','','WUNDERGROUND password:','ENABLE_HTTP'); +INSERT INTO "config" VALUES('HTTP_YOUSTATIONID','','WEATHERFORYOU station ID:','ENABLE_HTTP'); +INSERT INTO "config" VALUES('HTTP_YOUPASSWD','','WEATHERFORYOU password:','ENABLE_HTTP'); +INSERT INTO "config" VALUES('CAL_MULT_BAROMETER','1.0','Barometer calibration multiplier:',NULL); +INSERT INTO "config" VALUES('CAL_CONST_BAROMETER','0.0','Barometer calibration constant:',NULL); +INSERT INTO "config" VALUES('CAL_MULT_PRESSURE','1.0','Station Pressure calibration multiplier:',NULL); +INSERT INTO "config" VALUES('CAL_CONST_PRESSURE','0.0','Station Pressure calibration constant:',NULL); +INSERT INTO "config" VALUES('CAL_MULT_ALTIMETER','1.0','Altimeter calibration multiplier:',NULL); +INSERT INTO "config" VALUES('CAL_CONST_ALTIMETER','0.0','Altimeter calibration constant:',NULL); +INSERT INTO "config" VALUES('CAL_MULT_INTEMP','1.0','Inside Temp calibration multiplier:',NULL); +INSERT INTO "config" VALUES('CAL_CONST_INTEMP','0.0','Inside Temp calibration constant:',NULL); +INSERT INTO "config" VALUES('CAL_MULT_OUTTEMP','1.0','Outside Temp calibration multiplier:',NULL); +INSERT INTO "config" VALUES('CAL_CONST_OUTTEMP','0.0','Outside Temp calibration constant:',NULL); +INSERT INTO "config" VALUES('CAL_MULT_INHUMIDITY','1.0','Inside Humidity calibration multiplier:',NULL); +INSERT INTO "config" VALUES('CAL_CONST_INHUMIDITY','0.0','Inside Humidity calibration constant:',NULL); +INSERT INTO "config" VALUES('CAL_MULT_OUTHUMIDITY','1.0','Outside Humidity calibration multiplier:',NULL); +INSERT INTO "config" VALUES('CAL_CONST_OUTHUMIDITY','0.0','Outside Humidity calibration constant:',NULL); +INSERT INTO "config" VALUES('CAL_MULT_WINDSPEED','1.0','Wind Speed calibration multiplier:',NULL); +INSERT INTO "config" VALUES('CAL_CONST_WINDSPEED','0.0','Wind Speed calibration constant:',NULL); +INSERT INTO "config" VALUES('CAL_MULT_WINDDIR','1.0','Wind Direction calibration multiplier:',NULL); +INSERT INTO "config" VALUES('CAL_CONST_WINDDIR','0.0','Wind Direction calibration constant:',NULL); +INSERT INTO "config" VALUES('CAL_MULT_RAIN','1.0','Rain calibration multiplier:',NULL); +INSERT INTO "config" VALUES('CAL_CONST_RAIN','0.0','Rain calibration constant:',NULL); +INSERT INTO "config" VALUES('CAL_MULT_RAINRATE','1.0','Rain Rate calibration multiplier:',NULL); +INSERT INTO "config" VALUES('CAL_CONST_RAINRATE','0.0','Rain Rate calibration constant:',NULL); +INSERT INTO "config" VALUES('PROCMON_wviewd','0','Seconds to wait before restarting a non-responsive process (0 disables):','ENABLE_PROCMON'); +INSERT INTO "config" VALUES('PROCMON_htmlgend','0','Seconds to wait before restarting a non-responsive process (0 disables):','ENABLE_PROCMON'); +INSERT INTO "config" VALUES('PROCMON_wvalarmd','120','Seconds to wait before restarting a non-responsive process (0 disables):','ENABLE_PROCMON'); +INSERT INTO "config" VALUES('PROCMON_wvcwopd','120','Seconds to wait before restarting a non-responsive process (0 disables):','ENABLE_PROCMON'); +INSERT INTO "config" VALUES('PROCMON_wvhttpd','120','Seconds to wait before restarting a non-responsive process (0 disables):','ENABLE_PROCMON'); +INSERT INTO "config" VALUES('EMAIL_ADDRESS','address@server.com','Destination address for email alerts:',NULL); +INSERT INTO "config" VALUES('FROM_EMAIL_ADDRESS','from_address@other_server.com','From address for email alerts:',NULL); +INSERT INTO "config" VALUES('ENABLE_EMAIL_ALERTS','no','Send system alert emails?',NULL); +INSERT INTO "config" VALUES('SEND_TEST_EMAIL','no','Send a test email?',NULL); +INSERT INTO "config" VALUES('ADMIN_PASSWORD','9798e81db4d6cc93577169c395b90f66','Admin Password (md5):',''); +COMMIT; diff --git a/examples/html/Template-Skins-HOWTO.txt b/examples/html/Template-Skins-HOWTO.txt new file mode 100644 index 0000000..c4cf9bf --- /dev/null +++ b/examples/html/Template-Skins-HOWTO.txt @@ -0,0 +1,73 @@ +wview HTML Template Skin HOWTO +------------------------------ + +-- Overview +------------------------------------------------------------------------ +The objective is to allow contributors to submit new web site skins which +support standard or extended data (UV, ET and Solar Radiation) as well as +Imperial (US) or Metric units. To that end, each skin should provide a +"standard" and "plus" subdirectory containing templates and the appropriate +"html-templates.conf", "images.conf/images-metric.conf", "images-user.conf", +"graphics.conf" and "post-generate.sh" files. See the "classic" skin in the +wview distribution for an example implementation. + +-- Minimum Requirements +------------------------------------------------------------------------ +1) index.htx and/or index-day.htx/index-night.htx + This is the home page template. If "day/night" are provided and + listed in "html-templates.conf", wview will generate based on if it + is day time or night time. + +2) html-templates.conf + This specifies the html template files to generate. Contents are up + to the skin designer. + +3) images.conf/images-metric.conf + These are the US/Metric configuration files containing the images to + be generated. wviewhtmlconfig copies the appropriate file based on + the units selection during execution of "wviewconfig". Contents are + up to the skin designer but both files should be provided. + +4) images-user.conf (can be empty) + Allows new images to be designed and included in skins. + +5) graphics.conf (can be empty if default colors are desired) + Allows custom definition of colors and sizes for wview graphics. + +6) post-generate.sh + Allows custom post-generation actions to be defined by skin designers. + +7) Standard and Extended Data Support + Each skin should have a "standard" and "plus" subdirectory with + appropriate template and config files for each mode. + +8) Static File Support + Each skin may have a "static" subdirectory under . Files + in this directory will be copied to the HTML destination directory (i.e., + /var/wview/img) once when wviewhtmlconfig is run. Examples include page + backgrounds, css files, etc. + +Note: Skins will be located at: + .../wview-x.y.z/examples/html/ + The wviewhtmlconfig script will allow selection of skin and copy + templates and config files appropriately if the skin is organized + as described above. + To add your skin to wviewhtmlconfig, look around line 95 for: + "classic" ) + echo "Site skin $SITE_SKIN selected..." + ;; + and add a block for your skin below it as: + "classic" ) + echo "Site skin $SITE_SKIN selected..." + ;; + "" ) + echo "Site skin $SITE_SKIN selected..." + ;; + + +-- Submission for Inclusion In wview +------------------------------------------------------------------------ +Tar the directory up and send it to me at +mteel2005@gmail.com. I will test it and if all is good, include it in +"wviewhtmlconfig" and the next wview release. + diff --git a/examples/html/chrome/plus/Current_Conditions.htx b/examples/html/chrome/plus/Current_Conditions.htx new file mode 100644 index 0000000..9ce14df --- /dev/null +++ b/examples/html/chrome/plus/Current_Conditions.htx @@ -0,0 +1,60 @@ + + + +Current Conditions - <!--stationCity-->,<!--stationState--> + + +

+

+ + +

+
+

As of: +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Temperature: Wind Chill:
Humidity: Heat Index:
Wind: at  Dewpoint:
Barometer:  Rain Rate:  
Today's Rain:  Monthly Rain: 
Storm Total: Yearly Rain: 
+
+ + + + diff --git a/examples/html/chrome/plus/Current_Plus.htx b/examples/html/chrome/plus/Current_Plus.htx new file mode 100644 index 0000000..1cad87e --- /dev/null +++ b/examples/html/chrome/plus/Current_Plus.htx @@ -0,0 +1,249 @@ + + +Current Conditions: <!--stationCity-->,<!--stationState--> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+, + + + +

+ +    + +    + +    + +    + +

+
+ +

+ +

+
+ +

+ +    + +    + +    + +

+
+ +

+ +    + +

+
+ +

+ +    + +    + +    + +    + +

+ +
+
Temperature

+
Humidity

Dewpoint

Wind
at
+
Barometer
 
Today's Rain
 
+
Rain Rate
 
Storm Total
 
+
Monthly Rain
 
+
Yearly Rain
 
+
Wind Chill

+
Heat Index

+
+Today's Highs/Lows
+ +

High Temperature

Low Temperature

+
+ +

  at   +

  at   +

+
+ +

High Humidity

Low Humidity

+
+ +

  at   +

  at   +

+

High Dewpoint

Low Dewpoint

  + at  

  at   +

High Wind Speed

   + at  

High Barometer

Low Barometer

   + at  

   at   +

High Rain Rate

   + at  

Low Wind Chill

  + at  

High Heat Index

  + at  

+ + + diff --git a/examples/html/chrome/plus/Daily_Plus.htx b/examples/html/chrome/plus/Daily_Plus.htx new file mode 100644 index 0000000..0a3b68a --- /dev/null +++ b/examples/html/chrome/plus/Daily_Plus.htx @@ -0,0 +1,247 @@ + + +Last 24 Hours Weather: <!--stationCity-->,<!--stationState--> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + Observations for the Last 24 Hours
+ +
+
+
+
+ + + + Today's Highs/Lows + + + + + + + + , + + + +

High Temperature

Low Temperature

+

+

+ +

+ +     + +

+

+ +     + +

+

+ +

+

+ +     + +

+ + +

+ +     + +

+

+ +

+ + +
+

High Humidity

Low Humidity

+

+

+

High Dewpoint

Low Dewpoint

+

High Wind Speed

 

+

High Barometer

Low Barometer

+

 

 

+

Rain Total

 

+

High Rain Rate

 

+

Low Wind Chill

+

High Heat Index

+
+Yearly Highs/Lows

High Temperature

Low Temperature

+

+

High Humidity

Low Humidity

+

+

+

High Dewpoint

Low Dewpoint

+

High Wind Speed

 

+

High Barometer

Low Barometer

+

 

+ 

+

Rain Total

 

+

High Rain Rate

 

+

High Heat Index

+

Low Wind Chill

+
+ + + diff --git a/examples/html/chrome/plus/Monthly_Plus.htx b/examples/html/chrome/plus/Monthly_Plus.htx new file mode 100644 index 0000000..d2f3c1b --- /dev/null +++ b/examples/html/chrome/plus/Monthly_Plus.htx @@ -0,0 +1,229 @@ + + +Last 28 Days Weather: <!--stationCity-->,<!--stationState--> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + Observations for the Last 28 Days
+ +
+
+
+
+ + + + Monthly Highs/Lows + + + + + + + + , + + + +

High Temperature

Low Temperature

+

+ +

+ +     + +

+

+ +     + +

+

+ +

+

+ +     + +

+ + +

+ +     + +

+

+ +

+ + +
+

High Humidity

Low Humidity

+

+

High Dewpoint

Low Dewpoint

High Wind Speed

 

High Barometer

Low Barometer

+

 

 

Rain Total

 

High Rain Rate

 

Low Wind Chill

High Heat Index

+Yearly Highs/Lows

High Temperature

Low Temperature

+

High Humidity

Low Humidity

+

+

High Dewpoint

Low Dewpoint

High Wind Speed

 

High Barometer

Low Barometer

+

 

+ 

Rain Total

 

High Rain Rate

 

High Heat Index

Low Wind Chill

+ + + diff --git a/examples/html/chrome/plus/Weekly_Plus.htx b/examples/html/chrome/plus/Weekly_Plus.htx new file mode 100644 index 0000000..7e46b46 --- /dev/null +++ b/examples/html/chrome/plus/Weekly_Plus.htx @@ -0,0 +1,229 @@ + + +Last 7 Days Weather: <!--stationCity-->,<!--stationState--> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + Observations for the Last 7 Days
+ +
+
+
+
+ + + + Monthly Highs/Lows + + + + + + + + , + + + +

High Temperature

Low Temperature

+

+ +

+ +     + +

+

+ +     + +

+

+ +

+

+ +     + +

+ + +

+ +     + +

+

+ +

+ + +
+

High Humidity

Low Humidity

+

+

High Dewpoint

Low Dewpoint

High Wind Speed

 

High Barometer

Low Barometer

+

 

 

Rain Total

 

High Rain Rate

 

Low Wind Chill

High Heat Index

+Yearly Highs/Lows

High Temperature

Low Temperature

+

High Humidity

Low Humidity

+

+

High Dewpoint

Low Dewpoint

High Wind Speed

 

High Barometer

Low Barometer

+

 

+ 

Rain Total

 

High Rain Rate

 

High Heat Index

Low Wind Chill

+ + + diff --git a/examples/html/chrome/plus/Yearly_Plus.htx b/examples/html/chrome/plus/Yearly_Plus.htx new file mode 100644 index 0000000..2e59cbc --- /dev/null +++ b/examples/html/chrome/plus/Yearly_Plus.htx @@ -0,0 +1,180 @@ + + +Weather for the Last Year: <!--stationCity-->,<!--stationState--> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + Observations for the Last Year
+ +
+
+
+
+ + + + Yearly Highs/Lows + + + + + + + + , + + + +

High Temperature

Low Temperature

+

+ +

+ +     + +

+

+ +     + +

+

+ +     + +

+

+ +     + +

+

+ +     + +

+ + +

+ +     + +

+

+ +

+ + +
+

High Humidity

Low Humidity

+

+

High Dewpoint

Low Dewpoint

High Wind Speed

 

High Barometer

Low Barometer

+

 

+ 

Rain Total

 

High Rain Rate

 

High Heat Index

Low Wind Chill

+ + + diff --git a/examples/html/chrome/plus/almanac_Plus.htx b/examples/html/chrome/plus/almanac_Plus.htx new file mode 100644 index 0000000..26837a5 --- /dev/null +++ b/examples/html/chrome/plus/almanac_Plus.htx @@ -0,0 +1,1747 @@ + + + + <!--stationCity-->,<!--stationState--> Weather Almanac + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + + + + + + + +
+ + , Weather Almanac +
+ + - - + +
+ + Almanac Data for: + + +
+ + Sunrise: - Midday: - Sunset: - + Day Length:
+ Civil Twilight: -    + Astronomical Twilight: -
+ Moonrise: - Moonset: +

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
     
+ Temperature +   + Dew Point +   + Humidity +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + Current: + + + + + +
+ + Hour Average: + + + + + +
+ + Hour Change: + + + + + +
+ + Day Average: + + + + + +
+ + Day Change: + + + + + +
+ + Day High: + + + +   at   + + +
+ + Day Low: + + + +   at   + + +
+ + Week Average: + + + + + +
+ + Week Change: + + + + + +
+ + Month Average: + + + + + +
+ + Month High: + + + +  on  + + +
+ + Month Low: + + + +  on  + + +
+ + Year Average: + + + + + +
+ + Year High: + + + +  on  + + +
+ + Year Low: + + + +  on  + + +
+ + All Time Average: + + + + + +
+ + All Time High: + + + +  on  + + +
+ + All Time Low: + + + +  on  + + +
+
  + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + Current: + + + + + +
+ + Hour Average: + + + + + +
+ + Hour Change: + + + + + +
+ + Day Average: + + + + + +
+ + Day Change: + + + + + +
+ + Day High: + + + +   at   + + +
+ + Day Low: + + + +   at   + + +
+ + Week Average: + + + + + +
+ + Week Change: + + + + + +
+ + Month Average: + + + + + +
+ + Month High: + + + +  on  + + +
+ + Month Low: + + + +  on  + + +
+ + Year Average: + + + + + +
+ + Year High: + + + +  on  + + +
+ + Year Low: + + + +  on  + + +
+ + All Time Average: + + + + + +
+ + All Time High: + + + +  on  + + +
+ + All Time Low: + + + +  on  + + +
+
  + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + Current: + + + + + +
+ + Hour Average: + + + + + +
+ + Hour Change: + + + + + +
+ + Day Average: + + + + + +
+ + Day Change: + + + + + +
+ + Day High: + + + +   at   + + +
+ + Day Low: + + + +   at   + + +
+ + Week Average: + + + + + +
+ + Week Change: + + + + + +
+ + Month Average: + + + + + +
+ + Month High: + + + +  on  + + +
+ + Month Low: + + + +  on  + + +
+ + Year Average: + + + + + +
+ + Year High: + + + +  on  + + +
+ + Year Low: + + + +  on  + + +
+ + All Time Average: + + + + + +
+ + All Time High: + + + +  on  + + +
+ + All Time Low: + + + +  on  + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
     
+ Wind Chill +   + Heat Index +   + Wind Run +
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + Current: + + + + + +
+ + Day Low: + + + +   at   + + +
+ + Month Low: + + + +  on  + + +
+ + Year Low: + + + +  on  + + +
+ + All Time Low: + + + +  on  + + +
+
  + + + + + + + + + + + + + + + + + + + + + + +
+ + Current: + + + + + +
+ + Day High: + + + +   at   + + +
+ + Month High: + + + +  on  + + +
+ + Year High: + + + +  on  + + +
+ + All Time High: + + + +  on  + + +
+
  + + + + + + + + + + + + + + + + + + + + + + +
+ + Hour: + + + + + +
+ + Day: + + + + + +
+ + Week: + + + + + +
+ + Month: + + + + + +
+ + Year: + + + + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
     
+ Wind +   + Barometer +   + Rain +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + Current Direction: + + + + degrees + +
+ + Hour Dom Direction: + + + + degrees + +
+ + Hour Direction Change: + + + + degrees + +
+ + Day Dom Direction: + + + + degrees + +
+ + Day Direction Change: + + + + degrees + +
+ + Week Dom Direction: + + + + degrees + +
+ + Week Direction Change: + + + + degrees + +
+ + Month Dom Direction: + + + + degrees + +
+ + Year Dom Direction: + + + + degrees + +
+ + All Time Direction: + + + + degrees + +
+ + Current Speed: + + + + + +
+ + Hour Avg Speed: + + + + + +
+ + Hour Speed Change: + + + + + +
+ + Day Avg Speed: + + + + + +
+ + Day Speed Change: + + + + + +
+ + Day High Speed: + + + +   at   + + +
+ + Week Avg Speed: + + + + + +
+ + Week Speed Change: + + + + + +
+ + Month Avg Speed: + + + + + +
+ + Month High Speed: + + + +  on  + + +
+ + Year Avg Speed: + + + + + +
+ + Year High Speed: + + + +  on  + + +
+ + All Time Avg Speed: + + + + + +
+ + All Time High Speed: + + + +  on  + + +
+
  + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + Current: + + + + + +
+ + Hour Average: + + + + + +
+ + Hour Change: + + + + + +
+ + Day Average: + + + + + +
+ + Day Change: + + + + + +
+ + Day High: + + + +   at   + + +
+ + Day Low: + + + +   at   + + +
+ + Week Average: + + + + + +
+ + Week Change: + + + + + +
+ + Month Average: + + + + + +
+ + Month High: + + + +  on  + + +
+ + Month Low: + + + +  on  + + +
+ + Year Average: + + + + + +
+ + Year High: + + + +  on  + + +
+ + Year Low: + + + +  on  + + +
+ + All Time Average: + + + + + +
+ + All Time High: + + + +  on  + + +
+ + All Time Low: + + + +  on  + + +
+
  + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + Rate: + + + + + +
+ + Day: + + + + + +
+ + Storm: + + + + + +
+ + Storm Start: + + + + + +
+ + Day High Rate: + + + +   at   + + +
+ + Month: + + + + + +
+ + Month High Rate: + + + +  on  + + +
+ + Year (): + + + + + +
+ + Year High Rate: + + + +  on  + + +
+ + All Time High Rate: + + + +  on  + + +
+
+
+
+ + + + + + + + +
+ + + + + + wviewweather.com + + + + + +
+
+
+ + diff --git a/examples/html/chrome/plus/awekas_wl.htx-metric b/examples/html/chrome/plus/awekas_wl.htx-metric new file mode 100644 index 0000000..29b5be4 --- /dev/null +++ b/examples/html/chrome/plus/awekas_wl.htx-metric @@ -0,0 +1,32 @@ +AWEKAS_Template_start
+
+
+
+
+
+
+
+
+
+-
+-
+-
+-
+-
+-
+-
+
+
+
+
+
+
+
+
+
+
+w/m^2
+
+index
+Template_V1.5
+ diff --git a/examples/html/chrome/plus/awekas_wl.htx-us b/examples/html/chrome/plus/awekas_wl.htx-us new file mode 100644 index 0000000..917aa40 --- /dev/null +++ b/examples/html/chrome/plus/awekas_wl.htx-us @@ -0,0 +1,32 @@ +AWEKAS_Template_start
+
+
+
+
+
+
+
+
+
+-
+-
+-
+-
+-
+-
+-
+
+
+
+
+
+
+
+
+
+
+w/m^2
+
+index
+Template_V1.5
+ diff --git a/examples/html/chrome/plus/graphics.conf b/examples/html/chrome/plus/graphics.conf new file mode 100644 index 0000000..b7553e5 --- /dev/null +++ b/examples/html/chrome/plus/graphics.conf @@ -0,0 +1,165 @@ +# +# +# This file contains configuration information for graphics. +# +# Note: For parameters that enable/disable a feature, a value of "0" disables +# the feature and "1" enables it... +# +# Color values are RRGGBBTT where TT is the transparency level. +# + +################################# BUCKETS ################################# + +# Transparent background (0/1)? +BUCKET_TRANSPARENT=1 + +# Background color for the bucket image +BUCKET_BG_COLOR=0xF5F5F500 + +# Color for the lines and ticks in the bucket plot +BUCKET_FG_COLOR=0x00000000 + +# Color for the filled region of the bucket +BUCKET_CONTENT_COLOR=0x4282B400 + +# Color for line indicating high value +BUCKET_HIGH_COLOR=0xFF000000 + +# Color for line indicating low value +BUCKET_LOW_COLOR=0x00BFFF00 + +# Background color for the title +BUCKET_TITLE_BG_COLOR=0xD8D8D800 + +# Foreground color for the title +BUCKET_TITLE_FG_COLOR=0x00000000 + +# Color of text labels +BUCKET_TEXT_COLOR=0x00000000 + +# Width of bucket image +BUCKET_IMAGE_WIDTH=120 + +# Height of bucket image +BUCKET_IMAGE_HEIGHT=240 + +# Width of bucket itself +BUCKET_WIDTH=36 + + +################################# LINE CHARTS ################################# + +# Transparent background for all charts (0/1)? +CHART_TRANSPARENT=1 + +# Background color for the chart image rectangle +CHART_IMAGE_BG_COLOR=0xF5F5F500 + +# Background color for the plot +CHART_GRAPH_BG_COLOR=0xD8D8D800 + +# Color of the grid lines +CHART_GRID_COLOR=0xA0A00000 + +# Color of line for primary data values +CHART_FIRST_LINE_COLOR=0x4282B400 + +# Color of line for secondary data values +CHART_SECOND_LINE_COLOR=0xB4424200 + +# Color of line for tertiary data values +CHART_THIRD_LINE_COLOR=0x42B44200 + +# Background color for title +CHART_TITLE_BG_COLOR=0xD8D8D800 + +# Foreground color for title +CHART_TITLE_FG_COLOR=0x00000000 + +# Color for text labels +CHART_TEXT_COLOR=0x00000000 + +# Width of image +CHART_WIDTH=300 + +# Height of image +CHART_HEIGHT=180 + + +################################# BAR CHARTS ################################# + +# Background color for the image +BAR_IMAGE_BG_COLOR=0xF5F5F500 + +# Background color for the plot +BAR_GRAPH_BG_COLOR=0xD8D8D800 + +# Color for the bars +BAR_BAR_COLOR=0x4282B400 + +# Background color for the title bar +BAR_TITLE_BG_COLOR=0xD8D8D800 + +# Foreground color for the title bar +BAR_TITLE_FG_COLOR=0x00000000 + +# Color for grid lines +BAR_GRID_COLOR=0xA0A00000 + +# Color for text labels +BAR_TEXT_COLOR=0x00000000 + +# Width of image +BAR_WIDTH=300 + +# Height of image +BAR_HEIGHT=180 + + +################################# DIAL PLOTS ################################# + +# Transparent background (0/1)? +DIAL_TRANSPARENT=1 + +# Background color for dial +DIAL_BG_COLOR=0xD8D8D800 + +# Background color for image (doesn't matter becasue this color is transparent) +DIAL_IMAGE_BG_COLOR=Ox12121200 + +# Color of circle at center of dial +DIAL_CENTER_COLOR=0x6666FF00 + +# Text color of current value in center of dial +DIAL_CENTER_TEXT_COLOR=0x0000ff00 + +# Text color of value for high wind in center of plot +DIAL_CENTER_HIGH_COLOR=0xff000000 + +# Fill color of pointer +DIAL_POINTER_COLOR=0x6666FF00 + +# Outline color of pointer +DIAL_POINTER_OUTLINE_COLOR=0x6666ff00 + +# Color of tick for high value +DIAL_HIGH_COLOR=0xff000000 + +# Color of tick for low value +DIAL_LOW_COLOR=0x0000ff00 + +# Text color for high value on Temperature plot +DIAL_APP_COLOR=0x80808000 + +# Text color of labels +DIAL_TEXT_COLOR=0x00000000 + +# Width of image +DIAL_IMAGE_WIDTH=160 + +# Diameter of dial +DIAL_DIAMETER=156 + +# Diameter of circle at center of dial +DIAL_CTR_DIAMETER=15 + diff --git a/examples/html/chrome/plus/header-big.incx b/examples/html/chrome/plus/header-big.incx new file mode 100644 index 0000000..e969430 --- /dev/null +++ b/examples/html/chrome/plus/header-big.incx @@ -0,0 +1,43 @@ + + + + + + +
+ + + + + + + + + + + + + + + +
+ + , Weather +
+ + - - + +
+ + + +
+ + Sunrise: - Midday: - Sunset: - + Day Length:
+ Civil Twilight: -    + Astronomical Twilight: - +

+
+
+ diff --git a/examples/html/chrome/plus/header.incx b/examples/html/chrome/plus/header.incx new file mode 100644 index 0000000..f34547b --- /dev/null +++ b/examples/html/chrome/plus/header.incx @@ -0,0 +1,40 @@ + + + + + + +
+ + + + + + + + + + + + + + + +
+ + , Weather +
+ + - - + +
+ + + +
+ + Sunrise: - Sunset: - Moon: + +
+
+ diff --git a/examples/html/chrome/plus/html-templates.conf b/examples/html/chrome/plus/html-templates.conf new file mode 100644 index 0000000..da33a0b --- /dev/null +++ b/examples/html/chrome/plus/html-templates.conf @@ -0,0 +1,76 @@ +# +# html-templates.conf - define the HTML template files to use for +# HTML and generic generation +# +# Note: Template expansion is performed in the order the files are listed +# in this file. Thus, files to be included in other templates should +# be listed before the templates that include them. + +# File Format +# +# 1) lines beginning with '#' or are ignored +# + +# Column Format +# +# 1) template file name (must be in /etc/wview/html) +# + + +############################################################################### +### Standard Include Macros (should be generated first) +############################################################################### + +header.incx +header-big.incx +nav-buttons_Plus.incx +readings-plus.incx + +############################################################################### +### RSS Feed XML Template +############################################################################### + +### Standard Weather RSS feed template - file generated is wxrss.xml and can +### be added to any html template to add an RSS feed to that page +wxrss.xtx + +############################################################################### +### Home Page Templates +############################################################################### + +### Standard Home Page template, do not remove unless you are using the +### day/night alternative below +#index.htx + +### Day/Night Home Pages - if you want a different homepage template based on +### day or night, uncomment these and copy your templates to +### /etc/wview/html/index-day.htx and /etc/wview/html/index-night.htx +### respectively +index-day.htx +index-night.htx + +############################################################################### +### Historical Data Templates +############################################################################### + +### Extra sensor Extended Data +almanac_Plus.htx +Current_Plus.htx +Daily_Plus.htx +Weekly_Plus.htx +Monthly_Plus.htx +Yearly_Plus.htx +parameterlist.htx + +############################################################################### +### Current Conditions (Table Only) Template +############################################################################### + +#Current_Conditions.htx + +############################################################################### +### AWEKAS Data Submission Template +############################################################################### + +#awekas_wl.htx + diff --git a/examples/html/chrome/plus/images-metric.conf b/examples/html/chrome/plus/images-metric.conf new file mode 100755 index 0000000..2aae42f --- /dev/null +++ b/examples/html/chrome/plus/images-metric.conf @@ -0,0 +1,231 @@ +# +# images-metric.conf - configure what images wview will generate +# + +# File Format +# +# -> lines beginning with '#' or are ignored +# -> columns are whitespace delimited +# + +# Column Format +# +# 1) image filename +# 2) image label (can be translated as desired) +# 3) image unit label (can be translated as desired) +# 4) decimal places +# 5) image generator function index (see images.c) +# + +# Enabling/Disabling Image Generation +# +# -> To enable the generation of an image, uncomment it (remove any '#' + characters in front of the image definition) +# -> To disable the generation of an image, comment it out by placing one or + more '#' characters in front of it + + +################# D I A L S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +tempdial.png "Temp" C 0 109 +humiddial.png "Humidity" % 0 110 +wind.png "Wind" none 0 11 + + +################# B U C K E T S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +temp.png "Temperature" C 1 0 +humid.png "Humidity" % 1 1 +dew.png "Dewpoint" C 1 2 +wchill.png "Wind Chill" C 1 3 +hindex.png "Heat Index" C 1 4 +barom.png "Barometer" hPa 1 5 +dayrain.png "Day Rain" mm 1 6 +stormrain.png "Storm Rain" mm 1 7 +rainrate.png "Rain Rate" mm/hour 1 8 +monthrain.png "Month Rain" mm 1 9 +yearrain.png "Year Rain" mm 1 10 + + +################# C H A R T S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +#### DAY VALUES #### +tempdaycomp.png "Temperature/Dewpoint" C 0 42 +heatchillcomp.png "Wind Chill/Heat Index" C 0 43 +## intempdaycomp.png "Inside Temp/Inside Humidity" C/% 0 115 +humidday.png "Humidity" % 0 15 +wspeeddaycomp.png "Wind/Gust" km/h 0 137 +## wspeedday.png "Wind" km/h 0 21 +wdirday.png "Wind DIR" degrees 0 24 +## hiwspeedday.png "HI Wind" km/h 0 27 +baromday.png "Barometer" hPa 0 30 + +## tempday.png "Temperature" C 0 12 +## dewday.png "Dewpoint" C 0 18 +## wchillday.png "Wind Chill" C 0 36 +## hindexday.png "Heat Index" C 0 39 + +#### WEEK VALUES #### +tempweekcomp.png "Temperature/Dewpoint" C 0 127 +heatchillweekcomp.png "Wind Chill/Heat Index" C 0 128 +## intempweekcomp.png "Inside Temp/Inside Humidity" C/% 0 129 +humidweek.png "Humidity (Hourly Avg)" % 0 118 +wspeedweekcomp.png "Wind/Gust" km/h 0 138 +## wspeedweek.png "Wind (Hourly Avg)" km/h 0 120 +wdirweek.png "Wind DIR (Hourly Avg)" degrees 0 121 +## hiwspeedweek.png "HI Wind (Hourly Avg)" km/h 0 122 +baromweek.png "Barometer (Hourly Avg)" hPa 0 123 + +## tempweek.png "Temperature (Hourly Avg)" C 0 117 +## dewweek.png "Dewpoint (Hourly Avg)" C 0 119 +## wchillweek.png "Wind Chill (Hourly Avg)" C 0 125 +## hindexweek.png "Heat Index (Hourly Avg)" C 0 126 + +#### MONTH VALUES #### +tempmonthcomp.png "Temperature/Dewpoint" C 0 44 +heatchillmonthcomp.png "Wind Chill/Heat Index" C 0 45 +## intempmonthcomp.png "Inside Temp/Inside Humidity" C/% 0 116 +humidmonth.png "Humidity (Hourly Avg)" % 0 16 +wspeedmonthcomp.png "Wind/Gust" km/h 0 139 +## wspeedmonth.png "Wind (Hourly Avg)" km/h 0 22 +wdirmonth.png "Wind DIR (Hourly Avg)" degrees 0 25 +## hiwspeedmonth.png "HI Wind (Hourly Avg)" km/h 0 28 +barommonth.png "Barometer (Hourly Avg)" hPa 0 31 + +## tempmonth.png "Temperature (Hourly Avg)" C 0 13 +## dewmonth.png "Dewpoint (Hourly Avg)" C 0 19 +## wchillmonth.png "Wind Chill (Hourly Avg)" C 0 37 +## hindexmonth.png "Heat Index (Hourly Avg)" C 0 40 + +#### YEAR VALUES #### +tempyear.png "Temperature (Daily Avg)" C 0 14 +humidyear.png "Humidity (Daily Avg)" % 0 17 +dewyear.png "Dewpoint (Daily Avg)" C 0 20 +wspeedyear.png "Wind (Daily Avg)" km/h 0 23 +wdiryear.png "Wind DIR (Daily Avg)" degrees 0 26 +hiwspeedyear.png "HI Wind (Daily Avg)" km/h 0 29 +baromyear.png "Barometer (Daily Avg)" hPa 0 32 +wchillyear.png "Wind Chill (Daily Avg)" C 0 38 +hindexyear.png "Heat Index (Daily Avg)" C 0 41 + + +################# B A R C H A R T S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +rainday.png "Rain/hour" mm 0 33 +rainweek.png "Rain/day" mm 0 124 +rainmonth.png "Rain/day" mm 0 34 +rainyear.png "Rain/week" mm 0 35 + + + +################# W I N D R O S E S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +windroseday.png "Rose Day" none 0 133 +windroseweek.png "Rose Week" none 0 134 +windrosemonth.png "Rose Month" none 0 135 +windroseyear.png "Rose Year" none 0 136 + + +################# V P P L U S D A T A B U C K E T S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +UV.png "UV Index" index 1 106 +radiation.png "Radiation" watts/m^2 1 107 +ET.png "ET (day)" mm 1 108 + + +################# V P P L U S D A T A C H A R T S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +radiationDay.png "SolarRad" watts 0 46 +radiationWeek.png "SolarRad (Hourly Avg)" watts 0 130 +radiationMonth.png "SolarRad (Hourly Avg)" watts 0 47 +radiationYear.png "SolarRad (Daily Avg)" watts 0 48 +UVDay.png "UV Index" index 0 49 +UVWeek.png "UV Index (Hourly Avg)" index 0 131 +UVMonth.png "UV Index (Hourly Avg)" index 0 50 +UVYear.png "UV Index (Daily Avg)" index 0 51 +ETDay.png "EvaPot (Hourly)" mm/hr 1 52 +ETWeek.png "EvaPot (Daily)" mm/day 0 132 +ETMonth.png "EvaPot (Daily)" mm/day 0 53 +ETYear.png "EvaPot (Weekly)" mm/week 0 54 +## leafTemp1Day.png "LeafTemp1" C 0 55 +## leafTemp1Month.png "LeafTemp1 (Hourly Avg)" C 0 56 +## leafTemp1Year.png "LeafTemp1 (Daily Avg)" C 0 57 +## leafTemp2Day.png "LeafTemp2" C 0 58 +## leafTemp2Month.png "LeafTemp2 (Hourly Avg)" C 0 59 +## leafTemp2Year.png "LeafTemp2 (Daily Avg)" C 0 60 +## leafWetness1Day.png "LeafWet1 Index" (0-15) 0 61 +## leafWetness1Month.png "LeafWet1 Index (Hourly Avg)" (0-15) 0 62 +## leafWetness1Year.png "LeafWet1 Index (Daily Avg)" (0-15) 0 63 +## leafWetness2Day.png "LeafWet2 Index" (0-15) 0 64 +## leafWetness2Month.png "LeafWet2 Index (Hourly Avg)" (0-15) 0 65 +## leafWetness2Year.png "LeafWet2 Index (Daily Avg)" (0-15) 0 66 +## soilTemp1Day.png "SoilTemp1" C 0 67 +## soilTemp1Month.png "SoilTemp1 (Hourly Avg)" C 0 68 +## soilTemp1Year.png "SoilTemp1 (Daily Avg)" C 0 69 +## soilTemp2Day.png "SoilTemp2" C 0 70 +## soilTemp2Month.png "SoilTemp2 (Hourly Avg)" C 0 71 +## soilTemp2Year.png "SoilTemp2 (Daily Avg)" C 0 72 +## soilTemp3Day.png "SoilTemp3" C 0 73 +## soilTemp3Month.png "SoilTemp3 (Hourly Avg)" C 0 74 +## soilTemp3Year.png "SoilTemp3 (Daily Avg)" C 0 75 +## soilTemp4Day.png "SoilTemp4" C 0 76 +## soilTemp4Month.png "SoilTemp4 (Hourly Avg)" C 0 77 +## soilTemp4Year.png "SoilTemp4 (Daily Avg)" C 0 78 +## extraHumid1Day.png "ExtraHumidity1" % 0 79 +## extraHumid1Month.png "ExtraHumidity1 (Hourly Avg)" % 0 80 +## extraHumid1Year.png "ExtraHumidity1 (Daily Avg)" % 0 81 +## extraHumid2Day.png "ExtraHumidity2" % 0 82 +## extraHumid2Month.png "ExtraHumidity2 (Hourly Avg)" % 0 83 +## extraHumid2Year.png "ExtraHumidity2 (Daily Avg)" % 0 84 +## extraTemp1Day.png "ExtraTemp1" C 0 85 +## extraTemp1Month.png "ExtraTemp1 (Hourly Avg)" C 0 86 +## extraTemp1Year.png "ExtraTemp1 (Daily Avg)" C 0 87 +## extraTemp2Day.png "ExtraTemp2" C 0 88 +## extraTemp2Month.png "ExtraTemp2 (Hourly Avg)" C 0 89 +## extraTemp2Year.png "ExtraTemp2 (Daily Avg)" C 0 90 +## extraTemp3Day.png "ExtraTemp3" C 0 91 +## extraTemp3Month.png "ExtraTemp3 (Hourly Avg)" C 0 92 +## extraTemp3Year.png "ExtraTemp3 (Daily Avg)" C 0 93 +## soilMoist1Day.png "SoilMoist1" cb 0 94 +## soilMoist1Month.png "SoilMoist1 (Hourly Avg)" cb 0 95 +## soilMoist1Year.png "SoilMoist1 (Daily Avg)" cb 0 96 +## soilMoist2Day.png "SoilMoist2" cb 0 97 +## soilMoist2Month.png "SoilMoist2 (Hourly Avg)" cb 0 98 +## soilMoist2Year.png "SoilMoist2 (Daily Avg)" cb 0 99 +## soilMoist3Day.png "SoilMoist3" cb 0 100 +## soilMoist3Month.png "SoilMoist3 (Hourly Avg)" cb 0 101 +## soilMoist3Year.png "SoilMoist3 (Daily Avg)" cb 0 102 +## soilMoist4Day.png "SoilMoist4" cb 0 103 +## soilMoist4Month.png "SoilMoist4 (Hourly Avg)" cb 0 104 +## soilMoist4Year.png "SoilMoist4 (Daily Avg)" cb 0 105 + + +################# V P P L U S D I A L S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +netRainDay.png "Precip Day" mm 0 112 +netRainMonth.png "Precip Month" mm 0 113 +netRainYear.png "Precip Year" mm 0 114 + + +################# D I A G N O S T I C S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +#rxcheck.png "RX Good Packets" % 0 111 + diff --git a/examples/html/chrome/plus/images-user.conf b/examples/html/chrome/plus/images-user.conf new file mode 100644 index 0000000..04b6a1c --- /dev/null +++ b/examples/html/chrome/plus/images-user.conf @@ -0,0 +1,20 @@ +# +# images-user.conf - configure what user-defined images wview will generate +# + +# File Format +# +# 1) lines beginning with '#' or are ignored +# 2) whitespace delimited columns +# + +# Column Format +# +# 1) image filename +# 2) image label +# 3) image unit label +# 4) decimal places +# 5) image generator function index (see images-user.c) +# + +##intemp.png "In Temperature" degrees 1 0 diff --git a/examples/html/chrome/plus/images.conf b/examples/html/chrome/plus/images.conf new file mode 100755 index 0000000..62d61bf --- /dev/null +++ b/examples/html/chrome/plus/images.conf @@ -0,0 +1,225 @@ +# +# images.conf - configure what images wview will generate +# + +# File Format +# +# -> lines beginning with '#' or are ignored +# -> columns are whitespace delimited +# + +# Column Format +# +# 1) image filename +# 2) image label (can be translated as desired) +# 3) image units label (can be translated as desired) +# 4) decimal places +# 5) image generator function index (see images.c) +# + +# Enabling/Disabling Image Generation +# +# -> To enable the generation of an image, uncomment it (remove any '#' + characters in front of the image definition) +# -> To disable the generation of an image, comment it out by placing one or + more '#' characters in front of it + + +################# D I A L S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +tempdial.png "Temp" F 0 109 +humiddial.png "Humidity" none 0 110 +wind.png "Wind" none 0 11 +netRainDay.png "Precip Day" in 0 112 +netRainMonth.png "Precip Month" in 0 113 +netRainYear.png "Precip Year" in 0 114 + + +################# B U C K E T S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +temp.png "Temperature" F 1 0 +humid.png "Humidity" % 1 1 +dew.png "Dewpoint" F 1 2 +wchill.png "Wind Chill" F 1 3 +hindex.png "Heat Index" F 1 4 +barom.png "Barometer" inHg 2 5 +dayrain.png "Day Rain" inches 2 6 +stormrain.png "Storm Rain" inches 2 7 +rainrate.png "Rain Rate" in/hour 2 8 +monthrain.png "Month Rain" inches 2 9 +yearrain.png "Year Rain" inches 2 10 + + +################# C H A R T S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +#### DAY VALUES #### +tempdaycomp.png "Temperature/Dewpoint" F 0 42 +heatchillcomp.png "Wind Chill/Heat Index" F 0 43 +## intempdaycomp.png "Inside Temp/Inside Humidity" F/% 0 115 +humidday.png "Humidity" % 0 15 +wspeeddaycomp.png "Wind/Gust" mph 0 137 +## wspeedday.png "Wind" mph 0 21 +wdirday.png "Wind DIR" degrees 0 24 +## hiwspeedday.png "HI Wind" mph 0 27 +baromday.png "Barometer" inHg 1 30 + +## tempday.png "Temperature" F 0 12 +## dewday.png "Dewpoint" F 0 18 +## wchillday.png "Wind Chill" F 0 36 +## hindexday.png "Heat Index" F 0 39 + +#### WEEK VALUES #### +tempweekcomp.png "Temperature/Dewpoint" F 0 127 +heatchillweekcomp.png "Wind Chill/Heat Index" F 0 128 +## intempweekcomp.png "Inside Temp/Inside Humidity" F/% 0 129 +humidweek.png "Humidity (Hourly Avg)" % 0 118 +wspeedweekcomp.png "Wind/Gust" mph 0 138 +## wspeedweek.png "Wind (Hourly Avg)" mph 0 120 +wdirweek.png "Wind DIR (Hourly Avg)" degrees 0 121 +## hiwspeedweek.png "HI Wind (Hourly Avg)" mph 0 122 +baromweek.png "Barometer (Hourly Avg)" inHg 1 123 + +## tempweek.png "Temperature (Hourly Avg)" F 0 117 +## dewweek.png "Dewpoint (Hourly Avg)" F 0 119 +## wchillweek.png "Wind Chill (Hourly Avg)" F 0 125 +## hindexweek.png "Heat Index (Hourly Avg)" F 0 126 + +#### MONTH VALUES #### +tempmonthcomp.png "Temperature/Dewpoint" F 0 44 +heatchillmonthcomp.png "Wind Chill/Heat Index" F 0 45 +## intempmonthcomp.png "Inside Temp/Inside Humidity" deg/% 0 116 +humidmonth.png "Humidity (Hourly Avg)" % 0 16 +wspeedmonthcomp.png "Wind/Gust" mph 0 139 +## wspeedmonth.png "Wind (Hourly Avg)" mph 0 22 +wdirmonth.png "Wind DIR (Hourly Avg)" degrees 0 25 +## hiwspeedmonth.png "HI Wind (Hourly Avg)" mph 0 28 +barommonth.png "Barometer (Hourly Avg)" inHg 1 31 + +## tempmonth.png "Temperature (Hourly Avg)" F 0 13 +## dewmonth.png "Dewpoint (Hourly Avg)" F 0 19 +## wchillmonth.png "Wind Chill (Hourly Avg)" F 0 37 +## hindexmonth.png "Heat Index (Hourly Avg)" F 0 40 + +#### YEAR VALUES #### +tempyear.png "Temperature (Daily Avg)" F 0 14 +humidyear.png "Humidity (Daily Avg)" % 0 17 +dewyear.png "Dewpoint (Daily Avg)" F 0 20 +wspeedyear.png "Wind (Daily Avg)" mph 0 23 +wdiryear.png "Wind DIR (Daily Avg)" degrees 0 26 +hiwspeedyear.png "HI Wind (Daily Avg)" mph 0 29 +baromyear.png "Barometer (Daily Avg)" inHg 1 32 +wchillyear.png "Wind Chill (Daily Avg)" F 0 38 +hindexyear.png "Heat Index (Daily Avg)" F 0 41 + + +################# B A R C H A R T S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +rainday.png "Rain/hour" inches 2 33 +rainweek.png "Rain/day" inches 1 124 +rainmonth.png "Rain/day" inches 1 34 +rainyear.png "Rain/week" inches 1 35 + + + +################# W I N D R O S E S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +windroseday.png "Rose Day" none 0 133 +windroseweek.png "Rose Week" none 0 134 +windrosemonth.png "Rose Month" none 0 135 +windroseyear.png "Rose Year" none 0 136 + + +################# V P P L U S D A T A B U C K E T S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +UV.png "UV Index" index 1 106 +radiation.png "Radiation" watts/m^2 1 107 +ET.png "ET (day)" inches 3 108 + + +################# V P P L U S D A T A C H A R T S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +radiationDay.png "SolarRad" watts 0 46 +radiationWeek.png "SolarRad (Hourly Avg)" watts 0 130 +radiationMonth.png "SolarRad (Hourly Avg)" watts 0 47 +radiationYear.png "SolarRad (Daily Avg)" watts 0 48 +UVDay.png "UV Index" index 0 49 +UVWeek.png "UV Index (Hourly Avg)" index 0 131 +UVMonth.png "UV Index (Hourly Avg)" index 0 50 +UVYear.png "UV Index (Daily Avg)" index 0 51 +ETDay.png "EvaPot (Hourly)" inches/hr 2 52 +ETWeek.png "EvaPot (Daily)" inches/day 2 132 +ETMonth.png "EvaPot (Daily)" inches/day 2 53 +ETYear.png "EvaPot (Weekly)" inches/week 2 54 +## leafTemp1Day.png "LeafTemp1" F 0 55 +## leafTemp1Month.png "LeafTemp1 (Hourly Avg)" F 0 56 +## leafTemp1Year.png "LeafTemp1 (Daily Avg)" F 0 57 +## leafTemp2Day.png "LeafTemp2" F 0 58 +## leafTemp2Month.png "LeafTemp2 (Hourly Avg)" F 0 59 +## leafTemp2Year.png "LeafTemp2 (Daily Avg)" F 0 60 +## leafWetness1Day.png "LeafWet1 Index" (0-15) 0 61 +## leafWetness1Month.png "LeafWet1 Index (Hourly Avg)" (0-15) 0 62 +## leafWetness1Year.png "LeafWet1 Index (Daily Avg)" (0-15) 0 63 +## leafWetness2Day.png "LeafWet2 Index" (0-15) 0 64 +## leafWetness2Month.png "LeafWet2 Index (Hourly Avg)" (0-15) 0 65 +## leafWetness2Year.png "LeafWet2 Index (Daily Avg)" (0-15) 0 66 +## soilTemp1Day.png "SoilTemp1" F 0 67 +## soilTemp1Month.png "SoilTemp1 (Hourly Avg)" F 0 68 +## soilTemp1Year.png "SoilTemp1 (Daily Avg)" F 0 69 +## soilTemp2Day.png "SoilTemp2" F 0 70 +## soilTemp2Month.png "SoilTemp2 (Hourly Avg)" F 0 71 +## soilTemp2Year.png "SoilTemp2 (Daily Avg)" F 0 72 +## soilTemp3Day.png "SoilTemp3" F 0 73 +## soilTemp3Month.png "SoilTemp3 (Hourly Avg)" F 0 74 +## soilTemp3Year.png "SoilTemp3 (Daily Avg)" F 0 75 +## soilTemp4Day.png "SoilTemp4" F 0 76 +## soilTemp4Month.png "SoilTemp4 (Hourly Avg)" F 0 77 +## soilTemp4Year.png "SoilTemp4 (Daily Avg)" F 0 78 +## extraHumid1Day.png "ExtraHumidity1" % 0 79 +## extraHumid1Month.png "ExtraHumidity1 (Hourly Avg)" % 0 80 +## extraHumid1Year.png "ExtraHumidity1 (Daily Avg)" % 0 81 +## extraHumid2Day.png "ExtraHumidity2" % 0 82 +## extraHumid2Month.png "ExtraHumidity2 (Hourly Avg)" % 0 83 +## extraHumid2Year.png "ExtraHumidity2 (Daily Avg)" % 0 84 +## extraTemp1Day.png "ExtraTemp1" F 0 85 +## extraTemp1Month.png "ExtraTemp1 (Hourly Avg)" F 0 86 +## extraTemp1Year.png "ExtraTemp1 (Daily Avg)" F 0 87 +## extraTemp2Day.png "ExtraTemp2" F 0 88 +## extraTemp2Month.png "ExtraTemp2 (Hourly Avg)" F 0 89 +## extraTemp2Year.png "ExtraTemp2 (Daily Avg)" F 0 90 +## extraTemp3Day.png "ExtraTemp3" F 0 91 +## extraTemp3Month.png "ExtraTemp3 (Hourly Avg)" F 0 92 +## extraTemp3Year.png "ExtraTemp3 (Daily Avg)" F 0 93 +## soilMoist1Day.png "SoilMoist1" cbars 0 94 +## soilMoist1Month.png "SoilMoist1 (Hourly Avg)" cbars 0 95 +## soilMoist1Year.png "SoilMoist1 (Daily Avg)" cbars 0 96 +## soilMoist2Day.png "SoilMoist2" cbars 0 97 +## soilMoist2Month.png "SoilMoist2 (Hourly Avg)" cbars 0 98 +## soilMoist2Year.png "SoilMoist2 (Daily Avg)" cbars 0 99 +## soilMoist3Day.png "SoilMoist3" cbars 0 100 +## soilMoist3Month.png "SoilMoist3 (Hourly Avg)" cbars 0 101 +## soilMoist3Year.png "SoilMoist3 (Daily Avg)" cbars 0 102 +## soilMoist4Day.png "SoilMoist4" cbars 0 103 +## soilMoist4Month.png "SoilMoist4 (Hourly Avg)" cbars 0 104 +## soilMoist4Year.png "SoilMoist4 (Daily Avg)" cbars 0 105 + + +################# D I A G N O S T I C S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +#rxcheck.png "RX Good Packets" % 0 111 + diff --git a/examples/html/chrome/plus/index-day.htx b/examples/html/chrome/plus/index-day.htx new file mode 100644 index 0000000..1f3d8bb --- /dev/null +++ b/examples/html/chrome/plus/index-day.htx @@ -0,0 +1,377 @@ + + + + <!--stationCity-->,<!--stationState--> Weather + + + + + + + + +
+ + + + + + + + + + +
+ + + + + + + +
+ + + + + + + + + + +
+ +
+
+ + NEXRAD + +
+
+
+ + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + + + + + + +
+ + + + + + + +
+ + + +
+
+ + + + + + + + + + + + + + + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+
+ + + + + + + +
+ + + +
+
+
+
+
+
+ + + + + + + + + + + +
+ + + + + +
+
+ + + + + + + + + +
+ + Climatological Summaries:  + + + + + +
+ + Browse Archive Records:  + + + +
+
+
+
+
+ + + + + + + + +
+ + + + + + wviewweather.com + + + +   + +
+
+
+ + diff --git a/examples/html/chrome/plus/index-night.htx b/examples/html/chrome/plus/index-night.htx new file mode 100644 index 0000000..0e40886 --- /dev/null +++ b/examples/html/chrome/plus/index-night.htx @@ -0,0 +1,378 @@ + + + + <!--stationCity-->,<!--stationState--> Weather + + + + + + + + +
+ + + + + + + + + + +
+ + + + + + + +
+ + + + + + + + + + +
+ +
+
+ + NEXRAD + +
+
+
+ + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + + + + + + + +
+ + + + + + + +
+ + + +
+
+ + + + + + + + + + + + + + + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+
+ + + + + + + +
+ + + +
+
+
+
+
+
+ + + + + + + + + + + +
+ + + + + +
+
+ + + + + + + + + +
+ + Climatological Summaries:  + + + + + +
+ + Browse Archive Records:  + + + +
+
+
+
+
+ + + + + + + + +
+ + + + + + wviewweather.com + + + +   + +
+
+
+ + diff --git a/examples/html/chrome/plus/index.htx b/examples/html/chrome/plus/index.htx new file mode 100644 index 0000000..f5744e4 --- /dev/null +++ b/examples/html/chrome/plus/index.htx @@ -0,0 +1,377 @@ + + + + <!--stationCity-->,<!--stationState--> Weather + + + + + + + + +
+ + + + + + + + + + +
+ + + + + + + +
+ + + + + + + + + + +
+ +
+
+ + NEXRAD + +
+
+
+ + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + + + + + + +
+ + + + + + + +
+ + + +
+
+ + + + + + + + + + + + + + + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+
+ + + + + + + +
+ + + +
+
+
+
+
+
+ + + + + + + + + + + +
+ + + + + +
+
+ + + + + + + + + +
+ + Climatological Summaries:  + + + + + +
+ + Browse Archive Records:  + + + +
+
+
+
+
+ + + + + + + + +
+ + + + + + wviewweather.com + + + +   + +
+
+
+ + diff --git a/examples/html/chrome/plus/nav-buttons_Plus.incx b/examples/html/chrome/plus/nav-buttons_Plus.incx new file mode 100644 index 0000000..ce20750 --- /dev/null +++ b/examples/html/chrome/plus/nav-buttons_Plus.incx @@ -0,0 +1,14 @@ + + + + +
+ + + + + + + +
+ diff --git a/examples/html/chrome/plus/post-generate.sh b/examples/html/chrome/plus/post-generate.sh new file mode 100644 index 0000000..b5426c3 --- /dev/null +++ b/examples/html/chrome/plus/post-generate.sh @@ -0,0 +1,8 @@ +#/bin/sh + +# Add any post template generation processing or utilities here. Keep in mind this +# script runs in the htmlgend process's context so don't add anything that is overly +# time constrained. + +exit 0 + diff --git a/examples/html/chrome/plus/pre-generate.sh b/examples/html/chrome/plus/pre-generate.sh new file mode 100644 index 0000000..0c0d1cb --- /dev/null +++ b/examples/html/chrome/plus/pre-generate.sh @@ -0,0 +1,8 @@ +#/bin/sh + +# Add any pre template generation processing or utilities here. Keep in mind this +# script runs in the htmlgend process's context so don't add anything that is overly +# time constrained. + +exit 0 + diff --git a/examples/html/chrome/plus/readings-plus.incx b/examples/html/chrome/plus/readings-plus.incx new file mode 100644 index 0000000..8d7e8b8 --- /dev/null +++ b/examples/html/chrome/plus/readings-plus.incx @@ -0,0 +1,422 @@ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + Temperature: + + + + + +
+ + Wind Chill: + + + + + +
+ + Heat Index: + + + + + +
+ + Apparent Temp: + + + + + +
+ + Wet Bulb Temp: + + + + + +
+ + Dewpoint: + + + + + +
+ + Humidity: + + + + + +
+ + Barometer: + + + +    + +
+ + Wind: + + + +   at   +   + +
+ + High Wind: + + + + at + + +
+ + Recent Avg Wind: + + + + + +
+ + Recent Beaufort Scale: + + + + + +
+ + Today's Rain: + + + +   + +
+ + Rain Rate: + + + +   + +
+ + High Rain Rate: + + + + at + + +
+ + Storm Total: + + + +   + +
+ + Monthly Rain: + + + +   + +
+ + Yearly Rain (): + + + +   + +
+ + UV: + + + + + +
+ + ET: + + + +   + +
+ + Solar Radiation: + + + +  watts/m^2 + +
+ + Air Density: + + + +   + +
+ + Est. Cumulus Base: + + + +   + +
+ + High Temperature: + + + + at + + +
+ + Low Temperature: + + + + at + + +
+ + High Heat Index: + + + + at + + +
+ + Low Wind Chill: + + + + at + + +
+ + High Humidity: + + + + at + + +
+ + Low Humidity: + + + + at + + +
+ + High Dewpoint: + + + + at + + +
+ + Low Dewpoint: + + + + at + + +
+ + High Barometer: + + + + at + + +
+ + Low Barometer: + + + + at + + +
+
+ diff --git a/examples/html/chrome/plus/wxrss.xtx b/examples/html/chrome/plus/wxrss.xtx new file mode 100644 index 0000000..423afed --- /dev/null +++ b/examples/html/chrome/plus/wxrss.xtx @@ -0,0 +1,39 @@ + + + + + <!--stationCity--> <!--stationState--> + Insert_Your_WX_HomePage_URL_Here + Current Weather Conditions + en-us + , + 1 + + + + <!--stationCity-->,<!--stationState--> Weather Conditions: <!--stationDate--> <!--stationTime--> + Insert_Your_WX_HomePage_URL_Here + Current Weather Conditions + , + Insert_Your_WX_HomePage_URL_Here + - + + Temp:
+ Wind Chill:
+ Heat Index:
+ Humidity:
+ Dewpoint:
+ Barometer:
+ Wind: at
+ Rain Today:
+ Rain Rate:
+

+ ]]>
+
+
+
diff --git a/examples/html/chrome/standard/Current.htx b/examples/html/chrome/standard/Current.htx new file mode 100644 index 0000000..682400d --- /dev/null +++ b/examples/html/chrome/standard/Current.htx @@ -0,0 +1,237 @@ + + +Current Conditions: <!--stationCity-->,<!--stationState--> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+, + + + +

+ +    + +    + +    + +    + +

+
+ +

+ +    + +

+
+ +

+ +    + +

+
+ +

+ +    + +    + +    + +    + +

+ +
+
Temperature

+
Humidity

Dewpoint

Wind
at
+
Barometer
 
Today's Rain
 
+
Rain Rate
 
Storm Total
 
+
Monthly Rain
 
+
Yearly Rain
 
+
Wind Chill

+
Heat Index

+
+Today's Highs/Lows
+ +

High Temperature

Low Temperature

+
+ +

  at   +

  at   +

+
+ +

High Humidity

Low Humidity

+
+ +

  at   +

  at   +

+

High Dewpoint

Low Dewpoint

  + at  

  at   +

High Wind Speed

   + at  

High Barometer

Low Barometer

   + at  

   at   +

High Rain Rate

   + at  

Low Wind Chill

  + at  

High Heat Index

  + at  

+ + + diff --git a/examples/html/chrome/standard/Current_Conditions.htx b/examples/html/chrome/standard/Current_Conditions.htx new file mode 100644 index 0000000..9ce14df --- /dev/null +++ b/examples/html/chrome/standard/Current_Conditions.htx @@ -0,0 +1,60 @@ + + + +Current Conditions - <!--stationCity-->,<!--stationState--> + + +

+

+ + +

+

Current Weather +Conditions for ,

+

As of: +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Temperature: Wind Chill:
Humidity: Heat Index:
Wind: at  Dewpoint:
Barometer:  Rain Rate:  
Today's Rain:  Monthly Rain: 
Storm Total: Yearly Rain: 
+
+ + + + diff --git a/examples/html/chrome/standard/Daily.htx b/examples/html/chrome/standard/Daily.htx new file mode 100644 index 0000000..910678f --- /dev/null +++ b/examples/html/chrome/standard/Daily.htx @@ -0,0 +1,231 @@ + + +Last 24 Hours Weather: <!--stationCity-->,<!--stationState--> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + Observations for the Last 24 Hours
+ +
+
+
+
+ + + + Today's Highs/Lows + + + + + + + + , + + + +

High Temperature

Low Temperature

+

+

+ +

+ +     + +

+

+ +     + +

+

+ +

+

+ +     + +

+
+

High Humidity

Low Humidity

+

+

+

High Dewpoint

Low Dewpoint

+

High Wind Speed

 

+

High Barometer

Low Barometer

+

 

 

+

Rain Total

 

+

High Rain Rate

 

+

Low Wind Chill

+

High Heat Index

+
+Yearly Highs/Lows

High Temperature

Low Temperature

+

+

High Humidity

Low Humidity

+

+

+

High Dewpoint

Low Dewpoint

+

High Wind Speed

 

+

High Barometer

Low Barometer

+

 

+ 

+

Rain Total

 

+

High Rain Rate

 

+

High Heat Index

+

Low Wind Chill

+
+ + + diff --git a/examples/html/chrome/standard/Monthly.htx b/examples/html/chrome/standard/Monthly.htx new file mode 100644 index 0000000..b9508a4 --- /dev/null +++ b/examples/html/chrome/standard/Monthly.htx @@ -0,0 +1,214 @@ + + +Last 28 Days Weather: <!--stationCity-->,<!--stationState--> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + Observations for the Last 28 Days
+ +
+
+
+
+ + + + Monthly Highs/Lows + + + + + + + + , + + + +

High Temperature

Low Temperature

+

+ +

+ +     + +

+

+ +     + +

+

+ +

+

+ +     + +

+
+

High Humidity

Low Humidity

+

+

High Dewpoint

Low Dewpoint

High Wind Speed

 

High Barometer

Low Barometer

+

 

 

Rain Total

 

High Rain Rate

 

Low Wind Chill

High Heat Index

+Yearly Highs/Lows

High Temperature

Low Temperature

+

High Humidity

Low Humidity

+

+

High Dewpoint

Low Dewpoint

High Wind Speed

 

High Barometer

Low Barometer

+

 

+ 

Rain Total

 

High Rain Rate

 

High Heat Index

Low Wind Chill

+ + + diff --git a/examples/html/chrome/standard/Weekly.htx b/examples/html/chrome/standard/Weekly.htx new file mode 100644 index 0000000..4e75fd7 --- /dev/null +++ b/examples/html/chrome/standard/Weekly.htx @@ -0,0 +1,214 @@ + + +Last 7 Days Weather: <!--stationCity-->,<!--stationState--> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + Observations for the Last 7 Days
+ +
+
+
+
+ + + + Monthly Highs/Lows + + + + + + + + , + + + +

High Temperature

Low Temperature

+

+ +

+ +     + +

+

+ +     + +

+

+ +

+

+ +     + +

+
+

High Humidity

Low Humidity

+

+

High Dewpoint

Low Dewpoint

High Wind Speed

 

High Barometer

Low Barometer

+

 

 

Rain Total

 

High Rain Rate

 

Low Wind Chill

High Heat Index

+Yearly Highs/Lows

High Temperature

Low Temperature

+

High Humidity

Low Humidity

+

+

High Dewpoint

Low Dewpoint

High Wind Speed

 

High Barometer

Low Barometer

+

 

+ 

Rain Total

 

High Rain Rate

 

High Heat Index

Low Wind Chill

+ + + diff --git a/examples/html/chrome/standard/Yearly.htx b/examples/html/chrome/standard/Yearly.htx new file mode 100644 index 0000000..d762a2d --- /dev/null +++ b/examples/html/chrome/standard/Yearly.htx @@ -0,0 +1,165 @@ + + +Weather for the Last Year: <!--stationCity-->,<!--stationState--> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + Observations for the Last Year
+ +
+
+
+
+ + + + Yearly Highs/Lows + + + + + + + + , + + + +

High Temperature

Low Temperature

+

+ +

+ +     + +

+

+ +     + +

+

+ +     + +

+

+ +     + +

+

+ +     + +

+
+

High Humidity

Low Humidity

+

+

High Dewpoint

Low Dewpoint

High Wind Speed

 

High Barometer

Low Barometer

+

 

+ 

Rain Total

 

High Rain Rate

 

High Heat Index

Low Wind Chill

+ + + diff --git a/examples/html/chrome/standard/almanac.htx b/examples/html/chrome/standard/almanac.htx new file mode 100644 index 0000000..d522450 --- /dev/null +++ b/examples/html/chrome/standard/almanac.htx @@ -0,0 +1,1748 @@ + + + + <!--stationCity-->,<!--stationState--> Weather Almanac + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + + + + + + + +
+ + , Weather Almanac +
+ + - - + +
+ + Almanac Data for: + + +
+ + Sunrise: - Midday: - Sunset: - + Day Length:
+ Civil Twilight: -    + Astronomical Twilight: -
+ Moonrise: - Moonset: +

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
     
+ Temperature +   + Dew Point +   + Humidity +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + Current: + + + + + +
+ + Hour Average: + + + + + +
+ + Hour Change: + + + + + +
+ + Day Average: + + + + + +
+ + Day Change: + + + + + +
+ + Day High: + + + +   at   + + +
+ + Day Low: + + + +   at   + + +
+ + Week Average: + + + + + +
+ + Week Change: + + + + + +
+ + Month Average: + + + + + +
+ + Month High: + + + +  on  + + +
+ + Month Low: + + + +  on  + + +
+ + Year Average: + + + + + +
+ + Year High: + + + +  on  + + +
+ + Year Low: + + + +  on  + + +
+ + All Time Average: + + + + + +
+ + All Time High: + + + +  on  + + +
+ + All Time Low: + + + +  on  + + +
+
  + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + Current: + + + + + +
+ + Hour Average: + + + + + +
+ + Hour Change: + + + + + +
+ + Day Average: + + + + + +
+ + Day Change: + + + + + +
+ + Day High: + + + +   at   + + +
+ + Day Low: + + + +   at   + + +
+ + Week Average: + + + + + +
+ + Week Change: + + + + + +
+ + Month Average: + + + + + +
+ + Month High: + + + +  on  + + +
+ + Month Low: + + + +  on  + + +
+ + Year Average: + + + + + +
+ + Year High: + + + +  on  + + +
+ + Year Low: + + + +  on  + + +
+ + All Time Average: + + + + + +
+ + All Time High: + + + +  on  + + +
+ + All Time Low: + + + +  on  + + +
+
  + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + Current: + + + + + +
+ + Hour Average: + + + + + +
+ + Hour Change: + + + + + +
+ + Day Average: + + + + + +
+ + Day Change: + + + + + +
+ + Day High: + + + +   at   + + +
+ + Day Low: + + + +   at   + + +
+ + Week Average: + + + + + +
+ + Week Change: + + + + + +
+ + Month Average: + + + + + +
+ + Month High: + + + +  on  + + +
+ + Month Low: + + + +  on  + + +
+ + Year Average: + + + + + +
+ + Year High: + + + +  on  + + +
+ + Year Low: + + + +  on  + + +
+ + All Time Average: + + + + + +
+ + All Time High: + + + +  on  + + +
+ + All Time Low: + + + +  on  + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
     
+ Wind Chill +   + Heat Index +   + Wind Run +
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + Current: + + + + + +
+ + Day Low: + + + +   at   + + +
+ + Month Low: + + + +  on  + + +
+ + Year Low: + + + +  on  + + +
+ + All Time Low: + + + +  on  + + +
+
  + + + + + + + + + + + + + + + + + + + + + + + +
+ + Current: + + + + + +
+ + Day High: + + + +   at   + + +
+ + Month High: + + + +  on  + + +
+ + Year High: + + + +  on  + + +
+ + All Time High: + + + +  on  + + +
+
  + + + + + + + + + + + + + + + + + + + + + + +
+ + Hour: + + + + + +
+ + Day: + + + + + +
+ + Week: + + + + + +
+ + Month: + + + + + +
+ + Year: + + + + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
     
+ Wind +   + Barometer +   + Rain +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + Current Direction: + + + + degrees + +
+ + Hour Dom Direction: + + + + degrees + +
+ + Hour Direction Change: + + + + degrees + +
+ + Day Dom Direction: + + + + degrees + +
+ + Day Direction Change: + + + + degrees + +
+ + Week Dom Direction: + + + + degrees + +
+ + Week Direction Change: + + + + degrees + +
+ + Month Dom Direction: + + + + degrees + +
+ + Year Dom Direction: + + + + degrees + +
+ + All Time Direction: + + + + degrees + +
+ + Current Speed: + + + + + +
+ + Hour Avg Speed: + + + + + +
+ + Hour Speed Change: + + + + + +
+ + Day Avg Speed: + + + + + +
+ + Day Speed Change: + + + + + +
+ + Day High Speed: + + + +   at   + + +
+ + Week Avg Speed: + + + + + +
+ + Week Speed Change: + + + + + +
+ + Month Avg Speed: + + + + + +
+ + Month High Speed: + + + +  on  + + +
+ + Year Avg Speed: + + + + + +
+ + Year High Speed: + + + +  on  + + +
+ + All Time Avg Speed: + + + + + +
+ + All Time High Speed: + + + +  on  + + +
+
  + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + Current: + + + + + +
+ + Hour Average: + + + + + +
+ + Hour Change: + + + + + +
+ + Day Average: + + + + + +
+ + Day Change: + + + + + +
+ + Day High: + + + +   at   + + +
+ + Day Low: + + + +   at   + + +
+ + Week Average: + + + + + +
+ + Week Change: + + + + + +
+ + Month Average: + + + + + +
+ + Month High: + + + +  on  + + +
+ + Month Low: + + + +  on  + + +
+ + Year Average: + + + + + +
+ + Year High: + + + +  on  + + +
+ + Year Low: + + + +  on  + + +
+ + All Time Average: + + + + + +
+ + All Time High: + + + +  on  + + +
+ + All Time Low: + + + +  on  + + +
+
  + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + Rate: + + + + + +
+ + Day: + + + + + +
+ + Storm: + + + + + +
+ + Storm Start: + + + + + +
+ + Day High Rate: + + + +   at   + + +
+ + Month: + + + + + +
+ + Month High Rate: + + + +  on  + + +
+ + Year (): + + + + + +
+ + Year High Rate: + + + +  on  + + +
+ + All Time High Rate: + + + +  on  + + +
+
+
+
+ + + + + + + + +
+ + + + + + wviewweather.com + + + + + +
+
+
+ + diff --git a/examples/html/chrome/standard/awekas_wl.htx-metric b/examples/html/chrome/standard/awekas_wl.htx-metric new file mode 100644 index 0000000..29b5be4 --- /dev/null +++ b/examples/html/chrome/standard/awekas_wl.htx-metric @@ -0,0 +1,32 @@ +AWEKAS_Template_start
+
+
+
+
+
+
+
+
+
+-
+-
+-
+-
+-
+-
+-
+
+
+
+
+
+
+
+
+
+
+w/m^2
+
+index
+Template_V1.5
+ diff --git a/examples/html/chrome/standard/awekas_wl.htx-us b/examples/html/chrome/standard/awekas_wl.htx-us new file mode 100644 index 0000000..917aa40 --- /dev/null +++ b/examples/html/chrome/standard/awekas_wl.htx-us @@ -0,0 +1,32 @@ +AWEKAS_Template_start
+
+
+
+
+
+
+
+
+
+-
+-
+-
+-
+-
+-
+-
+
+
+
+
+
+
+
+
+
+
+w/m^2
+
+index
+Template_V1.5
+ diff --git a/examples/html/chrome/standard/data.htx b/examples/html/chrome/standard/data.htx new file mode 100644 index 0000000..058b659 --- /dev/null +++ b/examples/html/chrome/standard/data.htx @@ -0,0 +1,7 @@ +Current Conditions as of : Temperature +Humidity Dewpoint +Wind at +Barometer Wind Chill +Heat Index +Today's Rain Storm Total +Monthly Total Yearly Total Current Rain Rate \ No newline at end of file diff --git a/examples/html/chrome/standard/graphics.conf b/examples/html/chrome/standard/graphics.conf new file mode 100644 index 0000000..b7553e5 --- /dev/null +++ b/examples/html/chrome/standard/graphics.conf @@ -0,0 +1,165 @@ +# +# +# This file contains configuration information for graphics. +# +# Note: For parameters that enable/disable a feature, a value of "0" disables +# the feature and "1" enables it... +# +# Color values are RRGGBBTT where TT is the transparency level. +# + +################################# BUCKETS ################################# + +# Transparent background (0/1)? +BUCKET_TRANSPARENT=1 + +# Background color for the bucket image +BUCKET_BG_COLOR=0xF5F5F500 + +# Color for the lines and ticks in the bucket plot +BUCKET_FG_COLOR=0x00000000 + +# Color for the filled region of the bucket +BUCKET_CONTENT_COLOR=0x4282B400 + +# Color for line indicating high value +BUCKET_HIGH_COLOR=0xFF000000 + +# Color for line indicating low value +BUCKET_LOW_COLOR=0x00BFFF00 + +# Background color for the title +BUCKET_TITLE_BG_COLOR=0xD8D8D800 + +# Foreground color for the title +BUCKET_TITLE_FG_COLOR=0x00000000 + +# Color of text labels +BUCKET_TEXT_COLOR=0x00000000 + +# Width of bucket image +BUCKET_IMAGE_WIDTH=120 + +# Height of bucket image +BUCKET_IMAGE_HEIGHT=240 + +# Width of bucket itself +BUCKET_WIDTH=36 + + +################################# LINE CHARTS ################################# + +# Transparent background for all charts (0/1)? +CHART_TRANSPARENT=1 + +# Background color for the chart image rectangle +CHART_IMAGE_BG_COLOR=0xF5F5F500 + +# Background color for the plot +CHART_GRAPH_BG_COLOR=0xD8D8D800 + +# Color of the grid lines +CHART_GRID_COLOR=0xA0A00000 + +# Color of line for primary data values +CHART_FIRST_LINE_COLOR=0x4282B400 + +# Color of line for secondary data values +CHART_SECOND_LINE_COLOR=0xB4424200 + +# Color of line for tertiary data values +CHART_THIRD_LINE_COLOR=0x42B44200 + +# Background color for title +CHART_TITLE_BG_COLOR=0xD8D8D800 + +# Foreground color for title +CHART_TITLE_FG_COLOR=0x00000000 + +# Color for text labels +CHART_TEXT_COLOR=0x00000000 + +# Width of image +CHART_WIDTH=300 + +# Height of image +CHART_HEIGHT=180 + + +################################# BAR CHARTS ################################# + +# Background color for the image +BAR_IMAGE_BG_COLOR=0xF5F5F500 + +# Background color for the plot +BAR_GRAPH_BG_COLOR=0xD8D8D800 + +# Color for the bars +BAR_BAR_COLOR=0x4282B400 + +# Background color for the title bar +BAR_TITLE_BG_COLOR=0xD8D8D800 + +# Foreground color for the title bar +BAR_TITLE_FG_COLOR=0x00000000 + +# Color for grid lines +BAR_GRID_COLOR=0xA0A00000 + +# Color for text labels +BAR_TEXT_COLOR=0x00000000 + +# Width of image +BAR_WIDTH=300 + +# Height of image +BAR_HEIGHT=180 + + +################################# DIAL PLOTS ################################# + +# Transparent background (0/1)? +DIAL_TRANSPARENT=1 + +# Background color for dial +DIAL_BG_COLOR=0xD8D8D800 + +# Background color for image (doesn't matter becasue this color is transparent) +DIAL_IMAGE_BG_COLOR=Ox12121200 + +# Color of circle at center of dial +DIAL_CENTER_COLOR=0x6666FF00 + +# Text color of current value in center of dial +DIAL_CENTER_TEXT_COLOR=0x0000ff00 + +# Text color of value for high wind in center of plot +DIAL_CENTER_HIGH_COLOR=0xff000000 + +# Fill color of pointer +DIAL_POINTER_COLOR=0x6666FF00 + +# Outline color of pointer +DIAL_POINTER_OUTLINE_COLOR=0x6666ff00 + +# Color of tick for high value +DIAL_HIGH_COLOR=0xff000000 + +# Color of tick for low value +DIAL_LOW_COLOR=0x0000ff00 + +# Text color for high value on Temperature plot +DIAL_APP_COLOR=0x80808000 + +# Text color of labels +DIAL_TEXT_COLOR=0x00000000 + +# Width of image +DIAL_IMAGE_WIDTH=160 + +# Diameter of dial +DIAL_DIAMETER=156 + +# Diameter of circle at center of dial +DIAL_CTR_DIAMETER=15 + diff --git a/examples/html/chrome/standard/header-big.incx b/examples/html/chrome/standard/header-big.incx new file mode 100644 index 0000000..e969430 --- /dev/null +++ b/examples/html/chrome/standard/header-big.incx @@ -0,0 +1,43 @@ + + + + + + +
+ + + + + + + + + + + + + + + +
+ + , Weather +
+ + - - + +
+ + + +
+ + Sunrise: - Midday: - Sunset: - + Day Length:
+ Civil Twilight: -    + Astronomical Twilight: - +

+
+
+ diff --git a/examples/html/chrome/standard/header.incx b/examples/html/chrome/standard/header.incx new file mode 100644 index 0000000..f34547b --- /dev/null +++ b/examples/html/chrome/standard/header.incx @@ -0,0 +1,40 @@ + + + + + + +
+ + + + + + + + + + + + + + + +
+ + , Weather +
+ + - - + +
+ + + +
+ + Sunrise: - Sunset: - Moon: + +
+
+ diff --git a/examples/html/chrome/standard/html-templates.conf b/examples/html/chrome/standard/html-templates.conf new file mode 100644 index 0000000..9a83a3f --- /dev/null +++ b/examples/html/chrome/standard/html-templates.conf @@ -0,0 +1,76 @@ +# +# html-templates-classic.conf - define the HTML template files to use for HTML +# and generic generation +# +# Note: Template expansion is performed in the order the files are listed +# in this file. Thus, files to be included in other templates should +# be listed before the templates that include them. + +# File Format +# +# 1) lines beginning with '#' or are ignored +# + +# Column Format +# +# 1) template file name (must be in /etc/wview/html) +# + +############################################################################### +### Standard Include Macros (should be generated first) +############################################################################### + +header.incx +header-big.incx +nav-buttons.incx +readings.incx + +############################################################################### +### RSS Feed XML Template +############################################################################### + +### Standard Weather RSS feed template - file generated is wxrss.xml and can +### be added to any html template to add an RSS feed to that page +wxrss.xtx + + +############################################################################### +### Home Page Templates +############################################################################### + +### Standard Home Page template +#index.htx + +### Day/Night Home Pages - if you want a different homepage template based on +### day or night, uncomment these and copy your templates to +### /etc/wview/html/index-day.htx and /etc/wview/html/index-night.htx +### respectively +index-day.htx +index-night.htx + + +############################################################################### +### Historical Data Templates +############################################################################### + +### Standard (no extra sensors) Weather Data +almanac.htx +Current.htx +Daily.htx +Weekly.htx +Monthly.htx +Yearly.htx +parameterlist.htx + +############################################################################### +### Current Conditions (Table Only) Template +############################################################################### + +#Current_Conditions.htx + +############################################################################### +### AWEKAS Data Submission Template +############################################################################### + +#awekas_wl.htx + diff --git a/examples/html/chrome/standard/images-metric.conf b/examples/html/chrome/standard/images-metric.conf new file mode 100755 index 0000000..1beab44 --- /dev/null +++ b/examples/html/chrome/standard/images-metric.conf @@ -0,0 +1,231 @@ +# +# images-metric.conf - configure what images wview will generate +# + +# File Format +# +# -> lines beginning with '#' or are ignored +# -> columns are whitespace delimited +# + +# Column Format +# +# 1) image filename +# 2) image label (can be translated as desired) +# 3) image unit label (can be translated as desired) +# 4) decimal places +# 5) image generator function index (see images.c) +# + +# Enabling/Disabling Image Generation +# +# -> To enable the generation of an image, uncomment it (remove any '#' + characters in front of the image definition) +# -> To disable the generation of an image, comment it out by placing one or + more '#' characters in front of it + + +################# D I A L S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +tempdial.png "Temp" C 0 109 +humiddial.png "Humidity" % 0 110 +wind.png "Wind" none 0 11 + + +################# B U C K E T S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +temp.png "Temperature" C 1 0 +humid.png "Humidity" % 1 1 +dew.png "Dewpoint" C 1 2 +wchill.png "Wind Chill" C 1 3 +hindex.png "Heat Index" C 1 4 +barom.png "Barometer" hPa 1 5 +dayrain.png "Day Rain" mm 1 6 +stormrain.png "Storm Rain" mm 1 7 +rainrate.png "Rain Rate" mm/hour 1 8 +monthrain.png "Month Rain" mm 1 9 +yearrain.png "Year Rain" mm 1 10 + + +################# C H A R T S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +#### DAY VALUES #### +tempdaycomp.png "Temperature/Dewpoint" C 0 42 +heatchillcomp.png "Wind Chill/Heat Index" C 0 43 +## intempdaycomp.png "Inside Temp/Inside Humidity" C/% 0 115 +humidday.png "Humidity" % 0 15 +wspeeddaycomp.png "Wind/Gust" km/h 0 137 +## wspeedday.png "Wind" km/h 0 21 +wdirday.png "Wind DIR" degrees 0 24 +## hiwspeedday.png "HI Wind" km/h 0 27 +baromday.png "Barometer" hPa 0 30 + +## tempday.png "Temperature" C 0 12 +## dewday.png "Dewpoint" C 0 18 +## wchillday.png "Wind Chill" C 0 36 +## hindexday.png "Heat Index" C 0 39 + +#### WEEK VALUES #### +tempweekcomp.png "Temperature/Dewpoint" C 0 127 +heatchillweekcomp.png "Wind Chill/Heat Index" C 0 128 +## intempweekcomp.png "Inside Temp/Inside Humidity" C/% 0 129 +humidweek.png "Humidity (Hourly Avg)" % 0 118 +wspeedweekcomp.png "Wind/Gust" km/h 0 138 +## wspeedweek.png "Wind (Hourly Avg)" km/h 0 120 +wdirweek.png "Wind DIR (Hourly Avg)" degrees 0 121 +## hiwspeedweek.png "HI Wind (Hourly Avg)" km/h 0 122 +baromweek.png "Barometer (Hourly Avg)" hPa 0 123 + +## tempweek.png "Temperature (Hourly Avg)" C 0 117 +## dewweek.png "Dewpoint (Hourly Avg)" C 0 119 +## wchillweek.png "Wind Chill (Hourly Avg)" C 0 125 +## hindexweek.png "Heat Index (Hourly Avg)" C 0 126 + +#### MONTH VALUES #### +tempmonthcomp.png "Temperature/Dewpoint" C 0 44 +heatchillmonthcomp.png "Wind Chill/Heat Index" C 0 45 +## intempmonthcomp.png "Inside Temp/Inside Humidity" C/% 0 116 +humidmonth.png "Humidity (Hourly Avg)" % 0 16 +wspeedmonthcomp.png "Wind/Gust" km/h 0 139 +## wspeedmonth.png "Wind (Hourly Avg)" km/h 0 22 +wdirmonth.png "Wind DIR (Hourly Avg)" degrees 0 25 +## hiwspeedmonth.png "HI Wind (Hourly Avg)" km/h 0 28 +barommonth.png "Barometer (Hourly Avg)" hPa 0 31 + +## tempmonth.png "Temperature (Hourly Avg)" C 0 13 +## dewmonth.png "Dewpoint (Hourly Avg)" C 0 19 +## wchillmonth.png "Wind Chill (Hourly Avg)" C 0 37 +## hindexmonth.png "Heat Index (Hourly Avg)" C 0 40 + +#### YEAR VALUES #### +tempyear.png "Temperature (Daily Avg)" C 0 14 +humidyear.png "Humidity (Daily Avg)" % 0 17 +dewyear.png "Dewpoint (Daily Avg)" C 0 20 +wspeedyear.png "Wind (Daily Avg)" km/h 0 23 +wdiryear.png "Wind DIR (Daily Avg)" degrees 0 26 +hiwspeedyear.png "HI Wind (Daily Avg)" km/h 0 29 +baromyear.png "Barometer (Daily Avg)" hPa 0 32 +wchillyear.png "Wind Chill (Daily Avg)" C 0 38 +hindexyear.png "Heat Index (Daily Avg)" C 0 41 + + +################# B A R C H A R T S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +rainday.png "Rain/hour" mm 0 33 +rainweek.png "Rain/day" mm 0 124 +rainmonth.png "Rain/day" mm 0 34 +rainyear.png "Rain/week" mm 0 35 + + + +################# W I N D R O S E S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +windroseday.png "Rose Day" none 0 133 +windroseweek.png "Rose Week" none 0 134 +windrosemonth.png "Rose Month" none 0 135 +windroseyear.png "Rose Year" none 0 136 + + +################# V P P L U S D A T A B U C K E T S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +## UV.png "UV Index" index 1 106 +## radiation.png "Radiation" watts/m^2 1 107 +## ET.png "ET (day)" mm 1 108 + + +################# V P P L U S D A T A C H A R T S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +## radiationDay.png "SolarRad" watts 0 46 +## radiationWeek.png "SolarRad (Hourly Avg)" watts 0 130 +## radiationMonth.png "SolarRad (Hourly Avg)" watts 0 47 +## radiationYear.png "SolarRad (Daily Avg)" watts 0 48 +## UVDay.png "UV Index" index 0 49 +## UVWeek.png "UV Index (Hourly Avg)" index 0 131 +## UVMonth.png "UV Index (Hourly Avg)" index 0 50 +## UVYear.png "UV Index (Daily Avg)" index 0 51 +## ETDay.png "EvaPot (Hourly)" mm/hr 1 52 +## ETWeek.png "EvaPot (Daily)" mm/day 0 132 +## ETMonth.png "EvaPot (Daily)" mm/day 0 53 +## ETYear.png "EvaPot (Weekly)" mm/week 0 54 +## leafTemp1Day.png "LeafTemp1" C 0 55 +## leafTemp1Month.png "LeafTemp1 (Hourly Avg)" C 0 56 +## leafTemp1Year.png "LeafTemp1 (Daily Avg)" C 0 57 +## leafTemp2Day.png "LeafTemp2" C 0 58 +## leafTemp2Month.png "LeafTemp2 (Hourly Avg)" C 0 59 +## leafTemp2Year.png "LeafTemp2 (Daily Avg)" C 0 60 +## leafWetness1Day.png "LeafWet1 Index" (0-15) 0 61 +## leafWetness1Month.png "LeafWet1 Index (Hourly Avg)" (0-15) 0 62 +## leafWetness1Year.png "LeafWet1 Index (Daily Avg)" (0-15) 0 63 +## leafWetness2Day.png "LeafWet2 Index" (0-15) 0 64 +## leafWetness2Month.png "LeafWet2 Index (Hourly Avg)" (0-15) 0 65 +## leafWetness2Year.png "LeafWet2 Index (Daily Avg)" (0-15) 0 66 +## soilTemp1Day.png "SoilTemp1" C 0 67 +## soilTemp1Month.png "SoilTemp1 (Hourly Avg)" C 0 68 +## soilTemp1Year.png "SoilTemp1 (Daily Avg)" C 0 69 +## soilTemp2Day.png "SoilTemp2" C 0 70 +## soilTemp2Month.png "SoilTemp2 (Hourly Avg)" C 0 71 +## soilTemp2Year.png "SoilTemp2 (Daily Avg)" C 0 72 +## soilTemp3Day.png "SoilTemp3" C 0 73 +## soilTemp3Month.png "SoilTemp3 (Hourly Avg)" C 0 74 +## soilTemp3Year.png "SoilTemp3 (Daily Avg)" C 0 75 +## soilTemp4Day.png "SoilTemp4" C 0 76 +## soilTemp4Month.png "SoilTemp4 (Hourly Avg)" C 0 77 +## soilTemp4Year.png "SoilTemp4 (Daily Avg)" C 0 78 +## extraHumid1Day.png "ExtraHumidity1" % 0 79 +## extraHumid1Month.png "ExtraHumidity1 (Hourly Avg)" % 0 80 +## extraHumid1Year.png "ExtraHumidity1 (Daily Avg)" % 0 81 +## extraHumid2Day.png "ExtraHumidity2" % 0 82 +## extraHumid2Month.png "ExtraHumidity2 (Hourly Avg)" % 0 83 +## extraHumid2Year.png "ExtraHumidity2 (Daily Avg)" % 0 84 +## extraTemp1Day.png "ExtraTemp1" C 0 85 +## extraTemp1Month.png "ExtraTemp1 (Hourly Avg)" C 0 86 +## extraTemp1Year.png "ExtraTemp1 (Daily Avg)" C 0 87 +## extraTemp2Day.png "ExtraTemp2" C 0 88 +## extraTemp2Month.png "ExtraTemp2 (Hourly Avg)" C 0 89 +## extraTemp2Year.png "ExtraTemp2 (Daily Avg)" C 0 90 +## extraTemp3Day.png "ExtraTemp3" C 0 91 +## extraTemp3Month.png "ExtraTemp3 (Hourly Avg)" C 0 92 +## extraTemp3Year.png "ExtraTemp3 (Daily Avg)" C 0 93 +## soilMoist1Day.png "SoilMoist1" cb 0 94 +## soilMoist1Month.png "SoilMoist1 (Hourly Avg)" cb 0 95 +## soilMoist1Year.png "SoilMoist1 (Daily Avg)" cb 0 96 +## soilMoist2Day.png "SoilMoist2" cb 0 97 +## soilMoist2Month.png "SoilMoist2 (Hourly Avg)" cb 0 98 +## soilMoist2Year.png "SoilMoist2 (Daily Avg)" cb 0 99 +## soilMoist3Day.png "SoilMoist3" cb 0 100 +## soilMoist3Month.png "SoilMoist3 (Hourly Avg)" cb 0 101 +## soilMoist3Year.png "SoilMoist3 (Daily Avg)" cb 0 102 +## soilMoist4Day.png "SoilMoist4" cb 0 103 +## soilMoist4Month.png "SoilMoist4 (Hourly Avg)" cb 0 104 +## soilMoist4Year.png "SoilMoist4 (Daily Avg)" cb 0 105 + + +################# V P P L U S D I A L S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +netRainDay.png "Precip Day" mm 0 112 +netRainMonth.png "Precip Month" mm 0 113 +netRainYear.png "Precip Year" mm 0 114 + + +################# D I A G N O S T I C S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +#rxcheck.png "RX Good Packets" % 0 111 + diff --git a/examples/html/chrome/standard/images-user.conf b/examples/html/chrome/standard/images-user.conf new file mode 100644 index 0000000..04b6a1c --- /dev/null +++ b/examples/html/chrome/standard/images-user.conf @@ -0,0 +1,20 @@ +# +# images-user.conf - configure what user-defined images wview will generate +# + +# File Format +# +# 1) lines beginning with '#' or are ignored +# 2) whitespace delimited columns +# + +# Column Format +# +# 1) image filename +# 2) image label +# 3) image unit label +# 4) decimal places +# 5) image generator function index (see images-user.c) +# + +##intemp.png "In Temperature" degrees 1 0 diff --git a/examples/html/chrome/standard/images.conf b/examples/html/chrome/standard/images.conf new file mode 100755 index 0000000..6e840b5 --- /dev/null +++ b/examples/html/chrome/standard/images.conf @@ -0,0 +1,224 @@ +# +# images.conf - configure what images wview will generate +# + +# File Format +# +# -> lines beginning with '#' or are ignored +# -> columns are whitespace delimited +# + +# Column Format +# +# 1) image filename +# 2) image label (can be translated as desired) +# 3) image units label (can be translated as desired) +# 4) decimal places +# 5) image generator function index (see images.c) +# + +# Enabling/Disabling Image Generation +# +# -> To enable the generation of an image, uncomment it (remove any '#' + characters in front of the image definition) +# -> To disable the generation of an image, comment it out by placing one or + more '#' characters in front of it + + +################# D I A L S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +tempdial.png "Temp" F 0 109 +humiddial.png "Humidity" none 0 110 +wind.png "Wind" none 0 11 +netRainDay.png "Precip Day" in 0 112 +netRainMonth.png "Precip Month" in 0 113 +netRainYear.png "Precip Year" in 0 114 + + +################# B U C K E T S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +temp.png "Temperature" F 1 0 +humid.png "Humidity" % 1 1 +dew.png "Dewpoint" F 1 2 +wchill.png "Wind Chill" F 1 3 +hindex.png "Heat Index" F 1 4 +barom.png "Barometer" inHg 2 5 +dayrain.png "Day Rain" inches 2 6 +stormrain.png "Storm Rain" inches 2 7 +rainrate.png "Rain Rate" in/hour 2 8 +monthrain.png "Month Rain" inches 2 9 +yearrain.png "Year Rain" inches 2 10 + + +################# C H A R T S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +#### DAY VALUES #### +tempdaycomp.png "Temperature/Dewpoint" F 0 42 +heatchillcomp.png "Wind Chill/Heat Index" F 0 43 +## intempdaycomp.png "Inside Temp/Inside Humidity" F/% 0 115 +humidday.png "Humidity" % 0 15 +wspeeddaycomp.png "Wind/Gust" mph 0 137 +## wspeedday.png "Wind" mph 0 21 +wdirday.png "Wind DIR" degrees 0 24 +## hiwspeedday.png "HI Wind" mph 0 27 +baromday.png "Barometer" inHg 1 30 + +## tempday.png "Temperature" F 0 12 +## dewday.png "Dewpoint" F 0 18 +## wchillday.png "Wind Chill" F 0 36 +## hindexday.png "Heat Index" F 0 39 + +#### WEEK VALUES #### +tempweekcomp.png "Temperature/Dewpoint" F 0 127 +heatchillweekcomp.png "Wind Chill/Heat Index" F 0 128 +## intempweekcomp.png "Inside Temp/Inside Humidity" F/% 0 129 +humidweek.png "Humidity (Hourly Avg)" % 0 118 +wspeedweekcomp.png "Wind/Gust" mph 0 138 +## wspeedweek.png "Wind (Hourly Avg)" mph 0 120 +wdirweek.png "Wind DIR (Hourly Avg)" degrees 0 121 +## hiwspeedweek.png "HI Wind (Hourly Avg)" mph 0 122 +baromweek.png "Barometer (Hourly Avg)" inHg 1 123 + +## tempweek.png "Temperature (Hourly Avg)" F 0 117 +## dewweek.png "Dewpoint (Hourly Avg)" F 0 119 +## wchillweek.png "Wind Chill (Hourly Avg)" F 0 125 +## hindexweek.png "Heat Index (Hourly Avg)" F 0 126 + +#### MONTH VALUES #### +tempmonthcomp.png "Temperature/Dewpoint" F 0 44 +heatchillmonthcomp.png "Wind Chill/Heat Index" F 0 45 +## intempmonthcomp.png "Inside Temp/Inside Humidity" deg/% 0 116 +humidmonth.png "Humidity (Hourly Avg)" % 0 16 +wspeedmonthcomp.png "Wind/Gust" mph 0 139 +## wspeedmonth.png "Wind (Hourly Avg)" mph 0 22 +wdirmonth.png "Wind DIR (Hourly Avg)" degrees 0 25 +## hiwspeedmonth.png "HI Wind (Hourly Avg)" mph 0 28 +barommonth.png "Barometer (Hourly Avg)" inHg 1 31 + +## tempmonth.png "Temperature (Hourly Avg)" F 0 13 +## dewmonth.png "Dewpoint (Hourly Avg)" F 0 19 +## wchillmonth.png "Wind Chill (Hourly Avg)" F 0 37 +## hindexmonth.png "Heat Index (Hourly Avg)" F 0 40 + +#### YEAR VALUES #### +tempyear.png "Temperature (Daily Avg)" F 0 14 +humidyear.png "Humidity (Daily Avg)" % 0 17 +dewyear.png "Dewpoint (Daily Avg)" F 0 20 +wspeedyear.png "Wind (Daily Avg)" mph 0 23 +wdiryear.png "Wind DIR (Daily Avg)" degrees 0 26 +hiwspeedyear.png "HI Wind (Daily Avg)" mph 0 29 +baromyear.png "Barometer (Daily Avg)" inHg 1 32 +wchillyear.png "Wind Chill (Daily Avg)" F 0 38 +hindexyear.png "Heat Index (Daily Avg)" F 0 41 + + +################# B A R C H A R T S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +rainday.png "Rain/hour" inches 2 33 +rainweek.png "Rain/day" inches 1 124 +rainmonth.png "Rain/day" inches 1 34 +rainyear.png "Rain/week" inches 1 35 + + +################# W I N D R O S E S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +windroseday.png "Rose Day" none 0 133 +windroseweek.png "Rose Week" none 0 134 +windrosemonth.png "Rose Month" none 0 135 +windroseyear.png "Rose Year" none 0 136 + + +################# V P P L U S D A T A B U C K E T S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +## UV.png "UV Index" index 1 106 +## radiation.png "Radiation" watts/m^2 1 107 +## ET.png "ET (day)" inches 3 108 + + +################# V P P L U S D A T A C H A R T S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +## radiationDay.png "SolarRad" watts 0 46 +## radiationWeek.png "SolarRad (Hourly Avg)" watts 0 130 +## radiationMonth.png "SolarRad (Hourly Avg)" watts 0 47 +## radiationYear.png "SolarRad (Daily Avg)" watts 0 48 +## UVDay.png "UV Index" index 0 49 +## UVWeek.png "UV Index (Hourly Avg)" index 0 131 +## UVMonth.png "UV Index (Hourly Avg)" index 0 50 +## UVYear.png "UV Index (Daily Avg)" index 0 51 +## ETDay.png "EvaPot (Hourly)" inches/hr 2 52 +## ETWeek.png "EvaPot (Daily)" inches/day 2 132 +## ETMonth.png "EvaPot (Daily)" inches/day 2 53 +## ETYear.png "EvaPot (Weekly)" inches/week 2 54 +## leafTemp1Day.png "LeafTemp1" F 0 55 +## leafTemp1Month.png "LeafTemp1 (Hourly Avg)" F 0 56 +## leafTemp1Year.png "LeafTemp1 (Daily Avg)" F 0 57 +## leafTemp2Day.png "LeafTemp2" F 0 58 +## leafTemp2Month.png "LeafTemp2 (Hourly Avg)" F 0 59 +## leafTemp2Year.png "LeafTemp2 (Daily Avg)" F 0 60 +## leafWetness1Day.png "LeafWet1 Index" (0-15) 0 61 +## leafWetness1Month.png "LeafWet1 Index (Hourly Avg)" (0-15) 0 62 +## leafWetness1Year.png "LeafWet1 Index (Daily Avg)" (0-15) 0 63 +## leafWetness2Day.png "LeafWet2 Index" (0-15) 0 64 +## leafWetness2Month.png "LeafWet2 Index (Hourly Avg)" (0-15) 0 65 +## leafWetness2Year.png "LeafWet2 Index (Daily Avg)" (0-15) 0 66 +## soilTemp1Day.png "SoilTemp1" F 0 67 +## soilTemp1Month.png "SoilTemp1 (Hourly Avg)" F 0 68 +## soilTemp1Year.png "SoilTemp1 (Daily Avg)" F 0 69 +## soilTemp2Day.png "SoilTemp2" F 0 70 +## soilTemp2Month.png "SoilTemp2 (Hourly Avg)" F 0 71 +## soilTemp2Year.png "SoilTemp2 (Daily Avg)" F 0 72 +## soilTemp3Day.png "SoilTemp3" F 0 73 +## soilTemp3Month.png "SoilTemp3 (Hourly Avg)" F 0 74 +## soilTemp3Year.png "SoilTemp3 (Daily Avg)" F 0 75 +## soilTemp4Day.png "SoilTemp4" F 0 76 +## soilTemp4Month.png "SoilTemp4 (Hourly Avg)" F 0 77 +## soilTemp4Year.png "SoilTemp4 (Daily Avg)" F 0 78 +## extraHumid1Day.png "ExtraHumidity1" % 0 79 +## extraHumid1Month.png "ExtraHumidity1 (Hourly Avg)" % 0 80 +## extraHumid1Year.png "ExtraHumidity1 (Daily Avg)" % 0 81 +## extraHumid2Day.png "ExtraHumidity2" % 0 82 +## extraHumid2Month.png "ExtraHumidity2 (Hourly Avg)" % 0 83 +## extraHumid2Year.png "ExtraHumidity2 (Daily Avg)" % 0 84 +## extraTemp1Day.png "ExtraTemp1" F 0 85 +## extraTemp1Month.png "ExtraTemp1 (Hourly Avg)" F 0 86 +## extraTemp1Year.png "ExtraTemp1 (Daily Avg)" F 0 87 +## extraTemp2Day.png "ExtraTemp2" F 0 88 +## extraTemp2Month.png "ExtraTemp2 (Hourly Avg)" F 0 89 +## extraTemp2Year.png "ExtraTemp2 (Daily Avg)" F 0 90 +## extraTemp3Day.png "ExtraTemp3" F 0 91 +## extraTemp3Month.png "ExtraTemp3 (Hourly Avg)" F 0 92 +## extraTemp3Year.png "ExtraTemp3 (Daily Avg)" F 0 93 +## soilMoist1Day.png "SoilMoist1" cbars 0 94 +## soilMoist1Month.png "SoilMoist1 (Hourly Avg)" cbars 0 95 +## soilMoist1Year.png "SoilMoist1 (Daily Avg)" cbars 0 96 +## soilMoist2Day.png "SoilMoist2" cbars 0 97 +## soilMoist2Month.png "SoilMoist2 (Hourly Avg)" cbars 0 98 +## soilMoist2Year.png "SoilMoist2 (Daily Avg)" cbars 0 99 +## soilMoist3Day.png "SoilMoist3" cbars 0 100 +## soilMoist3Month.png "SoilMoist3 (Hourly Avg)" cbars 0 101 +## soilMoist3Year.png "SoilMoist3 (Daily Avg)" cbars 0 102 +## soilMoist4Day.png "SoilMoist4" cbars 0 103 +## soilMoist4Month.png "SoilMoist4 (Hourly Avg)" cbars 0 104 +## soilMoist4Year.png "SoilMoist4 (Daily Avg)" cbars 0 105 + + +################# D I A G N O S T I C S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +#rxcheck.png "RX Good Packets" % 0 111 + diff --git a/examples/html/chrome/standard/index-day.htx b/examples/html/chrome/standard/index-day.htx new file mode 100644 index 0000000..d771862 --- /dev/null +++ b/examples/html/chrome/standard/index-day.htx @@ -0,0 +1,378 @@ + + + + <!--stationCity-->,<!--stationState--> Weather + + + + + + + + +
+ + + + + + + + + + +
+ + + + + + + +
+ + + + + + + + + + +
+ +
+
+ + RADAR + +
+
+
+ + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + + + + + + + +
+ + + + + + + +
+ + + +
+
+ + + + + + + + + + + + + + + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+
+ + + + + + + +
+ + + +
+
+
+
+
+
+ + + + + + + + + + + +
+ + + + + +
+
+ + + + + + + + + +
+ + Climatological Summaries:  + + + + + +
+ + Browse Archive Records:  + + + +
+
+
+
+
+ + + + + + + + +
+ + + + + + wviewweather.com + + + +   + +
+
+
+ + diff --git a/examples/html/chrome/standard/index-night.htx b/examples/html/chrome/standard/index-night.htx new file mode 100644 index 0000000..0ad810a --- /dev/null +++ b/examples/html/chrome/standard/index-night.htx @@ -0,0 +1,378 @@ + + + + <!--stationCity-->,<!--stationState--> Weather + + + + + + + + +
+ + + + + + + + + + +
+ + + + + + + +
+ + + + + + + + + + +
+ +
+
+ + RADAR + +
+
+
+ + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + + + + + + + +
+ + + + + + + +
+ + + +
+
+ + + + + + + + + + + + + + + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+
+ + + + + + + +
+ + + +
+
+
+
+
+
+ + + + + + + + + + + +
+ + + + + +
+
+ + + + + + + + + +
+ + Climatological Summaries:  + + + + + +
+ + Browse Archive Records:  + + + +
+
+
+
+
+ + + + + + + + +
+ + + + + + wviewweather.com + + + +   + +
+
+
+ + diff --git a/examples/html/chrome/standard/index.htx b/examples/html/chrome/standard/index.htx new file mode 100644 index 0000000..c1bc2f7 --- /dev/null +++ b/examples/html/chrome/standard/index.htx @@ -0,0 +1,400 @@ + + + + <!--stationCity-->,<!--stationState--> Weather + + + + + + + + +
+ + + + + + + + + + +
+ + + + + + + +
+ + + + + + + + + + +
+ +
+
+ + RADAR + +
+
+
+ + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + + + + + + + + + + +
+ + + + + + + +
+ + + +
+
+ + + + + + + + + + + + + + + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+
+ + + + + + + + +
+ + + + + +
+
+ + + + + + + +
+ + + +
+
+
+
+
+
+ + + + + + + + + + + +
+ + + + + +
+
+ + + + + + + + + +
+ + Climatological Summaries:  + + + + + +
+ + Browse Archive Records:  + + + +
+
+
+
+
+ + + + + + + + +
+ + + + + + wviewweather.com + + + +   + +
+
+
+ + diff --git a/examples/html/chrome/standard/nav-buttons.incx b/examples/html/chrome/standard/nav-buttons.incx new file mode 100644 index 0000000..da5e454 --- /dev/null +++ b/examples/html/chrome/standard/nav-buttons.incx @@ -0,0 +1,14 @@ + + + + +
+ + + + + + + +
+ diff --git a/examples/html/chrome/standard/post-generate.sh b/examples/html/chrome/standard/post-generate.sh new file mode 100644 index 0000000..b5426c3 --- /dev/null +++ b/examples/html/chrome/standard/post-generate.sh @@ -0,0 +1,8 @@ +#/bin/sh + +# Add any post template generation processing or utilities here. Keep in mind this +# script runs in the htmlgend process's context so don't add anything that is overly +# time constrained. + +exit 0 + diff --git a/examples/html/chrome/standard/pre-generate.sh b/examples/html/chrome/standard/pre-generate.sh new file mode 100644 index 0000000..0c0d1cb --- /dev/null +++ b/examples/html/chrome/standard/pre-generate.sh @@ -0,0 +1,8 @@ +#/bin/sh + +# Add any pre template generation processing or utilities here. Keep in mind this +# script runs in the htmlgend process's context so don't add anything that is overly +# time constrained. + +exit 0 + diff --git a/examples/html/chrome/standard/readings.incx b/examples/html/chrome/standard/readings.incx new file mode 100644 index 0000000..a0e712c --- /dev/null +++ b/examples/html/chrome/standard/readings.incx @@ -0,0 +1,386 @@ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + Temperature: + + + + + +
+ + Wind Chill: + + + + + +
+ + Heat Index: + + + + + +
+ + Apparent Temp: + + + + + +
+ + Wet Bulb Temp: + + + + + +
+ + Dewpoint: + + + + + +
+ + Humidity: + + + + + +
+ + Barometer: + + + +    + +
+ + Wind: + + + +   at   +   + +
+ + High Wind: + + + + at + + +
+ + Recent Avg Wind: + + + + + +
+ + Recent Beaufort Scale: + + + + + +
+ + Today's Rain: + + + +   + +
+ + Rain Rate: + + + +   + +
+ + High Rain Rate: + + + + at + + +
+ + Storm Total: + + + +   + +
+ + Monthly Rain: + + + +   + +
+ + Yearly Rain (): + + + +   + +
+ + Air Density: + + + +   + +
+ + Est. Cumulus Base: + + + +   + +
+ + High Temperature: + + + + at + + +
+ + Low Temperature: + + + + at + + +
+ + High Heat Index: + + + + at + + +
+ + Low Wind Chill: + + + + at + + +
+ + High Humidity: + + + + at + + +
+ + Low Humidity: + + + + at + + +
+ + High Dewpoint: + + + + at + + +
+ + Low Dewpoint: + + + + at + + +
+ + High Barometer: + + + + at + + +
+ + Low Barometer: + + + + at + + +
+
+ diff --git a/examples/html/chrome/standard/wxrss.xtx b/examples/html/chrome/standard/wxrss.xtx new file mode 100644 index 0000000..423afed --- /dev/null +++ b/examples/html/chrome/standard/wxrss.xtx @@ -0,0 +1,39 @@ + + + + + <!--stationCity--> <!--stationState--> + Insert_Your_WX_HomePage_URL_Here + Current Weather Conditions + en-us + , + 1 + + + + <!--stationCity-->,<!--stationState--> Weather Conditions: <!--stationDate--> <!--stationTime--> + Insert_Your_WX_HomePage_URL_Here + Current Weather Conditions + , + Insert_Your_WX_HomePage_URL_Here + - + + Temp:
+ Wind Chill:
+ Heat Index:
+ Humidity:
+ Dewpoint:
+ Barometer:
+ Wind: at
+ Rain Today:
+ Rain Rate:
+

+ ]]>
+
+
+
diff --git a/examples/html/chrome/static/Clouds-blue.jpg b/examples/html/chrome/static/Clouds-blue.jpg new file mode 100755 index 0000000000000000000000000000000000000000..13dab762949e42b0b7f1dedc328b311d2102251b GIT binary patch literal 6068 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 R#Hv@v_MP&-T=|82AbA?B)!BKmY*RIbeGR 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 literal 0 HcmV?d00001 diff --git a/examples/html/chrome/static/above-clouds.jpg b/examples/html/chrome/static/above-clouds.jpg new file mode 100755 index 0000000000000000000000000000000000000000..ecdebe5576a84825660689871721fb2ce20a0e36 GIT binary patch literal 3862 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 literal 0 HcmV?d00001 diff --git a/examples/html/chrome/static/bucket_bg.png b/examples/html/chrome/static/bucket_bg.png new file mode 100644 index 0000000000000000000000000000000000000000..65b293419e2d22c3a8db7bd667cbf5745b218ef5 GIT binary patch literal 13032 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 DfsCCx literal 0 HcmV?d00001 diff --git a/examples/html/chrome/static/fc-icon-partlycloudy.gif b/examples/html/chrome/static/fc-icon-partlycloudy.gif new file mode 100755 index 0000000000000000000000000000000000000000..b703c7e502d391a82487eac3830a5933b8fe2ee2 GIT binary patch literal 2086 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 literal 0 HcmV?d00001 diff --git a/examples/html/chrome/static/fc-icon-partlycloudyandsnow.gif b/examples/html/chrome/static/fc-icon-partlycloudyandsnow.gif new file mode 100755 index 0000000000000000000000000000000000000000..8198c8b5453c870f63611a69f2009ebf2fd9d107 GIT binary patch literal 2299 zcmchW{Z|uL8pm&5i4b6r0FfeO01*%|v7q4UZipOSQdFE34`SdICu40{Ay^G~03d`qoldXUV;ELaQerR|j7Fo$WHOu0R;v}qal7445QM|w za5|kXmy0AxilS(mW*Eln^|CC>aU9R{3{6|jX2xh>%qABCXiP`xG^7S4QG|0jSqDKX z0j<|?6iK3hLV-`r|0k#CNCe&NvlaQsT|&nh@3qI3sWncFqJK&#@-JnSCl9XGQGBOx~g6>?nsLoE305;C)RRco)VK z2yX|RZ*XdO0$9yPNa}c3CUgLXa_}w<=Rlwn;T-@v0nd3MV}>LOT?phT$T}g7K}rKj z1iAoZ43O4CS_dfG3eD(zEnI7IS1qj$eN&62YHI23Et+RHmh21)W=cjzz^nrU>pDBSKanQ^SdXfRrR zFAMzNE9K{nKmP?FgFqmQ${XtSG=XUJ-O-`GTdjd%LYY8yKMEv*%ID%s19whHpFDi> zuuSwxO7|LjVq9+NBAeO}IafICADWh2pa_ZmUep#}vCc68Cj0LX1h37Xb7$oRqMO<$ z+or@V+mhC1*et`+bB|m0JRqjZ`tJoqtRY5g(V32*h+myg>x>;-(T@J+<>ILRY*|+_ zRytz);j`+gRR!(ppFFQ+vf$bukA?{vH+Cvl?Mci3V)>Syuj^jTWkzjm-lo)#9Q8+9*|Y z-WijGqzzew{wZTXJXciO5L1&Ej~um?{$c3{e<2q(h9_48UAZ)JTZ4FUn$0bcDyy|Z z`5>9;?0gInO1%$$0sQgS!T4IHxK25X%8N?A|T(N}#{F;5?KKK*Uo!P>jm!navcYlJ(s zDOo+l_=r62$U;u^VR1hdRikQbi!!~M9k@!8no)^hi)DH=5mQZh&OF=NjQl!%p(VO% zB1E>-^53$zRaY;0q|sF~9$Eb9LabplL_6;u&)qnB*5fyz)&*L~shsHA2^3p;&FP^c z+e;g(E`@iuCe#O|m41mMSoJ&-T|M8Z&?uNC(T9tjU(g>_3Tq}rTVUxcQJ~uG7Fq+m zmioo&dJ<+G}*duzu&^o#CLbi5E}vpZf-$`e%UngZ1GD#>@dEQ3*-9~O#& zDiq(hMAy|a(&(OIZG*Tjr}|2L?{fF7U?fKMepAp;?$Gpws#q#0`v@wTyZrUV*68EK z>ISadFZG(RKlD#?mk&h-$BD^Rn|3Yl8FV)UY#sqkw_2`GQL&0QFE&icQ}ss#rv0ag z*_qhnzT+E~{ot13W7Y?=?y@eG2&l@Ay-buH88#>ePCdF%REKOor~^~_){IA;?6UUm zvx;Y{oBIxYEVPLJF;Cs`?79BDXzaXQSm4(%(``%dY-^OmfWK)j}Ya&F7mQ#KrK5Nz3VTpfRmb5>!p)7S(j(m5)Gd z{#pN&(pnhMo4BYtKWW`xn`&ydb_Df;jI#lU4we5dC8X4n{b|VdbA$Fb;Fv`k-&*JQ zmMkf!A?i_4>DC)JzfC@#drmeJEF7Cd)lC();@M%9BCl(5!nIMVoo$sIlcWbv<9lt> z%6s$Tl+vlNoo}YY&MTK^6?o*k*WZe~oJXaXU!L^eH9NfItBl*4lLG&`aHI0pZ1d`i z8o6twI4tm(RJ|Jw*qsumlu9hg)o90ibFmTERJV0RAHH2(9@al5Q4cOG+EV^7{I0fT zg|#7|M0ra!*mirR-Xk}<84oBOD=K}|WFZndiLFzllXgu0}+E0lDE%lCT`|UhB}LLlEM5U&l+aM?~#AK-(lFnh{fO+{{t*n B{s;g7 literal 0 HcmV?d00001 diff --git a/examples/html/chrome/static/fc-icon-rain.gif b/examples/html/chrome/static/fc-icon-rain.gif new file mode 100755 index 0000000000000000000000000000000000000000..faa5cd10f973dd6ea196b847d2e7fa5caae2fc4f GIT binary patch literal 2127 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 literal 0 HcmV?d00001 diff --git a/examples/html/chrome/static/fc-icon-snowandrain.gif b/examples/html/chrome/static/fc-icon-snowandrain.gif new file mode 100755 index 0000000000000000000000000000000000000000..4d802b46adb828b9aa14a3f6c97ff3789fb4b522 GIT binary patch literal 2583 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 G7XBZJBE3EU literal 0 HcmV?d00001 diff --git a/examples/html/chrome/static/fc-icon-sunny.gif b/examples/html/chrome/static/fc-icon-sunny.gif new file mode 100755 index 0000000000000000000000000000000000000000..c3e988ce712600db5be3abb86bf59480968d1253 GIT binary patch literal 1555 zcmW-edrVt(6vuz1?FCxa-BO_CA>*bDnPVAun@WMrwiE_+D0H|Xvf0wT0aq-ujCQ6g z<*}_i?H-O!=>~JtVc15O8EMk;xENWkdth~lR}ffzOfN>wT{Pxr_2ipRzTb1sA166m ziwd%y*Oh<`;1K{2!a%j2tk49^Mh^-|jgnB}UL5mcDD4>x4!FD$KoefdVfJExzyK2< zDVKvX>xnghTmuLh8no*J6*!3lQi+nX5J5l*f@US9#{z0VsR0wDs0x(Q0Lq95D?%eY zXvC`EA+6-uyl6K;>1EcLI2^hjihv)*(1sIPKdX>xo3I_nBT+oX%Au|YIzzj(}^kUG1LYjcVK^QPX zQUeJbdQs>JE$AVshNKb_81zO!Z|E2b(q2fJA!USt3K-BpQU*yJl4~F#fdmSP2uOQi z&U*H~raW~z(p0UK<; zakUo%mZBY|aO8yzB^D04xz4u2cICE{9fm1n#R&qHG$)M{`&@COkOOKP$u3LdF%ZcS zC|0e1uwJxUmYyS3Jj>y~q*{8Eyx0HcL|fC!+_VGfLJqgZqS|u6Aly^-UL{)ZNql@!4g?>aZN!~3>kPM?_}J?9QfGR}rib$zBFw)b{OV{F7jp-^?j*Ww7qpI_2B1ieq*pBCj015x8rX%*PI{Q zU6nD1j>mwAPTdnZtEvutcJHY5q4dBP;>?DbHmo*#sWrB!Fh)LfC(XfOMLjM&=|APd z8yZ&5Y}~)1H?wKVr7*@#@iP+QP^)|EhZ~VsBDE=Dw!%l5ny7ySTSkU;`L-PfR4cAyLG1b@V9+@s3 z)aiQZkuHwlSJ?v)#-IJfCf;QzB_vhDek8em4x2ZPb?Y-i!x$AU=7vw0~ROc4+RuCFXKZyUq6x?h@b5_-*ZS}O=;q8Z@w#MM^{c~ zs(1Cfe~sTaU4wkKpYtL$x_iW`1xUnBaf*T~VS){?)g3Hg{1`AhP%j1AjFY z`c8k3+M|{ic(N` + + +Current Conditions - <!--stationCity-->,<!--stationState--> + + +

+

+ + +

+

Current Weather +Conditions for ,

+

As of: +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Temperature: Wind Chill:
Humidity: Heat Index:
Wind: at  Dewpoint:
Barometer:  Rain Rate:  
Today's Rain:  Monthly Rain: 
Storm Total: Yearly Rain: 
+
+ + + + diff --git a/examples/html/classic/plus/Current_Plus.htx b/examples/html/classic/plus/Current_Plus.htx new file mode 100644 index 0000000..ba6614b --- /dev/null +++ b/examples/html/classic/plus/Current_Plus.htx @@ -0,0 +1,223 @@ + + +Current Conditions: <!--stationCity-->,<!--stationState--> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+, + + + +

+    +    +    +    + +

+
+ +

+ +

+
+ +

+    +    +    + +

+
+ +

+    + +

+
+ +

+    +    +    +    + +

+ +
+
Temperature

+
Humidity

Dewpoint

Wind
at
+
Barometer
 
Today's Rain
 
+
Rain Rate
 
Storm Total
 
+
Monthly Rain
 
+
Yearly Rain
 
+
Wind Chill

+
Heat Index

+
+Today's Highs/Lows
+ +

High Temperature

Low Temperature

+
+ +

  at   +

  at   +

+
+ +

High Humidity

Low Humidity

+
+ +

  at   +

  at   +

+

High Dewpoint

Low Dewpoint

  + at  

  at   +

High Wind Speed

   + at  

High Barometer

Low Barometer

   + at  

   at   +

High Rain Rate

   + at  

Low Wind Chill

  + at  

High Heat Index

  + at  

+ + + diff --git a/examples/html/classic/plus/Daily_Plus.htx b/examples/html/classic/plus/Daily_Plus.htx new file mode 100644 index 0000000..ca7a289 --- /dev/null +++ b/examples/html/classic/plus/Daily_Plus.htx @@ -0,0 +1,233 @@ + + +Last 24 Hours Weather: <!--stationCity-->,<!--stationState--> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + Observations for the Last 24 Hours
+ +
+
+
+
+ + + + Today's Highs/Lows + + + + + + + + , + + + +

High Temperature

Low Temperature

+

+

+ +

+     + +

+

+     + +

+

+ +

+

+     + +

+ + +

+     + +

+

+ +

+ + +
+

High Humidity

Low Humidity

+

+

+

High Dewpoint

Low Dewpoint

+

High Wind Speed

 

+

High Barometer

Low Barometer

+

 

 

+

Rain Total

 

+

High Rain Rate

 

+

Low Wind Chill

+

High Heat Index

+
+Yearly Highs/Lows

High Temperature

Low Temperature

+

+

High Humidity

Low Humidity

+

+

+

High Dewpoint

Low Dewpoint

+

High Wind Speed

 

+

High Barometer

Low Barometer

+

 

+ 

+

Rain Total

 

+

High Rain Rate

 

+

High Heat Index

+

Low Wind Chill

+
+ + + diff --git a/examples/html/classic/plus/Monthly_Plus.htx b/examples/html/classic/plus/Monthly_Plus.htx new file mode 100644 index 0000000..d701d21 --- /dev/null +++ b/examples/html/classic/plus/Monthly_Plus.htx @@ -0,0 +1,215 @@ + + +Last 28 Days Weather: <!--stationCity-->,<!--stationState--> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + Observations for the Last 28 Days
+ +
+
+
+
+ + + + Monthly Highs/Lows + + + + + + + + , + + + +

High Temperature

Low Temperature

+

+ +

+     + +

+

+     + +

+

+ +

+

+     + +

+ + +

+     + +

+

+ +

+ + +
+

High Humidity

Low Humidity

+

+

High Dewpoint

Low Dewpoint

High Wind Speed

 

High Barometer

Low Barometer

+

 

 

Rain Total

 

High Rain Rate

 

Low Wind Chill

High Heat Index

+Yearly Highs/Lows

High Temperature

Low Temperature

+

High Humidity

Low Humidity

+

+

High Dewpoint

Low Dewpoint

High Wind Speed

 

High Barometer

Low Barometer

+

 

+ 

Rain Total

 

High Rain Rate

 

High Heat Index

Low Wind Chill

+ + + diff --git a/examples/html/classic/plus/Weekly_Plus.htx b/examples/html/classic/plus/Weekly_Plus.htx new file mode 100644 index 0000000..f8995e3 --- /dev/null +++ b/examples/html/classic/plus/Weekly_Plus.htx @@ -0,0 +1,215 @@ + + +Last 7 Days Weather: <!--stationCity-->,<!--stationState--> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + Observations for the Last 7 Days
+ +
+
+
+
+ + + + Monthly Highs/Lows + + + + + + + + , + + + +

High Temperature

Low Temperature

+

+ +

+     + +

+

+     + +

+

+ +

+

+     + +

+ + +

+     + +

+

+ +

+ + +
+

High Humidity

Low Humidity

+

+

High Dewpoint

Low Dewpoint

High Wind Speed

 

High Barometer

Low Barometer

+

 

 

Rain Total

 

High Rain Rate

 

Low Wind Chill

High Heat Index

+Yearly Highs/Lows

High Temperature

Low Temperature

+

High Humidity

Low Humidity

+

+

High Dewpoint

Low Dewpoint

High Wind Speed

 

High Barometer

Low Barometer

+

 

+ 

Rain Total

 

High Rain Rate

 

High Heat Index

Low Wind Chill

+ + + diff --git a/examples/html/classic/plus/Yearly_Plus.htx b/examples/html/classic/plus/Yearly_Plus.htx new file mode 100644 index 0000000..5782827 --- /dev/null +++ b/examples/html/classic/plus/Yearly_Plus.htx @@ -0,0 +1,161 @@ + + +Weather for the Last Year: <!--stationCity-->,<!--stationState--> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + Observations for the Last Year
+ +
+
+
+
+ + + + Yearly Highs/Lows + + + + + + + + , + + + +

High Temperature

Low Temperature

+

+ +

+     + +

+

+     + +

+

+     + +

+

+     + +

+

+     + +

+ + +

+     + +

+

+ +

+ + +
+

High Humidity

Low Humidity

+

+

High Dewpoint

Low Dewpoint

High Wind Speed

 

High Barometer

Low Barometer

+

 

+ 

Rain Total

 

High Rain Rate

 

High Heat Index

Low Wind Chill

+ + + diff --git a/examples/html/classic/plus/almanac_Plus.htx b/examples/html/classic/plus/almanac_Plus.htx new file mode 100644 index 0000000..b11acc5 --- /dev/null +++ b/examples/html/classic/plus/almanac_Plus.htx @@ -0,0 +1,1748 @@ + + + + <!--stationCity-->,<!--stationState--> Weather Almanac + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + + + + + + + +
+ + , Weather Almanac +
+ + - - + +
+ + Almanac Data for: + + +
+ + Sunrise: - Midday: - Sunset: - + Day Length:
+ Civil Twilight: -    + Astronomical Twilight: -
+ Moonrise: - Moonset: +

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
     
+ Temperature +   + Dew Point +   + Humidity +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + Current: + + + + + +
+ + Hour Average: + + + + + +
+ + Hour Change: + + + + + +
+ + Day Average: + + + + + +
+ + Day Change: + + + + + +
+ + Day High: + + + +   at   + + +
+ + Day Low: + + + +   at   + + +
+ + Week Average: + + + + + +
+ + Week Change: + + + + + +
+ + Month Average: + + + + + +
+ + Month High: + + + +  on  + + +
+ + Month Low: + + + +  on  + + +
+ + Year Average: + + + + + +
+ + Year High: + + + +  on  + + +
+ + Year Low: + + + +  on  + + +
+ + All Time Average: + + + + + +
+ + All Time High: + + + +  on  + + +
+ + All Time Low: + + + +  on  + + +
+
  + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + Current: + + + + + +
+ + Hour Average: + + + + + +
+ + Hour Change: + + + + + +
+ + Day Average: + + + + + +
+ + Day Change: + + + + + +
+ + Day High: + + + +   at   + + +
+ + Day Low: + + + +   at   + + +
+ + Week Average: + + + + + +
+ + Week Change: + + + + + +
+ + Month Average: + + + + + +
+ + Month High: + + + +  on  + + +
+ + Month Low: + + + +  on  + + +
+ + Year Average: + + + + + +
+ + Year High: + + + +  on  + + +
+ + Year Low: + + + +  on  + + +
+ + All Time Average: + + + + + +
+ + All Time High: + + + +  on  + + +
+ + All Time Low: + + + +  on  + + +
+
  + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + Current: + + + + + +
+ + Hour Average: + + + + + +
+ + Hour Change: + + + + + +
+ + Day Average: + + + + + +
+ + Day Change: + + + + + +
+ + Day High: + + + +   at   + + +
+ + Day Low: + + + +   at   + + +
+ + Week Average: + + + + + +
+ + Week Change: + + + + + +
+ + Month Average: + + + + + +
+ + Month High: + + + +  on  + + +
+ + Month Low: + + + +  on  + + +
+ + Year Average: + + + + + +
+ + Year High: + + + +  on  + + +
+ + Year Low: + + + +  on  + + +
+ + All Time Average: + + + + + +
+ + All Time High: + + + +  on  + + +
+ + All Time Low: + + + +  on  + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
     
+ Wind Chill +   + Heat Index +   + Wind Run +
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + Current: + + + + + +
+ + Day Low: + + + +   at   + + +
+ + Month Low: + + + +  on  + + +
+ + Year Low: + + + +  on  + + +
+ + All Time Low: + + + +  on  + + +
+
  + + + + + + + + + + + + + + + + + + + + + + + +
+ + Current: + + + + + +
+ + Day High: + + + +   at   + + +
+ + Month High: + + + +  on  + + +
+ + Year High: + + + +  on  + + +
+ + All Time High: + + + +  on  + + +
+
  + + + + + + + + + + + + + + + + + + + + + + +
+ + Hour: + + + + + +
+ + Day: + + + + + +
+ + Week: + + + + + +
+ + Month: + + + + + +
+ + Year: + + + + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
     
+ Wind +   + Barometer +   + Rain +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + Current Direction: + + + + degrees + +
+ + Hour Dom Direction: + + + + degrees + +
+ + Hour Direction Change: + + + + degrees + +
+ + Day Dom Direction: + + + + degrees + +
+ + Day Direction Change: + + + + degrees + +
+ + Week Dom Direction: + + + + degrees + +
+ + Week Direction Change: + + + + degrees + +
+ + Month Dom Direction: + + + + degrees + +
+ + Year Dom Direction: + + + + degrees + +
+ + All Time Direction: + + + + degrees + +
+ + Current Speed: + + + + + +
+ + Hour Avg Speed: + + + + + +
+ + Hour Speed Change: + + + + + +
+ + Day Avg Speed: + + + + + +
+ + Day Speed Change: + + + + + +
+ + Day High Speed: + + + +   at   + + +
+ + Week Avg Speed: + + + + + +
+ + Week Speed Change: + + + + + +
+ + Month Avg Speed: + + + + + +
+ + Month High Speed: + + + +  on  + + +
+ + Year Avg Speed: + + + + + +
+ + Year High Speed: + + + +  on  + + +
+ + All Time Avg Speed: + + + + + +
+ + All Time High Speed: + + + +  on  + + +
+
  + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + Current: + + + + + +
+ + Hour Average: + + + + + +
+ + Hour Change: + + + + + +
+ + Day Average: + + + + + +
+ + Day Change: + + + + + +
+ + Day High: + + + +   at   + + +
+ + Day Low: + + + +   at   + + +
+ + Week Average: + + + + + +
+ + Week Change: + + + + + +
+ + Month Average: + + + + + +
+ + Month High: + + + +  on  + + +
+ + Month Low: + + + +  on  + + +
+ + Year Average: + + + + + +
+ + Year High: + + + +  on  + + +
+ + Year Low: + + + +  on  + + +
+ + All Time Average: + + + + + +
+ + All Time High: + + + +  on  + + +
+ + All Time Low: + + + +  on  + + +
+
  + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + Rate: + + + + + +
+ + Day: + + + + + +
+ + Storm: + + + + + +
+ + Storm Start: + + + + + +
+ + Day High Rate: + + + +   at   + + +
+ + Month: + + + + + +
+ + Month High Rate: + + + +  on  + + +
+ + Year (): + + + + + +
+ + Year High Rate: + + + +  on  + + +
+ + All Time High Rate: + + + +  on  + + +
+
+
+
+ + + + + + + + +
+ + + + + + wviewweather.com + + + + + +
+
+
+ + diff --git a/examples/html/classic/plus/awekas_wl.htx-metric b/examples/html/classic/plus/awekas_wl.htx-metric new file mode 100644 index 0000000..29b5be4 --- /dev/null +++ b/examples/html/classic/plus/awekas_wl.htx-metric @@ -0,0 +1,32 @@ +AWEKAS_Template_start
+
+
+
+
+
+
+
+
+
+-
+-
+-
+-
+-
+-
+-
+
+
+
+
+
+
+
+
+
+
+w/m^2
+
+index
+Template_V1.5
+ diff --git a/examples/html/classic/plus/awekas_wl.htx-us b/examples/html/classic/plus/awekas_wl.htx-us new file mode 100644 index 0000000..917aa40 --- /dev/null +++ b/examples/html/classic/plus/awekas_wl.htx-us @@ -0,0 +1,32 @@ +AWEKAS_Template_start
+
+
+
+
+
+
+
+
+
+-
+-
+-
+-
+-
+-
+-
+
+
+
+
+
+
+
+
+
+
+w/m^2
+
+index
+Template_V1.5
+ diff --git a/examples/html/classic/plus/graphics.conf b/examples/html/classic/plus/graphics.conf new file mode 100644 index 0000000..3d1a6ca --- /dev/null +++ b/examples/html/classic/plus/graphics.conf @@ -0,0 +1,165 @@ +#@ +# +# This file contains configuration information for graphics. +# +# Note: For parameters that enable/disable a feature, a value of "0" disables +# the feature and "1" enables it... +# +# Color values are RRGGBBTT where TT is the transparency level. +# + +################################# BUCKETS ################################# + +# Transparent background (0/1)? +BUCKET_TRANSPARENT=0 + +# Background color for the bucket image +BUCKET_BG_COLOR=0xF5F5F500 + +# Color for the lines and ticks in the bucket plot +BUCKET_FG_COLOR=0x00000000 + +# Color for the filled region of the bucket +BUCKET_CONTENT_COLOR=0x4282B400 + +# Color for line indicating high value +BUCKET_HIGH_COLOR=0xFF000000 + +# Color for line indicating low value +BUCKET_LOW_COLOR=0x00BFFF00 + +# Background color for the title +BUCKET_TITLE_BG_COLOR=0xD8D8D800 + +# Foreground color for the title +BUCKET_TITLE_FG_COLOR=0x00000000 + +# Color of text labels +BUCKET_TEXT_COLOR=0x00000000 + +# Width of bucket image +BUCKET_IMAGE_WIDTH=120 + +# Height of bucket image +BUCKET_IMAGE_HEIGHT=240 + +# Width of bucket itself +BUCKET_WIDTH=36 + + +################################# LINE CHARTS ################################# + +# Transparent background for all charts (0/1)? +CHART_TRANSPARENT=0 + +# Background color for the chart image rectangle +CHART_IMAGE_BG_COLOR=0xF5F5F500 + +# Background color for the plot +CHART_GRAPH_BG_COLOR=0xD8D8D800 + +# Color of the grid lines +CHART_GRID_COLOR=0xA0A00000 + +# Color of line for primary data values +CHART_FIRST_LINE_COLOR=0x4282B400 + +# Color of line for secondary data values +CHART_SECOND_LINE_COLOR=0xB4424200 + +# Color of line for tertiary data values +CHART_THIRD_LINE_COLOR=0x42B44200 + +# Background color for title +CHART_TITLE_BG_COLOR=0xD8D8D800 + +# Foreground color for title +CHART_TITLE_FG_COLOR=0x00000000 + +# Color for text labels +CHART_TEXT_COLOR=0x00000000 + +# Width of image +CHART_WIDTH=300 + +# Height of image +CHART_HEIGHT=180 + + +################################# BAR CHARTS ################################# + +# Background color for the image +BAR_IMAGE_BG_COLOR=0xF5F5F500 + +# Background color for the plot +BAR_GRAPH_BG_COLOR=0xD8D8D800 + +# Color for the bars +BAR_BAR_COLOR=0x4282B400 + +# Background color for the title bar +BAR_TITLE_BG_COLOR=0xD8D8D800 + +# Foreground color for the title bar +BAR_TITLE_FG_COLOR=0x00000000 + +# Color for grid lines +BAR_GRID_COLOR=0xA0A00000 + +# Color for text labels +BAR_TEXT_COLOR=0x00000000 + +# Width of image +BAR_WIDTH=300 + +# Height of image +BAR_HEIGHT=180 + + +################################# DIAL PLOTS ################################# + +# Transparent background (0/1)? +DIAL_TRANSPARENT=0 + +# Background color for dial +DIAL_BG_COLOR=0xD8D8D800 + +# Background color for image (doesn't matter becasue this color is transparent) +DIAL_IMAGE_BG_COLOR=Ox12121200 + +# Color of circle at center of dial +DIAL_CENTER_COLOR=0x6666FF00 + +# Text color of current value in center of dial +DIAL_CENTER_TEXT_COLOR=0x0000ff00 + +# Text color of value for high wind in center of plot +DIAL_CENTER_HIGH_COLOR=0xff000000 + +# Fill color of pointer +DIAL_POINTER_COLOR=0x6666FF00 + +# Outline color of pointer +DIAL_POINTER_OUTLINE_COLOR=0x6666ff00 + +# Color of tick for high value +DIAL_HIGH_COLOR=0xff000000 + +# Color of tick for low value +DIAL_LOW_COLOR=0x0000ff00 + +# Text color for high value on Temperature plot +DIAL_APP_COLOR=0x80808000 + +# Text color of labels +DIAL_TEXT_COLOR=0x00000000 + +# Width of image +DIAL_IMAGE_WIDTH=160 + +# Diameter of dial +DIAL_DIAMETER=156 + +# Diameter of circle at center of dial +DIAL_CTR_DIAMETER=15 + diff --git a/examples/html/classic/plus/header-big.incx b/examples/html/classic/plus/header-big.incx new file mode 100644 index 0000000..e969430 --- /dev/null +++ b/examples/html/classic/plus/header-big.incx @@ -0,0 +1,43 @@ + + + + + + +
+ + + + + + + + + + + + + + + +
+ + , Weather +
+ + - - + +
+ + + +
+ + Sunrise: - Midday: - Sunset: - + Day Length:
+ Civil Twilight: -    + Astronomical Twilight: - +

+
+
+ diff --git a/examples/html/classic/plus/header.incx b/examples/html/classic/plus/header.incx new file mode 100644 index 0000000..f34547b --- /dev/null +++ b/examples/html/classic/plus/header.incx @@ -0,0 +1,40 @@ + + + + + + +
+ + + + + + + + + + + + + + + +
+ + , Weather +
+ + - - + +
+ + + +
+ + Sunrise: - Sunset: - Moon: + +
+
+ diff --git a/examples/html/classic/plus/html-templates.conf b/examples/html/classic/plus/html-templates.conf new file mode 100644 index 0000000..da33a0b --- /dev/null +++ b/examples/html/classic/plus/html-templates.conf @@ -0,0 +1,76 @@ +# +# html-templates.conf - define the HTML template files to use for +# HTML and generic generation +# +# Note: Template expansion is performed in the order the files are listed +# in this file. Thus, files to be included in other templates should +# be listed before the templates that include them. + +# File Format +# +# 1) lines beginning with '#' or are ignored +# + +# Column Format +# +# 1) template file name (must be in /etc/wview/html) +# + + +############################################################################### +### Standard Include Macros (should be generated first) +############################################################################### + +header.incx +header-big.incx +nav-buttons_Plus.incx +readings-plus.incx + +############################################################################### +### RSS Feed XML Template +############################################################################### + +### Standard Weather RSS feed template - file generated is wxrss.xml and can +### be added to any html template to add an RSS feed to that page +wxrss.xtx + +############################################################################### +### Home Page Templates +############################################################################### + +### Standard Home Page template, do not remove unless you are using the +### day/night alternative below +#index.htx + +### Day/Night Home Pages - if you want a different homepage template based on +### day or night, uncomment these and copy your templates to +### /etc/wview/html/index-day.htx and /etc/wview/html/index-night.htx +### respectively +index-day.htx +index-night.htx + +############################################################################### +### Historical Data Templates +############################################################################### + +### Extra sensor Extended Data +almanac_Plus.htx +Current_Plus.htx +Daily_Plus.htx +Weekly_Plus.htx +Monthly_Plus.htx +Yearly_Plus.htx +parameterlist.htx + +############################################################################### +### Current Conditions (Table Only) Template +############################################################################### + +#Current_Conditions.htx + +############################################################################### +### AWEKAS Data Submission Template +############################################################################### + +#awekas_wl.htx + diff --git a/examples/html/classic/plus/images-metric.conf b/examples/html/classic/plus/images-metric.conf new file mode 100755 index 0000000..2aae42f --- /dev/null +++ b/examples/html/classic/plus/images-metric.conf @@ -0,0 +1,231 @@ +# +# images-metric.conf - configure what images wview will generate +# + +# File Format +# +# -> lines beginning with '#' or are ignored +# -> columns are whitespace delimited +# + +# Column Format +# +# 1) image filename +# 2) image label (can be translated as desired) +# 3) image unit label (can be translated as desired) +# 4) decimal places +# 5) image generator function index (see images.c) +# + +# Enabling/Disabling Image Generation +# +# -> To enable the generation of an image, uncomment it (remove any '#' + characters in front of the image definition) +# -> To disable the generation of an image, comment it out by placing one or + more '#' characters in front of it + + +################# D I A L S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +tempdial.png "Temp" C 0 109 +humiddial.png "Humidity" % 0 110 +wind.png "Wind" none 0 11 + + +################# B U C K E T S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +temp.png "Temperature" C 1 0 +humid.png "Humidity" % 1 1 +dew.png "Dewpoint" C 1 2 +wchill.png "Wind Chill" C 1 3 +hindex.png "Heat Index" C 1 4 +barom.png "Barometer" hPa 1 5 +dayrain.png "Day Rain" mm 1 6 +stormrain.png "Storm Rain" mm 1 7 +rainrate.png "Rain Rate" mm/hour 1 8 +monthrain.png "Month Rain" mm 1 9 +yearrain.png "Year Rain" mm 1 10 + + +################# C H A R T S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +#### DAY VALUES #### +tempdaycomp.png "Temperature/Dewpoint" C 0 42 +heatchillcomp.png "Wind Chill/Heat Index" C 0 43 +## intempdaycomp.png "Inside Temp/Inside Humidity" C/% 0 115 +humidday.png "Humidity" % 0 15 +wspeeddaycomp.png "Wind/Gust" km/h 0 137 +## wspeedday.png "Wind" km/h 0 21 +wdirday.png "Wind DIR" degrees 0 24 +## hiwspeedday.png "HI Wind" km/h 0 27 +baromday.png "Barometer" hPa 0 30 + +## tempday.png "Temperature" C 0 12 +## dewday.png "Dewpoint" C 0 18 +## wchillday.png "Wind Chill" C 0 36 +## hindexday.png "Heat Index" C 0 39 + +#### WEEK VALUES #### +tempweekcomp.png "Temperature/Dewpoint" C 0 127 +heatchillweekcomp.png "Wind Chill/Heat Index" C 0 128 +## intempweekcomp.png "Inside Temp/Inside Humidity" C/% 0 129 +humidweek.png "Humidity (Hourly Avg)" % 0 118 +wspeedweekcomp.png "Wind/Gust" km/h 0 138 +## wspeedweek.png "Wind (Hourly Avg)" km/h 0 120 +wdirweek.png "Wind DIR (Hourly Avg)" degrees 0 121 +## hiwspeedweek.png "HI Wind (Hourly Avg)" km/h 0 122 +baromweek.png "Barometer (Hourly Avg)" hPa 0 123 + +## tempweek.png "Temperature (Hourly Avg)" C 0 117 +## dewweek.png "Dewpoint (Hourly Avg)" C 0 119 +## wchillweek.png "Wind Chill (Hourly Avg)" C 0 125 +## hindexweek.png "Heat Index (Hourly Avg)" C 0 126 + +#### MONTH VALUES #### +tempmonthcomp.png "Temperature/Dewpoint" C 0 44 +heatchillmonthcomp.png "Wind Chill/Heat Index" C 0 45 +## intempmonthcomp.png "Inside Temp/Inside Humidity" C/% 0 116 +humidmonth.png "Humidity (Hourly Avg)" % 0 16 +wspeedmonthcomp.png "Wind/Gust" km/h 0 139 +## wspeedmonth.png "Wind (Hourly Avg)" km/h 0 22 +wdirmonth.png "Wind DIR (Hourly Avg)" degrees 0 25 +## hiwspeedmonth.png "HI Wind (Hourly Avg)" km/h 0 28 +barommonth.png "Barometer (Hourly Avg)" hPa 0 31 + +## tempmonth.png "Temperature (Hourly Avg)" C 0 13 +## dewmonth.png "Dewpoint (Hourly Avg)" C 0 19 +## wchillmonth.png "Wind Chill (Hourly Avg)" C 0 37 +## hindexmonth.png "Heat Index (Hourly Avg)" C 0 40 + +#### YEAR VALUES #### +tempyear.png "Temperature (Daily Avg)" C 0 14 +humidyear.png "Humidity (Daily Avg)" % 0 17 +dewyear.png "Dewpoint (Daily Avg)" C 0 20 +wspeedyear.png "Wind (Daily Avg)" km/h 0 23 +wdiryear.png "Wind DIR (Daily Avg)" degrees 0 26 +hiwspeedyear.png "HI Wind (Daily Avg)" km/h 0 29 +baromyear.png "Barometer (Daily Avg)" hPa 0 32 +wchillyear.png "Wind Chill (Daily Avg)" C 0 38 +hindexyear.png "Heat Index (Daily Avg)" C 0 41 + + +################# B A R C H A R T S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +rainday.png "Rain/hour" mm 0 33 +rainweek.png "Rain/day" mm 0 124 +rainmonth.png "Rain/day" mm 0 34 +rainyear.png "Rain/week" mm 0 35 + + + +################# W I N D R O S E S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +windroseday.png "Rose Day" none 0 133 +windroseweek.png "Rose Week" none 0 134 +windrosemonth.png "Rose Month" none 0 135 +windroseyear.png "Rose Year" none 0 136 + + +################# V P P L U S D A T A B U C K E T S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +UV.png "UV Index" index 1 106 +radiation.png "Radiation" watts/m^2 1 107 +ET.png "ET (day)" mm 1 108 + + +################# V P P L U S D A T A C H A R T S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +radiationDay.png "SolarRad" watts 0 46 +radiationWeek.png "SolarRad (Hourly Avg)" watts 0 130 +radiationMonth.png "SolarRad (Hourly Avg)" watts 0 47 +radiationYear.png "SolarRad (Daily Avg)" watts 0 48 +UVDay.png "UV Index" index 0 49 +UVWeek.png "UV Index (Hourly Avg)" index 0 131 +UVMonth.png "UV Index (Hourly Avg)" index 0 50 +UVYear.png "UV Index (Daily Avg)" index 0 51 +ETDay.png "EvaPot (Hourly)" mm/hr 1 52 +ETWeek.png "EvaPot (Daily)" mm/day 0 132 +ETMonth.png "EvaPot (Daily)" mm/day 0 53 +ETYear.png "EvaPot (Weekly)" mm/week 0 54 +## leafTemp1Day.png "LeafTemp1" C 0 55 +## leafTemp1Month.png "LeafTemp1 (Hourly Avg)" C 0 56 +## leafTemp1Year.png "LeafTemp1 (Daily Avg)" C 0 57 +## leafTemp2Day.png "LeafTemp2" C 0 58 +## leafTemp2Month.png "LeafTemp2 (Hourly Avg)" C 0 59 +## leafTemp2Year.png "LeafTemp2 (Daily Avg)" C 0 60 +## leafWetness1Day.png "LeafWet1 Index" (0-15) 0 61 +## leafWetness1Month.png "LeafWet1 Index (Hourly Avg)" (0-15) 0 62 +## leafWetness1Year.png "LeafWet1 Index (Daily Avg)" (0-15) 0 63 +## leafWetness2Day.png "LeafWet2 Index" (0-15) 0 64 +## leafWetness2Month.png "LeafWet2 Index (Hourly Avg)" (0-15) 0 65 +## leafWetness2Year.png "LeafWet2 Index (Daily Avg)" (0-15) 0 66 +## soilTemp1Day.png "SoilTemp1" C 0 67 +## soilTemp1Month.png "SoilTemp1 (Hourly Avg)" C 0 68 +## soilTemp1Year.png "SoilTemp1 (Daily Avg)" C 0 69 +## soilTemp2Day.png "SoilTemp2" C 0 70 +## soilTemp2Month.png "SoilTemp2 (Hourly Avg)" C 0 71 +## soilTemp2Year.png "SoilTemp2 (Daily Avg)" C 0 72 +## soilTemp3Day.png "SoilTemp3" C 0 73 +## soilTemp3Month.png "SoilTemp3 (Hourly Avg)" C 0 74 +## soilTemp3Year.png "SoilTemp3 (Daily Avg)" C 0 75 +## soilTemp4Day.png "SoilTemp4" C 0 76 +## soilTemp4Month.png "SoilTemp4 (Hourly Avg)" C 0 77 +## soilTemp4Year.png "SoilTemp4 (Daily Avg)" C 0 78 +## extraHumid1Day.png "ExtraHumidity1" % 0 79 +## extraHumid1Month.png "ExtraHumidity1 (Hourly Avg)" % 0 80 +## extraHumid1Year.png "ExtraHumidity1 (Daily Avg)" % 0 81 +## extraHumid2Day.png "ExtraHumidity2" % 0 82 +## extraHumid2Month.png "ExtraHumidity2 (Hourly Avg)" % 0 83 +## extraHumid2Year.png "ExtraHumidity2 (Daily Avg)" % 0 84 +## extraTemp1Day.png "ExtraTemp1" C 0 85 +## extraTemp1Month.png "ExtraTemp1 (Hourly Avg)" C 0 86 +## extraTemp1Year.png "ExtraTemp1 (Daily Avg)" C 0 87 +## extraTemp2Day.png "ExtraTemp2" C 0 88 +## extraTemp2Month.png "ExtraTemp2 (Hourly Avg)" C 0 89 +## extraTemp2Year.png "ExtraTemp2 (Daily Avg)" C 0 90 +## extraTemp3Day.png "ExtraTemp3" C 0 91 +## extraTemp3Month.png "ExtraTemp3 (Hourly Avg)" C 0 92 +## extraTemp3Year.png "ExtraTemp3 (Daily Avg)" C 0 93 +## soilMoist1Day.png "SoilMoist1" cb 0 94 +## soilMoist1Month.png "SoilMoist1 (Hourly Avg)" cb 0 95 +## soilMoist1Year.png "SoilMoist1 (Daily Avg)" cb 0 96 +## soilMoist2Day.png "SoilMoist2" cb 0 97 +## soilMoist2Month.png "SoilMoist2 (Hourly Avg)" cb 0 98 +## soilMoist2Year.png "SoilMoist2 (Daily Avg)" cb 0 99 +## soilMoist3Day.png "SoilMoist3" cb 0 100 +## soilMoist3Month.png "SoilMoist3 (Hourly Avg)" cb 0 101 +## soilMoist3Year.png "SoilMoist3 (Daily Avg)" cb 0 102 +## soilMoist4Day.png "SoilMoist4" cb 0 103 +## soilMoist4Month.png "SoilMoist4 (Hourly Avg)" cb 0 104 +## soilMoist4Year.png "SoilMoist4 (Daily Avg)" cb 0 105 + + +################# V P P L U S D I A L S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +netRainDay.png "Precip Day" mm 0 112 +netRainMonth.png "Precip Month" mm 0 113 +netRainYear.png "Precip Year" mm 0 114 + + +################# D I A G N O S T I C S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +#rxcheck.png "RX Good Packets" % 0 111 + diff --git a/examples/html/classic/plus/images-user.conf b/examples/html/classic/plus/images-user.conf new file mode 100644 index 0000000..04b6a1c --- /dev/null +++ b/examples/html/classic/plus/images-user.conf @@ -0,0 +1,20 @@ +# +# images-user.conf - configure what user-defined images wview will generate +# + +# File Format +# +# 1) lines beginning with '#' or are ignored +# 2) whitespace delimited columns +# + +# Column Format +# +# 1) image filename +# 2) image label +# 3) image unit label +# 4) decimal places +# 5) image generator function index (see images-user.c) +# + +##intemp.png "In Temperature" degrees 1 0 diff --git a/examples/html/classic/plus/images.conf b/examples/html/classic/plus/images.conf new file mode 100755 index 0000000..62d61bf --- /dev/null +++ b/examples/html/classic/plus/images.conf @@ -0,0 +1,225 @@ +# +# images.conf - configure what images wview will generate +# + +# File Format +# +# -> lines beginning with '#' or are ignored +# -> columns are whitespace delimited +# + +# Column Format +# +# 1) image filename +# 2) image label (can be translated as desired) +# 3) image units label (can be translated as desired) +# 4) decimal places +# 5) image generator function index (see images.c) +# + +# Enabling/Disabling Image Generation +# +# -> To enable the generation of an image, uncomment it (remove any '#' + characters in front of the image definition) +# -> To disable the generation of an image, comment it out by placing one or + more '#' characters in front of it + + +################# D I A L S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +tempdial.png "Temp" F 0 109 +humiddial.png "Humidity" none 0 110 +wind.png "Wind" none 0 11 +netRainDay.png "Precip Day" in 0 112 +netRainMonth.png "Precip Month" in 0 113 +netRainYear.png "Precip Year" in 0 114 + + +################# B U C K E T S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +temp.png "Temperature" F 1 0 +humid.png "Humidity" % 1 1 +dew.png "Dewpoint" F 1 2 +wchill.png "Wind Chill" F 1 3 +hindex.png "Heat Index" F 1 4 +barom.png "Barometer" inHg 2 5 +dayrain.png "Day Rain" inches 2 6 +stormrain.png "Storm Rain" inches 2 7 +rainrate.png "Rain Rate" in/hour 2 8 +monthrain.png "Month Rain" inches 2 9 +yearrain.png "Year Rain" inches 2 10 + + +################# C H A R T S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +#### DAY VALUES #### +tempdaycomp.png "Temperature/Dewpoint" F 0 42 +heatchillcomp.png "Wind Chill/Heat Index" F 0 43 +## intempdaycomp.png "Inside Temp/Inside Humidity" F/% 0 115 +humidday.png "Humidity" % 0 15 +wspeeddaycomp.png "Wind/Gust" mph 0 137 +## wspeedday.png "Wind" mph 0 21 +wdirday.png "Wind DIR" degrees 0 24 +## hiwspeedday.png "HI Wind" mph 0 27 +baromday.png "Barometer" inHg 1 30 + +## tempday.png "Temperature" F 0 12 +## dewday.png "Dewpoint" F 0 18 +## wchillday.png "Wind Chill" F 0 36 +## hindexday.png "Heat Index" F 0 39 + +#### WEEK VALUES #### +tempweekcomp.png "Temperature/Dewpoint" F 0 127 +heatchillweekcomp.png "Wind Chill/Heat Index" F 0 128 +## intempweekcomp.png "Inside Temp/Inside Humidity" F/% 0 129 +humidweek.png "Humidity (Hourly Avg)" % 0 118 +wspeedweekcomp.png "Wind/Gust" mph 0 138 +## wspeedweek.png "Wind (Hourly Avg)" mph 0 120 +wdirweek.png "Wind DIR (Hourly Avg)" degrees 0 121 +## hiwspeedweek.png "HI Wind (Hourly Avg)" mph 0 122 +baromweek.png "Barometer (Hourly Avg)" inHg 1 123 + +## tempweek.png "Temperature (Hourly Avg)" F 0 117 +## dewweek.png "Dewpoint (Hourly Avg)" F 0 119 +## wchillweek.png "Wind Chill (Hourly Avg)" F 0 125 +## hindexweek.png "Heat Index (Hourly Avg)" F 0 126 + +#### MONTH VALUES #### +tempmonthcomp.png "Temperature/Dewpoint" F 0 44 +heatchillmonthcomp.png "Wind Chill/Heat Index" F 0 45 +## intempmonthcomp.png "Inside Temp/Inside Humidity" deg/% 0 116 +humidmonth.png "Humidity (Hourly Avg)" % 0 16 +wspeedmonthcomp.png "Wind/Gust" mph 0 139 +## wspeedmonth.png "Wind (Hourly Avg)" mph 0 22 +wdirmonth.png "Wind DIR (Hourly Avg)" degrees 0 25 +## hiwspeedmonth.png "HI Wind (Hourly Avg)" mph 0 28 +barommonth.png "Barometer (Hourly Avg)" inHg 1 31 + +## tempmonth.png "Temperature (Hourly Avg)" F 0 13 +## dewmonth.png "Dewpoint (Hourly Avg)" F 0 19 +## wchillmonth.png "Wind Chill (Hourly Avg)" F 0 37 +## hindexmonth.png "Heat Index (Hourly Avg)" F 0 40 + +#### YEAR VALUES #### +tempyear.png "Temperature (Daily Avg)" F 0 14 +humidyear.png "Humidity (Daily Avg)" % 0 17 +dewyear.png "Dewpoint (Daily Avg)" F 0 20 +wspeedyear.png "Wind (Daily Avg)" mph 0 23 +wdiryear.png "Wind DIR (Daily Avg)" degrees 0 26 +hiwspeedyear.png "HI Wind (Daily Avg)" mph 0 29 +baromyear.png "Barometer (Daily Avg)" inHg 1 32 +wchillyear.png "Wind Chill (Daily Avg)" F 0 38 +hindexyear.png "Heat Index (Daily Avg)" F 0 41 + + +################# B A R C H A R T S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +rainday.png "Rain/hour" inches 2 33 +rainweek.png "Rain/day" inches 1 124 +rainmonth.png "Rain/day" inches 1 34 +rainyear.png "Rain/week" inches 1 35 + + + +################# W I N D R O S E S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +windroseday.png "Rose Day" none 0 133 +windroseweek.png "Rose Week" none 0 134 +windrosemonth.png "Rose Month" none 0 135 +windroseyear.png "Rose Year" none 0 136 + + +################# V P P L U S D A T A B U C K E T S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +UV.png "UV Index" index 1 106 +radiation.png "Radiation" watts/m^2 1 107 +ET.png "ET (day)" inches 3 108 + + +################# V P P L U S D A T A C H A R T S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +radiationDay.png "SolarRad" watts 0 46 +radiationWeek.png "SolarRad (Hourly Avg)" watts 0 130 +radiationMonth.png "SolarRad (Hourly Avg)" watts 0 47 +radiationYear.png "SolarRad (Daily Avg)" watts 0 48 +UVDay.png "UV Index" index 0 49 +UVWeek.png "UV Index (Hourly Avg)" index 0 131 +UVMonth.png "UV Index (Hourly Avg)" index 0 50 +UVYear.png "UV Index (Daily Avg)" index 0 51 +ETDay.png "EvaPot (Hourly)" inches/hr 2 52 +ETWeek.png "EvaPot (Daily)" inches/day 2 132 +ETMonth.png "EvaPot (Daily)" inches/day 2 53 +ETYear.png "EvaPot (Weekly)" inches/week 2 54 +## leafTemp1Day.png "LeafTemp1" F 0 55 +## leafTemp1Month.png "LeafTemp1 (Hourly Avg)" F 0 56 +## leafTemp1Year.png "LeafTemp1 (Daily Avg)" F 0 57 +## leafTemp2Day.png "LeafTemp2" F 0 58 +## leafTemp2Month.png "LeafTemp2 (Hourly Avg)" F 0 59 +## leafTemp2Year.png "LeafTemp2 (Daily Avg)" F 0 60 +## leafWetness1Day.png "LeafWet1 Index" (0-15) 0 61 +## leafWetness1Month.png "LeafWet1 Index (Hourly Avg)" (0-15) 0 62 +## leafWetness1Year.png "LeafWet1 Index (Daily Avg)" (0-15) 0 63 +## leafWetness2Day.png "LeafWet2 Index" (0-15) 0 64 +## leafWetness2Month.png "LeafWet2 Index (Hourly Avg)" (0-15) 0 65 +## leafWetness2Year.png "LeafWet2 Index (Daily Avg)" (0-15) 0 66 +## soilTemp1Day.png "SoilTemp1" F 0 67 +## soilTemp1Month.png "SoilTemp1 (Hourly Avg)" F 0 68 +## soilTemp1Year.png "SoilTemp1 (Daily Avg)" F 0 69 +## soilTemp2Day.png "SoilTemp2" F 0 70 +## soilTemp2Month.png "SoilTemp2 (Hourly Avg)" F 0 71 +## soilTemp2Year.png "SoilTemp2 (Daily Avg)" F 0 72 +## soilTemp3Day.png "SoilTemp3" F 0 73 +## soilTemp3Month.png "SoilTemp3 (Hourly Avg)" F 0 74 +## soilTemp3Year.png "SoilTemp3 (Daily Avg)" F 0 75 +## soilTemp4Day.png "SoilTemp4" F 0 76 +## soilTemp4Month.png "SoilTemp4 (Hourly Avg)" F 0 77 +## soilTemp4Year.png "SoilTemp4 (Daily Avg)" F 0 78 +## extraHumid1Day.png "ExtraHumidity1" % 0 79 +## extraHumid1Month.png "ExtraHumidity1 (Hourly Avg)" % 0 80 +## extraHumid1Year.png "ExtraHumidity1 (Daily Avg)" % 0 81 +## extraHumid2Day.png "ExtraHumidity2" % 0 82 +## extraHumid2Month.png "ExtraHumidity2 (Hourly Avg)" % 0 83 +## extraHumid2Year.png "ExtraHumidity2 (Daily Avg)" % 0 84 +## extraTemp1Day.png "ExtraTemp1" F 0 85 +## extraTemp1Month.png "ExtraTemp1 (Hourly Avg)" F 0 86 +## extraTemp1Year.png "ExtraTemp1 (Daily Avg)" F 0 87 +## extraTemp2Day.png "ExtraTemp2" F 0 88 +## extraTemp2Month.png "ExtraTemp2 (Hourly Avg)" F 0 89 +## extraTemp2Year.png "ExtraTemp2 (Daily Avg)" F 0 90 +## extraTemp3Day.png "ExtraTemp3" F 0 91 +## extraTemp3Month.png "ExtraTemp3 (Hourly Avg)" F 0 92 +## extraTemp3Year.png "ExtraTemp3 (Daily Avg)" F 0 93 +## soilMoist1Day.png "SoilMoist1" cbars 0 94 +## soilMoist1Month.png "SoilMoist1 (Hourly Avg)" cbars 0 95 +## soilMoist1Year.png "SoilMoist1 (Daily Avg)" cbars 0 96 +## soilMoist2Day.png "SoilMoist2" cbars 0 97 +## soilMoist2Month.png "SoilMoist2 (Hourly Avg)" cbars 0 98 +## soilMoist2Year.png "SoilMoist2 (Daily Avg)" cbars 0 99 +## soilMoist3Day.png "SoilMoist3" cbars 0 100 +## soilMoist3Month.png "SoilMoist3 (Hourly Avg)" cbars 0 101 +## soilMoist3Year.png "SoilMoist3 (Daily Avg)" cbars 0 102 +## soilMoist4Day.png "SoilMoist4" cbars 0 103 +## soilMoist4Month.png "SoilMoist4 (Hourly Avg)" cbars 0 104 +## soilMoist4Year.png "SoilMoist4 (Daily Avg)" cbars 0 105 + + +################# D I A G N O S T I C S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +#rxcheck.png "RX Good Packets" % 0 111 + diff --git a/examples/html/classic/plus/index-day.htx b/examples/html/classic/plus/index-day.htx new file mode 100644 index 0000000..b9e8faf --- /dev/null +++ b/examples/html/classic/plus/index-day.htx @@ -0,0 +1,374 @@ + + + + <!--stationCity-->,<!--stationState--> Weather + + + + + + + + +
+ + + + + + + + + + +
+ + + + + + + +
+ + + + + + + + + + +
+ +
+
+ + NEXRAD + +
+
+
+ + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + + + + + + + +
+ + + + + + + +
+ + + +
+
+ + + + + + + + + + + + + + + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+
+ + + + + + + +
+ + + +
+
+
+
+
+
+ + + + + + + + + + + +
+ + + + + +
+
+ + + + + + + + + +
+ + Climatological Summaries:  + + + + + +
+ + Browse Archive Records:  + + + +
+
+
+
+
+ + + + + + + + +
+ + + + + + wviewweather.com + + + +   + +
+
+
+ + diff --git a/examples/html/classic/plus/index-night.htx b/examples/html/classic/plus/index-night.htx new file mode 100644 index 0000000..28fdb27 --- /dev/null +++ b/examples/html/classic/plus/index-night.htx @@ -0,0 +1,374 @@ + + + + <!--stationCity-->,<!--stationState--> Weather + + + + + + + + +
+ + + + + + + + + + +
+ + + + + + + +
+ + + + + + + + + + +
+ +
+
+ + NEXRAD + +
+
+
+ + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + + + + + + + +
+ + + + + + + +
+ + + +
+
+ + + + + + + + + + + + + + + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+
+ + + + + + + +
+ + + +
+
+
+
+
+
+ + + + + + + + + + + +
+ + + + + +
+
+ + + + + + + + + +
+ + Climatological Summaries:  + + + + + +
+ + Browse Archive Records:  + + + +
+
+
+
+
+ + + + + + + + +
+ + + + + + wviewweather.com + + + +   + +
+
+
+ + diff --git a/examples/html/classic/plus/index.htx b/examples/html/classic/plus/index.htx new file mode 100644 index 0000000..b9e8faf --- /dev/null +++ b/examples/html/classic/plus/index.htx @@ -0,0 +1,374 @@ + + + + <!--stationCity-->,<!--stationState--> Weather + + + + + + + + +
+ + + + + + + + + + +
+ + + + + + + +
+ + + + + + + + + + +
+ +
+
+ + NEXRAD + +
+
+
+ + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + + + + + + + +
+ + + + + + + +
+ + + +
+
+ + + + + + + + + + + + + + + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+
+ + + + + + + +
+ + + +
+
+
+
+
+
+ + + + + + + + + + + +
+ + + + + +
+
+ + + + + + + + + +
+ + Climatological Summaries:  + + + + + +
+ + Browse Archive Records:  + + + +
+
+
+
+
+ + + + + + + + +
+ + + + + + wviewweather.com + + + +   + +
+
+
+ + diff --git a/examples/html/classic/plus/nav-buttons_Plus.incx b/examples/html/classic/plus/nav-buttons_Plus.incx new file mode 100644 index 0000000..ce20750 --- /dev/null +++ b/examples/html/classic/plus/nav-buttons_Plus.incx @@ -0,0 +1,14 @@ + + + + +
+ + + + + + + +
+ diff --git a/examples/html/classic/plus/post-generate.sh b/examples/html/classic/plus/post-generate.sh new file mode 100644 index 0000000..b5426c3 --- /dev/null +++ b/examples/html/classic/plus/post-generate.sh @@ -0,0 +1,8 @@ +#/bin/sh + +# Add any post template generation processing or utilities here. Keep in mind this +# script runs in the htmlgend process's context so don't add anything that is overly +# time constrained. + +exit 0 + diff --git a/examples/html/classic/plus/pre-generate.sh b/examples/html/classic/plus/pre-generate.sh new file mode 100644 index 0000000..0c0d1cb --- /dev/null +++ b/examples/html/classic/plus/pre-generate.sh @@ -0,0 +1,8 @@ +#/bin/sh + +# Add any pre template generation processing or utilities here. Keep in mind this +# script runs in the htmlgend process's context so don't add anything that is overly +# time constrained. + +exit 0 + diff --git a/examples/html/classic/plus/readings-plus.incx b/examples/html/classic/plus/readings-plus.incx new file mode 100644 index 0000000..8d7e8b8 --- /dev/null +++ b/examples/html/classic/plus/readings-plus.incx @@ -0,0 +1,422 @@ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + Temperature: + + + + + +
+ + Wind Chill: + + + + + +
+ + Heat Index: + + + + + +
+ + Apparent Temp: + + + + + +
+ + Wet Bulb Temp: + + + + + +
+ + Dewpoint: + + + + + +
+ + Humidity: + + + + + +
+ + Barometer: + + + +    + +
+ + Wind: + + + +   at   +   + +
+ + High Wind: + + + + at + + +
+ + Recent Avg Wind: + + + + + +
+ + Recent Beaufort Scale: + + + + + +
+ + Today's Rain: + + + +   + +
+ + Rain Rate: + + + +   + +
+ + High Rain Rate: + + + + at + + +
+ + Storm Total: + + + +   + +
+ + Monthly Rain: + + + +   + +
+ + Yearly Rain (): + + + +   + +
+ + UV: + + + + + +
+ + ET: + + + +   + +
+ + Solar Radiation: + + + +  watts/m^2 + +
+ + Air Density: + + + +   + +
+ + Est. Cumulus Base: + + + +   + +
+ + High Temperature: + + + + at + + +
+ + Low Temperature: + + + + at + + +
+ + High Heat Index: + + + + at + + +
+ + Low Wind Chill: + + + + at + + +
+ + High Humidity: + + + + at + + +
+ + Low Humidity: + + + + at + + +
+ + High Dewpoint: + + + + at + + +
+ + Low Dewpoint: + + + + at + + +
+ + High Barometer: + + + + at + + +
+ + Low Barometer: + + + + at + + +
+
+ diff --git a/examples/html/classic/plus/wxrss.xtx b/examples/html/classic/plus/wxrss.xtx new file mode 100644 index 0000000..423afed --- /dev/null +++ b/examples/html/classic/plus/wxrss.xtx @@ -0,0 +1,39 @@ + + + + + <!--stationCity--> <!--stationState--> + Insert_Your_WX_HomePage_URL_Here + Current Weather Conditions + en-us + , + 1 + + + + <!--stationCity-->,<!--stationState--> Weather Conditions: <!--stationDate--> <!--stationTime--> + Insert_Your_WX_HomePage_URL_Here + Current Weather Conditions + , + Insert_Your_WX_HomePage_URL_Here + - + + Temp:
+ Wind Chill:
+ Heat Index:
+ Humidity:
+ Dewpoint:
+ Barometer:
+ Wind: at
+ Rain Today:
+ Rain Rate:
+

+ ]]>
+
+
+
diff --git a/examples/html/classic/standard/Current.htx b/examples/html/classic/standard/Current.htx new file mode 100644 index 0000000..125a5dc --- /dev/null +++ b/examples/html/classic/standard/Current.htx @@ -0,0 +1,216 @@ + + +Current Conditions: <!--stationCity-->,<!--stationState--> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+, + + + +

+    +    +    +    + +

+
+ +

+    + +

+
+ +

+    + +

+
+ +

+    +    +    +    + +

+ +
+
Temperature

+
Humidity

Dewpoint

Wind
at
+
Barometer
 
Today's Rain
 
+
Rain Rate
 
Storm Total
 
+
Monthly Rain
 
+
Yearly Rain
 
+
Wind Chill

+
Heat Index

+
+Today's Highs/Lows
+ +

High Temperature

Low Temperature

+
+ +

  at   +

  at   +

+
+ +

High Humidity

Low Humidity

+
+ +

  at   +

  at   +

+

High Dewpoint

Low Dewpoint

  + at  

  at   +

High Wind Speed

   + at  

High Barometer

Low Barometer

   + at  

   at   +

High Rain Rate

   + at  

Low Wind Chill

  + at  

High Heat Index

  + at  

+ + + diff --git a/examples/html/classic/standard/Current_Conditions.htx b/examples/html/classic/standard/Current_Conditions.htx new file mode 100644 index 0000000..9ce14df --- /dev/null +++ b/examples/html/classic/standard/Current_Conditions.htx @@ -0,0 +1,60 @@ + + + +Current Conditions - <!--stationCity-->,<!--stationState--> + + +

+

+ + +

+

Current Weather +Conditions for ,

+

As of: +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Temperature: Wind Chill:
Humidity: Heat Index:
Wind: at  Dewpoint:
Barometer:  Rain Rate:  
Today's Rain:  Monthly Rain: 
Storm Total: Yearly Rain: 
+
+ + + + diff --git a/examples/html/classic/standard/Daily.htx b/examples/html/classic/standard/Daily.htx new file mode 100644 index 0000000..481dbfb --- /dev/null +++ b/examples/html/classic/standard/Daily.htx @@ -0,0 +1,221 @@ + + +Last 24 Hours Weather: <!--stationCity-->,<!--stationState--> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + Observations for the Last 24 Hours
+ +
+
+
+
+ + + + Today's Highs/Lows + + + + + + + + , + + + +

High Temperature

Low Temperature

+

+

+ +

+     + +

+

+     + +

+

+ +

+

+     + +

+
+

High Humidity

Low Humidity

+

+

+

High Dewpoint

Low Dewpoint

+

High Wind Speed

 

+

High Barometer

Low Barometer

+

 

 

+

Rain Total

 

+

High Rain Rate

 

+

Low Wind Chill

+

High Heat Index

+
+Yearly Highs/Lows

High Temperature

Low Temperature

+

+

High Humidity

Low Humidity

+

+

+

High Dewpoint

Low Dewpoint

+

High Wind Speed

 

+

High Barometer

Low Barometer

+

 

+ 

+

Rain Total

 

+

High Rain Rate

 

+

High Heat Index

+

Low Wind Chill

+
+ + + diff --git a/examples/html/classic/standard/Monthly.htx b/examples/html/classic/standard/Monthly.htx new file mode 100644 index 0000000..51c851a --- /dev/null +++ b/examples/html/classic/standard/Monthly.htx @@ -0,0 +1,204 @@ + + +Last 28 Days Weather: <!--stationCity-->,<!--stationState--> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + Observations for the Last 28 Days
+ +
+
+
+
+ + + + Monthly Highs/Lows + + + + + + + + , + + + +

High Temperature

Low Temperature

+

+ +

+     + +

+

+     + +

+

+ +

+

+     + +

+
+

High Humidity

Low Humidity

+

+

High Dewpoint

Low Dewpoint

High Wind Speed

 

High Barometer

Low Barometer

+

 

 

Rain Total

 

High Rain Rate

 

Low Wind Chill

High Heat Index

+Yearly Highs/Lows

High Temperature

Low Temperature

+

High Humidity

Low Humidity

+

+

High Dewpoint

Low Dewpoint

High Wind Speed

 

High Barometer

Low Barometer

+

 

+ 

Rain Total

 

High Rain Rate

 

High Heat Index

Low Wind Chill

+ + + diff --git a/examples/html/classic/standard/Weekly.htx b/examples/html/classic/standard/Weekly.htx new file mode 100644 index 0000000..6966fac --- /dev/null +++ b/examples/html/classic/standard/Weekly.htx @@ -0,0 +1,204 @@ + + +Last 7 Days Weather: <!--stationCity-->,<!--stationState--> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + Observations for the Last 7 Days
+ +
+
+
+
+ + + + Monthly Highs/Lows + + + + + + + + , + + + +

High Temperature

Low Temperature

+

+ +

+     + +

+

+     + +

+

+ +

+

+     + +

+
+

High Humidity

Low Humidity

+

+

High Dewpoint

Low Dewpoint

High Wind Speed

 

High Barometer

Low Barometer

+

 

 

Rain Total

 

High Rain Rate

 

Low Wind Chill

High Heat Index

+Yearly Highs/Lows

High Temperature

Low Temperature

+

High Humidity

Low Humidity

+

+

High Dewpoint

Low Dewpoint

High Wind Speed

 

High Barometer

Low Barometer

+

 

+ 

Rain Total

 

High Rain Rate

 

High Heat Index

Low Wind Chill

+ + + diff --git a/examples/html/classic/standard/Yearly.htx b/examples/html/classic/standard/Yearly.htx new file mode 100644 index 0000000..e2d9cf8 --- /dev/null +++ b/examples/html/classic/standard/Yearly.htx @@ -0,0 +1,150 @@ + + +Weather for the Last Year: <!--stationCity-->,<!--stationState--> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + Observations for the Last Year
+ +
+
+
+
+ + + + Yearly Highs/Lows + + + + + + + + , + + + +

High Temperature

Low Temperature

+

+ +

+     + +

+

+     + +

+

+     + +

+

+     + +

+

+     + +

+
+

High Humidity

Low Humidity

+

+

High Dewpoint

Low Dewpoint

High Wind Speed

 

High Barometer

Low Barometer

+

 

+ 

Rain Total

 

High Rain Rate

 

High Heat Index

Low Wind Chill

+ + + diff --git a/examples/html/classic/standard/almanac.htx b/examples/html/classic/standard/almanac.htx new file mode 100644 index 0000000..d522450 --- /dev/null +++ b/examples/html/classic/standard/almanac.htx @@ -0,0 +1,1748 @@ + + + + <!--stationCity-->,<!--stationState--> Weather Almanac + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + + + + + + + +
+ + , Weather Almanac +
+ + - - + +
+ + Almanac Data for: + + +
+ + Sunrise: - Midday: - Sunset: - + Day Length:
+ Civil Twilight: -    + Astronomical Twilight: -
+ Moonrise: - Moonset: +

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
     
+ Temperature +   + Dew Point +   + Humidity +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + Current: + + + + + +
+ + Hour Average: + + + + + +
+ + Hour Change: + + + + + +
+ + Day Average: + + + + + +
+ + Day Change: + + + + + +
+ + Day High: + + + +   at   + + +
+ + Day Low: + + + +   at   + + +
+ + Week Average: + + + + + +
+ + Week Change: + + + + + +
+ + Month Average: + + + + + +
+ + Month High: + + + +  on  + + +
+ + Month Low: + + + +  on  + + +
+ + Year Average: + + + + + +
+ + Year High: + + + +  on  + + +
+ + Year Low: + + + +  on  + + +
+ + All Time Average: + + + + + +
+ + All Time High: + + + +  on  + + +
+ + All Time Low: + + + +  on  + + +
+
  + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + Current: + + + + + +
+ + Hour Average: + + + + + +
+ + Hour Change: + + + + + +
+ + Day Average: + + + + + +
+ + Day Change: + + + + + +
+ + Day High: + + + +   at   + + +
+ + Day Low: + + + +   at   + + +
+ + Week Average: + + + + + +
+ + Week Change: + + + + + +
+ + Month Average: + + + + + +
+ + Month High: + + + +  on  + + +
+ + Month Low: + + + +  on  + + +
+ + Year Average: + + + + + +
+ + Year High: + + + +  on  + + +
+ + Year Low: + + + +  on  + + +
+ + All Time Average: + + + + + +
+ + All Time High: + + + +  on  + + +
+ + All Time Low: + + + +  on  + + +
+
  + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + Current: + + + + + +
+ + Hour Average: + + + + + +
+ + Hour Change: + + + + + +
+ + Day Average: + + + + + +
+ + Day Change: + + + + + +
+ + Day High: + + + +   at   + + +
+ + Day Low: + + + +   at   + + +
+ + Week Average: + + + + + +
+ + Week Change: + + + + + +
+ + Month Average: + + + + + +
+ + Month High: + + + +  on  + + +
+ + Month Low: + + + +  on  + + +
+ + Year Average: + + + + + +
+ + Year High: + + + +  on  + + +
+ + Year Low: + + + +  on  + + +
+ + All Time Average: + + + + + +
+ + All Time High: + + + +  on  + + +
+ + All Time Low: + + + +  on  + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
     
+ Wind Chill +   + Heat Index +   + Wind Run +
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + Current: + + + + + +
+ + Day Low: + + + +   at   + + +
+ + Month Low: + + + +  on  + + +
+ + Year Low: + + + +  on  + + +
+ + All Time Low: + + + +  on  + + +
+
  + + + + + + + + + + + + + + + + + + + + + + + +
+ + Current: + + + + + +
+ + Day High: + + + +   at   + + +
+ + Month High: + + + +  on  + + +
+ + Year High: + + + +  on  + + +
+ + All Time High: + + + +  on  + + +
+
  + + + + + + + + + + + + + + + + + + + + + + +
+ + Hour: + + + + + +
+ + Day: + + + + + +
+ + Week: + + + + + +
+ + Month: + + + + + +
+ + Year: + + + + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
     
+ Wind +   + Barometer +   + Rain +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + Current Direction: + + + + degrees + +
+ + Hour Dom Direction: + + + + degrees + +
+ + Hour Direction Change: + + + + degrees + +
+ + Day Dom Direction: + + + + degrees + +
+ + Day Direction Change: + + + + degrees + +
+ + Week Dom Direction: + + + + degrees + +
+ + Week Direction Change: + + + + degrees + +
+ + Month Dom Direction: + + + + degrees + +
+ + Year Dom Direction: + + + + degrees + +
+ + All Time Direction: + + + + degrees + +
+ + Current Speed: + + + + + +
+ + Hour Avg Speed: + + + + + +
+ + Hour Speed Change: + + + + + +
+ + Day Avg Speed: + + + + + +
+ + Day Speed Change: + + + + + +
+ + Day High Speed: + + + +   at   + + +
+ + Week Avg Speed: + + + + + +
+ + Week Speed Change: + + + + + +
+ + Month Avg Speed: + + + + + +
+ + Month High Speed: + + + +  on  + + +
+ + Year Avg Speed: + + + + + +
+ + Year High Speed: + + + +  on  + + +
+ + All Time Avg Speed: + + + + + +
+ + All Time High Speed: + + + +  on  + + +
+
  + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + Current: + + + + + +
+ + Hour Average: + + + + + +
+ + Hour Change: + + + + + +
+ + Day Average: + + + + + +
+ + Day Change: + + + + + +
+ + Day High: + + + +   at   + + +
+ + Day Low: + + + +   at   + + +
+ + Week Average: + + + + + +
+ + Week Change: + + + + + +
+ + Month Average: + + + + + +
+ + Month High: + + + +  on  + + +
+ + Month Low: + + + +  on  + + +
+ + Year Average: + + + + + +
+ + Year High: + + + +  on  + + +
+ + Year Low: + + + +  on  + + +
+ + All Time Average: + + + + + +
+ + All Time High: + + + +  on  + + +
+ + All Time Low: + + + +  on  + + +
+
  + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + Rate: + + + + + +
+ + Day: + + + + + +
+ + Storm: + + + + + +
+ + Storm Start: + + + + + +
+ + Day High Rate: + + + +   at   + + +
+ + Month: + + + + + +
+ + Month High Rate: + + + +  on  + + +
+ + Year (): + + + + + +
+ + Year High Rate: + + + +  on  + + +
+ + All Time High Rate: + + + +  on  + + +
+
+
+
+ + + + + + + + +
+ + + + + + wviewweather.com + + + + + +
+
+
+ + diff --git a/examples/html/classic/standard/awekas_wl.htx-metric b/examples/html/classic/standard/awekas_wl.htx-metric new file mode 100644 index 0000000..29b5be4 --- /dev/null +++ b/examples/html/classic/standard/awekas_wl.htx-metric @@ -0,0 +1,32 @@ +AWEKAS_Template_start
+
+
+
+
+
+
+
+
+
+-
+-
+-
+-
+-
+-
+-
+
+
+
+
+
+
+
+
+
+
+w/m^2
+
+index
+Template_V1.5
+ diff --git a/examples/html/classic/standard/awekas_wl.htx-us b/examples/html/classic/standard/awekas_wl.htx-us new file mode 100644 index 0000000..917aa40 --- /dev/null +++ b/examples/html/classic/standard/awekas_wl.htx-us @@ -0,0 +1,32 @@ +AWEKAS_Template_start
+
+
+
+
+
+
+
+
+
+-
+-
+-
+-
+-
+-
+-
+
+
+
+
+
+
+
+
+
+
+w/m^2
+
+index
+Template_V1.5
+ diff --git a/examples/html/classic/standard/data.htx b/examples/html/classic/standard/data.htx new file mode 100644 index 0000000..058b659 --- /dev/null +++ b/examples/html/classic/standard/data.htx @@ -0,0 +1,7 @@ +Current Conditions as of : Temperature +Humidity Dewpoint +Wind at +Barometer Wind Chill +Heat Index +Today's Rain Storm Total +Monthly Total Yearly Total Current Rain Rate \ No newline at end of file diff --git a/examples/html/classic/standard/graphics.conf b/examples/html/classic/standard/graphics.conf new file mode 100644 index 0000000..3d1a6ca --- /dev/null +++ b/examples/html/classic/standard/graphics.conf @@ -0,0 +1,165 @@ +#@ +# +# This file contains configuration information for graphics. +# +# Note: For parameters that enable/disable a feature, a value of "0" disables +# the feature and "1" enables it... +# +# Color values are RRGGBBTT where TT is the transparency level. +# + +################################# BUCKETS ################################# + +# Transparent background (0/1)? +BUCKET_TRANSPARENT=0 + +# Background color for the bucket image +BUCKET_BG_COLOR=0xF5F5F500 + +# Color for the lines and ticks in the bucket plot +BUCKET_FG_COLOR=0x00000000 + +# Color for the filled region of the bucket +BUCKET_CONTENT_COLOR=0x4282B400 + +# Color for line indicating high value +BUCKET_HIGH_COLOR=0xFF000000 + +# Color for line indicating low value +BUCKET_LOW_COLOR=0x00BFFF00 + +# Background color for the title +BUCKET_TITLE_BG_COLOR=0xD8D8D800 + +# Foreground color for the title +BUCKET_TITLE_FG_COLOR=0x00000000 + +# Color of text labels +BUCKET_TEXT_COLOR=0x00000000 + +# Width of bucket image +BUCKET_IMAGE_WIDTH=120 + +# Height of bucket image +BUCKET_IMAGE_HEIGHT=240 + +# Width of bucket itself +BUCKET_WIDTH=36 + + +################################# LINE CHARTS ################################# + +# Transparent background for all charts (0/1)? +CHART_TRANSPARENT=0 + +# Background color for the chart image rectangle +CHART_IMAGE_BG_COLOR=0xF5F5F500 + +# Background color for the plot +CHART_GRAPH_BG_COLOR=0xD8D8D800 + +# Color of the grid lines +CHART_GRID_COLOR=0xA0A00000 + +# Color of line for primary data values +CHART_FIRST_LINE_COLOR=0x4282B400 + +# Color of line for secondary data values +CHART_SECOND_LINE_COLOR=0xB4424200 + +# Color of line for tertiary data values +CHART_THIRD_LINE_COLOR=0x42B44200 + +# Background color for title +CHART_TITLE_BG_COLOR=0xD8D8D800 + +# Foreground color for title +CHART_TITLE_FG_COLOR=0x00000000 + +# Color for text labels +CHART_TEXT_COLOR=0x00000000 + +# Width of image +CHART_WIDTH=300 + +# Height of image +CHART_HEIGHT=180 + + +################################# BAR CHARTS ################################# + +# Background color for the image +BAR_IMAGE_BG_COLOR=0xF5F5F500 + +# Background color for the plot +BAR_GRAPH_BG_COLOR=0xD8D8D800 + +# Color for the bars +BAR_BAR_COLOR=0x4282B400 + +# Background color for the title bar +BAR_TITLE_BG_COLOR=0xD8D8D800 + +# Foreground color for the title bar +BAR_TITLE_FG_COLOR=0x00000000 + +# Color for grid lines +BAR_GRID_COLOR=0xA0A00000 + +# Color for text labels +BAR_TEXT_COLOR=0x00000000 + +# Width of image +BAR_WIDTH=300 + +# Height of image +BAR_HEIGHT=180 + + +################################# DIAL PLOTS ################################# + +# Transparent background (0/1)? +DIAL_TRANSPARENT=0 + +# Background color for dial +DIAL_BG_COLOR=0xD8D8D800 + +# Background color for image (doesn't matter becasue this color is transparent) +DIAL_IMAGE_BG_COLOR=Ox12121200 + +# Color of circle at center of dial +DIAL_CENTER_COLOR=0x6666FF00 + +# Text color of current value in center of dial +DIAL_CENTER_TEXT_COLOR=0x0000ff00 + +# Text color of value for high wind in center of plot +DIAL_CENTER_HIGH_COLOR=0xff000000 + +# Fill color of pointer +DIAL_POINTER_COLOR=0x6666FF00 + +# Outline color of pointer +DIAL_POINTER_OUTLINE_COLOR=0x6666ff00 + +# Color of tick for high value +DIAL_HIGH_COLOR=0xff000000 + +# Color of tick for low value +DIAL_LOW_COLOR=0x0000ff00 + +# Text color for high value on Temperature plot +DIAL_APP_COLOR=0x80808000 + +# Text color of labels +DIAL_TEXT_COLOR=0x00000000 + +# Width of image +DIAL_IMAGE_WIDTH=160 + +# Diameter of dial +DIAL_DIAMETER=156 + +# Diameter of circle at center of dial +DIAL_CTR_DIAMETER=15 + diff --git a/examples/html/classic/standard/header-big.incx b/examples/html/classic/standard/header-big.incx new file mode 100644 index 0000000..e969430 --- /dev/null +++ b/examples/html/classic/standard/header-big.incx @@ -0,0 +1,43 @@ + + + + + + +
+ + + + + + + + + + + + + + + +
+ + , Weather +
+ + - - + +
+ + + +
+ + Sunrise: - Midday: - Sunset: - + Day Length:
+ Civil Twilight: -    + Astronomical Twilight: - +

+
+
+ diff --git a/examples/html/classic/standard/header.incx b/examples/html/classic/standard/header.incx new file mode 100644 index 0000000..f34547b --- /dev/null +++ b/examples/html/classic/standard/header.incx @@ -0,0 +1,40 @@ + + + + + + +
+ + + + + + + + + + + + + + + +
+ + , Weather +
+ + - - + +
+ + + +
+ + Sunrise: - Sunset: - Moon: + +
+
+ diff --git a/examples/html/classic/standard/html-templates.conf b/examples/html/classic/standard/html-templates.conf new file mode 100644 index 0000000..9a83a3f --- /dev/null +++ b/examples/html/classic/standard/html-templates.conf @@ -0,0 +1,76 @@ +# +# html-templates-classic.conf - define the HTML template files to use for HTML +# and generic generation +# +# Note: Template expansion is performed in the order the files are listed +# in this file. Thus, files to be included in other templates should +# be listed before the templates that include them. + +# File Format +# +# 1) lines beginning with '#' or are ignored +# + +# Column Format +# +# 1) template file name (must be in /etc/wview/html) +# + +############################################################################### +### Standard Include Macros (should be generated first) +############################################################################### + +header.incx +header-big.incx +nav-buttons.incx +readings.incx + +############################################################################### +### RSS Feed XML Template +############################################################################### + +### Standard Weather RSS feed template - file generated is wxrss.xml and can +### be added to any html template to add an RSS feed to that page +wxrss.xtx + + +############################################################################### +### Home Page Templates +############################################################################### + +### Standard Home Page template +#index.htx + +### Day/Night Home Pages - if you want a different homepage template based on +### day or night, uncomment these and copy your templates to +### /etc/wview/html/index-day.htx and /etc/wview/html/index-night.htx +### respectively +index-day.htx +index-night.htx + + +############################################################################### +### Historical Data Templates +############################################################################### + +### Standard (no extra sensors) Weather Data +almanac.htx +Current.htx +Daily.htx +Weekly.htx +Monthly.htx +Yearly.htx +parameterlist.htx + +############################################################################### +### Current Conditions (Table Only) Template +############################################################################### + +#Current_Conditions.htx + +############################################################################### +### AWEKAS Data Submission Template +############################################################################### + +#awekas_wl.htx + diff --git a/examples/html/classic/standard/images-metric.conf b/examples/html/classic/standard/images-metric.conf new file mode 100755 index 0000000..1beab44 --- /dev/null +++ b/examples/html/classic/standard/images-metric.conf @@ -0,0 +1,231 @@ +# +# images-metric.conf - configure what images wview will generate +# + +# File Format +# +# -> lines beginning with '#' or are ignored +# -> columns are whitespace delimited +# + +# Column Format +# +# 1) image filename +# 2) image label (can be translated as desired) +# 3) image unit label (can be translated as desired) +# 4) decimal places +# 5) image generator function index (see images.c) +# + +# Enabling/Disabling Image Generation +# +# -> To enable the generation of an image, uncomment it (remove any '#' + characters in front of the image definition) +# -> To disable the generation of an image, comment it out by placing one or + more '#' characters in front of it + + +################# D I A L S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +tempdial.png "Temp" C 0 109 +humiddial.png "Humidity" % 0 110 +wind.png "Wind" none 0 11 + + +################# B U C K E T S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +temp.png "Temperature" C 1 0 +humid.png "Humidity" % 1 1 +dew.png "Dewpoint" C 1 2 +wchill.png "Wind Chill" C 1 3 +hindex.png "Heat Index" C 1 4 +barom.png "Barometer" hPa 1 5 +dayrain.png "Day Rain" mm 1 6 +stormrain.png "Storm Rain" mm 1 7 +rainrate.png "Rain Rate" mm/hour 1 8 +monthrain.png "Month Rain" mm 1 9 +yearrain.png "Year Rain" mm 1 10 + + +################# C H A R T S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +#### DAY VALUES #### +tempdaycomp.png "Temperature/Dewpoint" C 0 42 +heatchillcomp.png "Wind Chill/Heat Index" C 0 43 +## intempdaycomp.png "Inside Temp/Inside Humidity" C/% 0 115 +humidday.png "Humidity" % 0 15 +wspeeddaycomp.png "Wind/Gust" km/h 0 137 +## wspeedday.png "Wind" km/h 0 21 +wdirday.png "Wind DIR" degrees 0 24 +## hiwspeedday.png "HI Wind" km/h 0 27 +baromday.png "Barometer" hPa 0 30 + +## tempday.png "Temperature" C 0 12 +## dewday.png "Dewpoint" C 0 18 +## wchillday.png "Wind Chill" C 0 36 +## hindexday.png "Heat Index" C 0 39 + +#### WEEK VALUES #### +tempweekcomp.png "Temperature/Dewpoint" C 0 127 +heatchillweekcomp.png "Wind Chill/Heat Index" C 0 128 +## intempweekcomp.png "Inside Temp/Inside Humidity" C/% 0 129 +humidweek.png "Humidity (Hourly Avg)" % 0 118 +wspeedweekcomp.png "Wind/Gust" km/h 0 138 +## wspeedweek.png "Wind (Hourly Avg)" km/h 0 120 +wdirweek.png "Wind DIR (Hourly Avg)" degrees 0 121 +## hiwspeedweek.png "HI Wind (Hourly Avg)" km/h 0 122 +baromweek.png "Barometer (Hourly Avg)" hPa 0 123 + +## tempweek.png "Temperature (Hourly Avg)" C 0 117 +## dewweek.png "Dewpoint (Hourly Avg)" C 0 119 +## wchillweek.png "Wind Chill (Hourly Avg)" C 0 125 +## hindexweek.png "Heat Index (Hourly Avg)" C 0 126 + +#### MONTH VALUES #### +tempmonthcomp.png "Temperature/Dewpoint" C 0 44 +heatchillmonthcomp.png "Wind Chill/Heat Index" C 0 45 +## intempmonthcomp.png "Inside Temp/Inside Humidity" C/% 0 116 +humidmonth.png "Humidity (Hourly Avg)" % 0 16 +wspeedmonthcomp.png "Wind/Gust" km/h 0 139 +## wspeedmonth.png "Wind (Hourly Avg)" km/h 0 22 +wdirmonth.png "Wind DIR (Hourly Avg)" degrees 0 25 +## hiwspeedmonth.png "HI Wind (Hourly Avg)" km/h 0 28 +barommonth.png "Barometer (Hourly Avg)" hPa 0 31 + +## tempmonth.png "Temperature (Hourly Avg)" C 0 13 +## dewmonth.png "Dewpoint (Hourly Avg)" C 0 19 +## wchillmonth.png "Wind Chill (Hourly Avg)" C 0 37 +## hindexmonth.png "Heat Index (Hourly Avg)" C 0 40 + +#### YEAR VALUES #### +tempyear.png "Temperature (Daily Avg)" C 0 14 +humidyear.png "Humidity (Daily Avg)" % 0 17 +dewyear.png "Dewpoint (Daily Avg)" C 0 20 +wspeedyear.png "Wind (Daily Avg)" km/h 0 23 +wdiryear.png "Wind DIR (Daily Avg)" degrees 0 26 +hiwspeedyear.png "HI Wind (Daily Avg)" km/h 0 29 +baromyear.png "Barometer (Daily Avg)" hPa 0 32 +wchillyear.png "Wind Chill (Daily Avg)" C 0 38 +hindexyear.png "Heat Index (Daily Avg)" C 0 41 + + +################# B A R C H A R T S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +rainday.png "Rain/hour" mm 0 33 +rainweek.png "Rain/day" mm 0 124 +rainmonth.png "Rain/day" mm 0 34 +rainyear.png "Rain/week" mm 0 35 + + + +################# W I N D R O S E S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +windroseday.png "Rose Day" none 0 133 +windroseweek.png "Rose Week" none 0 134 +windrosemonth.png "Rose Month" none 0 135 +windroseyear.png "Rose Year" none 0 136 + + +################# V P P L U S D A T A B U C K E T S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +## UV.png "UV Index" index 1 106 +## radiation.png "Radiation" watts/m^2 1 107 +## ET.png "ET (day)" mm 1 108 + + +################# V P P L U S D A T A C H A R T S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +## radiationDay.png "SolarRad" watts 0 46 +## radiationWeek.png "SolarRad (Hourly Avg)" watts 0 130 +## radiationMonth.png "SolarRad (Hourly Avg)" watts 0 47 +## radiationYear.png "SolarRad (Daily Avg)" watts 0 48 +## UVDay.png "UV Index" index 0 49 +## UVWeek.png "UV Index (Hourly Avg)" index 0 131 +## UVMonth.png "UV Index (Hourly Avg)" index 0 50 +## UVYear.png "UV Index (Daily Avg)" index 0 51 +## ETDay.png "EvaPot (Hourly)" mm/hr 1 52 +## ETWeek.png "EvaPot (Daily)" mm/day 0 132 +## ETMonth.png "EvaPot (Daily)" mm/day 0 53 +## ETYear.png "EvaPot (Weekly)" mm/week 0 54 +## leafTemp1Day.png "LeafTemp1" C 0 55 +## leafTemp1Month.png "LeafTemp1 (Hourly Avg)" C 0 56 +## leafTemp1Year.png "LeafTemp1 (Daily Avg)" C 0 57 +## leafTemp2Day.png "LeafTemp2" C 0 58 +## leafTemp2Month.png "LeafTemp2 (Hourly Avg)" C 0 59 +## leafTemp2Year.png "LeafTemp2 (Daily Avg)" C 0 60 +## leafWetness1Day.png "LeafWet1 Index" (0-15) 0 61 +## leafWetness1Month.png "LeafWet1 Index (Hourly Avg)" (0-15) 0 62 +## leafWetness1Year.png "LeafWet1 Index (Daily Avg)" (0-15) 0 63 +## leafWetness2Day.png "LeafWet2 Index" (0-15) 0 64 +## leafWetness2Month.png "LeafWet2 Index (Hourly Avg)" (0-15) 0 65 +## leafWetness2Year.png "LeafWet2 Index (Daily Avg)" (0-15) 0 66 +## soilTemp1Day.png "SoilTemp1" C 0 67 +## soilTemp1Month.png "SoilTemp1 (Hourly Avg)" C 0 68 +## soilTemp1Year.png "SoilTemp1 (Daily Avg)" C 0 69 +## soilTemp2Day.png "SoilTemp2" C 0 70 +## soilTemp2Month.png "SoilTemp2 (Hourly Avg)" C 0 71 +## soilTemp2Year.png "SoilTemp2 (Daily Avg)" C 0 72 +## soilTemp3Day.png "SoilTemp3" C 0 73 +## soilTemp3Month.png "SoilTemp3 (Hourly Avg)" C 0 74 +## soilTemp3Year.png "SoilTemp3 (Daily Avg)" C 0 75 +## soilTemp4Day.png "SoilTemp4" C 0 76 +## soilTemp4Month.png "SoilTemp4 (Hourly Avg)" C 0 77 +## soilTemp4Year.png "SoilTemp4 (Daily Avg)" C 0 78 +## extraHumid1Day.png "ExtraHumidity1" % 0 79 +## extraHumid1Month.png "ExtraHumidity1 (Hourly Avg)" % 0 80 +## extraHumid1Year.png "ExtraHumidity1 (Daily Avg)" % 0 81 +## extraHumid2Day.png "ExtraHumidity2" % 0 82 +## extraHumid2Month.png "ExtraHumidity2 (Hourly Avg)" % 0 83 +## extraHumid2Year.png "ExtraHumidity2 (Daily Avg)" % 0 84 +## extraTemp1Day.png "ExtraTemp1" C 0 85 +## extraTemp1Month.png "ExtraTemp1 (Hourly Avg)" C 0 86 +## extraTemp1Year.png "ExtraTemp1 (Daily Avg)" C 0 87 +## extraTemp2Day.png "ExtraTemp2" C 0 88 +## extraTemp2Month.png "ExtraTemp2 (Hourly Avg)" C 0 89 +## extraTemp2Year.png "ExtraTemp2 (Daily Avg)" C 0 90 +## extraTemp3Day.png "ExtraTemp3" C 0 91 +## extraTemp3Month.png "ExtraTemp3 (Hourly Avg)" C 0 92 +## extraTemp3Year.png "ExtraTemp3 (Daily Avg)" C 0 93 +## soilMoist1Day.png "SoilMoist1" cb 0 94 +## soilMoist1Month.png "SoilMoist1 (Hourly Avg)" cb 0 95 +## soilMoist1Year.png "SoilMoist1 (Daily Avg)" cb 0 96 +## soilMoist2Day.png "SoilMoist2" cb 0 97 +## soilMoist2Month.png "SoilMoist2 (Hourly Avg)" cb 0 98 +## soilMoist2Year.png "SoilMoist2 (Daily Avg)" cb 0 99 +## soilMoist3Day.png "SoilMoist3" cb 0 100 +## soilMoist3Month.png "SoilMoist3 (Hourly Avg)" cb 0 101 +## soilMoist3Year.png "SoilMoist3 (Daily Avg)" cb 0 102 +## soilMoist4Day.png "SoilMoist4" cb 0 103 +## soilMoist4Month.png "SoilMoist4 (Hourly Avg)" cb 0 104 +## soilMoist4Year.png "SoilMoist4 (Daily Avg)" cb 0 105 + + +################# V P P L U S D I A L S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +netRainDay.png "Precip Day" mm 0 112 +netRainMonth.png "Precip Month" mm 0 113 +netRainYear.png "Precip Year" mm 0 114 + + +################# D I A G N O S T I C S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +#rxcheck.png "RX Good Packets" % 0 111 + diff --git a/examples/html/classic/standard/images-user.conf b/examples/html/classic/standard/images-user.conf new file mode 100644 index 0000000..04b6a1c --- /dev/null +++ b/examples/html/classic/standard/images-user.conf @@ -0,0 +1,20 @@ +# +# images-user.conf - configure what user-defined images wview will generate +# + +# File Format +# +# 1) lines beginning with '#' or are ignored +# 2) whitespace delimited columns +# + +# Column Format +# +# 1) image filename +# 2) image label +# 3) image unit label +# 4) decimal places +# 5) image generator function index (see images-user.c) +# + +##intemp.png "In Temperature" degrees 1 0 diff --git a/examples/html/classic/standard/images.conf b/examples/html/classic/standard/images.conf new file mode 100755 index 0000000..88e0aed --- /dev/null +++ b/examples/html/classic/standard/images.conf @@ -0,0 +1,225 @@ +# +# images.conf - configure what images wview will generate +# + +# File Format +# +# -> lines beginning with '#' or are ignored +# -> columns are whitespace delimited +# + +# Column Format +# +# 1) image filename +# 2) image label (can be translated as desired) +# 3) image units label (can be translated as desired) +# 4) decimal places +# 5) image generator function index (see images.c) +# + +# Enabling/Disabling Image Generation +# +# -> To enable the generation of an image, uncomment it (remove any '#' + characters in front of the image definition) +# -> To disable the generation of an image, comment it out by placing one or + more '#' characters in front of it + + +################# D I A L S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +tempdial.png "Temp" F 0 109 +humiddial.png "Humidity" none 0 110 +wind.png "Wind" none 0 11 +netRainDay.png "Precip Day" in 0 112 +netRainMonth.png "Precip Month" in 0 113 +netRainYear.png "Precip Year" in 0 114 + + +################# B U C K E T S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +temp.png "Temperature" F 1 0 +humid.png "Humidity" % 1 1 +dew.png "Dewpoint" F 1 2 +wchill.png "Wind Chill" F 1 3 +hindex.png "Heat Index" F 1 4 +barom.png "Barometer" inHg 2 5 +dayrain.png "Day Rain" inches 2 6 +stormrain.png "Storm Rain" inches 2 7 +rainrate.png "Rain Rate" in/hour 2 8 +monthrain.png "Month Rain" inches 2 9 +yearrain.png "Year Rain" inches 2 10 + + +################# C H A R T S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +#### DAY VALUES #### +tempdaycomp.png "Temperature/Dewpoint" F 0 42 +heatchillcomp.png "Wind Chill/Heat Index" F 0 43 +## intempdaycomp.png "Inside Temp/Inside Humidity" F/% 0 115 +humidday.png "Humidity" % 0 15 +wspeeddaycomp.png "Wind/Gust" mph 0 137 +## wspeedday.png "Wind" mph 0 21 +wdirday.png "Wind DIR" degrees 0 24 +## hiwspeedday.png "HI Wind" mph 0 27 +baromday.png "Barometer" inHg 1 30 + +## tempday.png "Temperature" F 0 12 +## dewday.png "Dewpoint" F 0 18 +## wchillday.png "Wind Chill" F 0 36 +## hindexday.png "Heat Index" F 0 39 + +#### WEEK VALUES #### +tempweekcomp.png "Temperature/Dewpoint" F 0 127 +heatchillweekcomp.png "Wind Chill/Heat Index" F 0 128 +## intempweekcomp.png "Inside Temp/Inside Humidity" F/% 0 129 +humidweek.png "Humidity (Hourly Avg)" % 0 118 +wspeedweekcomp.png "Wind/Gust" mph 0 138 +## wspeedweek.png "Wind (Hourly Avg)" mph 0 120 +wdirweek.png "Wind DIR (Hourly Avg)" degrees 0 121 +## hiwspeedweek.png "HI Wind (Hourly Avg)" mph 0 122 +baromweek.png "Barometer (Hourly Avg)" inHg 1 123 + +## tempweek.png "Temperature (Hourly Avg)" F 0 117 +## dewweek.png "Dewpoint (Hourly Avg)" F 0 119 +## wchillweek.png "Wind Chill (Hourly Avg)" F 0 125 +## hindexweek.png "Heat Index (Hourly Avg)" F 0 126 + +#### MONTH VALUES #### +tempmonthcomp.png "Temperature/Dewpoint" F 0 44 +heatchillmonthcomp.png "Wind Chill/Heat Index" F 0 45 +## intempmonthcomp.png "Inside Temp/Inside Humidity" deg/% 0 116 +humidmonth.png "Humidity (Hourly Avg)" % 0 16 +wspeedmonthcomp.png "Wind/Gust" mph 0 139 +## wspeedmonth.png "Wind (Hourly Avg)" mph 0 22 +wdirmonth.png "Wind DIR (Hourly Avg)" degrees 0 25 +## hiwspeedmonth.png "HI Wind (Hourly Avg)" mph 0 28 +barommonth.png "Barometer (Hourly Avg)" inHg 1 31 + +## tempmonth.png "Temperature (Hourly Avg)" F 0 13 +## dewmonth.png "Dewpoint (Hourly Avg)" F 0 19 +## wchillmonth.png "Wind Chill (Hourly Avg)" F 0 37 +## hindexmonth.png "Heat Index (Hourly Avg)" F 0 40 + +#### YEAR VALUES #### +tempyear.png "Temperature (Daily Avg)" F 0 14 +humidyear.png "Humidity (Daily Avg)" % 0 17 +dewyear.png "Dewpoint (Daily Avg)" F 0 20 +wspeedyear.png "Wind (Daily Avg)" mph 0 23 +wdiryear.png "Wind DIR (Daily Avg)" degrees 0 26 +hiwspeedyear.png "HI Wind (Daily Avg)" mph 0 29 +baromyear.png "Barometer (Daily Avg)" inHg 1 32 +wchillyear.png "Wind Chill (Daily Avg)" F 0 38 +hindexyear.png "Heat Index (Daily Avg)" F 0 41 + + +################# B A R C H A R T S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +rainday.png "Rain/hour" inches 2 33 +rainweek.png "Rain/day" inches 1 124 +rainmonth.png "Rain/day" inches 1 34 +rainyear.png "Rain/week" inches 1 35 + + + +################# W I N D R O S E S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +windroseday.png "Rose Day" none 0 133 +windroseweek.png "Rose Week" none 0 134 +windrosemonth.png "Rose Month" none 0 135 +windroseyear.png "Rose Year" none 0 136 + + +################# V P P L U S D A T A B U C K E T S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +## UV.png "UV Index" index 1 106 +## radiation.png "Radiation" watts/m^2 1 107 +## ET.png "ET (day)" inches 3 108 + + +################# V P P L U S D A T A C H A R T S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +## radiationDay.png "SolarRad" watts 0 46 +## radiationWeek.png "SolarRad (Hourly Avg)" watts 0 130 +## radiationMonth.png "SolarRad (Hourly Avg)" watts 0 47 +## radiationYear.png "SolarRad (Daily Avg)" watts 0 48 +## UVDay.png "UV Index" index 0 49 +## UVWeek.png "UV Index (Hourly Avg)" index 0 131 +## UVMonth.png "UV Index (Hourly Avg)" index 0 50 +## UVYear.png "UV Index (Daily Avg)" index 0 51 +## ETDay.png "EvaPot (Hourly)" inches/hr 2 52 +## ETWeek.png "EvaPot (Daily)" inches/day 2 132 +## ETMonth.png "EvaPot (Daily)" inches/day 2 53 +## ETYear.png "EvaPot (Weekly)" inches/week 2 54 +## leafTemp1Day.png "LeafTemp1" F 0 55 +## leafTemp1Month.png "LeafTemp1 (Hourly Avg)" F 0 56 +## leafTemp1Year.png "LeafTemp1 (Daily Avg)" F 0 57 +## leafTemp2Day.png "LeafTemp2" F 0 58 +## leafTemp2Month.png "LeafTemp2 (Hourly Avg)" F 0 59 +## leafTemp2Year.png "LeafTemp2 (Daily Avg)" F 0 60 +## leafWetness1Day.png "LeafWet1 Index" (0-15) 0 61 +## leafWetness1Month.png "LeafWet1 Index (Hourly Avg)" (0-15) 0 62 +## leafWetness1Year.png "LeafWet1 Index (Daily Avg)" (0-15) 0 63 +## leafWetness2Day.png "LeafWet2 Index" (0-15) 0 64 +## leafWetness2Month.png "LeafWet2 Index (Hourly Avg)" (0-15) 0 65 +## leafWetness2Year.png "LeafWet2 Index (Daily Avg)" (0-15) 0 66 +## soilTemp1Day.png "SoilTemp1" F 0 67 +## soilTemp1Month.png "SoilTemp1 (Hourly Avg)" F 0 68 +## soilTemp1Year.png "SoilTemp1 (Daily Avg)" F 0 69 +## soilTemp2Day.png "SoilTemp2" F 0 70 +## soilTemp2Month.png "SoilTemp2 (Hourly Avg)" F 0 71 +## soilTemp2Year.png "SoilTemp2 (Daily Avg)" F 0 72 +## soilTemp3Day.png "SoilTemp3" F 0 73 +## soilTemp3Month.png "SoilTemp3 (Hourly Avg)" F 0 74 +## soilTemp3Year.png "SoilTemp3 (Daily Avg)" F 0 75 +## soilTemp4Day.png "SoilTemp4" F 0 76 +## soilTemp4Month.png "SoilTemp4 (Hourly Avg)" F 0 77 +## soilTemp4Year.png "SoilTemp4 (Daily Avg)" F 0 78 +## extraHumid1Day.png "ExtraHumidity1" % 0 79 +## extraHumid1Month.png "ExtraHumidity1 (Hourly Avg)" % 0 80 +## extraHumid1Year.png "ExtraHumidity1 (Daily Avg)" % 0 81 +## extraHumid2Day.png "ExtraHumidity2" % 0 82 +## extraHumid2Month.png "ExtraHumidity2 (Hourly Avg)" % 0 83 +## extraHumid2Year.png "ExtraHumidity2 (Daily Avg)" % 0 84 +## extraTemp1Day.png "ExtraTemp1" F 0 85 +## extraTemp1Month.png "ExtraTemp1 (Hourly Avg)" F 0 86 +## extraTemp1Year.png "ExtraTemp1 (Daily Avg)" F 0 87 +## extraTemp2Day.png "ExtraTemp2" F 0 88 +## extraTemp2Month.png "ExtraTemp2 (Hourly Avg)" F 0 89 +## extraTemp2Year.png "ExtraTemp2 (Daily Avg)" F 0 90 +## extraTemp3Day.png "ExtraTemp3" F 0 91 +## extraTemp3Month.png "ExtraTemp3 (Hourly Avg)" F 0 92 +## extraTemp3Year.png "ExtraTemp3 (Daily Avg)" F 0 93 +## soilMoist1Day.png "SoilMoist1" cbars 0 94 +## soilMoist1Month.png "SoilMoist1 (Hourly Avg)" cbars 0 95 +## soilMoist1Year.png "SoilMoist1 (Daily Avg)" cbars 0 96 +## soilMoist2Day.png "SoilMoist2" cbars 0 97 +## soilMoist2Month.png "SoilMoist2 (Hourly Avg)" cbars 0 98 +## soilMoist2Year.png "SoilMoist2 (Daily Avg)" cbars 0 99 +## soilMoist3Day.png "SoilMoist3" cbars 0 100 +## soilMoist3Month.png "SoilMoist3 (Hourly Avg)" cbars 0 101 +## soilMoist3Year.png "SoilMoist3 (Daily Avg)" cbars 0 102 +## soilMoist4Day.png "SoilMoist4" cbars 0 103 +## soilMoist4Month.png "SoilMoist4 (Hourly Avg)" cbars 0 104 +## soilMoist4Year.png "SoilMoist4 (Daily Avg)" cbars 0 105 + + +################# D I A G N O S T I C S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +#rxcheck.png "RX Good Packets" % 0 111 + diff --git a/examples/html/classic/standard/index-day.htx b/examples/html/classic/standard/index-day.htx new file mode 100644 index 0000000..bba9f05 --- /dev/null +++ b/examples/html/classic/standard/index-day.htx @@ -0,0 +1,374 @@ + + + + <!--stationCity-->,<!--stationState--> Weather + + + + + + + + +
+ + + + + + + + + + +
+ + + + + + + +
+ + + + + + + + + + +
+ +
+
+ + RADAR + +
+
+
+ + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + + + + + + + +
+ + + + + + + +
+ + + +
+
+ + + + + + + + + + + + + + + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+
+ + + + + + + +
+ + + +
+
+
+
+
+
+ + + + + + + + + + + +
+ + + + + +
+
+ + + + + + + + + +
+ + Climatological Summaries:  + + + + + +
+ + Browse Archive Records:  + + + +
+
+
+
+
+ + + + + + + + +
+ + + + + + wviewweather.com + + + +   + +
+
+
+ + diff --git a/examples/html/classic/standard/index-night.htx b/examples/html/classic/standard/index-night.htx new file mode 100644 index 0000000..3ba1d7d --- /dev/null +++ b/examples/html/classic/standard/index-night.htx @@ -0,0 +1,374 @@ + + + + <!--stationCity-->,<!--stationState--> Weather + + + + + + + + +
+ + + + + + + + + + +
+ + + + + + + +
+ + + + + + + + + + +
+ +
+
+ + RADAR + +
+
+
+ + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + + + + + + + +
+ + + + + + + +
+ + + +
+
+ + + + + + + + + + + + + + + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+
+ + + + + + + +
+ + + +
+
+
+
+
+
+ + + + + + + + + + + +
+ + + + + +
+
+ + + + + + + + + +
+ + Climatological Summaries:  + + + + + +
+ + Browse Archive Records:  + + + +
+
+
+
+
+ + + + + + + + +
+ + + + + + wviewweather.com + + + +   + +
+
+
+ + diff --git a/examples/html/classic/standard/index.htx b/examples/html/classic/standard/index.htx new file mode 100644 index 0000000..bba9f05 --- /dev/null +++ b/examples/html/classic/standard/index.htx @@ -0,0 +1,374 @@ + + + + <!--stationCity-->,<!--stationState--> Weather + + + + + + + + +
+ + + + + + + + + + +
+ + + + + + + +
+ + + + + + + + + + +
+ +
+
+ + RADAR + +
+
+
+ + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + + + + + + + +
+ + + + + + + +
+ + + +
+
+ + + + + + + + + + + + + + + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+
+ + + + + + + +
+ + + +
+
+
+
+
+
+ + + + + + + + + + + +
+ + + + + +
+
+ + + + + + + + + +
+ + Climatological Summaries:  + + + + + +
+ + Browse Archive Records:  + + + +
+
+
+
+
+ + + + + + + + +
+ + + + + + wviewweather.com + + + +   + +
+
+
+ + diff --git a/examples/html/classic/standard/nav-buttons.incx b/examples/html/classic/standard/nav-buttons.incx new file mode 100644 index 0000000..da5e454 --- /dev/null +++ b/examples/html/classic/standard/nav-buttons.incx @@ -0,0 +1,14 @@ + + + + +
+ + + + + + + +
+ diff --git a/examples/html/classic/standard/post-generate.sh b/examples/html/classic/standard/post-generate.sh new file mode 100644 index 0000000..b5426c3 --- /dev/null +++ b/examples/html/classic/standard/post-generate.sh @@ -0,0 +1,8 @@ +#/bin/sh + +# Add any post template generation processing or utilities here. Keep in mind this +# script runs in the htmlgend process's context so don't add anything that is overly +# time constrained. + +exit 0 + diff --git a/examples/html/classic/standard/pre-generate.sh b/examples/html/classic/standard/pre-generate.sh new file mode 100644 index 0000000..0c0d1cb --- /dev/null +++ b/examples/html/classic/standard/pre-generate.sh @@ -0,0 +1,8 @@ +#/bin/sh + +# Add any pre template generation processing or utilities here. Keep in mind this +# script runs in the htmlgend process's context so don't add anything that is overly +# time constrained. + +exit 0 + diff --git a/examples/html/classic/standard/readings.incx b/examples/html/classic/standard/readings.incx new file mode 100644 index 0000000..a0e712c --- /dev/null +++ b/examples/html/classic/standard/readings.incx @@ -0,0 +1,386 @@ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + Temperature: + + + + + +
+ + Wind Chill: + + + + + +
+ + Heat Index: + + + + + +
+ + Apparent Temp: + + + + + +
+ + Wet Bulb Temp: + + + + + +
+ + Dewpoint: + + + + + +
+ + Humidity: + + + + + +
+ + Barometer: + + + +    + +
+ + Wind: + + + +   at   +   + +
+ + High Wind: + + + + at + + +
+ + Recent Avg Wind: + + + + + +
+ + Recent Beaufort Scale: + + + + + +
+ + Today's Rain: + + + +   + +
+ + Rain Rate: + + + +   + +
+ + High Rain Rate: + + + + at + + +
+ + Storm Total: + + + +   + +
+ + Monthly Rain: + + + +   + +
+ + Yearly Rain (): + + + +   + +
+ + Air Density: + + + +   + +
+ + Est. Cumulus Base: + + + +   + +
+ + High Temperature: + + + + at + + +
+ + Low Temperature: + + + + at + + +
+ + High Heat Index: + + + + at + + +
+ + Low Wind Chill: + + + + at + + +
+ + High Humidity: + + + + at + + +
+ + Low Humidity: + + + + at + + +
+ + High Dewpoint: + + + + at + + +
+ + Low Dewpoint: + + + + at + + +
+ + High Barometer: + + + + at + + +
+ + Low Barometer: + + + + at + + +
+
+ diff --git a/examples/html/classic/standard/wxrss.xtx b/examples/html/classic/standard/wxrss.xtx new file mode 100644 index 0000000..423afed --- /dev/null +++ b/examples/html/classic/standard/wxrss.xtx @@ -0,0 +1,39 @@ + + + + + <!--stationCity--> <!--stationState--> + Insert_Your_WX_HomePage_URL_Here + Current Weather Conditions + en-us + , + 1 + + + + <!--stationCity-->,<!--stationState--> Weather Conditions: <!--stationDate--> <!--stationTime--> + Insert_Your_WX_HomePage_URL_Here + Current Weather Conditions + , + Insert_Your_WX_HomePage_URL_Here + - + + Temp:
+ Wind Chill:
+ Heat Index:
+ Humidity:
+ Dewpoint:
+ Barometer:
+ Wind: at
+ Rain Today:
+ Rain Rate:
+

+ ]]>
+
+
+
diff --git a/examples/html/classic/static/Clouds-blue.jpg b/examples/html/classic/static/Clouds-blue.jpg new file mode 100755 index 0000000000000000000000000000000000000000..13dab762949e42b0b7f1dedc328b311d2102251b GIT binary patch literal 6068 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 R#Hv@v_MP&-T=|82AbA?B)!BKmY*RIbeGR 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 literal 0 HcmV?d00001 diff --git a/examples/html/classic/static/above-clouds.jpg b/examples/html/classic/static/above-clouds.jpg new file mode 100755 index 0000000000000000000000000000000000000000..ecdebe5576a84825660689871721fb2ce20a0e36 GIT binary patch literal 3862 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 literal 0 HcmV?d00001 diff --git a/examples/html/classic/static/bucket_bg.png b/examples/html/classic/static/bucket_bg.png new file mode 100644 index 0000000000000000000000000000000000000000..65b293419e2d22c3a8db7bd667cbf5745b218ef5 GIT binary patch literal 13032 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 DfsCCx literal 0 HcmV?d00001 diff --git a/examples/html/classic/static/fc-icon-partlycloudy.gif b/examples/html/classic/static/fc-icon-partlycloudy.gif new file mode 100755 index 0000000000000000000000000000000000000000..b703c7e502d391a82487eac3830a5933b8fe2ee2 GIT binary patch literal 2086 zcmds$|5sD@8OJ~QMlK+D2@oPhB#I;vAs!ALX?U*Ixxcyj7$%43Z$-RDhqH$ zz-v_&k-1w1G}Kkn0TO20-8B)s#MyI4tzPNT-h;Gyu{y81>0O=Ye3t$jd(QKm^Land z^Ld@;`Qa%kU6a3}rX1t~5da9G!C)|%OlGs$VzF4QR-4Ucx7!^K2ae+eLAYElk|f=3 zx5wl0dc71y(KO9449l_{$MHNb2!beztj)?;%w7~QW&>@=p>nj87UkU}r2!&CvlauD zh6*&r5e`}lM3!OAIdmFe(ttpDm>j^_46Iqp6al8GvM70 z)|SRw0FN6u3!s7-D-b+(-l3%}fU^SL285v70Tcz1XCX)Nb|5%_;5Lgctw5kmP~w20 z+yVha0^oLwNLfV^6GT$I`4ZpZ46p)_F9W$X2}WI!ueW=UZBN<$_Nn}9qd)6Zm5f*U zrP8T|shjdTdRvvg5Y^eFXJ0lwP&-rNi>myRyHh=JRogRve4;qeuGwNJyWEsfM!x>q zud6obtyj9^em?kqzg`yR%33>kE<)my9+-%Ib!fkaeiW0tw(@1t*B>?B%_{G~{CaCw zctl}pantOqL>{VEXB4$+9~_U%+gEk}Lh7+ow>s6Ex$e2ku}cf`S5*&VerZUg&Umg` zKT_AWDDPp}liIKP3-QsLIk@r2qc>`shhcw`_L5W{vLFHL>(k%=n%VSu7_N*^r~HZt zl;)+Cr*AxmsgT9Lum4LRasN>6lq@2<9!O-xMyCYPRg2)pG%Rk z4IPP*I;T&fKbuK@)o zqZDZVwD03b`s@~k+6i0c;>#kxpNju*o#nnu`Lv~T!5d9ax}?>kH-UXo^Sge98k_b> zLyK)yGAz07Uew7M^V+QD9dkyf%kptaGV)wsT(SPxSg#^F^(H1&p8Y7%vEX8bMIO7u z(6#hMVfTY&cit}DRcp>1SskHyJXT4ZPp<~J>Um{Gx^Hgt__h6WuypN>Jr}>-!rtxamo_K8``EuRIvXP;iid$S+=_dZ;QJFx9#5qW zE@(Y?tKx{Guh!K2SQ@3*_1}>nQ2*dNzV+Z?5NpjIFebX@Tae^~Rn+J64Of37kEw9A zl$6A)bg2)}s;4-bkJfBX@qW|(%Uy@%>)Yck*>_BTzp~_dsk&(A23lpR99KR&H1>t@ zdBgcp^M4}tOtyp+Yy7SM?s+yc1^o}&GJCzblf7NdiUNbuRKG+$48j`cPs^9*gl*ES zDbN>Qm#f;aw%g8SO`5B5f%3LQSO4#N%2#K+EbslT-kBHKv|gTyo{m)M@a3Q-YA0y@ z4AqByQmy_2tei@k%bd-B6xAIOq3Vj=b~$uqpFehdYskcPqMX_4ln%+4hG+RCe{Q{@ z3Y}|-p7kSU%_Y^Na!Kf4v)dysbk7tYcPca{=_?6mFrx~|ATU{$Q!zoD11DCkYh0O& zNgpbftO~>0_};G5s3wTA=h*!?yFc@yPP2klQ2e z$e8a!*xs?g-mb=4Yy5#9nn;9*lK%0t+Y@zTEB^Mx_>OXiPqPRcFN(GA-XHDk?U`|A zxR*6{=&Ixf5`_?SSgbCEievUK&#?O!ox?H~5H&$qvQ z*827-Dk-7eM!Duv^OeVA0 zY_V9ZRx6I<1VNA_Nl}#B?WSqk-0C*!DZSb%0=NixNPQyhpDSDloC=3NTQHLtVR(eL=a~LNl2TaTMONcnjBILjUl8$g1I6fZGtp22!WIY zdaM*_gtQuZ%mzJ5({9LQW)p;rKNN%vq!=$5Um1fLztwt!T&cnoN-=_J4MwvSSE%(8 zxypi*a)nZYpqN61D%3bZqL`dPT7yY#Fr#v%1XH3)eJZLHp(>JcTdX)HR|pUlqF~y! zB7~`{c@m{kYf`9Xh)XF0tpb+KCm|T{*Xe}&d9%@_$+^OTK`!tCip=r2 zl4J4RJTTZ&J3jBjPb=PZp&xN1^^_cIPJ7Q$*M4X%GP2-|&+9%KS)yy@kJT22m%e=^ z#8_W=Qy8Ape`$eVa7Y)qePUrj)=?aq^sY_!@L7IIRT@fS;>W|IC)VZ}^7(ZMs6IIQ zzhw5{fxJVBn_6CH&vxDFd$aAAm*Tm4?W*+|&Hd%)i?g-|XG~l!8!&wlo3rnyFzi^- zt-jkIje|vY!J&|hb4l`%GW%r5_I+o0i^$aV*qoMMn;Nr6g6N#KKreViIv@@>ERg zg576zNBQl7(1NWmruV0@ovt1(FYW`DPh!7~9k?aV9$&&wyq4GlmItE$&bl`hkmW2W zItF&R9nuLzy zxpiT3*Pc9HTE!kIYm~~>01GNf>f7^6?eqB0z@0Ux^A4wQPI7hvyH}93-^St&eVd%z zH79aJwPY?ACB1BTW2Wne71Er@uN)PsZb*DG!1{(ec$mcD>im84U9m%`&u^o<_AONm zuRhd0F64xk#@S~=%batzV_Md9tN;12qS#fTS&r-NJgjJ1c5c8Y@krC7;hB(pV86xJ zRM|ORS9-~skCw)`*E{Z)&&*ED7ZxXUW}d0O6#SZt#zKey7AeOnfcbzy>?vq+3P3m0V#1S zPqK4)ERI)uxpvj2lybrRU(BN1wbx(VsJ_>`u2-YalAm~bFFwFnm9#6pwlkujto*)f zz}zOA<2OCGJ^bR#VH;~<_~bWz{@J!ZXZ{bJekn0}r#F8?{n#tlhg&fF<4@iWYSeQx z*F8V=F5$4;t4W@@;uFADzi-NoD|slgRT4_27>>`Yp>GlTu>os-JD{oXP4-?6uyFZUK^ZfmJ6lV2PR zH)npC?A@eeh1R|EDzTj#W=!D)WyKA_!Nk*zm1uj`(3ckiYw|dPC06|0^;c~*8RXTG z%Hq<&2(LYpAypSPZ!<$H=X zF%u1J-;_0ho=Y=Z$=wx2fpO;o!GU_y5)0;il~z})s}yRYFY+|$^_O(No12W_b{Ay& z^Hw!HEn&U&D(YWcF8I$-R^&~<-DoKHuU)8%oYsV&_jmSG4?Pl`wRL0`%L3*3zS^_( z#aX>C0&4pw!zWWZHdNauud(YBr@x^uDV4KmE_3d`)g{qcttGDjUa#eSpR-vLKNste#`G*`QC_^kq*si zG;}<$H_Izx)zb!9sqRj|%a-UHz4(sMWqs9URdmEDZh_yUo9{ni4{!hDk63WX-9}Jz zVsrRnb_Uon&UQu4-v3diYsNmE9Q>v^rX=I|i=M{boTx}?C-(T-y#wCf;En$QHSO22 literal 0 HcmV?d00001 diff --git a/examples/html/classic/static/fc-icon-partlycloudyandsnow.gif b/examples/html/classic/static/fc-icon-partlycloudyandsnow.gif new file mode 100755 index 0000000000000000000000000000000000000000..8198c8b5453c870f63611a69f2009ebf2fd9d107 GIT binary patch literal 2299 zcmchW{Z|uL8pm&5i4b6r0FfeO01*%|v7q4UZipOSQdFE34`SdICu40{Ay^G~03d`qoldXUV;ELaQerR|j7Fo$WHOu0R;v}qal7445QM|w za5|kXmy0AxilS(mW*Eln^|CC>aU9R{3{6|jX2xh>%qABCXiP`xG^7S4QG|0jSqDKX z0j<|?6iK3hLV-`r|0k#CNCe&NvlaQsT|&nh@3qI3sWncFqJK&#@-JnSCl9XGQGBOx~g6>?nsLoE305;C)RRco)VK z2yX|RZ*XdO0$9yPNa}c3CUgLXa_}w<=Rlwn;T-@v0nd3MV}>LOT?phT$T}g7K}rKj z1iAoZ43O4CS_dfG3eD(zEnI7IS1qj$eN&62YHI23Et+RHmh21)W=cjzz^nrU>pDBSKanQ^SdXfRrR zFAMzNE9K{nKmP?FgFqmQ${XtSG=XUJ-O-`GTdjd%LYY8yKMEv*%ID%s19whHpFDi> zuuSwxO7|LjVq9+NBAeO}IafICADWh2pa_ZmUep#}vCc68Cj0LX1h37Xb7$oRqMO<$ z+or@V+mhC1*et`+bB|m0JRqjZ`tJoqtRY5g(V32*h+myg>x>;-(T@J+<>ILRY*|+_ zRytz);j`+gRR!(ppFFQ+vf$bukA?{vH+Cvl?Mci3V)>Syuj^jTWkzjm-lo)#9Q8+9*|Y z-WijGqzzew{wZTXJXciO5L1&Ej~um?{$c3{e<2q(h9_48UAZ)JTZ4FUn$0bcDyy|Z z`5>9;?0gInO1%$$0sQgS!T4IHxK25X%8N?A|T(N}#{F;5?KKK*Uo!P>jm!navcYlJ(s zDOo+l_=r62$U;u^VR1hdRikQbi!!~M9k@!8no)^hi)DH=5mQZh&OF=NjQl!%p(VO% zB1E>-^53$zRaY;0q|sF~9$Eb9LabplL_6;u&)qnB*5fyz)&*L~shsHA2^3p;&FP^c z+e;g(E`@iuCe#O|m41mMSoJ&-T|M8Z&?uNC(T9tjU(g>_3Tq}rTVUxcQJ~uG7Fq+m zmioo&dJ<+G}*duzu&^o#CLbi5E}vpZf-$`e%UngZ1GD#>@dEQ3*-9~O#& zDiq(hMAy|a(&(OIZG*Tjr}|2L?{fF7U?fKMepAp;?$Gpws#q#0`v@wTyZrUV*68EK z>ISadFZG(RKlD#?mk&h-$BD^Rn|3Yl8FV)UY#sqkw_2`GQL&0QFE&icQ}ss#rv0ag z*_qhnzT+E~{ot13W7Y?=?y@eG2&l@Ay-buH88#>ePCdF%REKOor~^~_){IA;?6UUm zvx;Y{oBIxYEVPLJF;Cs`?79BDXzaXQSm4(%(``%dY-^OmfWK)j}Ya&F7mQ#KrK5Nz3VTpfRmb5>!p)7S(j(m5)Gd z{#pN&(pnhMo4BYtKWW`xn`&ydb_Df;jI#lU4we5dC8X4n{b|VdbA$Fb;Fv`k-&*JQ zmMkf!A?i_4>DC)JzfC@#drmeJEF7Cd)lC();@M%9BCl(5!nIMVoo$sIlcWbv<9lt> z%6s$Tl+vlNoo}YY&MTK^6?o*k*WZe~oJXaXU!L^eH9NfItBl*4lLG&`aHI0pZ1d`i z8o6twI4tm(RJ|Jw*qsumlu9hg)o90ibFmTERJV0RAHH2(9@al5Q4cOG+EV^7{I0fT zg|#7|M0ra!*mirR-Xk}<84oBOD=K}|WFZndiLFzllXgu0}+E0lDE%lCT`|UhB}LLlEM5U&l+aM?~#AK-(lFnh{fO+{{t*n B{s;g7 literal 0 HcmV?d00001 diff --git a/examples/html/classic/static/fc-icon-rain.gif b/examples/html/classic/static/fc-icon-rain.gif new file mode 100755 index 0000000000000000000000000000000000000000..faa5cd10f973dd6ea196b847d2e7fa5caae2fc4f GIT binary patch literal 2127 zcmeH`{Wp|}0)XEcGv=L4%rIl74Bi>GgY7tzI;B!O-^RxZ(JEuSqgjMPxcOSmhcV+L zj8)m{W_%QTV-rfp+RT`lOf6Z9Ro3;Db7Seol}x4U-u)l$kN5clo*$lb9{&Ko?*VZr z-~(&`z}D8*r%#_YH#a|i{P^L+hxhN_TP&81jg9s7^|iIN)z#INwRdmcyjfmeURqjO zTwGjOSeT!mfBEv|+^g4fb8|B@Gt<-4Q&Urulamt@6E9x87#|-W8yg$>eSTzQWO#UZ zXlUr!vuA^Yg98HtPoF+D4gGF1nG6PlUa!~bbiKX3j~+eh>FMe2?(XX9dSLkV{{8!% zot+&W9qsMyZEbCLJG$=Pz1!N_dgsoa=B_8r&CN|sO}B5~zIE$XLqkJ-eZ5wzt*x!S zdGqG=>({ScyY}53T}@5R)vH%8U%p&jU0qdGrO{|ADk{p$%gf5jN^84HOG}H3i`8ni zN~J0)DpD$ya=Bb8m0rAfv7n&fn{R*k=F;{2{QSJUyquhz?Ck8UtgOt;%#4hT^z`(! zw6xT$g4ER1bE;eCatqI%J)4}IeCEuV#KgpegoOC`c!@-E`t<3zxHvfPMr>@XNF<7h zi4h8g(b3T-Po9j5iaK%PL}X-SL_|dR*`o09@UYZx!@|NsLqm@pI~EcW5*!>H6ciK~ z7#I)`;P3Cx=ks|y9_Mrkhr?mB*(?^zPo&uQm26i~Hu;NG;Q#zD3he$<^5+WxxCJ0U zs-R7)SAY&)LUUW4L4|h~02d;Ub-u zWl(POite6N1MET@7e45Gh>lX(5L{5tiwXK6ty^#-JY1ht;^5BDZwADk$4d}U8a}s= zSJsg@)EC8~qlz%GG+SS+US@6YamrYuH>fIm*#&PO?s2m&A&`m27z$G+$B>dl6!Dce z(=>jSJ1b72#zTkVS^$JNy|0GoErGh_))lVoN~VVWy}o*w{oOOP4aqZ9y(vY^$*j;KGG^;8UT)k*W+X2BlHQpD?FL}L`K|ypc)lQvM~W@f@SV~lqGp|vpI_h+*)39b^bRgG z6SA$ci;X+PNZ>kjvJkv{aoH-Ln(^aK5ihPYI5zJ1gK+{}T3R3O3p@f%5^@E#`FkI; zRIV~&73fy(+XvE>Kp)0A8Kx;6CI+GT=)+-5B+d^{Fp=Q0I{wY{9&x`*j)f&~lU+IG zUS)JGj>LY-YE%{S?wBNpoqG5&DU3>ch0Hl0}qzW5L{A>CURC71}s^~L|Dy6 zMgZ3CjnInN+oCNbiQ8Bh*JxWgsFijvoukVwVwICPqRyD$t*w+}X`zsmhmib&v^`>( zv9dd`0;5Q)OZEteL2O>bZxjd>7fMaAT}wC2V#!E5g>_NwN}3KnPHA=2u!W9ds@8-}L>vJjK%t zwRHUZYaA&6=D8?{=gqHGo8N=<62{!eH<7hdKhT%=&*hC>BGNfL97-G>o{J(C_iKn^0#>7Nw6`xl=+bB<=9+1}9>v%)f<5-2VTV1Nd+5Y@ z_w(ikrbzlk#;vbF4)^Us|B7I4`>%{BAXsCiM^1%9b<_acK+j4+H4I&&3O;}BYm7o_ z!HX1-l8OZm`iY?*sZWqMu>+Avt<~e*|6qz%f@HfhARZjQ5A8E(!X2-A!d36x@nUc~ zaSfXGGyB@v2VX$yO2PLEkL~UIq{r!!(swAP-*%s4iqSRbuboXt*B6Eq0Fh?NOu)9E ZtfY31K*!*C)-L9tH1}(?(|zI^`t`QyirA3l6oT3UMl{{7 z`Eq`Ker|5=#fulSv$M~iKcAVInVz0Do6XOjJ)4@Enw*@Rn3xzJAAj=X$>Yb5A3b{X z;K75jv9Zz7QIpAZ|Ni}vk&)ry;d}S)-MxEvXlMvQkio&hfq?;|(b(VLZ!j42dj0L& zw{P9Lb@S%UzP`R2H*Q?Ne*N0DYrVa_Jv}|$-Q8NP_UhHEmoH!L>gu|5=~8EBXGcfJ zg$ozXpFiK;-rm;M*4o;t(P++|J$vTNnU1HBoeV$48w3`Wo1Q0 z#o@z;%gf6T9XeE2RwfdO4jeeJfB*iHl9J-$;-aFW!otFWf`a_~{M_8!y?gfxg~C01 z_T=Q`WM^k*Wo2b%W@cn$2n2%NyLYFhrKP5(rlh3o+O=!v&Ye4U>_|>dPD)BjOibLq zeS1PeLVSFDTwENV&yS6bjfshgj*i~Cb!${qRAgi%kH_P3xf~8BA|fIzER4-&vskR) z;9%hY{O<+){!;#W0U)dZAc4x$Y7A0>oo@=FZSAijdqy=QT6h2j`s|x*T+~a6P}uj$ zHLCWZBOYr~%m7<<&&Jg^8Y=6_>Bss;G%~~zd8~cN4+$_se_>RL_ni-?J;$Bsh%G?) zf(*1w&CSInn<0bD)+f!3jD`_ZzpyK2_5SFAD%_6EBp&mLL2m8zy-s8?%Ts&DpuUS# z%Mg;APXIv~QUs&xT>{FV?v+H4Xc6jgL(ln=J?j&jyoD;t>(WF2GMiCvT+B6nM zs6mkqkFFW8Ka(%M_Tb zQ*kQ7bZX|qYk06EoI6O5x4Y7E!_fpFv+$rdM(H#5YIC$eR+y_v_1?pUbS&i07*-xs0BAzW{aYqtpvSuA=_y=l@gj{!Q&4A?X>vjv;)Qj~BYIc+-0-SB?w6r-FMK$vb!AZHAa0#z zi^651ds^X^*QrvtmLfw0FEx@f!=PO6&w*&%l5dG5TqT{9-Ia zDYhg%6?+gVmG>#f77eFThB=4(kWnT$Efiu}e&q2yjoBcBA4}~kJmLVr zcuSpz<-@?*Y6bWhT|bJ)j1miBa_LhEAzcVsGg*`(9+X*bmZ2SI0hYZI6Fe>tTo4o7 zeGIPFOQEO~3GQdIazm{rQ`)ORW11k6$|UyH1C2$=b1XXeSm8^KZMph-3M^&H(8BZz zcTM{$pa$jM%crDkS*(qEwxoReES`u0wrOy#3lISy8*F}JN@RD$SZ4_J%EA^sn{C&Q z+ab2?V@ll~9hre literal 0 HcmV?d00001 diff --git a/examples/html/classic/static/fc-icon-snowandrain.gif b/examples/html/classic/static/fc-icon-snowandrain.gif new file mode 100755 index 0000000000000000000000000000000000000000..4d802b46adb828b9aa14a3f6c97ff3789fb4b522 GIT binary patch literal 2583 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 G7XBZJBE3EU literal 0 HcmV?d00001 diff --git a/examples/html/classic/static/fc-icon-sunny.gif b/examples/html/classic/static/fc-icon-sunny.gif new file mode 100755 index 0000000000000000000000000000000000000000..c3e988ce712600db5be3abb86bf59480968d1253 GIT binary patch literal 1555 zcmW-edrVt(6vuz1?FCxa-BO_CA>*bDnPVAun@WMrwiE_+D0H|Xvf0wT0aq-ujCQ6g z<*}_i?H-O!=>~JtVc15O8EMk;xENWkdth~lR}ffzOfN>wT{Pxr_2ipRzTb1sA166m ziwd%y*Oh<`;1K{2!a%j2tk49^Mh^-|jgnB}UL5mcDD4>x4!FD$KoefdVfJExzyK2< zDVKvX>xnghTmuLh8no*J6*!3lQi+nX5J5l*f@US9#{z0VsR0wDs0x(Q0Lq95D?%eY zXvC`EA+6-uyl6K;>1EcLI2^hjihv)*(1sIPKdX>xo3I_nBT+oX%Au|YIzzj(}^kUG1LYjcVK^QPX zQUeJbdQs>JE$AVshNKb_81zO!Z|E2b(q2fJA!USt3K-BpQU*yJl4~F#fdmSP2uOQi z&U*H~raW~z(p0UK<; zakUo%mZBY|aO8yzB^D04xz4u2cICE{9fm1n#R&qHG$)M{`&@COkOOKP$u3LdF%ZcS zC|0e1uwJxUmYyS3Jj>y~q*{8Eyx0HcL|fC!+_VGfLJqgZqS|u6Aly^-UL{)ZNql@!4g?>aZN!~3>kPM?_}J?9QfGR}rib$zBFw)b{OV{F7jp-^?j*Ww7qpI_2B1ieq*pBCj015x8rX%*PI{Q zU6nD1j>mwAPTdnZtEvutcJHY5q4dBP;>?DbHmo*#sWrB!Fh)LfC(XfOMLjM&=|APd z8yZ&5Y}~)1H?wKVr7*@#@iP+QP^)|EhZ~VsBDE=Dw!%l5ny7ySTSkU;`L-PfR4cAyLG1b@V9+@s3 z)aiQZkuHwlSJ?v)#-IJfCf;QzB_vhDek8em4x2ZPb?Y-i!x$AU=7vw0~ROc4+RuCFXKZyUq6x?h@b5_-*ZS}O=;q8Z@w#MM^{c~ zs(1Cfe~sTaU4wkKpYtL$x_iW`1xUnBaf*T~VS){?)g3Hg{1`AhP%j1AjFY z`c8k3+M|{ic(N` + + + Current: <!--stationCity--> <!--stationState--> + + + + + + + + + + + + + +
+ + + +
+ +

+Current Weather Conditions +

+ + + + + + + +
+ + + + + + + + + + + + +
+ + + + + + +
 
+ + + + + +
 
+ + + +
+
+ + + +
+ +
+ + + + + diff --git a/examples/html/exfoliation/plus/Current_Conditions.htx b/examples/html/exfoliation/plus/Current_Conditions.htx new file mode 100644 index 0000000..f14db7f --- /dev/null +++ b/examples/html/exfoliation/plus/Current_Conditions.htx @@ -0,0 +1,80 @@ + + + + Current: <!--stationCity--> <!--stationState--> + + + + + + + + + + + + + + +
+
+ - -
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
Temperature:
Humidity:
Wind: at
Barometer:
Today's Rain:
Storm Total:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
Wind Chill:
Heat Index:
Dewpoint:
Rain Rate:
Monthly Rain:
Yearly Rain:
+
+ + + diff --git a/examples/html/exfoliation/plus/Daily.htx b/examples/html/exfoliation/plus/Daily.htx new file mode 100644 index 0000000..a14cb4e --- /dev/null +++ b/examples/html/exfoliation/plus/Daily.htx @@ -0,0 +1,72 @@ + + + + 24 Hours: <!--stationCity--> <!--stationState--> + + + + + + + + + + + + + +
+ + + +
+ +

+Observations for the Last 24 Hours +

+ + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +
+ + + + + diff --git a/examples/html/exfoliation/plus/Monthly.htx b/examples/html/exfoliation/plus/Monthly.htx new file mode 100644 index 0000000..7931131 --- /dev/null +++ b/examples/html/exfoliation/plus/Monthly.htx @@ -0,0 +1,72 @@ + + + + 28 Days: <!--stationCity--> <!--stationState--> + + + + + + + + + + + + + +
+ + + +
+ +

+Observations for the Last 28 Days +

+ + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +
+ + + + + diff --git a/examples/html/exfoliation/plus/Weekly.htx b/examples/html/exfoliation/plus/Weekly.htx new file mode 100644 index 0000000..7011a5c --- /dev/null +++ b/examples/html/exfoliation/plus/Weekly.htx @@ -0,0 +1,72 @@ + + + + 7 Days: <!--stationCity--> <!--stationState--> + + + + + + + + + + + + + +
+ + + +
+ +

+Observations for the Last 7 Days +

+ + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +
+ + + + + diff --git a/examples/html/exfoliation/plus/Yearly.htx b/examples/html/exfoliation/plus/Yearly.htx new file mode 100644 index 0000000..ca2483b --- /dev/null +++ b/examples/html/exfoliation/plus/Yearly.htx @@ -0,0 +1,76 @@ + + + + Year: <!--stationCity--> <!--stationState--> + + + + + + + + + + + + + +
+ + + +
+ +

+Observations for the Last 365 Days +

+ + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +
+ + + + + diff --git a/examples/html/exfoliation/plus/almanac.htx b/examples/html/exfoliation/plus/almanac.htx new file mode 100644 index 0000000..8191dbc --- /dev/null +++ b/examples/html/exfoliation/plus/almanac.htx @@ -0,0 +1,736 @@ + + + + Almanac: <!--stationCity--> <!--stationState--> + + + + + + + + + + + + + +
+ + + + + + + + + + + + +
+Climatological Summaries:  + + + +
+Browse Archive Records:  + + +
+ +
+ +

Almanac Data

+ + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
Time:
Date:
+
+ + + + + + + + + + + + + + + + + +
Station:
Latitude:
Longitude:
Elevation:
+
+ + + + + + + + + + + + + + + + + +
Sunrise:
Midday:
Sunset:
Day Length:
+
+ + + + + + + + + + + + + + + + + +
Civil Twilight: -
Astronomical Twilight: -
Moonrise:
Moonset:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TemperatureDew PointHumidity
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current:
Hour Average:
Hour Change:
Day Average:
Day Change:
Day High: at 
Day Low: at 
Week Average:
Week Change:
Month Average:
Month High: on 
Month Low: on 
Year Average:
Year High: on 
Year Low: on 
All Time Average:
All Time High: on 
All Time Low: on 
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current:
Hour Average:
Hour Change:
Day Average:
Day Change:
Day High: at 
Day Low: at 
Week Average:
Week Change:
Month Average:
Month High: on 
Month Low: on 
Year Average:
Year High: on 
Year Low: on 
All Time Average:
All Time High: on 
All Time Low: on 
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current:
Hour Average:
Hour Change:
Day Average:
Day Change:
Day High: at 
Day Low: at 
Week Average:
Week Change:
Month Average:
Month High: on 
Month Low: on 
Year Average:
Year High: on 
Year Low: on 
All Time Average:
All Time High: on 
All Time Low: on 
+
Wind ChillHeat IndexWind Run
+ + + + + + + + + + + + + + + + + + + + + +
Current:
Day Low: at 
Month Low: on 
Year Low: on 
All Time Low: on 
+
+ + + + + + + + + + + + + + + + + + + + + +
Current:
Day High: at 
Month High: on 
Year High: on 
All Time High: on 
+
+ + + + + + + + + + + + + + + + + + + + + +
Hour:
Day:
Week:
Month:
Year:
+
WindBarometerRain
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current Direction: degrees
Hour Dom Direction: degrees
Hour Direction Change: degrees
Day Dom Direction: degrees
Day Direction Change: degrees
Week Dom Direction: degrees
Week Direction Change: degrees
Month Dom Direction: degrees
Year Dom Direction: degrees
All Time Direction: degrees
Current Speed:
Hour Avg Speed:
Hour Speed Change:
Day Avg Speed:
Day Speed Change:
Day High Speed: at 
Week Avg Speed:
Week Speed Change:
Month Avg Speed:
Month High Speed: on 
Year Avg Speed:
Year High Speed: on 
All Time Avg Speed:
All Time High Speed: on 
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current:
Hour Average:
Hour Change:
Day Average:
Day Change:
Day High: at 
Day Low: at 
Week Average:
Week Change:
Month Average:
Month High: on 
Month Low: on 
Year Average:
Year High: on 
Year Low: on 
All Time Average:
All Time High: on 
All Time Low: on 
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Rate:
Day:
Storm:
Storm Start:
Day High Rate: at 
Month:
Month High Rate: on 
Year ():
Year High Rate: on 
All Time High Rate: on 
+
+ +
+ + + + + diff --git a/examples/html/exfoliation/plus/awekas_wl.htx-metric b/examples/html/exfoliation/plus/awekas_wl.htx-metric new file mode 100644 index 0000000..a5b1ce7 --- /dev/null +++ b/examples/html/exfoliation/plus/awekas_wl.htx-metric @@ -0,0 +1,32 @@ +AWEKAS_Template_start
+
+
+
+
+
+
+
+
+
+-
+-
+-
+-
+-
+-
+-
+
+
+
+
+
+
+
+
+
+
+w/m^2
+
+index
+Template_V1.5
+ diff --git a/examples/html/exfoliation/plus/awekas_wl.htx-us b/examples/html/exfoliation/plus/awekas_wl.htx-us new file mode 100644 index 0000000..ecc2cf3 --- /dev/null +++ b/examples/html/exfoliation/plus/awekas_wl.htx-us @@ -0,0 +1,32 @@ +AWEKAS_Template_start
+
+
+
+
+
+
+
+
+
+-
+-
+-
+-
+-
+-
+-
+
+
+
+
+
+
+
+
+
+
+w/m^2
+
+index
+Template_V1.5
+ diff --git a/examples/html/exfoliation/plus/curreadings.incx b/examples/html/exfoliation/plus/curreadings.incx new file mode 100644 index 0000000..68906e9 --- /dev/null +++ b/examples/html/exfoliation/plus/curreadings.incx @@ -0,0 +1,121 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current Conditions
Temperature:
Humidity:
Dewpoint:
Barometer:
Heat Index:
Wind Chill:
Wind: at
Rain:
Rain Rate:
Storm Total:
Monthly Rain:
Yearly Rain:
 
Today's Highs/Lows
High Temperature:
Low Temperature:
High Humidity:
Low Humidity:
High Dewpoint:
Low Dewpoint:
High Barometer:
Low Barometer:
High Heat Index:
Low Wind Chill:
High Wind Speed:
High Rain Rate:
diff --git a/examples/html/exfoliation/plus/footer.incx b/examples/html/exfoliation/plus/footer.incx new file mode 100644 index 0000000..5423c6d --- /dev/null +++ b/examples/html/exfoliation/plus/footer.incx @@ -0,0 +1,5 @@ + diff --git a/examples/html/exfoliation/plus/graphics.conf b/examples/html/exfoliation/plus/graphics.conf new file mode 100644 index 0000000..88d6342 --- /dev/null +++ b/examples/html/exfoliation/plus/graphics.conf @@ -0,0 +1,157 @@ +# graphics.conf for exfoliation + +################################# BUCKETS ################################# + +# Transparent background (0/1)? +BUCKET_TRANSPARENT=1 + +# Background color for the bucket image +BUCKET_BG_COLOR=0xF5F5F500 + +# Color for the lines and ticks in the bucket plot +BUCKET_FG_COLOR=0x00000000 + +# Color for the filled region of the bucket +BUCKET_CONTENT_COLOR=0x90d08000 + +# Color for line indicating high value +BUCKET_HIGH_COLOR=0xFF000000 + +# Color for line indicating low value +BUCKET_LOW_COLOR=0x0055FF00 + +# Background color for the title +BUCKET_TITLE_BG_COLOR=0xD8D8D800 + +# Foreground color for the title +BUCKET_TITLE_FG_COLOR=0x00000000 + +# Color of text labels +BUCKET_TEXT_COLOR=0x00000000 + +# Width of bucket image +BUCKET_IMAGE_WIDTH=120 + +# Height of bucket image +BUCKET_IMAGE_HEIGHT=240 + +# Width of bucket itself +BUCKET_WIDTH=36 + + +################################# LINE CHARTS ################################# + +# Transparent background for all charts (0/1)? +CHART_TRANSPARENT=1 + +# Background color for the chart image rectangle +CHART_IMAGE_BG_COLOR=0xeeeeee00 + +# Background color for the plot +CHART_GRAPH_BG_COLOR=0xD8D8D800 + +# Color of the grid lines +CHART_GRID_COLOR=0xc0c0c000 + +# Color of line for primary data values +CHART_FIRST_LINE_COLOR=0x30a03000 + +# Color of line for secondary data values +CHART_SECOND_LINE_COLOR=0x90d08000 + +# Color of line for tertiary data values +CHART_THIRD_LINE_COLOR=0x010a0100 + +# Background color for title +CHART_TITLE_BG_COLOR=0xD8D8D800 + +# Foreground color for title +CHART_TITLE_FG_COLOR=0x00000000 + +# Color for text labels +CHART_TEXT_COLOR=0x88888800 + +# Width of image +CHART_WIDTH=300 + +# Height of image +CHART_HEIGHT=180 + + +################################# BAR CHARTS ################################# + +# Background color for the image +BAR_IMAGE_BG_COLOR=0xF5F5F500 + +# Background color for the plot +BAR_GRAPH_BG_COLOR=0xD8D8D800 + +# Color for the bars +BAR_BAR_COLOR=0x90d08000 + +# Background color for the title bar +BAR_TITLE_BG_COLOR=0xD8D8D800 + +# Foreground color for the title bar +BAR_TITLE_FG_COLOR=0x00000000 + +# Color for grid lines +BAR_GRID_COLOR=0xc0c0c000 + +# Color for text labels +BAR_TEXT_COLOR=0x88888800 + +# Width of image +BAR_WIDTH=300 + +# Height of image +BAR_HEIGHT=180 + + +################################# DIAL PLOTS ################################# + +# Transparent background (0/1)? +DIAL_TRANSPARENT=0 + +# Background color for dial +DIAL_BG_COLOR=0xeaeaea00 + +# Background color for image (doesn't matter becasue this color is transparent) +DIAL_IMAGE_BG_COLOR=Ox12121200 + +# Color of circle at center of dial +DIAL_CENTER_COLOR=0x30a03000 + +# Text color of current value in center of dial +DIAL_CENTER_TEXT_COLOR=0x30a03000 + +# Text color of value for high wind in center of plot +DIAL_CENTER_HIGH_COLOR=0x30a03000 + +# Fill color of pointer +DIAL_POINTER_COLOR=0x90d08000 + +# Outline color of pointer +DIAL_POINTER_OUTLINE_COLOR=0x30a03000 + +# Color of tick for high value +DIAL_HIGH_COLOR=0xff000000 + +# Color of tick for low value +DIAL_LOW_COLOR=0x0000ff00 + +# Text color for high value on Temperature plot +DIAL_APP_COLOR=0x90d08000 + +# Text color of labels +DIAL_TEXT_COLOR=0x88888800 + +# Width of image +DIAL_IMAGE_WIDTH=160 + +# Diameter of dial +DIAL_DIAMETER=156 + +# Diameter of circle at center of dial +DIAL_CTR_DIAMETER=15 + diff --git a/examples/html/exfoliation/plus/hiloreadings.incx b/examples/html/exfoliation/plus/hiloreadings.incx new file mode 100644 index 0000000..84b5434 --- /dev/null +++ b/examples/html/exfoliation/plus/hiloreadings.incx @@ -0,0 +1,138 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Highs/LowsTodayMonthYear
High Temperature:
+

+

+
Low Temperature:
+

+

+
High Humidity:
+

+

+
Low Humidity:
+

+

+
High Dewpoint:
+

+

+
Low Dewpoint:
+

+

+
High Barometer:
+

+

+
Low Barometer:
+

+

+
High Heat Index:
+

+

+
Low Wind Chill:
+

+

+
High Wind Speed:
+

+

+
High Rain Rate:
+

+

+
Rain Total:
diff --git a/examples/html/exfoliation/plus/html-templates.conf b/examples/html/exfoliation/plus/html-templates.conf new file mode 100644 index 0000000..c87a417 --- /dev/null +++ b/examples/html/exfoliation/plus/html-templates.conf @@ -0,0 +1,32 @@ +# html-template.conf for exfoliation + +### Standard Include Macros (should be generated first) +location.incx +navctls.incx +readings.incx +hiloreadings.incx +curreadings.incx +footer.incx +index.incx + +### RSS Feed XML Template +wxrss.xtx + +### HTML index Page templates +#index.htx +index-day.htx +index-night.htx + +### Standard (no extra sensors) Weather Data +almanac.htx +Current.htx +Daily.htx +Weekly.htx +Monthly.htx +Yearly.htx +Current_Conditions.htx +phone.htx + +### AWEKAS Data Submission Template +#awekas_wl.htx + diff --git a/examples/html/exfoliation/plus/images-metric.conf b/examples/html/exfoliation/plus/images-metric.conf new file mode 100755 index 0000000..9ccc60b --- /dev/null +++ b/examples/html/exfoliation/plus/images-metric.conf @@ -0,0 +1,233 @@ +# +# images-metric.conf - configure what images wview will generate +# + +# File Format +# +# -> lines beginning with '#' or are ignored +# -> columns are whitespace delimited +# + +# Column Format +# +# 1) image filename +# 2) image label (can be translated as desired) +# 3) image unit label (can be translated as desired) +# 4) decimal places +# 5) image generator function index (see images.c) +# + +# Enabling/Disabling Image Generation +# +# -> To enable the generation of an image, uncomment it (remove any '#' + characters in front of the image definition) +# -> To disable the generation of an image, comment it out by placing one or + more '#' characters in front of it + + +################# D I A L S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +tempdial.png "Temp" C 0 109 +humiddial.png "Humidity" % 0 110 +wind.png "Wind" none 0 11 + + +################# B U C K E T S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +temp.png "Temperature" C 1 0 +humid.png "Humidity" % 1 1 +dew.png "Dewpoint" C 1 2 +wchill.png "Wind Chill" C 1 3 +hindex.png "Heat Index" C 1 4 +barom.png "Barometer" hPa 1 5 +dayrain.png "Day Rain" mm 1 6 +stormrain.png "Storm Rain" mm 1 7 +rainrate.png "Rain Rate" mm/hour 1 8 +monthrain.png "Month Rain" mm 1 9 +yearrain.png "Year Rain" mm 1 10 + + +################# C H A R T S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +#### DAY VALUES #### +tempdaycomp.png "Temperature/Dewpoint" C 0 42 +heatchillcomp.png "Wind Chill/Heat Index" C 0 43 +## intempdaycomp.png "Inside Temp/Inside Humidity" C/% 0 115 +humidday.png "Humidity" % 0 15 +wspeeddaycomp.png "Wind/Gust" km/h 0 137 +## wspeedday.png "Wind" km/h 0 21 +wdirday.png "Wind DIR" degrees 0 24 +## hiwspeedday.png "HI Wind" km/h 0 27 +baromday.png "Barometer" hPa 0 30 + +## tempday.png "Temperature" C 0 12 +## dewday.png "Dewpoint" C 0 18 +## wchillday.png "Wind Chill" C 0 36 +## hindexday.png "Heat Index" C 0 39 + +#### WEEK VALUES #### +tempweekcomp.png "Temperature/Dewpoint" C 0 127 +heatchillweekcomp.png "Wind Chill/Heat Index" C 0 128 +## intempweekcomp.png "Inside Temp/Inside Humidity" C/% 0 129 +humidweek.png "Humidity (Hourly Avg)" % 0 118 +wspeedweekcomp.png "Wind/Gust" km/h 0 138 +## wspeedweek.png "Wind (Hourly Avg)" km/h 0 120 +wdirweek.png "Wind DIR (Hourly Avg)" degrees 0 121 +## hiwspeedweek.png "HI Wind (Hourly Avg)" km/h 0 122 +baromweek.png "Barometer (Hourly Avg)" hPa 0 123 + +## tempweek.png "Temperature (Hourly Avg)" C 0 117 +## dewweek.png "Dewpoint (Hourly Avg)" C 0 119 +## wchillweek.png "Wind Chill (Hourly Avg)" C 0 125 +## hindexweek.png "Heat Index (Hourly Avg)" C 0 126 + +#### MONTH VALUES #### +tempmonthcomp.png "Temperature/Dewpoint" C 0 44 +heatchillmonthcomp.png "Wind Chill/Heat Index" C 0 45 +## intempmonthcomp.png "Inside Temp/Inside Humidity" C/% 0 116 +humidmonth.png "Humidity (Hourly Avg)" % 0 16 +wspeedmonthcomp.png "Wind/Gust" km/h 0 139 +## wspeedmonth.png "Wind (Hourly Avg)" km/h 0 22 +wdirmonth.png "Wind DIR (Hourly Avg)" degrees 0 25 +## hiwspeedmonth.png "HI Wind (Hourly Avg)" km/h 0 28 +barommonth.png "Barometer (Hourly Avg)" hPa 0 31 + +## tempmonth.png "Temperature (Hourly Avg)" C 0 13 +## dewmonth.png "Dewpoint (Hourly Avg)" C 0 19 +## wchillmonth.png "Wind Chill (Hourly Avg)" C 0 37 +## hindexmonth.png "Heat Index (Hourly Avg)" C 0 40 + +#### YEAR VALUES #### +tempyear.png "Temperature (Daily Avg)" C 0 14 +humidyear.png "Humidity (Daily Avg)" % 0 17 +dewyear.png "Dewpoint (Daily Avg)" C 0 20 +wspeedyear.png "Wind (Daily Avg)" km/h 0 23 +wdiryear.png "Wind DIR (Daily Avg)" degrees 0 26 +hiwspeedyear.png "HI Wind (Daily Avg)" km/h 0 29 +baromyear.png "Barometer (Daily Avg)" hPa 0 32 +wchillyear.png "Wind Chill (Daily Avg)" C 0 38 +hindexyear.png "Heat Index (Daily Avg)" C 0 41 + + +################# B A R C H A R T S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +rainday.png "Rain/hour" mm 0 33 +rainweek.png "Rain/day" mm 0 124 +rainmonth.png "Rain/day" mm 0 34 +rainyear.png "Rain/week" mm 0 35 + + + +################# W I N D R O S E S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +windroseday.png "Rose Day" none 0 133 +windroseweek.png "Rose Week" none 0 134 +windrosemonth.png "Rose Month" none 0 135 +windroseyear.png "Rose Year" none 0 136 + + + +################# V P P L U S D A T A B U C K E T S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +UV.png "UV Index" index 1 106 +radiation.png "Radiation" watts/m^2 1 107 +ET.png "ET (day)" mm 1 108 + + +################# V P P L U S D A T A C H A R T S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +radiationDay.png "SolarRad" watts 0 46 +radiationWeek.png "SolarRad (Hourly Avg)" watts 0 130 +radiationMonth.png "SolarRad (Hourly Avg)" watts 0 47 +radiationYear.png "SolarRad (Daily Avg)" watts 0 48 +UVDay.png "UV Index" index 0 49 +UVWeek.png "UV Index (Hourly Avg)" index 0 131 +UVMonth.png "UV Index (Hourly Avg)" index 0 50 +UVYear.png "UV Index (Daily Avg)" index 0 51 +ETDay.png "EvaPot (Hourly)" mm/hr 1 52 +ETWeek.png "EvaPot (Daily)" mm/day 0 132 +ETMonth.png "EvaPot (Daily)" mm/day 0 53 +ETYear.png "EvaPot (Weekly)" mm/week 0 54 +## leafTemp1Day.png "LeafTemp1" C 0 55 +## leafTemp1Month.png "LeafTemp1 (Hourly Avg)" C 0 56 +## leafTemp1Year.png "LeafTemp1 (Daily Avg)" C 0 57 +## leafTemp2Day.png "LeafTemp2" C 0 58 +## leafTemp2Month.png "LeafTemp2 (Hourly Avg)" C 0 59 +## leafTemp2Year.png "LeafTemp2 (Daily Avg)" C 0 60 +## leafWetness1Day.png "LeafWet1 Index" (0-15) 0 61 +## leafWetness1Month.png "LeafWet1 Index (Hourly Avg)" (0-15) 0 62 +## leafWetness1Year.png "LeafWet1 Index (Daily Avg)" (0-15) 0 63 +## leafWetness2Day.png "LeafWet2 Index" (0-15) 0 64 +## leafWetness2Month.png "LeafWet2 Index (Hourly Avg)" (0-15) 0 65 +## leafWetness2Year.png "LeafWet2 Index (Daily Avg)" (0-15) 0 66 +## soilTemp1Day.png "SoilTemp1" C 0 67 +## soilTemp1Month.png "SoilTemp1 (Hourly Avg)" C 0 68 +## soilTemp1Year.png "SoilTemp1 (Daily Avg)" C 0 69 +## soilTemp2Day.png "SoilTemp2" C 0 70 +## soilTemp2Month.png "SoilTemp2 (Hourly Avg)" C 0 71 +## soilTemp2Year.png "SoilTemp2 (Daily Avg)" C 0 72 +## soilTemp3Day.png "SoilTemp3" C 0 73 +## soilTemp3Month.png "SoilTemp3 (Hourly Avg)" C 0 74 +## soilTemp3Year.png "SoilTemp3 (Daily Avg)" C 0 75 +## soilTemp4Day.png "SoilTemp4" C 0 76 +## soilTemp4Month.png "SoilTemp4 (Hourly Avg)" C 0 77 +## soilTemp4Year.png "SoilTemp4 (Daily Avg)" C 0 78 +## extraHumid1Day.png "ExtraHumidity1" % 0 79 +## extraHumid1Month.png "ExtraHumidity1 (Hourly Avg)" % 0 80 +## extraHumid1Year.png "ExtraHumidity1 (Daily Avg)" % 0 81 +## extraHumid2Day.png "ExtraHumidity2" % 0 82 +## extraHumid2Month.png "ExtraHumidity2 (Hourly Avg)" % 0 83 +## extraHumid2Year.png "ExtraHumidity2 (Daily Avg)" % 0 84 +## extraTemp1Day.png "ExtraTemp1" C 0 85 +## extraTemp1Month.png "ExtraTemp1 (Hourly Avg)" C 0 86 +## extraTemp1Year.png "ExtraTemp1 (Daily Avg)" C 0 87 +## extraTemp2Day.png "ExtraTemp2" C 0 88 +## extraTemp2Month.png "ExtraTemp2 (Hourly Avg)" C 0 89 +## extraTemp2Year.png "ExtraTemp2 (Daily Avg)" C 0 90 +## extraTemp3Day.png "ExtraTemp3" C 0 91 +## extraTemp3Month.png "ExtraTemp3 (Hourly Avg)" C 0 92 +## extraTemp3Year.png "ExtraTemp3 (Daily Avg)" C 0 93 +## soilMoist1Day.png "SoilMoist1" cb 0 94 +## soilMoist1Month.png "SoilMoist1 (Hourly Avg)" cb 0 95 +## soilMoist1Year.png "SoilMoist1 (Daily Avg)" cb 0 96 +## soilMoist2Day.png "SoilMoist2" cb 0 97 +## soilMoist2Month.png "SoilMoist2 (Hourly Avg)" cb 0 98 +## soilMoist2Year.png "SoilMoist2 (Daily Avg)" cb 0 99 +## soilMoist3Day.png "SoilMoist3" cb 0 100 +## soilMoist3Month.png "SoilMoist3 (Hourly Avg)" cb 0 101 +## soilMoist3Year.png "SoilMoist3 (Daily Avg)" cb 0 102 +## soilMoist4Day.png "SoilMoist4" cb 0 103 +## soilMoist4Month.png "SoilMoist4 (Hourly Avg)" cb 0 104 +## soilMoist4Year.png "SoilMoist4 (Daily Avg)" cb 0 105 + + + +################# V P P L U S D I A L S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +netRainDay.png "Precip Day" mm 0 112 +netRainMonth.png "Precip Month" mm 0 113 +netRainYear.png "Precip Year" mm 0 114 + + +################# D I A G N O S T I C S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +#rxcheck.png "RX Good Packets" % 0 111 + diff --git a/examples/html/exfoliation/plus/images-user.conf b/examples/html/exfoliation/plus/images-user.conf new file mode 100644 index 0000000..9b1692b --- /dev/null +++ b/examples/html/exfoliation/plus/images-user.conf @@ -0,0 +1,4 @@ +# images-user.conf for exfoliation +# +# filename label units dec index +intemp.png "In Temperature" degrees 1 0 diff --git a/examples/html/exfoliation/plus/images.conf b/examples/html/exfoliation/plus/images.conf new file mode 100755 index 0000000..a35a478 --- /dev/null +++ b/examples/html/exfoliation/plus/images.conf @@ -0,0 +1,200 @@ +# images.conf for exfoliation + +################# D I A L S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +tempdial.png "Temp" F 0 109 +humiddial.png "Humidity" none 0 110 +wind.png "Wind" none 0 11 +netRainDay.png "Rain Day" in 0 112 +netRainMonth.png "Rain Month" in 0 113 +netRainYear.png "Rain Year" in 0 114 + + +################# B U C K E T S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +temp.png "Temperature" F 1 0 +humid.png "Humidity" % 1 1 +dew.png "Dewpoint" F 1 2 +wchill.png "Wind Chill" F 1 3 +hindex.png "Heat Index" F 1 4 +barom.png "Barometer" inHg 2 5 +dayrain.png "Day Rain" inches 2 6 +stormrain.png "Storm Rain" inches 2 7 +rainrate.png "Rain Rate" in/hour 2 8 +monthrain.png "Month Rain" inches 2 9 +yearrain.png "Year Rain" inches 2 10 + + +################# C H A R T S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +#### DAY VALUES #### +tempdaycomp.png "Temperature/Dewpoint" F 0 42 +heatchillcomp.png "Wind Chill/Heat Index" F 0 43 +intempdaycomp.png "Inside Temperature/Humidity" F/% 0 115 +humidday.png "Humidity" % 0 15 +wspeeddaycomp.png "Wind/Gust" mph 0 137 +## wspeedday.png "Wind" mph 0 21 +wdirday.png "Wind Direction" degrees 0 24 +## hiwspeedday.png "HI Wind" mph 0 27 +baromday.png "Barometer" inHg 1 30 + +## tempday.png "Temperature" F 0 12 +## dewday.png "Dewpoint" F 0 18 +## wchillday.png "Wind Chill" F 0 36 +## hindexday.png "Heat Index" F 0 39 + +#### WEEK VALUES #### +tempweekcomp.png "Temperature/Dewpoint" F 0 127 +heatchillweekcomp.png "Wind Chill/Heat Index" F 0 128 +intempweekcomp.png "Inside Temperature/Humidity" F/% 0 129 +humidweek.png "Humidity (Hourly Avg)" % 0 118 +wspeedweekcomp.png "Wind/Gust" mph 0 138 +## wspeedweek.png "Wind (Hourly Avg)" mph 0 120 +wdirweek.png "Wind Direction (Hourly Avg)" degrees 0 121 +## hiwspeedweek.png "HI Wind (Hourly Avg)" mph 0 122 +baromweek.png "Barometer (Hourly Avg)" inHg 1 123 + +## tempweek.png "Temperature (Hourly Avg)" F 0 117 +## dewweek.png "Dewpoint (Hourly Avg)" F 0 119 +## wchillweek.png "Wind Chill (Hourly Avg)" F 0 125 +## hindexweek.png "Heat Index (Hourly Avg)" F 0 126 + +#### MONTH VALUES #### +tempmonthcomp.png "Temperature/Dewpoint" F 0 44 +heatchillmonthcomp.png "Wind Chill/Heat Index" F 0 45 +intempmonthcomp.png "Inside Temperature/Humidity" F/% 0 116 +humidmonth.png "Humidity (Hourly Avg)" % 0 16 +wspeedmonthcomp.png "Wind/Gust" mph 0 139 +## wspeedmonth.png "Wind (Hourly Avg)" mph 0 22 +wdirmonth.png "Wind Direction (Hourly Avg)" degrees 0 25 +## hiwspeedmonth.png "HI Wind (Hourly Avg)" mph 0 28 +barommonth.png "Barometer (Hourly Avg)" inHg 1 31 + +## tempmonth.png "Temperature (Hourly Avg)" F 0 13 +## dewmonth.png "Dewpoint (Hourly Avg)" F 0 19 +## wchillmonth.png "Wind Chill (Hourly Avg)" F 0 37 +## hindexmonth.png "Heat Index (Hourly Avg)" F 0 40 + +#### YEAR VALUES #### +tempyear.png "Temperature (Daily Avg)" F 0 14 +humidyear.png "Humidity (Daily Avg)" % 0 17 +dewyear.png "Dewpoint (Daily Avg)" F 0 20 +wspeedyear.png "Wind (Daily Avg)" mph 0 23 +wdiryear.png "Wind Direction (Daily Avg)" degrees 0 26 +hiwspeedyear.png "HI Wind (Daily Avg)" mph 0 29 +baromyear.png "Barometer (Daily Avg)" inHg 1 32 +wchillyear.png "Wind Chill (Daily Avg)" F 0 38 +hindexyear.png "Heat Index (Daily Avg)" F 0 41 + + +################# B A R C H A R T S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +rainday.png "Rain/hour" inches 2 33 +rainweek.png "Rain/day" inches 1 124 +rainmonth.png "Rain/day" inches 1 34 +rainyear.png "Rain/week" inches 1 35 + + +################# W I N D R O S E S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +windroseday.png "Wind Day" none 0 133 +windroseweek.png "Wind Week" none 0 134 +windrosemonth.png "Wind Month" none 0 135 +windroseyear.png "Wind Year" none 0 136 + + + +################# V P P L U S D A T A B U C K E T S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +UV.png "UV Index" index 1 106 +radiation.png "Radiation" watts/m^2 1 107 +ET.png "ET (day)" inches 3 108 + +################# V P P L U S D A T A C H A R T S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +radiationDay.png "SolarRad" watts 0 46 +radiationWeek.png "SolarRad (Hourly Avg)" watts 0 130 +radiationMonth.png "SolarRad (Hourly Avg)" watts 0 47 +radiationYear.png "SolarRad (Daily Avg)" watts 0 48 +UVDay.png "UV Index" index 0 49 +UVWeek.png "UV Index (Hourly Avg)" index 0 131 +UVMonth.png "UV Index (Hourly Avg)" index 0 50 +UVYear.png "UV Index (Daily Avg)" index 0 51 +ETDay.png "EvaPot (Hourly)" inches/hr 2 52 +ETWeek.png "EvaPot (Daily)" inches/day 2 132 +ETMonth.png "EvaPot (Daily)" inches/day 2 53 +ETYear.png "EvaPot (Weekly)" inches/week 2 54 +## leafTemp1Day.png "LeafTemp1" F 0 55 +## leafTemp1Month.png "LeafTemp1 (Hourly Avg)" F 0 56 +## leafTemp1Year.png "LeafTemp1 (Daily Avg)" F 0 57 +## leafTemp2Day.png "LeafTemp2" F 0 58 +## leafTemp2Month.png "LeafTemp2 (Hourly Avg)" F 0 59 +## leafTemp2Year.png "LeafTemp2 (Daily Avg)" F 0 60 +## leafWetness1Day.png "LeafWet1 Index" (0-15) 0 61 +## leafWetness1Month.png "LeafWet1 Index (Hourly Avg)" (0-15) 0 62 +## leafWetness1Year.png "LeafWet1 Index (Daily Avg)" (0-15) 0 63 +## leafWetness2Day.png "LeafWet2 Index" (0-15) 0 64 +## leafWetness2Month.png "LeafWet2 Index (Hourly Avg)" (0-15) 0 65 +## leafWetness2Year.png "LeafWet2 Index (Daily Avg)" (0-15) 0 66 +## soilTemp1Day.png "SoilTemp1" F 0 67 +## soilTemp1Month.png "SoilTemp1 (Hourly Avg)" F 0 68 +## soilTemp1Year.png "SoilTemp1 (Daily Avg)" F 0 69 +## soilTemp2Day.png "SoilTemp2" F 0 70 +## soilTemp2Month.png "SoilTemp2 (Hourly Avg)" F 0 71 +## soilTemp2Year.png "SoilTemp2 (Daily Avg)" F 0 72 +## soilTemp3Day.png "SoilTemp3" F 0 73 +## soilTemp3Month.png "SoilTemp3 (Hourly Avg)" F 0 74 +## soilTemp3Year.png "SoilTemp3 (Daily Avg)" F 0 75 +## soilTemp4Day.png "SoilTemp4" F 0 76 +## soilTemp4Month.png "SoilTemp4 (Hourly Avg)" F 0 77 +## soilTemp4Year.png "SoilTemp4 (Daily Avg)" F 0 78 +## extraHumid1Day.png "ExtraHumidity1" % 0 79 +## extraHumid1Month.png "ExtraHumidity1 (Hourly Avg)" % 0 80 +## extraHumid1Year.png "ExtraHumidity1 (Daily Avg)" % 0 81 +## extraHumid2Day.png "ExtraHumidity2" % 0 82 +## extraHumid2Month.png "ExtraHumidity2 (Hourly Avg)" % 0 83 +## extraHumid2Year.png "ExtraHumidity2 (Daily Avg)" % 0 84 +## extraTemp1Day.png "ExtraTemp1" F 0 85 +## extraTemp1Month.png "ExtraTemp1 (Hourly Avg)" F 0 86 +## extraTemp1Year.png "ExtraTemp1 (Daily Avg)" F 0 87 +## extraTemp2Day.png "ExtraTemp2" F 0 88 +## extraTemp2Month.png "ExtraTemp2 (Hourly Avg)" F 0 89 +## extraTemp2Year.png "ExtraTemp2 (Daily Avg)" F 0 90 +## extraTemp3Day.png "ExtraTemp3" F 0 91 +## extraTemp3Month.png "ExtraTemp3 (Hourly Avg)" F 0 92 +## extraTemp3Year.png "ExtraTemp3 (Daily Avg)" F 0 93 +## soilMoist1Day.png "SoilMoist1" cbars 0 94 +## soilMoist1Month.png "SoilMoist1 (Hourly Avg)" cbars 0 95 +## soilMoist1Year.png "SoilMoist1 (Daily Avg)" cbars 0 96 +## soilMoist2Day.png "SoilMoist2" cbars 0 97 +## soilMoist2Month.png "SoilMoist2 (Hourly Avg)" cbars 0 98 +## soilMoist2Year.png "SoilMoist2 (Daily Avg)" cbars 0 99 +## soilMoist3Day.png "SoilMoist3" cbars 0 100 +## soilMoist3Month.png "SoilMoist3 (Hourly Avg)" cbars 0 101 +## soilMoist3Year.png "SoilMoist3 (Daily Avg)" cbars 0 102 +## soilMoist4Day.png "SoilMoist4" cbars 0 103 +## soilMoist4Month.png "SoilMoist4 (Hourly Avg)" cbars 0 104 +## soilMoist4Year.png "SoilMoist4 (Daily Avg)" cbars 0 105 + + + +################# D I A G N O S T I C S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +#rxcheck.png "RX Good Packets" % 0 111 + diff --git a/examples/html/exfoliation/plus/index-day.htx b/examples/html/exfoliation/plus/index-day.htx new file mode 100644 index 0000000..2594a7f --- /dev/null +++ b/examples/html/exfoliation/plus/index-day.htx @@ -0,0 +1,15 @@ + + + + Weather: <!--stationCity--> <!--stationState--> + + + + + + + + + diff --git a/examples/html/exfoliation/plus/index-night.htx b/examples/html/exfoliation/plus/index-night.htx new file mode 100644 index 0000000..68804ba --- /dev/null +++ b/examples/html/exfoliation/plus/index-night.htx @@ -0,0 +1,15 @@ + + + + Weather: <!--stationCity--> <!--stationState--> + + + + + + + + + diff --git a/examples/html/exfoliation/plus/index.htx b/examples/html/exfoliation/plus/index.htx new file mode 100644 index 0000000..7c97210 --- /dev/null +++ b/examples/html/exfoliation/plus/index.htx @@ -0,0 +1,15 @@ + + + + Weather: <!--stationCity--> <!--stationState--> + + + + + + + + + diff --git a/examples/html/exfoliation/plus/index.incx b/examples/html/exfoliation/plus/index.incx new file mode 100644 index 0000000..b5d6e33 --- /dev/null +++ b/examples/html/exfoliation/plus/index.incx @@ -0,0 +1,94 @@ + + + + + + + + +
+ + + +
+ + + + + + + + + +
+ + + + + + + + +
+ +RADAR + +
 
+ +
+
+ + + + + + + + + + +
+ + + +
+ + + +
+ + + +
+
+ + + + + + + + + + + + + +
+ + +
+ + +
+ + +
+ +
+ +
+ +
+ + diff --git a/examples/html/exfoliation/plus/location.incx b/examples/html/exfoliation/plus/location.incx new file mode 100644 index 0000000..ee31e53 --- /dev/null +++ b/examples/html/exfoliation/plus/location.incx @@ -0,0 +1,8 @@ +

+ + +
+ - - +
+ +

diff --git a/examples/html/exfoliation/plus/navctls.incx b/examples/html/exfoliation/plus/navctls.incx new file mode 100644 index 0000000..c55cbe5 --- /dev/null +++ b/examples/html/exfoliation/plus/navctls.incx @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/examples/html/exfoliation/plus/phone.htx b/examples/html/exfoliation/plus/phone.htx new file mode 100644 index 0000000..c7474ee --- /dev/null +++ b/examples/html/exfoliation/plus/phone.htx @@ -0,0 +1,131 @@ + + + + weather at <!--stationCity--> <!--stationState--> + + + + + + + + + + + + + +
+ °
+ +
+ ° +
+ ° +
+ + + + + + + + + + + + + + + + + + + + + + +
+

HUMIDITY

+ +
+

DEWPOINT

+ ° +
+

HEAT INDEX

+ ° +
+

HIGH HEAT INDEX

+ ° + +
+

WIND CHILL

+ ° +
+

LOW WIND CHILL

+ ° + +
+

RAINFALL

+ +
+

HIGH RAIN RATE

+ +
+

WIND

+ +
+

HIGH WIND

+ +
+ + + + + + +
+ RADAR + + FORECAST +
+ + + diff --git a/examples/html/exfoliation/plus/post-generate.sh b/examples/html/exfoliation/plus/post-generate.sh new file mode 100755 index 0000000..89b3f77 --- /dev/null +++ b/examples/html/exfoliation/plus/post-generate.sh @@ -0,0 +1,19 @@ +#/bin/sh + +# configure the img directory if it is not already configured. this is for +# flash-based systems where the img directory is a ramdisk. +STATIC_DIR=/opt/wview/etc/skins/exfoliation/static +WWW_DIR=/var/wview/img +for d in NOAA Archive; do + if [ ! -d $WWW_DIR/$d ]; then + mkdir $WWW_DIR/$d + fi +done +for f in exfoliation.css exfoliation.js; do + if [ ! -f $WWW_DIR/$f ]; then + cp $SKIN_DIR/$f $WWW_DIR + fi +done + +exit 0 + diff --git a/examples/html/exfoliation/plus/readings.incx b/examples/html/exfoliation/plus/readings.incx new file mode 100644 index 0000000..56037b9 --- /dev/null +++ b/examples/html/exfoliation/plus/readings.incx @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Sunrise:
Sunset:
Moon:
 
Temperature:
Wind Chill:
Heat Index:
Apparent Temp:
Wet Bulb Temp:
Dewpoint:
Humidity:
Barometer: 
Wind: at
High Wind: at
Average Wind:
Beaufort Scale:
Today's Rain:
Rain Rate:
High Rain Rate: at
Storm Total:
Monthly Rain:
Yearly Rain ():
Air Density:
Cumulus Base:
High Temperature: at
Low Temperature: at
High Heat Index: at
Low Wind Chill: at
High Humidity: at
Low Humidity: at
High Dewpoint: at
Low Dewpoint: at
High Barometer: at
Low Barometer: at
Solar Radiation: watts/m^2
UV:
ET:
+ diff --git a/examples/html/exfoliation/plus/wxrss.xtx b/examples/html/exfoliation/plus/wxrss.xtx new file mode 100644 index 0000000..423afed --- /dev/null +++ b/examples/html/exfoliation/plus/wxrss.xtx @@ -0,0 +1,39 @@ + + + + + <!--stationCity--> <!--stationState--> + Insert_Your_WX_HomePage_URL_Here + Current Weather Conditions + en-us + , + 1 + + + + <!--stationCity-->,<!--stationState--> Weather Conditions: <!--stationDate--> <!--stationTime--> + Insert_Your_WX_HomePage_URL_Here + Current Weather Conditions + , + Insert_Your_WX_HomePage_URL_Here + - + + Temp:
+ Wind Chill:
+ Heat Index:
+ Humidity:
+ Dewpoint:
+ Barometer:
+ Wind: at
+ Rain Today:
+ Rain Rate:
+

+ ]]>
+
+
+
diff --git a/examples/html/exfoliation/readme.txt b/examples/html/exfoliation/readme.txt new file mode 100644 index 0000000..623cb2a --- /dev/null +++ b/examples/html/exfoliation/readme.txt @@ -0,0 +1,80 @@ +exfoliation for wview +Copyright 2011 Matthew Wall, all rights reserved + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +This is a skin for Mark Teel's wview weather collection/display software. +It has been tested with wview 5.19.0, both standard and extended data. + +This skin was created to provide a baseline display of weather data in a +compact, fairly minimalist format. The URLs for the web pages generated +by this skin are backward-compatible to at least wview 5.19.0. The intent +is to remain close to the original classic and chrome skins in terms of +information content, but clean up the layout and implementation. + +This skin also adds a 'phone' page targeted toward mobile devices. Thanks +to MarkF for the initial phone implementation. + +Revision History + 0.7 12feb12 + * added the plus configuration + * adjustments to layout of phone page + 0.6 12dec11 + * added post-generate.sh script to create NOAA and Archive directories + and copy static elements to the web directory if they do not already + exist there. this is primarily useful when the img directory is a + ramdisk, since its contents will be obliterated on system restarts. + 0.5 21nov11 + * forgot to update html-templates.conf and graphics.conf + 0.4 21nov11 + * use skin name for the css and js files to avoid conflicts + * use width/height on image classes to speed rendering + * simplify the navigation controls + * keep the reporting forms on the almanac page only to reduce clutter + * added phone.htx for mobile phone display + 0.3 19nov11 + * remove more extraneous files + * subdue the date/time for hi/ho readings in almanac + * simplify Current_Conditions even more + * added a simple makefile to automate the housekeeping chores + * remove more extraneous markup and spaces from readings tables + * apply date/time css to readings table + 0.2 19nov11 + * fix a few typos in the readings tables + * share the hi/lo readings across all time periods + * use bold for the metric values + * added time/date of the hi/lo readings to the hi/lo table + * reorder the graphs to be consistent across all pages + * reorder items in the hi/lo table to follow graph order + * remove everything (almost) from the current page that is not current + * tested on ie6, ff3, safari5, opera11 + 0.1 16nov11 + * initial release + + +Installation Instructions + +To install the skin, do something like this: + +# stop the wview daemons +/etc/init.d/wview stop + +# make a backup of the current configuration and templates +cd /etc/wview +tar cvfz site-template-yymmdd.tgz html *.conf *.sh +# make a backup of the current site +cd /var/wview +tar cvfz site-yymmdd.tgz img + +# extract the exfoliation contents and install them +cd /etc/wview/skins +tar xvfz exfoliation-for-wview-x.y.z.tgz +cp exfoliation/static/* /var/wview/img +cp exfoliation/standard/*.htx /etc/wview/html +cp exfoliation/standard/*.incx /etc/wview/html +cp exfoliation/standard/*.conf /etc/wview + +# start the wview daemons +/etc/init.d/wview start diff --git a/examples/html/exfoliation/standard/Current.htx b/examples/html/exfoliation/standard/Current.htx new file mode 100644 index 0000000..826af3d --- /dev/null +++ b/examples/html/exfoliation/standard/Current.htx @@ -0,0 +1,68 @@ + + + + Current: <!--stationCity--> <!--stationState--> + + + + + + + + + + + + + +
+ + + +
+ +

+Current Weather Conditions +

+ + + + + + + +
+ + + + + + + + +
+ + + + + + +
 
+ + + + + +
+
+ + + +
+ +
+ + + + + diff --git a/examples/html/exfoliation/standard/Current_Conditions.htx b/examples/html/exfoliation/standard/Current_Conditions.htx new file mode 100644 index 0000000..f14db7f --- /dev/null +++ b/examples/html/exfoliation/standard/Current_Conditions.htx @@ -0,0 +1,80 @@ + + + + Current: <!--stationCity--> <!--stationState--> + + + + + + + + + + + + + + +
+
+ - -
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
Temperature:
Humidity:
Wind: at
Barometer:
Today's Rain:
Storm Total:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
Wind Chill:
Heat Index:
Dewpoint:
Rain Rate:
Monthly Rain:
Yearly Rain:
+
+ + + diff --git a/examples/html/exfoliation/standard/Daily.htx b/examples/html/exfoliation/standard/Daily.htx new file mode 100644 index 0000000..479df98 --- /dev/null +++ b/examples/html/exfoliation/standard/Daily.htx @@ -0,0 +1,64 @@ + + + + 24 Hours: <!--stationCity--> <!--stationState--> + + + + + + + + + + + + + +
+ + + +
+ +

+Observations for the Last 24 Hours +

+ + + + + + + + + +
+ + + + + + + + + + + + + + + + + +
+
+ +
+ +
+ + + + + diff --git a/examples/html/exfoliation/standard/Monthly.htx b/examples/html/exfoliation/standard/Monthly.htx new file mode 100644 index 0000000..3f1c111 --- /dev/null +++ b/examples/html/exfoliation/standard/Monthly.htx @@ -0,0 +1,64 @@ + + + + 28 Days: <!--stationCity--> <!--stationState--> + + + + + + + + + + + + + +
+ + + +
+ +

+Observations for the Last 28 Days +

+ + + + + + + + + +
+ + + + + + + + + + + + + + + + + +
+
+ +
+ +
+ + + + + diff --git a/examples/html/exfoliation/standard/Weekly.htx b/examples/html/exfoliation/standard/Weekly.htx new file mode 100644 index 0000000..075b3ed --- /dev/null +++ b/examples/html/exfoliation/standard/Weekly.htx @@ -0,0 +1,64 @@ + + + + 7 Days: <!--stationCity--> <!--stationState--> + + + + + + + + + + + + + +
+ + + +
+ +

+Observations for the Last 7 Days +

+ + + + + + + + + +
+ + + + + + + + + + + + + + + + + +
+
+ +
+ +
+ + + + + diff --git a/examples/html/exfoliation/standard/Yearly.htx b/examples/html/exfoliation/standard/Yearly.htx new file mode 100644 index 0000000..cb3f7a9 --- /dev/null +++ b/examples/html/exfoliation/standard/Yearly.htx @@ -0,0 +1,68 @@ + + + + Year: <!--stationCity--> <!--stationState--> + + + + + + + + + + + + + +
+ + + +
+ +

+Observations for the Last 365 Days +

+ + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +
+ + + + + diff --git a/examples/html/exfoliation/standard/almanac.htx b/examples/html/exfoliation/standard/almanac.htx new file mode 100644 index 0000000..8191dbc --- /dev/null +++ b/examples/html/exfoliation/standard/almanac.htx @@ -0,0 +1,736 @@ + + + + Almanac: <!--stationCity--> <!--stationState--> + + + + + + + + + + + + + +
+ + + + +
+ + + + + + + +
+Climatological Summaries:  + + + +
+Browse Archive Records:  + + +
+
+
+ +

Almanac Data

+ + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
Time:
Date:
+
+ + + + + + + + + + + + + + + + + +
Station:
Latitude:
Longitude:
Elevation:
+
+ + + + + + + + + + + + + + + + + +
Sunrise:
Midday:
Sunset:
Day Length:
+
+ + + + + + + + + + + + + + + + + +
Civil Twilight: -
Astronomical Twilight: -
Moonrise:
Moonset:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TemperatureDew PointHumidity
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current:
Hour Average:
Hour Change:
Day Average:
Day Change:
Day High: at 
Day Low: at 
Week Average:
Week Change:
Month Average:
Month High: on 
Month Low: on 
Year Average:
Year High: on 
Year Low: on 
All Time Average:
All Time High: on 
All Time Low: on 
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current:
Hour Average:
Hour Change:
Day Average:
Day Change:
Day High: at 
Day Low: at 
Week Average:
Week Change:
Month Average:
Month High: on 
Month Low: on 
Year Average:
Year High: on 
Year Low: on 
All Time Average:
All Time High: on 
All Time Low: on 
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current:
Hour Average:
Hour Change:
Day Average:
Day Change:
Day High: at 
Day Low: at 
Week Average:
Week Change:
Month Average:
Month High: on 
Month Low: on 
Year Average:
Year High: on 
Year Low: on 
All Time Average:
All Time High: on 
All Time Low: on 
+
Wind ChillHeat IndexWind Run
+ + + + + + + + + + + + + + + + + + + + + +
Current:
Day Low: at 
Month Low: on 
Year Low: on 
All Time Low: on 
+
+ + + + + + + + + + + + + + + + + + + + + +
Current:
Day High: at 
Month High: on 
Year High: on 
All Time High: on 
+
+ + + + + + + + + + + + + + + + + + + + + +
Hour:
Day:
Week:
Month:
Year:
+
WindBarometerRain
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current Direction: degrees
Hour Dom Direction: degrees
Hour Direction Change: degrees
Day Dom Direction: degrees
Day Direction Change: degrees
Week Dom Direction: degrees
Week Direction Change: degrees
Month Dom Direction: degrees
Year Dom Direction: degrees
All Time Direction: degrees
Current Speed:
Hour Avg Speed:
Hour Speed Change:
Day Avg Speed:
Day Speed Change:
Day High Speed: at 
Week Avg Speed:
Week Speed Change:
Month Avg Speed:
Month High Speed: on 
Year Avg Speed:
Year High Speed: on 
All Time Avg Speed:
All Time High Speed: on 
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current:
Hour Average:
Hour Change:
Day Average:
Day Change:
Day High: at 
Day Low: at 
Week Average:
Week Change:
Month Average:
Month High: on 
Month Low: on 
Year Average:
Year High: on 
Year Low: on 
All Time Average:
All Time High: on 
All Time Low: on 
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Rate:
Day:
Storm:
Storm Start:
Day High Rate: at 
Month:
Month High Rate: on 
Year ():
Year High Rate: on 
All Time High Rate: on 
+
+ +
+ + + + + diff --git a/examples/html/exfoliation/standard/awekas_wl.htx-metric b/examples/html/exfoliation/standard/awekas_wl.htx-metric new file mode 100644 index 0000000..a5b1ce7 --- /dev/null +++ b/examples/html/exfoliation/standard/awekas_wl.htx-metric @@ -0,0 +1,32 @@ +AWEKAS_Template_start
+
+
+
+
+
+
+
+
+
+-
+-
+-
+-
+-
+-
+-
+
+
+
+
+
+
+
+
+
+
+w/m^2
+
+index
+Template_V1.5
+ diff --git a/examples/html/exfoliation/standard/awekas_wl.htx-us b/examples/html/exfoliation/standard/awekas_wl.htx-us new file mode 100644 index 0000000..ecc2cf3 --- /dev/null +++ b/examples/html/exfoliation/standard/awekas_wl.htx-us @@ -0,0 +1,32 @@ +AWEKAS_Template_start
+
+
+
+
+
+
+
+
+
+-
+-
+-
+-
+-
+-
+-
+
+
+
+
+
+
+
+
+
+
+w/m^2
+
+index
+Template_V1.5
+ diff --git a/examples/html/exfoliation/standard/curreadings.incx b/examples/html/exfoliation/standard/curreadings.incx new file mode 100644 index 0000000..68906e9 --- /dev/null +++ b/examples/html/exfoliation/standard/curreadings.incx @@ -0,0 +1,121 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current Conditions
Temperature:
Humidity:
Dewpoint:
Barometer:
Heat Index:
Wind Chill:
Wind: at
Rain:
Rain Rate:
Storm Total:
Monthly Rain:
Yearly Rain:
 
Today's Highs/Lows
High Temperature:
Low Temperature:
High Humidity:
Low Humidity:
High Dewpoint:
Low Dewpoint:
High Barometer:
Low Barometer:
High Heat Index:
Low Wind Chill:
High Wind Speed:
High Rain Rate:
diff --git a/examples/html/exfoliation/standard/footer.incx b/examples/html/exfoliation/standard/footer.incx new file mode 100644 index 0000000..5423c6d --- /dev/null +++ b/examples/html/exfoliation/standard/footer.incx @@ -0,0 +1,5 @@ + diff --git a/examples/html/exfoliation/standard/graphics.conf b/examples/html/exfoliation/standard/graphics.conf new file mode 100644 index 0000000..88d6342 --- /dev/null +++ b/examples/html/exfoliation/standard/graphics.conf @@ -0,0 +1,157 @@ +# graphics.conf for exfoliation + +################################# BUCKETS ################################# + +# Transparent background (0/1)? +BUCKET_TRANSPARENT=1 + +# Background color for the bucket image +BUCKET_BG_COLOR=0xF5F5F500 + +# Color for the lines and ticks in the bucket plot +BUCKET_FG_COLOR=0x00000000 + +# Color for the filled region of the bucket +BUCKET_CONTENT_COLOR=0x90d08000 + +# Color for line indicating high value +BUCKET_HIGH_COLOR=0xFF000000 + +# Color for line indicating low value +BUCKET_LOW_COLOR=0x0055FF00 + +# Background color for the title +BUCKET_TITLE_BG_COLOR=0xD8D8D800 + +# Foreground color for the title +BUCKET_TITLE_FG_COLOR=0x00000000 + +# Color of text labels +BUCKET_TEXT_COLOR=0x00000000 + +# Width of bucket image +BUCKET_IMAGE_WIDTH=120 + +# Height of bucket image +BUCKET_IMAGE_HEIGHT=240 + +# Width of bucket itself +BUCKET_WIDTH=36 + + +################################# LINE CHARTS ################################# + +# Transparent background for all charts (0/1)? +CHART_TRANSPARENT=1 + +# Background color for the chart image rectangle +CHART_IMAGE_BG_COLOR=0xeeeeee00 + +# Background color for the plot +CHART_GRAPH_BG_COLOR=0xD8D8D800 + +# Color of the grid lines +CHART_GRID_COLOR=0xc0c0c000 + +# Color of line for primary data values +CHART_FIRST_LINE_COLOR=0x30a03000 + +# Color of line for secondary data values +CHART_SECOND_LINE_COLOR=0x90d08000 + +# Color of line for tertiary data values +CHART_THIRD_LINE_COLOR=0x010a0100 + +# Background color for title +CHART_TITLE_BG_COLOR=0xD8D8D800 + +# Foreground color for title +CHART_TITLE_FG_COLOR=0x00000000 + +# Color for text labels +CHART_TEXT_COLOR=0x88888800 + +# Width of image +CHART_WIDTH=300 + +# Height of image +CHART_HEIGHT=180 + + +################################# BAR CHARTS ################################# + +# Background color for the image +BAR_IMAGE_BG_COLOR=0xF5F5F500 + +# Background color for the plot +BAR_GRAPH_BG_COLOR=0xD8D8D800 + +# Color for the bars +BAR_BAR_COLOR=0x90d08000 + +# Background color for the title bar +BAR_TITLE_BG_COLOR=0xD8D8D800 + +# Foreground color for the title bar +BAR_TITLE_FG_COLOR=0x00000000 + +# Color for grid lines +BAR_GRID_COLOR=0xc0c0c000 + +# Color for text labels +BAR_TEXT_COLOR=0x88888800 + +# Width of image +BAR_WIDTH=300 + +# Height of image +BAR_HEIGHT=180 + + +################################# DIAL PLOTS ################################# + +# Transparent background (0/1)? +DIAL_TRANSPARENT=0 + +# Background color for dial +DIAL_BG_COLOR=0xeaeaea00 + +# Background color for image (doesn't matter becasue this color is transparent) +DIAL_IMAGE_BG_COLOR=Ox12121200 + +# Color of circle at center of dial +DIAL_CENTER_COLOR=0x30a03000 + +# Text color of current value in center of dial +DIAL_CENTER_TEXT_COLOR=0x30a03000 + +# Text color of value for high wind in center of plot +DIAL_CENTER_HIGH_COLOR=0x30a03000 + +# Fill color of pointer +DIAL_POINTER_COLOR=0x90d08000 + +# Outline color of pointer +DIAL_POINTER_OUTLINE_COLOR=0x30a03000 + +# Color of tick for high value +DIAL_HIGH_COLOR=0xff000000 + +# Color of tick for low value +DIAL_LOW_COLOR=0x0000ff00 + +# Text color for high value on Temperature plot +DIAL_APP_COLOR=0x90d08000 + +# Text color of labels +DIAL_TEXT_COLOR=0x88888800 + +# Width of image +DIAL_IMAGE_WIDTH=160 + +# Diameter of dial +DIAL_DIAMETER=156 + +# Diameter of circle at center of dial +DIAL_CTR_DIAMETER=15 + diff --git a/examples/html/exfoliation/standard/hiloreadings.incx b/examples/html/exfoliation/standard/hiloreadings.incx new file mode 100644 index 0000000..84b5434 --- /dev/null +++ b/examples/html/exfoliation/standard/hiloreadings.incx @@ -0,0 +1,138 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Highs/LowsTodayMonthYear
High Temperature:
+

+

+
Low Temperature:
+

+

+
High Humidity:
+

+

+
Low Humidity:
+

+

+
High Dewpoint:
+

+

+
Low Dewpoint:
+

+

+
High Barometer:
+

+

+
Low Barometer:
+

+

+
High Heat Index:
+

+

+
Low Wind Chill:
+

+

+
High Wind Speed:
+

+

+
High Rain Rate:
+

+

+
Rain Total:
diff --git a/examples/html/exfoliation/standard/html-templates.conf b/examples/html/exfoliation/standard/html-templates.conf new file mode 100644 index 0000000..c87a417 --- /dev/null +++ b/examples/html/exfoliation/standard/html-templates.conf @@ -0,0 +1,32 @@ +# html-template.conf for exfoliation + +### Standard Include Macros (should be generated first) +location.incx +navctls.incx +readings.incx +hiloreadings.incx +curreadings.incx +footer.incx +index.incx + +### RSS Feed XML Template +wxrss.xtx + +### HTML index Page templates +#index.htx +index-day.htx +index-night.htx + +### Standard (no extra sensors) Weather Data +almanac.htx +Current.htx +Daily.htx +Weekly.htx +Monthly.htx +Yearly.htx +Current_Conditions.htx +phone.htx + +### AWEKAS Data Submission Template +#awekas_wl.htx + diff --git a/examples/html/exfoliation/standard/images-metric.conf b/examples/html/exfoliation/standard/images-metric.conf new file mode 100755 index 0000000..59d2020 --- /dev/null +++ b/examples/html/exfoliation/standard/images-metric.conf @@ -0,0 +1,233 @@ +# +# images-metric.conf - configure what images wview will generate +# + +# File Format +# +# -> lines beginning with '#' or are ignored +# -> columns are whitespace delimited +# + +# Column Format +# +# 1) image filename +# 2) image label (can be translated as desired) +# 3) image unit label (can be translated as desired) +# 4) decimal places +# 5) image generator function index (see images.c) +# + +# Enabling/Disabling Image Generation +# +# -> To enable the generation of an image, uncomment it (remove any '#' + characters in front of the image definition) +# -> To disable the generation of an image, comment it out by placing one or + more '#' characters in front of it + + +################# D I A L S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +tempdial.png "Temp" C 0 109 +humiddial.png "Humidity" % 0 110 +wind.png "Wind" none 0 11 + + +################# B U C K E T S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +temp.png "Temperature" C 1 0 +humid.png "Humidity" % 1 1 +dew.png "Dewpoint" C 1 2 +wchill.png "Wind Chill" C 1 3 +hindex.png "Heat Index" C 1 4 +barom.png "Barometer" hPa 1 5 +dayrain.png "Day Rain" mm 1 6 +stormrain.png "Storm Rain" mm 1 7 +rainrate.png "Rain Rate" mm/hour 1 8 +monthrain.png "Month Rain" mm 1 9 +yearrain.png "Year Rain" mm 1 10 + + +################# C H A R T S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +#### DAY VALUES #### +tempdaycomp.png "Temperature/Dewpoint" C 0 42 +heatchillcomp.png "Wind Chill/Heat Index" C 0 43 +## intempdaycomp.png "Inside Temp/Inside Humidity" C/% 0 115 +humidday.png "Humidity" % 0 15 +wspeeddaycomp.png "Wind/Gust" km/h 0 137 +## wspeedday.png "Wind" km/h 0 21 +wdirday.png "Wind DIR" degrees 0 24 +## hiwspeedday.png "HI Wind" km/h 0 27 +baromday.png "Barometer" hPa 0 30 + +## tempday.png "Temperature" C 0 12 +## dewday.png "Dewpoint" C 0 18 +## wchillday.png "Wind Chill" C 0 36 +## hindexday.png "Heat Index" C 0 39 + +#### WEEK VALUES #### +tempweekcomp.png "Temperature/Dewpoint" C 0 127 +heatchillweekcomp.png "Wind Chill/Heat Index" C 0 128 +## intempweekcomp.png "Inside Temp/Inside Humidity" C/% 0 129 +humidweek.png "Humidity (Hourly Avg)" % 0 118 +wspeedweekcomp.png "Wind/Gust" km/h 0 138 +## wspeedweek.png "Wind (Hourly Avg)" km/h 0 120 +wdirweek.png "Wind DIR (Hourly Avg)" degrees 0 121 +## hiwspeedweek.png "HI Wind (Hourly Avg)" km/h 0 122 +baromweek.png "Barometer (Hourly Avg)" hPa 0 123 + +## tempweek.png "Temperature (Hourly Avg)" C 0 117 +## dewweek.png "Dewpoint (Hourly Avg)" C 0 119 +## wchillweek.png "Wind Chill (Hourly Avg)" C 0 125 +## hindexweek.png "Heat Index (Hourly Avg)" C 0 126 + +#### MONTH VALUES #### +tempmonthcomp.png "Temperature/Dewpoint" C 0 44 +heatchillmonthcomp.png "Wind Chill/Heat Index" C 0 45 +## intempmonthcomp.png "Inside Temp/Inside Humidity" C/% 0 116 +humidmonth.png "Humidity (Hourly Avg)" % 0 16 +wspeedmonthcomp.png "Wind/Gust" km/h 0 139 +## wspeedmonth.png "Wind (Hourly Avg)" km/h 0 22 +wdirmonth.png "Wind DIR (Hourly Avg)" degrees 0 25 +## hiwspeedmonth.png "HI Wind (Hourly Avg)" km/h 0 28 +barommonth.png "Barometer (Hourly Avg)" hPa 0 31 + +## tempmonth.png "Temperature (Hourly Avg)" C 0 13 +## dewmonth.png "Dewpoint (Hourly Avg)" C 0 19 +## wchillmonth.png "Wind Chill (Hourly Avg)" C 0 37 +## hindexmonth.png "Heat Index (Hourly Avg)" C 0 40 + +#### YEAR VALUES #### +tempyear.png "Temperature (Daily Avg)" C 0 14 +humidyear.png "Humidity (Daily Avg)" % 0 17 +dewyear.png "Dewpoint (Daily Avg)" C 0 20 +wspeedyear.png "Wind (Daily Avg)" km/h 0 23 +wdiryear.png "Wind DIR (Daily Avg)" degrees 0 26 +hiwspeedyear.png "HI Wind (Daily Avg)" km/h 0 29 +baromyear.png "Barometer (Daily Avg)" hPa 0 32 +wchillyear.png "Wind Chill (Daily Avg)" C 0 38 +hindexyear.png "Heat Index (Daily Avg)" C 0 41 + + +################# B A R C H A R T S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +rainday.png "Rain/hour" mm 0 33 +rainweek.png "Rain/day" mm 0 124 +rainmonth.png "Rain/day" mm 0 34 +rainyear.png "Rain/week" mm 0 35 + + + +################# W I N D R O S E S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +windroseday.png "Rose Day" none 0 133 +windroseweek.png "Rose Week" none 0 134 +windrosemonth.png "Rose Month" none 0 135 +windroseyear.png "Rose Year" none 0 136 + + + +################# V P P L U S D A T A B U C K E T S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +#UV.png "UV Index" index 1 106 +#radiation.png "Radiation" watts/m^2 1 107 +#ET.png "ET (day)" mm 1 108 + + +################# V P P L U S D A T A C H A R T S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +#radiationDay.png "SolarRad" watts 0 46 +#radiationWeek.png "SolarRad (Hourly Avg)" watts 0 130 +#radiationMonth.png "SolarRad (Hourly Avg)" watts 0 47 +#radiationYear.png "SolarRad (Daily Avg)" watts 0 48 +#UVDay.png "UV Index" index 0 49 +#UVWeek.png "UV Index (Hourly Avg)" index 0 131 +#UVMonth.png "UV Index (Hourly Avg)" index 0 50 +#UVYear.png "UV Index (Daily Avg)" index 0 51 +#ETDay.png "EvaPot (Hourly)" mm/hr 1 52 +#ETWeek.png "EvaPot (Daily)" mm/day 0 132 +#ETMonth.png "EvaPot (Daily)" mm/day 0 53 +#ETYear.png "EvaPot (Weekly)" mm/week 0 54 +## leafTemp1Day.png "LeafTemp1" C 0 55 +## leafTemp1Month.png "LeafTemp1 (Hourly Avg)" C 0 56 +## leafTemp1Year.png "LeafTemp1 (Daily Avg)" C 0 57 +## leafTemp2Day.png "LeafTemp2" C 0 58 +## leafTemp2Month.png "LeafTemp2 (Hourly Avg)" C 0 59 +## leafTemp2Year.png "LeafTemp2 (Daily Avg)" C 0 60 +## leafWetness1Day.png "LeafWet1 Index" (0-15) 0 61 +## leafWetness1Month.png "LeafWet1 Index (Hourly Avg)" (0-15) 0 62 +## leafWetness1Year.png "LeafWet1 Index (Daily Avg)" (0-15) 0 63 +## leafWetness2Day.png "LeafWet2 Index" (0-15) 0 64 +## leafWetness2Month.png "LeafWet2 Index (Hourly Avg)" (0-15) 0 65 +## leafWetness2Year.png "LeafWet2 Index (Daily Avg)" (0-15) 0 66 +## soilTemp1Day.png "SoilTemp1" C 0 67 +## soilTemp1Month.png "SoilTemp1 (Hourly Avg)" C 0 68 +## soilTemp1Year.png "SoilTemp1 (Daily Avg)" C 0 69 +## soilTemp2Day.png "SoilTemp2" C 0 70 +## soilTemp2Month.png "SoilTemp2 (Hourly Avg)" C 0 71 +## soilTemp2Year.png "SoilTemp2 (Daily Avg)" C 0 72 +## soilTemp3Day.png "SoilTemp3" C 0 73 +## soilTemp3Month.png "SoilTemp3 (Hourly Avg)" C 0 74 +## soilTemp3Year.png "SoilTemp3 (Daily Avg)" C 0 75 +## soilTemp4Day.png "SoilTemp4" C 0 76 +## soilTemp4Month.png "SoilTemp4 (Hourly Avg)" C 0 77 +## soilTemp4Year.png "SoilTemp4 (Daily Avg)" C 0 78 +## extraHumid1Day.png "ExtraHumidity1" % 0 79 +## extraHumid1Month.png "ExtraHumidity1 (Hourly Avg)" % 0 80 +## extraHumid1Year.png "ExtraHumidity1 (Daily Avg)" % 0 81 +## extraHumid2Day.png "ExtraHumidity2" % 0 82 +## extraHumid2Month.png "ExtraHumidity2 (Hourly Avg)" % 0 83 +## extraHumid2Year.png "ExtraHumidity2 (Daily Avg)" % 0 84 +## extraTemp1Day.png "ExtraTemp1" C 0 85 +## extraTemp1Month.png "ExtraTemp1 (Hourly Avg)" C 0 86 +## extraTemp1Year.png "ExtraTemp1 (Daily Avg)" C 0 87 +## extraTemp2Day.png "ExtraTemp2" C 0 88 +## extraTemp2Month.png "ExtraTemp2 (Hourly Avg)" C 0 89 +## extraTemp2Year.png "ExtraTemp2 (Daily Avg)" C 0 90 +## extraTemp3Day.png "ExtraTemp3" C 0 91 +## extraTemp3Month.png "ExtraTemp3 (Hourly Avg)" C 0 92 +## extraTemp3Year.png "ExtraTemp3 (Daily Avg)" C 0 93 +## soilMoist1Day.png "SoilMoist1" cb 0 94 +## soilMoist1Month.png "SoilMoist1 (Hourly Avg)" cb 0 95 +## soilMoist1Year.png "SoilMoist1 (Daily Avg)" cb 0 96 +## soilMoist2Day.png "SoilMoist2" cb 0 97 +## soilMoist2Month.png "SoilMoist2 (Hourly Avg)" cb 0 98 +## soilMoist2Year.png "SoilMoist2 (Daily Avg)" cb 0 99 +## soilMoist3Day.png "SoilMoist3" cb 0 100 +## soilMoist3Month.png "SoilMoist3 (Hourly Avg)" cb 0 101 +## soilMoist3Year.png "SoilMoist3 (Daily Avg)" cb 0 102 +## soilMoist4Day.png "SoilMoist4" cb 0 103 +## soilMoist4Month.png "SoilMoist4 (Hourly Avg)" cb 0 104 +## soilMoist4Year.png "SoilMoist4 (Daily Avg)" cb 0 105 + + + +################# V P P L U S D I A L S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +netRainDay.png "Precip Day" mm 0 112 +netRainMonth.png "Precip Month" mm 0 113 +netRainYear.png "Precip Year" mm 0 114 + + +################# D I A G N O S T I C S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +#rxcheck.png "RX Good Packets" % 0 111 + diff --git a/examples/html/exfoliation/standard/images-user.conf b/examples/html/exfoliation/standard/images-user.conf new file mode 100644 index 0000000..9b1692b --- /dev/null +++ b/examples/html/exfoliation/standard/images-user.conf @@ -0,0 +1,4 @@ +# images-user.conf for exfoliation +# +# filename label units dec index +intemp.png "In Temperature" degrees 1 0 diff --git a/examples/html/exfoliation/standard/images.conf b/examples/html/exfoliation/standard/images.conf new file mode 100755 index 0000000..7e192ee --- /dev/null +++ b/examples/html/exfoliation/standard/images.conf @@ -0,0 +1,200 @@ +# images.conf for exfoliation + +################# D I A L S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +tempdial.png "Temp" F 0 109 +humiddial.png "Humidity" none 0 110 +wind.png "Wind" none 0 11 +netRainDay.png "Rain Day" in 0 112 +netRainMonth.png "Rain Month" in 0 113 +netRainYear.png "Rain Year" in 0 114 + + +################# B U C K E T S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +temp.png "Temperature" F 1 0 +humid.png "Humidity" % 1 1 +dew.png "Dewpoint" F 1 2 +wchill.png "Wind Chill" F 1 3 +hindex.png "Heat Index" F 1 4 +barom.png "Barometer" inHg 2 5 +dayrain.png "Day Rain" inches 2 6 +stormrain.png "Storm Rain" inches 2 7 +rainrate.png "Rain Rate" in/hour 2 8 +monthrain.png "Month Rain" inches 2 9 +yearrain.png "Year Rain" inches 2 10 + + +################# C H A R T S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +#### DAY VALUES #### +tempdaycomp.png "Temperature/Dewpoint" F 0 42 +heatchillcomp.png "Wind Chill/Heat Index" F 0 43 +intempdaycomp.png "Inside Temperature/Humidity" F/% 0 115 +humidday.png "Humidity" % 0 15 +wspeeddaycomp.png "Wind/Gust" mph 0 137 +## wspeedday.png "Wind" mph 0 21 +wdirday.png "Wind Direction" degrees 0 24 +## hiwspeedday.png "HI Wind" mph 0 27 +baromday.png "Barometer" inHg 1 30 + +## tempday.png "Temperature" F 0 12 +## dewday.png "Dewpoint" F 0 18 +## wchillday.png "Wind Chill" F 0 36 +## hindexday.png "Heat Index" F 0 39 + +#### WEEK VALUES #### +tempweekcomp.png "Temperature/Dewpoint" F 0 127 +heatchillweekcomp.png "Wind Chill/Heat Index" F 0 128 +intempweekcomp.png "Inside Temperature/Humidity" F/% 0 129 +humidweek.png "Humidity (Hourly Avg)" % 0 118 +wspeedweekcomp.png "Wind/Gust" mph 0 138 +## wspeedweek.png "Wind (Hourly Avg)" mph 0 120 +wdirweek.png "Wind Direction (Hourly Avg)" degrees 0 121 +## hiwspeedweek.png "HI Wind (Hourly Avg)" mph 0 122 +baromweek.png "Barometer (Hourly Avg)" inHg 1 123 + +## tempweek.png "Temperature (Hourly Avg)" F 0 117 +## dewweek.png "Dewpoint (Hourly Avg)" F 0 119 +## wchillweek.png "Wind Chill (Hourly Avg)" F 0 125 +## hindexweek.png "Heat Index (Hourly Avg)" F 0 126 + +#### MONTH VALUES #### +tempmonthcomp.png "Temperature/Dewpoint" F 0 44 +heatchillmonthcomp.png "Wind Chill/Heat Index" F 0 45 +intempmonthcomp.png "Inside Temperature/Humidity" F/% 0 116 +humidmonth.png "Humidity (Hourly Avg)" % 0 16 +wspeedmonthcomp.png "Wind/Gust" mph 0 139 +## wspeedmonth.png "Wind (Hourly Avg)" mph 0 22 +wdirmonth.png "Wind Direction (Hourly Avg)" degrees 0 25 +## hiwspeedmonth.png "HI Wind (Hourly Avg)" mph 0 28 +barommonth.png "Barometer (Hourly Avg)" inHg 1 31 + +## tempmonth.png "Temperature (Hourly Avg)" F 0 13 +## dewmonth.png "Dewpoint (Hourly Avg)" F 0 19 +## wchillmonth.png "Wind Chill (Hourly Avg)" F 0 37 +## hindexmonth.png "Heat Index (Hourly Avg)" F 0 40 + +#### YEAR VALUES #### +tempyear.png "Temperature (Daily Avg)" F 0 14 +humidyear.png "Humidity (Daily Avg)" % 0 17 +dewyear.png "Dewpoint (Daily Avg)" F 0 20 +wspeedyear.png "Wind (Daily Avg)" mph 0 23 +wdiryear.png "Wind Direction (Daily Avg)" degrees 0 26 +hiwspeedyear.png "HI Wind (Daily Avg)" mph 0 29 +baromyear.png "Barometer (Daily Avg)" inHg 1 32 +wchillyear.png "Wind Chill (Daily Avg)" F 0 38 +hindexyear.png "Heat Index (Daily Avg)" F 0 41 + + +################# B A R C H A R T S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +rainday.png "Rain/hour" inches 2 33 +rainweek.png "Rain/day" inches 1 124 +rainmonth.png "Rain/day" inches 1 34 +rainyear.png "Rain/week" inches 1 35 + + +################# W I N D R O S E S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +windroseday.png "Wind Day" none 0 133 +windroseweek.png "Wind Week" none 0 134 +windrosemonth.png "Wind Month" none 0 135 +windroseyear.png "Wind Year" none 0 136 + + + +################# V P P L U S D A T A B U C K E T S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +#UV.png "UV Index" index 1 106 +#radiation.png "Radiation" watts/m^2 1 107 +#ET.png "ET (day)" inches 3 108 + +################# V P P L U S D A T A C H A R T S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +#radiationDay.png "SolarRad" watts 0 46 +#radiationWeek.png "SolarRad (Hourly Avg)" watts 0 130 +#radiationMonth.png "SolarRad (Hourly Avg)" watts 0 47 +#radiationYear.png "SolarRad (Daily Avg)" watts 0 48 +#UVDay.png "UV Index" index 0 49 +#UVWeek.png "UV Index (Hourly Avg)" index 0 131 +#UVMonth.png "UV Index (Hourly Avg)" index 0 50 +#UVYear.png "UV Index (Daily Avg)" index 0 51 +#ETDay.png "EvaPot (Hourly)" inches/hr 2 52 +#ETWeek.png "EvaPot (Daily)" inches/day 2 132 +#ETMonth.png "EvaPot (Daily)" inches/day 2 53 +#ETYear.png "EvaPot (Weekly)" inches/week 2 54 +## leafTemp1Day.png "LeafTemp1" F 0 55 +## leafTemp1Month.png "LeafTemp1 (Hourly Avg)" F 0 56 +## leafTemp1Year.png "LeafTemp1 (Daily Avg)" F 0 57 +## leafTemp2Day.png "LeafTemp2" F 0 58 +## leafTemp2Month.png "LeafTemp2 (Hourly Avg)" F 0 59 +## leafTemp2Year.png "LeafTemp2 (Daily Avg)" F 0 60 +## leafWetness1Day.png "LeafWet1 Index" (0-15) 0 61 +## leafWetness1Month.png "LeafWet1 Index (Hourly Avg)" (0-15) 0 62 +## leafWetness1Year.png "LeafWet1 Index (Daily Avg)" (0-15) 0 63 +## leafWetness2Day.png "LeafWet2 Index" (0-15) 0 64 +## leafWetness2Month.png "LeafWet2 Index (Hourly Avg)" (0-15) 0 65 +## leafWetness2Year.png "LeafWet2 Index (Daily Avg)" (0-15) 0 66 +## soilTemp1Day.png "SoilTemp1" F 0 67 +## soilTemp1Month.png "SoilTemp1 (Hourly Avg)" F 0 68 +## soilTemp1Year.png "SoilTemp1 (Daily Avg)" F 0 69 +## soilTemp2Day.png "SoilTemp2" F 0 70 +## soilTemp2Month.png "SoilTemp2 (Hourly Avg)" F 0 71 +## soilTemp2Year.png "SoilTemp2 (Daily Avg)" F 0 72 +## soilTemp3Day.png "SoilTemp3" F 0 73 +## soilTemp3Month.png "SoilTemp3 (Hourly Avg)" F 0 74 +## soilTemp3Year.png "SoilTemp3 (Daily Avg)" F 0 75 +## soilTemp4Day.png "SoilTemp4" F 0 76 +## soilTemp4Month.png "SoilTemp4 (Hourly Avg)" F 0 77 +## soilTemp4Year.png "SoilTemp4 (Daily Avg)" F 0 78 +## extraHumid1Day.png "ExtraHumidity1" % 0 79 +## extraHumid1Month.png "ExtraHumidity1 (Hourly Avg)" % 0 80 +## extraHumid1Year.png "ExtraHumidity1 (Daily Avg)" % 0 81 +## extraHumid2Day.png "ExtraHumidity2" % 0 82 +## extraHumid2Month.png "ExtraHumidity2 (Hourly Avg)" % 0 83 +## extraHumid2Year.png "ExtraHumidity2 (Daily Avg)" % 0 84 +## extraTemp1Day.png "ExtraTemp1" F 0 85 +## extraTemp1Month.png "ExtraTemp1 (Hourly Avg)" F 0 86 +## extraTemp1Year.png "ExtraTemp1 (Daily Avg)" F 0 87 +## extraTemp2Day.png "ExtraTemp2" F 0 88 +## extraTemp2Month.png "ExtraTemp2 (Hourly Avg)" F 0 89 +## extraTemp2Year.png "ExtraTemp2 (Daily Avg)" F 0 90 +## extraTemp3Day.png "ExtraTemp3" F 0 91 +## extraTemp3Month.png "ExtraTemp3 (Hourly Avg)" F 0 92 +## extraTemp3Year.png "ExtraTemp3 (Daily Avg)" F 0 93 +## soilMoist1Day.png "SoilMoist1" cbars 0 94 +## soilMoist1Month.png "SoilMoist1 (Hourly Avg)" cbars 0 95 +## soilMoist1Year.png "SoilMoist1 (Daily Avg)" cbars 0 96 +## soilMoist2Day.png "SoilMoist2" cbars 0 97 +## soilMoist2Month.png "SoilMoist2 (Hourly Avg)" cbars 0 98 +## soilMoist2Year.png "SoilMoist2 (Daily Avg)" cbars 0 99 +## soilMoist3Day.png "SoilMoist3" cbars 0 100 +## soilMoist3Month.png "SoilMoist3 (Hourly Avg)" cbars 0 101 +## soilMoist3Year.png "SoilMoist3 (Daily Avg)" cbars 0 102 +## soilMoist4Day.png "SoilMoist4" cbars 0 103 +## soilMoist4Month.png "SoilMoist4 (Hourly Avg)" cbars 0 104 +## soilMoist4Year.png "SoilMoist4 (Daily Avg)" cbars 0 105 + + + +################# D I A G N O S T I C S ################# + +# Filename Label Units DP FNIndex +#--------------------- ------------------------------ ---------- -- ------- +#rxcheck.png "RX Good Packets" % 0 111 + diff --git a/examples/html/exfoliation/standard/index-day.htx b/examples/html/exfoliation/standard/index-day.htx new file mode 100644 index 0000000..2594a7f --- /dev/null +++ b/examples/html/exfoliation/standard/index-day.htx @@ -0,0 +1,15 @@ + + + + Weather: <!--stationCity--> <!--stationState--> + + + + + + + + + diff --git a/examples/html/exfoliation/standard/index-night.htx b/examples/html/exfoliation/standard/index-night.htx new file mode 100644 index 0000000..68804ba --- /dev/null +++ b/examples/html/exfoliation/standard/index-night.htx @@ -0,0 +1,15 @@ + + + + Weather: <!--stationCity--> <!--stationState--> + + + + + + + + + diff --git a/examples/html/exfoliation/standard/index.htx b/examples/html/exfoliation/standard/index.htx new file mode 100644 index 0000000..7c97210 --- /dev/null +++ b/examples/html/exfoliation/standard/index.htx @@ -0,0 +1,15 @@ + + + + Weather: <!--stationCity--> <!--stationState--> + + + + + + + + + diff --git a/examples/html/exfoliation/standard/index.incx b/examples/html/exfoliation/standard/index.incx new file mode 100644 index 0000000..b5d6e33 --- /dev/null +++ b/examples/html/exfoliation/standard/index.incx @@ -0,0 +1,94 @@ + + + + + + + + +
+ + + +
+ + + + + + + + + +
+ + + + + + + + +
+ +RADAR + +
 
+ +
+
+ + + + + + + + + + +
+ + + +
+ + + +
+ + + +
+
+ + + + + + + + + + + + + +
+ + +
+ + +
+ + +
+ +
+ +
+ +
+ + diff --git a/examples/html/exfoliation/standard/location.incx b/examples/html/exfoliation/standard/location.incx new file mode 100644 index 0000000..ee31e53 --- /dev/null +++ b/examples/html/exfoliation/standard/location.incx @@ -0,0 +1,8 @@ +

+ + +
+ - - +
+ +

diff --git a/examples/html/exfoliation/standard/navctls.incx b/examples/html/exfoliation/standard/navctls.incx new file mode 100644 index 0000000..c55cbe5 --- /dev/null +++ b/examples/html/exfoliation/standard/navctls.incx @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/examples/html/exfoliation/standard/phone.htx b/examples/html/exfoliation/standard/phone.htx new file mode 100644 index 0000000..c7474ee --- /dev/null +++ b/examples/html/exfoliation/standard/phone.htx @@ -0,0 +1,131 @@ + + + + weather at <!--stationCity--> <!--stationState--> + + + + + + + + + + + + + +
+ °
+ +
+ ° +
+ ° +
+ + + + + + + + + + + + + + + + + + + + + + +
+

HUMIDITY

+ +
+

DEWPOINT

+ ° +
+

HEAT INDEX

+ ° +
+

HIGH HEAT INDEX

+ ° + +
+

WIND CHILL

+ ° +
+

LOW WIND CHILL

+ ° + +
+

RAINFALL

+ +
+

HIGH RAIN RATE

+ +
+

WIND

+ +
+

HIGH WIND

+ +
+ + + + + + +
+ RADAR + + FORECAST +
+ + + diff --git a/examples/html/exfoliation/standard/post-generate.sh b/examples/html/exfoliation/standard/post-generate.sh new file mode 100755 index 0000000..ac6aec3 --- /dev/null +++ b/examples/html/exfoliation/standard/post-generate.sh @@ -0,0 +1,19 @@ +#/bin/sh + +# configure the img directory if it is not already configured. this is for +# flash-based systems where the img directory is a ramdisk. +STATIC_DIR=/opt/wview/etc/skins/exfoliation/static +WWW_DIR=/var/wview/img +for d in NOAA Archive; do + if [ ! -d $WWW_DIR/$d ]; then + mkdir -p $WWW_DIR/$d + fi +done +for f in exfoliation.css exfoliation.js; do + if [ ! -f $WWW_DIR/$f -a -f $SKIN_DIR/$f ]; then + cp $SKIN_DIR/$f $WWW_DIR + fi +done + +exit 0 + diff --git a/examples/html/exfoliation/standard/readings.incx b/examples/html/exfoliation/standard/readings.incx new file mode 100644 index 0000000..0b7d16d --- /dev/null +++ b/examples/html/exfoliation/standard/readings.incx @@ -0,0 +1,138 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Sunrise:
Sunset:
Moon:
 
Temperature:
Wind Chill:
Heat Index:
Apparent Temp:
Wet Bulb Temp:
Dewpoint:
Humidity:
Barometer: 
Wind: at
High Wind: at
Average Wind:
Beaufort Scale:
Today's Rain:
Rain Rate:
High Rain Rate: at
Storm Total:
Monthly Rain:
Yearly Rain ():
Air Density:
Cumulus Base:
High Temperature: at
Low Temperature: at
High Heat Index: at
Low Wind Chill: at
High Humidity: at
Low Humidity: at
High Dewpoint: at
Low Dewpoint: at
High Barometer: at
Low Barometer: at
+ diff --git a/examples/html/exfoliation/standard/wxrss.xtx b/examples/html/exfoliation/standard/wxrss.xtx new file mode 100644 index 0000000..423afed --- /dev/null +++ b/examples/html/exfoliation/standard/wxrss.xtx @@ -0,0 +1,39 @@ + + + + + <!--stationCity--> <!--stationState--> + Insert_Your_WX_HomePage_URL_Here + Current Weather Conditions + en-us + , + 1 + + + + <!--stationCity-->,<!--stationState--> Weather Conditions: <!--stationDate--> <!--stationTime--> + Insert_Your_WX_HomePage_URL_Here + Current Weather Conditions + , + Insert_Your_WX_HomePage_URL_Here + - + + Temp:
+ Wind Chill:
+ Heat Index:
+ Humidity:
+ Dewpoint:
+ Barometer:
+ Wind: at
+ Rain Today:
+ Rain Rate:
+

+ ]]>
+
+
+
diff --git a/examples/html/exfoliation/static/exfoliation.css b/examples/html/exfoliation/static/exfoliation.css new file mode 100644 index 0000000..982b1ed --- /dev/null +++ b/examples/html/exfoliation/static/exfoliation.css @@ -0,0 +1,114 @@ +/* wview stylesheet for exfoliation skin + $Id$ + Copyright 2011 Matthew Wall, all rights reserved +*/ +body { + font-family: arial, helvetica, sans-serif; + font-size: 10pt; +} +h1 { + font-size: 110%; +} +h2 { + font-size: 100%; +} +.copyright { + font-size: 8pt; + font-style: italic; + color: #aaaaaa; +} +.page_description { + width: 25%; +} +#station_title { + font-size: 140%; + font-weight: bold; +} +#station_location { + font-size: 60%; +} +#station_time { + font-size: 60%; +} +#navigation_controls { + font-size: 85%; + width: 100%; +} +#report_controls { + font-size: 80%; + width: 100%; +} +.metric_title { + font-size: 90%; + font-weight: bold; + text-align: center; +} +.metric_name { + font-size: 70%; + text-align: right; +} +.metric_value { + font-size: 70%; + font-weight: bold; + text-align: left; +} +.metric_units { + font-size: 70%; + text-align: left; +} +.hilo_time { + font-size: 70%; + text-align: left; + color: #aaaaaa; +} +.chart { +/* background-color: #eeeeee; */ +/* background: url(chart_bg.png) no-repeat center; */ + width: 300; + height: 180; +} +.dial { +/* background-color: #eeeeee; */ +/* background: url(dial_bg.png) no-repeat center; */ + width: 160; + height: 160; +} +.bucket { +/* background-color: #eeeeee; */ +/* background: url(bucket_bg.png) no-repeat center; */ + width: 120; + height: 240; +} +#index_plain { + background-color: #ffffff; +/* background-image: url(django_bg.jpg); */ +} +#index_day { + background-color: #ffffff; +/* background-image: url(Clouds.jpg); */ +} +#index_night { + background-color: #ffffff; +/* background-image: url(above_clouds.jpg); */ +} +#almanac { + background-color: #ffffff; +} +#current { + background-color: #ffffff; +} +#daily { + background-color: #ffffff; +} +#weekly { + background-color: #ffffff; +} +#monthly { + background-color: #ffffff; +} +#yearly { + background-color: #ffffff; +} +#current_summary { + background-color: #ffffff; +} diff --git a/examples/html/exfoliation/static/exfoliation.js b/examples/html/exfoliation/static/exfoliation.js new file mode 100644 index 0000000..d424875 --- /dev/null +++ b/examples/html/exfoliation/static/exfoliation.js @@ -0,0 +1,107 @@ +// wview javascript +// $Id$ +// refactored nov 2011 by Matthew Wall + +function createDaySelect(name) { + document.write(""); +} + +function createMonthlySelect(name) { + var month = new Array; + month[0]="Jan"; + month[1]="Feb"; + month[2]="Mar"; + month[3]="Apr"; + month[4]="May"; + month[5]="Jun"; + month[6]="Jul"; + month[7]="Aug"; + month[8]="Sep"; + month[9]="Oct"; + month[10]="Nov"; + month[11]="Dec"; + + var mydate = new Date(); + var mymonth = mydate.getMonth(); + var monthstring = ""; + + document.write(""); +} + +function createYearlySelect(name) { + var mydate = new Date(); + var myyear = (mydate.getYear() % 1900) + 1900; + var yearstring = ""; + + document.write(""); +} + +function openNoaaFile(month, year) { + var url = "NOAA/NOAA-"; + url = url + year; + if (month != '') { + url = url + "-"; + url = url + month; + } + + url = url + ".txt"; + window.location=url; +} + +function openARCFile(day, month, year) { + var url = "Archive/ARC-" + year + "-" + month + "-" + day + ".txt"; + window.location=url; +} + +function openURL(urlname) { + window.location=urlname; +} diff --git a/examples/html/parameterlist.htx b/examples/html/parameterlist.htx new file mode 100644 index 0000000..b327f5f --- /dev/null +++ b/examples/html/parameterlist.htx @@ -0,0 +1,411 @@ +############################ wview version ############################
+wviewVersion=
+
+############################ wview up time ############################
+wviewUpTime=
+
+############################ station info ############################
+stationName=
+stationType=
+stationCity=
+stationState=
+stationElevation=
+stationLatitude=
+stationLongitude=
+localRadarURL=
+localForecastURL=
+
+############################ unit labels ############################
+tempUnit=
+humUnit=
+windUnit=
+barUnit=
+rateUnit=
+rainUnit=
+airDensityUnit=
+cumulusBaseUnit=
+
+############################ Current Conditions ############################
+stationDate=
+stationDateMetric=
+stationTime=
+stationTimeNoSecs=
+sunriseTime=
+sunsetTime=
+middayTime=
+dayLength=
+civilriseTime=
+civilsetTime=
+astroriseTime=
+astrosetTime=
+moonriseTime=
+moonsetTime=
+insideTemp=
+insideHumidity=
+outsideTemp=
+windChill=
+intervalAvgWindChill=
+outsideHumidity=
+outsideHeatIndex=
+apparentTemp=
+wetBulbTemp=
+windDirection=
+windDirectionDegrees=
+windSpeed=
+intervalAvgWindSpeed=
+windGustSpeed=
+windGustDirectionDegrees=
+windBeaufortScale=
+intervalAvgBeaufortScale=
+outsideDewPt=
+barometer=
+baromtrend=
+stationPressure=
+altimeter=
+rainRate=
+dailyRain=
+dailyRainMM=
+monthlyRain=
+stormRain=
+stormStart=
+totalRain=
+rainSeasonStart=
+UV=
+ET=
+solarRad=
+moonPhase=
+airDensity=
+cumulusBase=
+
+############################ VP-Specific ############################
+extraTemp1=
+extraTemp2=
+extraTemp3=
+soilTemp1=
+soilTemp2=
+soilTemp3=
+soilTemp4=
+leafTemp1=
+leafTemp2=
+soilMoist1=
+soilMoist2=
+leafWet1=
+leafWet2=
+extraHumid1=
+extraHumid2=
+forecastRule=
+forecastIcon=
+forecastIconFile=
+rxCheckPercent=
+tenMinuteAvgWindSpeed=
+txBatteryStatus=
+consBatteryVoltage=
+
+############################ WXT510-Specific ############################
+wxt510Hail=
+wxt510Hailrate=
+wxt510HeatingTemp=
+wxt510HeatingVoltage=
+wxt510SupplyVoltage=
+wxt510ReferenceVoltage=
+wxt510RainDuration=
+wxt510RainPeakRate=
+wxt510HailDuration=
+wxt510HailPeakRate=
+wxt510Rain=
+wxt510RainDurationMin=
+wxt510HailDurationMin=
+
+############################ WMR918-Specific ############################
+wmr918Humid3=
+wmr918Pool=
+wmr918Tendency=
+wmr918WindBatteryStatus=
+wmr918RainBatteryStatus=
+wmr918OutTempBatteryStatus=
+wmr918InTempBatteryStatus=
+wmr918poolTempBatteryStatus=
+wmr918extra1BatteryStatus=
+wmr918extra2BatteryStatus=
+wmr918extra3BatteryStatus=
+
+###################### Generic Extra Parameters #########################
+genExtraTemp1=
+genExtraTemp2=
+genExtraTemp3=
+genExtraTemp4=
+genExtraTemp5=
+genExtraTemp6=
+genExtraTemp7=
+genExtraTemp8=
+genExtraTemp9=
+genExtraTemp10=
+genExtraTemp11=
+genExtraTemp12=
+genExtraTemp13=
+genExtraTemp14=
+genExtraTemp15=
+genExtraTemp16=
+genExtraHumidity1=
+genExtraHumidity2=
+genExtraHumidity3=
+genExtraHumidity4=
+genExtraHumidity5=
+genExtraHumidity6=
+genExtraHumidity7=
+genExtraHumidity8=
+genExtraHumidity9=
+genExtraHumidity10=
+genExtraHumidity11=
+genExtraHumidity12=
+genExtraHumidity13=
+genExtraHumidity14=
+genExtraHumidity15=
+genExtraHumidity16=
+genExtraTempBatteryStatus1=
+genExtraTempBatteryStatus2=
+genExtraTempBatteryStatus3=
+genExtraTempBatteryStatus4=
+genExtraTempBatteryStatus5=
+genExtraTempBatteryStatus6=
+genExtraTempBatteryStatus7=
+genExtraTempBatteryStatus8=
+genExtraTempBatteryStatus9=
+genExtraTempBatteryStatus10=
+genExtraTempBatteryStatus11=
+genExtraTempBatteryStatus12=
+genExtraTempBatteryStatus13=
+genExtraTempBatteryStatus14=
+genExtraTempBatteryStatus15=
+genExtraTempBatteryStatus16=
+genExtraWindBatteryStatus=
+genExtraOutTempBatteryStatus=
+genExtraConsoleBatteryStatus=
+genExtraUVBatteryStatus=
+genExtraSolarBatteryStatus=
+genExtraRainBatteryStatus=
+############################ Highs/Lows ############################
+hiOutsideTemp=
+hiOutsideTempTime=
+lowOutsideTemp=
+lowOutsideTempTime=
+hiHumidity=
+hiHumTime=
+lowHumidity=
+lowHumTime=
+hiDewpoint=
+hiDewpointTime=
+lowDewpoint=
+lowDewpointTime=
+hiWindSpeed=
+hiWindSpeedTime=
+hiBarometer=
+hiBarometerTime=
+lowBarometer=
+lowBarometerTime=
+hiRainRate=
+hiRainRateTime=
+lowWindchill=
+lowWindchillTime=
+hiHeatindex=
+hiHeatindexTime=
+hiRadiation=
+hiRadiationTime=
+hiUV=
+hiUVTime=
+
+hiMonthlyOutsideTemp=
+lowMonthlyOutsideTemp=
+hiMonthlyHumidity=
+lowMonthlyHumidity=
+hiMonthlyDewpoint=
+lowMonthlyDewpoint=
+hiMonthlyWindSpeed=
+hiMonthlyBarometer=
+lowMonthlyBarometer=
+lowMonthlyWindchill=
+hiMonthlyHeatindex=
+hiMonthlyRainRate=
+hiMonthlyRadiation=
+hiMonthlyUV=
+
+hiYearlyOutsideTemp=
+lowYearlyOutsideTemp=
+hiYearlyHumidity=
+lowYearlyHumidity=
+hiYearlyDewpoint=
+lowYearlyDewpoint=
+hiYearlyWindSpeed=
+hiYearlyBarometer=
+lowYearlyBarometer=
+lowYearlyWindchill=
+hiYearlyHeatindex=
+hiYearlyRainRate=
+hiYearlyRadiation=
+hiYearlyUV=
+
+hiAllTimeOutsideTemp=
+lowAllTimeOutsideTemp=
+hiAllTimeHumidity=
+lowAllTimeHumidity=
+hiAllTimeDewpoint=
+lowAllTimeDewpoint=
+hiAllTimeWindSpeed=
+hiAllTimeBarometer=
+lowAllTimeBarometer=
+lowAllTimeWindchill=
+hiAllTimeHeatindex=
+hiAllTimeRainRate=
+hiAllTimeRadiation=
+hiAllTimeUV=
+
+#### hourly values
+hourrain=
+hourwindrun=
+houravgtemp=
+houravgwind=
+hourdomwinddir=
+houravghumid=
+houravgdewpt=
+houravgbarom=
+
+hourchangetemp=
+hourchangewind=
+hourchangewinddir=
+hourchangehumid=
+hourchangedewpt=
+hourchangebarom=
+
+#### daily values
+daywindrun=
+dayavgtemp=
+dayavgwind=
+daydomwinddir=
+dayhighwinddir=
+dayavghumid=
+dayavgdewpt=
+dayavgbarom=
+
+daychangetemp=
+daychangewind=
+daychangewinddir=
+daychangehumid=
+daychangedewpt=
+daychangebarom=
+
+#### 7 day values
+weekwindrun=
+weekavgtemp=
+weekavgwind=
+weekdomwinddir=
+weekavghumid=
+weekavgdewpt=
+weekavgbarom=
+
+weekchangetemp=
+weekchangewind=
+weekchangewinddir=
+weekchangehumid=
+weekchangedewpt=
+weekchangebarom=
+
+#### monthly values
+monthtodatewindrun=
+monthtodateavgtemp=
+monthtodateavgwind=
+monthtodatedomwinddir=
+monthtodateavghumid=
+monthtodateavgdewpt=
+monthtodateavgbarom=
+
+monthtodatemaxtempdate=
+monthtodatemintempdate=
+monthtodateminchilldate=
+monthtodatemaxheatdate=
+monthtodatemaxhumiddate=
+monthtodateminhumiddate=
+monthtodatemaxdewptdate=
+monthtodatemindewptdate=
+monthtodatemaxbaromdate=
+monthtodateminbaromdate=
+monthtodatemaxwinddate=
+monthtodatemaxgustdate=
+monthtodatemaxrainratedate=
+
+#### yearly values
+yeartodatewindrun=
+yeartodateavgtemp=
+yeartodateavgwind=
+yeartodatedomwinddir=
+yeartodateavghumid=
+yeartodateavgdewpt=
+yeartodateavgbarom=
+
+yeartodatemaxtempdate=
+yeartodatemintempdate=
+yeartodateminchilldate=
+yeartodatemaxheatdate=
+yeartodatemaxhumiddate=
+yeartodateminhumiddate=
+yeartodatemaxdewptdate=
+yeartodatemindewptdate=
+yeartodatemaxbaromdate=
+yeartodateminbaromdate=
+yeartodatemaxwinddate=
+yeartodatemaxgustdate=
+yeartodatemaxrainratedate=
+
+#### all-time values
+alltimeavgtemp=
+alltimeavgwind=
+alltimedomwinddir=
+alltimeavghumid=
+alltimeavgdewpt=
+alltimeavgbarom=
+
+alltimemaxtempdate=
+alltimemintempdate=
+alltimeminchilldate=
+alltimemaxheatdate=
+alltimemaxhumiddate=
+alltimeminhumiddate=
+alltimemaxdewptdate=
+alltimemindewptdate=
+alltimemaxbaromdate=
+alltimeminbaromdate=
+alltimemaxwinddate=
+alltimemaxgustdate=
+alltimemaxrainratedate=
+
+windSpeed_ms=
+windGustSpeed_ms=
+intervalAvgWindSpeed_ms=
+tenMinuteAvgWindSpeed_ms=
+hiWindSpeed_ms=
+hiMonthlyWindSpeed_ms=
+hiYearlyWindSpeed_ms=
+houravgwind_ms=
+dayavgwind_ms=
+weekavgwind_ms=
+monthtodateavgwind_ms=
+yeartodateavgwind_ms=
+hourchangewind_ms=
+daychangewind_ms=
+weekchangewind_ms=
+
+windSpeed_kts=
+windGustSpeed_kts=
+intervalAvgWindSpeed_kts=
+tenMinuteAvgWindSpeed_kts=
+hiWindSpeed_kts=
+hiMonthlyWindSpeed_kts=
+hiYearlyWindSpeed_kts=
+houravgwind_kts=
+dayavgwind_kts=
+weekavgwind_kts=
+monthtodateavgwind_kts=
+yeartodateavgwind_kts=
+hourchangewind_kts=
+daychangewind_kts=
+weekchangewind_kts=
+
diff --git a/examples/html/parameterlist.txt b/examples/html/parameterlist.txt new file mode 100755 index 0000000..58ed1db --- /dev/null +++ b/examples/html/parameterlist.txt @@ -0,0 +1,416 @@ +############################ wview version ############################ + + +############################ wview up time ############################ + + +############################ station info ############################ + + + + + + + + + + +############################ unit labels ############################ + + + + + + + + + +############################ Current Conditions ############################ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + // yearly rain + + + + + + + + + + + +############################ VP-Specific ############################ + + + + + + + + + + + + + + + + + + + + + + + + +############################ WXT510-Specific ############################ + + + + + + + + + + + + + + +############################ WMR918-Specific ############################ + + + + + + + + + + + + + +###################### Generic Extra Parameters ######################### + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +############################ Highs/Lows ############################ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +#### hourly values + + + + + + + + + + + + + + + + +#### daily values + + + + + + + + + + + + + + + + +#### 7 day values + + + + + + + + + + + + + + + +#### monthly values + + + + + + + + + + + + + + + + + + + + + + +#### yearly values + + + + + + + + + + + + + + + + + + + + + + +#### all-time values + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/rsyslog/99-wview.conf b/examples/rsyslog/99-wview.conf new file mode 100644 index 0000000..fd651f1 --- /dev/null +++ b/examples/rsyslog/99-wview.conf @@ -0,0 +1,6 @@ +:programname,isequal,"radmrouted" /var/log/wview.log +:programname,isequal,"radmrouted" ~ +:programname,isequal,"htmlgend" /var/log/wview.log +:programname,isequal,"htmlgend" ~ +:programname,startswith,"wv" /var/log/wview.log +:programname,startswith,"wv" ~ diff --git a/examples/scripts/rainInLastWeek.sh b/examples/scripts/rainInLastWeek.sh new file mode 100644 index 0000000..ace9d06 --- /dev/null +++ b/examples/scripts/rainInLastWeek.sh @@ -0,0 +1,18 @@ +#!/bin/sh + +## rainInLastWeek.sh +## +## Script to retrieve the last week's rain. +## Change the value 604800 (number of seconds in a week) if you want a different +## time frame. +## +## Contributed by n1otx + +## Define the location of the archive database (adjust if necessary): +#WVIEW_ARCHIVE_DB=/usr/local/var/wview/archive/wview-archive.sdb +#WVIEW_ARCHIVE_DB=/opt/local/var/wview/archive/wview-archive.sdb +WVIEW_ARCHIVE_DB=/var/lib/wview/archive/wview-archive.sdb + +sqlite3 $WVIEW_ARCHIVE_DB \ + "SELECT sum(rain) As Sum FROM archive WHERE dateTime BETWEEN \ + strftime('%s','now')-604800 AND strftime('%s','now');" diff --git a/examples/scripts/sqlite2mysql-HOWTO.txt b/examples/scripts/sqlite2mysql-HOWTO.txt new file mode 100644 index 0000000..e312027 --- /dev/null +++ b/examples/scripts/sqlite2mysql-HOWTO.txt @@ -0,0 +1,16 @@ +copy the sqlite archive file to a working directory of your choice: +cp /usr/local/var/wview/archive/wview-archive.sdb . + +Dump the sqlite file, in this case: +sqlite3 wview-archive.sdb .dump .quit >> wview-archive-dump.sql + +then run sqlite2mysql: +sqlite2mysql.sh < wview-archive-dump.sql > wview-archive-mysql-import.sql + +Let's create an empty database to test the import.sql: +mysql> create database wview_test; +Query OK, 1 row affected (0.02 sec) + +Exit mysql and import wview-archive-mysql-import.sql: +mysql -u al -p wview_test < wview-archive-mysql-import.sql +enter password, wait a bit, and the import is done. diff --git a/examples/scripts/sqlite2mysql.sh b/examples/scripts/sqlite2mysql.sh new file mode 100755 index 0000000..c87e03d --- /dev/null +++ b/examples/scripts/sqlite2mysql.sh @@ -0,0 +1,10 @@ +#!/bin/sed -f +# +# Input: sqlite3 .dump format +# Output: MySQL input format +# +/BEGIN TRANSACTION;/d +/interval INTEGER NOT NULL/s/interval/arcInt/ +/dateTime INTEGER NOT NULL UNIQUE PRIMARY KEY/s/UNIQUE // +s/"/`/g +/COMMIT;/d diff --git a/examples/scripts/wview-ftp.sh b/examples/scripts/wview-ftp.sh new file mode 100644 index 0000000..d959be8 --- /dev/null +++ b/examples/scripts/wview-ftp.sh @@ -0,0 +1,74 @@ +#!/bin/sh + +# Author: Jerry Fiddler + +# a script to replace wvftpd, which I can't get to work reliably +# this runs automatically after htmlgend, if it's named /etc/wview/ +post-generate.sh +# +# it looks in the wview img directory, and ftp's anything newer than +the last time the script was run + +# These are parameters that are user-specific, and must be set for the +specific user + +FTP_USER=XXXXXXX +FTP_PASS=XXXXXX +FTP_HOST_DIR=MyISP.com/public_html/wview/img/ +FTP_ARGS=iV +#FTP_ARGS-iv + +# These are probably fine, but can be changed if desired, or if images +are stored elsewhere + +IMG_DIR=/var/wview/img +FTP_LAST_SENT_FILE=ftp-last-sent +FTP_ERROR_LOG=/var/wview/ftperrors + +# Set VERBOSE to 1 to log all ftp transers to /var/log/messages. 0 +turns logging off. + +VERBOSE=0 +LOG_TAG=wview_post-generate + +#Nothing else should need to be changed + +URL=ftp://$FTP_USER:$FTP_PASS@$FTP_HOST_DIR + +cd $IMG_DIR + +if [ -e $FTP_LAST_SENT_FILE ] +then + FILES_TO_SEND=`find . -type f -newer $FTP_LAST_SENT_FILE -print` +else + FILES_TO_SEND=`find . -type f -print` + fi + +if [ ! -z "$FILES_TO_SEND" ] +then + if [ $VERBOSE != 0 ] + then + logger -t $LOG_TAG ftp $FILES_TO_SEND + fi + + # echo >> $FTP_ERROR_LOG $URL $FILES_TO_SEND + + ftp 2>> $FTP_ERROR_LOG -$FTP_ARGS -u $URL $FILES_TO_SEND + RETVAL=$? + + if [ $RETVAL = 0 ] + then + cat /dev/null >$FTP_LAST_SENT_FILE # to mark the last transfer + else + logger -t $LOG_TAG ftp returned error code $RETVAL! + date >> $FTP_ERROR_LOG + fi +else + # no files to send + if [ $VERBOSE != 0 ] + then + logger -t $LOG_TAG no files to send + fi +fi + +exit 0 diff --git a/ftp/Makefile.am b/ftp/Makefile.am new file mode 100755 index 0000000..842942d --- /dev/null +++ b/ftp/Makefile.am @@ -0,0 +1,41 @@ +# Makefile - wviewftpd + +# files to include in the distro but not build + +#define the executable to be built +bin_PROGRAMS = wviewftpd + +# define include directories +INCLUDES = -I$(top_srcdir)/common \ + -I$(prefix)/include \ + -DWV_CONFIG_DIR=\"$(sysconfdir)/wview\" \ + -DWV_RUN_DIR=\"$(localstatedir)/wview\" \ + -D_GNU_SOURCE + +# define the sources +wviewftpd_SOURCES = \ + $(top_srcdir)/common/wvutils.c \ + $(top_srcdir)/common/wvconfig.c \ + $(top_srcdir)/common/status.c \ + $(top_srcdir)/common/status.h \ + $(top_srcdir)/ftp/ftp.c \ + $(top_srcdir)/ftp/ftpUtils.c \ + $(top_srcdir)/common/datadefs.h \ + $(top_srcdir)/common/dbsqlite.h \ + $(top_srcdir)/common/services.h \ + $(top_srcdir)/common/sysdefs.h \ + $(top_srcdir)/common/wvconfig.h \ + $(top_srcdir)/ftp/ftp.h \ + $(top_srcdir)/ftp/ftpUtils.h + + +# define libraries +wviewftpd_LDADD = -lssl -lcurl + +# define library directories +wviewftpd_LDFLAGS = -L$(prefix)/lib -L$(prefix)/usr/lib -L/usr/lib + +if CROSSCOMPILE +wviewftpd_LDFLAGS += $(prefix)/lib/crt1.o $(prefix)/lib/crti.o $(prefix)/lib/crtn.o +endif + diff --git a/ftp/ftp.c b/ftp/ftp.c new file mode 100755 index 0000000..54bea42 --- /dev/null +++ b/ftp/ftp.c @@ -0,0 +1,423 @@ +/*--------------------------------------------------------------------------- + + FILENAME: + ftp.c + + PURPOSE: + Provide the wview ftp generator entry point. + + REVISION HISTORY: + Date Engineer Revision Remarks + 01/10/04 M.S. Teel 0 Original + + NOTES: + + + LICENSE: + Copyright (c) 2004, Mark S. Teel (mark@teel.ws) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +/* ... System include files +*/ +#include +#include +#include + +/* ... Library include files +*/ +#include + +/* ... Local include files +*/ +#include + + +/* ... global memory declarations +*/ + +/* ... global memory referenced +*/ + +/* ... static (local) memory declarations +*/ +static FTP_WORK ftpWork; + +static char* ftpStatusLabels[STATUS_STATS_MAX] = +{ + "Rules defined", + "Rules sent", + NULL, + NULL +}; + +/* ... methods +*/ +/* ... system initialization +*/ +static int ftpSysInit (FTP_WORK *work) +{ + char devPath[256], temp[64]; + struct stat fileData; + + /* ... check for our daemon's pid file, don't run if it isn't there + */ + sprintf (devPath, "%s/%s", WVIEW_RUN_DIR, WVD_LOCK_FILE_NAME); + if (stat (devPath, &fileData) != 0) + { + radMsgLogInit (PROC_NAME_FTP, TRUE, TRUE); + radMsgLog (PRI_CATASTROPHIC, + "wview daemon lock file %s does not exist - aborting!", + devPath); + radMsgLogExit (); + return ERROR; + } + + sprintf (work->pidFile, "%s/%s", WVIEW_RUN_DIR, FTP_LOCK_FILE_NAME); + sprintf (work->fifoFile, "%s/dev/%s", WVIEW_RUN_DIR, PROC_NAME_FTP); + sprintf (work->statusFile, "%s/%s", WVIEW_STATUS_DIRECTORY, FTP_STATUS_FILE_NAME); + sprintf (work->daemonQname, "%s/dev/%s", WVIEW_RUN_DIR, PROC_NAME_DAEMON); + sprintf (work->wviewdir, "%s/img", WVIEW_RUN_DIR); + + /* ... check for our pid file, don't run if it IS there + */ + if (stat (work->pidFile, &fileData) == 0) + { + radMsgLogInit (PROC_NAME_FTP, TRUE, TRUE); + radMsgLog (PRI_CATASTROPHIC, + "lock file %s exists, older copy may be running - aborting!", + work->pidFile); + radMsgLogExit (); + return ERROR; + } + + return OK; +} + +/* ... system exit +*/ +static int ftpSysExit (FTP_WORK *work) +{ + struct stat fileData; + + /* ... delete our pid file + */ + if (stat (work->pidFile, &fileData) == 0) + { + unlink (work->pidFile); + } + + return OK; +} + + +void FTPDefaultSigHandler (int signum) +{ + int retVal; + + switch (signum) + { + case SIGHUP: + // user wants us to change the verbosity setting + retVal = wvutilsToggleVerbosity (); + radMsgLog (PRI_STATUS, "wviewftpd: SIGHUP - toggling log verbosity %s", + ((retVal == 0) ? "OFF" : "ON")); + + radProcessSignalCatch(signum, FTPDefaultSigHandler); + return; + + case SIGBUS: + case SIGFPE: + case SIGSEGV: + case SIGXFSZ: + case SIGSYS: + // unrecoverable radProcessSignalCatch- we must exit right now! + radMsgLog (PRI_CATASTROPHIC, + "wviewftpd: recv unrecoverable signal %d: aborting!", + signum); + abort (); + + case SIGCHLD: + // it is normal behavior to have children finishing up + wvutilsWaitForChildren(); + radProcessSignalCatch(signum, FTPDefaultSigHandler); + break; + + default: + // we can allow the process to exit normally... + if (ftpWork.exiting) + { + radProcessSignalCatch(signum, FTPDefaultSigHandler); + return; + } + + radMsgLog (PRI_CATASTROPHIC, "wviewftpd: recv signal %d: exiting!", signum); + + ftpWork.exiting = TRUE; + radProcessSetExitFlag (); + + radProcessSignalCatch(signum, FTPDefaultSigHandler); + break; + } + + return; +} + +static void msgHandler +( + char *srcQueueName, + UINT msgType, + void *msg, + UINT length, + void *userData +) +{ + return; +} + +static void evtHandler +( + UINT eventsRx, + UINT rxData, + void *userData +) +{ + return; +} + +static void timerHandler (void *parm) +{ + uint64_t msOffset = radTimeGetMSSinceEpoch (); + int rules; + int64_t netOffset; + + // ... allow for timer latency + if (ftpWork.msOffset == 0) + { + // first time through + ftpWork.msOffset = msOffset; + netOffset = 0; + } + else + { + netOffset = msOffset - ftpWork.msOffset; + while (netOffset >= 60000LL) + { + netOffset -= 60000LL; + } + } + + while (ftpWork.msOffset < msOffset) + { + ftpWork.msOffset += 60000ULL; + } + + radProcessTimerStart(ftpWork.timer, (uint32_t)(60000LL - netOffset)); + + + // ... process the ftp rules + rules = ftpUtilsSendFiles(ftpWork.ftpId, ftpWork.wviewdir); + + if (rules > 0) + { + wvutilsLogEvent (PRI_STATUS, "FTP: %d rules processed", rules); + } + else if (rules < 0) + { + wvutilsLogEvent (PRI_MEDIUM, "FTP: ftpUtilsSendFiles failed!"); + } + + return; +} + + +/* ... the main entry point for the ftp process +*/ +int main (int argc, char *argv[]) +{ + void (*alarmHandler)(int); + STIM stim; + int i; + int seconds; + time_t ntime; + struct tm locTime; + long offset, retVal; + long msOffset; + FILE *pidfile; + int runAsDaemon = TRUE; + + if (argc > 1) + { + if (!strcmp(argv[1], "-f")) + { + runAsDaemon = FALSE; + } + } + + memset (&ftpWork, 0, sizeof (ftpWork)); + + /* ... initialize some system stuff first + */ + retVal = ftpSysInit (&ftpWork); + if (retVal == ERROR) + { + radMsgLogInit (PROC_NAME_FTP, FALSE, TRUE); + radMsgLog (PRI_CATASTROPHIC, "ftp init failed!"); + radMsgLogExit (); + exit (1); + } + else if (retVal == ERROR_ABORT) + { + exit (2); + } + + + /* ... call the global radlib system init function + */ + if (radSystemInit (WVIEW_SYSTEM_ID) == ERROR) + { + radMsgLogInit (PROC_NAME_FTP, TRUE, TRUE); + radMsgLog (PRI_CATASTROPHIC, "radSystemInit failed!"); + radMsgLogExit (); + exit (1); + } + + + /* ... call the radlib process init function + */ + if (radProcessInit (PROC_NAME_FTP, + ftpWork.fifoFile, + PROC_NUM_TIMERS_FTP, + runAsDaemon, // TRUE for daemon + msgHandler, + evtHandler, + NULL) + == ERROR) + { + printf ("radProcessInit failed: %s", PROC_NAME_FTP); + radSystemExit (WVIEW_SYSTEM_ID); + exit (1); + } + + ftpWork.myPid = getpid (); + pidfile = fopen (ftpWork.pidFile, "w"); + if (pidfile == NULL) + { + radMsgLog (PRI_CATASTROPHIC, "lock file create failed!"); + radProcessExit (); + radSystemExit (WVIEW_SYSTEM_ID); + exit (1); + } + fprintf (pidfile, "%d", getpid ()); + fclose (pidfile); + + + alarmHandler = radProcessSignalGetHandler (SIGALRM); + radProcessSignalCatchAll (FTPDefaultSigHandler); + radProcessSignalCatch (SIGALRM, alarmHandler); + radProcessSignalRelease(SIGABRT); + + + ftpWork.timer = radTimerCreate (NULL, timerHandler, NULL); + if (ftpWork.timer == NULL) + { + radMsgLog (PRI_HIGH, "radTimerCreate failed"); + ftpSysExit (&ftpWork); + radProcessExit (); + radSystemExit (WVIEW_SYSTEM_ID); + exit (1); + } + + // ... initialize the ftp utilities + retVal = ftpUtilsInit (&ftpWork.ftpData); + + if (retVal != OK) + { + if (retVal == ERROR) + { + radMsgLog (PRI_HIGH, "ftpUtilsInit failed"); + } + radTimerDelete (ftpWork.timer); + ftpSysExit (&ftpWork); + radProcessExit (); + radSystemExit (WVIEW_SYSTEM_ID); + exit (1); + } + else + { + ftpWork.ftpId = &ftpWork.ftpData; + } + + + if (statusInit(ftpWork.statusFile, ftpStatusLabels) == ERROR) + { + radMsgLog (PRI_HIGH, "ALARM status init failed - exiting..."); + ftpSysExit (&ftpWork); + radProcessExit (); + radSystemExit (WVIEW_SYSTEM_ID); + exit (1); + } + statusUpdate(STATUS_BOOTING); + statusUpdateStat(FTP_STATS_RULES_DEFINED, + radListGetNumberOfNodes(&ftpWork.ftpData.rules)); + + // ... start THE timer + ntime = time (NULL); + localtime_r (&ntime, &locTime); + seconds = locTime.tm_sec - 15; // start at 15 secs past + + /* ... start the ftp timer to go off 1 min past the next 5 minute mark + */ + offset = locTime.tm_min % 5; + if (offset) + offset = 6 - offset; + else + offset = 1; + + radMsgLog (PRI_HIGH, + "starting ftp timer for %d mins %d secs", + ((seconds > 0) ? ((offset > 0) ? offset-1 : offset) : offset), + (seconds > 0) ? 60 - seconds : (-1) * seconds); + + msOffset = radTimeGetMSSinceEpoch () % 1000; + msOffset -= 250; // land on 250 ms mark + + + ftpWork.msOffset = 0; + radProcessTimerStart (ftpWork.timer, + ((((offset * 60) - seconds) * 1000)) - msOffset); + + statusUpdate(STATUS_RUNNING); + statusUpdateMessage("Normal operation"); + + + while (! ftpWork.exiting) + { + /* ... wait on timers, events, file descriptors, msgs, everything! + */ + if (radProcessWait (0) == ERROR) + { + ftpWork.exiting = TRUE; + } + } + + + statusUpdateMessage("exiting normally"); + radMsgLog (PRI_STATUS, "exiting normally..."); + statusUpdate(STATUS_SHUTDOWN); + + if (ftpWork.ftpId != NULL) + { + ftpUtilsExit (ftpWork.ftpId); + } + + radTimerDelete (ftpWork.timer); + ftpSysExit (&ftpWork); + radProcessExit (); + radSystemExit (WVIEW_SYSTEM_ID); + exit (0); +} + diff --git a/ftp/ftp.h b/ftp/ftp.h new file mode 100644 index 0000000..21ab660 --- /dev/null +++ b/ftp/ftp.h @@ -0,0 +1,86 @@ +#ifndef INC_ftph +#define INC_ftph +/*--------------------------------------------------------------------------- + + FILENAME: + ftp.h + + PURPOSE: + Provide the wview FTP generator definitions. + + REVISION HISTORY: + Date Engineer Revision Remarks + 01/10/04 M.S. Teel 0 Original + + NOTES: + + + LICENSE: + Copyright (c) 2004, Mark S. Teel (mark@teel.ws) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +/* ... System include files +*/ +#include +#include +#include +#include +#include +#include +#include + +/* ... Library include files +*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* ... Local include files +*/ +#include +#include +#include + + +/* ... API definitions +*/ + +/* !!!!!!!!!!!!!!!!!! HIDDEN, NOT FOR API USE !!!!!!!!!!!!!!!!!! +*/ + +typedef struct +{ + pid_t myPid; + char pidFile[128]; + char fifoFile[128]; + char statusFile[128]; + char daemonQname[128]; + char wviewdir[128]; + TIMER_ID timer; + uint64_t msOffset; + FTP_DATA ftpData; + FTP_ID ftpId; + int exiting; +} FTP_WORK; + +/* !!!!!!!!!!!!!!!!!!!! END HIDDEN SECTION !!!!!!!!!!!!!!!!!!!!! +*/ + + + +/* ... API function prototypes +*/ + + + +#endif diff --git a/ftp/ftpUtils.c b/ftp/ftpUtils.c new file mode 100644 index 0000000..c6a98ab --- /dev/null +++ b/ftp/ftpUtils.c @@ -0,0 +1,509 @@ +/*--------------------------------------------------------------------------- + + FILENAME: + ftpUtils.c + + PURPOSE: + Provide the ftp utilities. + + REVISION HISTORY: + Date Engineer Revision Remarks + 01/08/04 M.S. Teel 0 Original + + NOTES: + + + LICENSE: + Copyright (c) 2004, Mark S. Teel (mark@teel.ws) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +/* ... System include files +*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* ... Library include files +*/ +#include + +/* ... Local include files +*/ +#include + + +/* ... global memory declarations +*/ + +/* ... global memory referenced +*/ +extern void FTPDefaultSigHandler (int signum); + +/* ... static (local) memory declarations +*/ +static char FTPRule[FTP_MAX_PATH]; +static char curlError[CURL_ERROR_SIZE]; + + +static int FilterFile(const struct dirent *dp) +{ + if (fnmatch (FTPRule, dp->d_name, FNM_FILE_NAME) == 0) + { + return TRUE; + } + else + { + return FALSE; + } +} + +static int SendRule(FTP_ID id, FTP_RULE_ID rule, const char* markerFile) +{ + static char FTPCmndLine[FTP_MAX_CMND_LENGTH]; + struct dirent **namelist; + struct stat statbuf; + int index, logindex, numFiles, cmndLength, hdrLength, retVal = 0; + char logBfr[RADMSGLOG_MAX_LENGTH]; + time_t lastUpdate; + char dirnameStr[FTP_MAX_PATH], basenameStr[FTP_MAX_PATH], loginStr[128]; + char tempRule[_MAX_PATH]; + CURLcode res; + FILE* sendFile; + + // dirname and basename may be destructive of the input string: + wvstrncpy(tempRule, rule->src, FTP_MAX_PATH); + wvstrncpy(dirnameStr, dirname(tempRule), FTP_MAX_PATH); + wvstrncpy(tempRule, rule->src, FTP_MAX_PATH); + wvstrncpy(basenameStr, basename(tempRule), FTP_MAX_PATH); + wvstrncpy(FTPRule, basenameStr, FTP_MAX_PATH); + + numFiles = scandir(dirnameStr, &namelist, FilterFile, alphasort); + if (numFiles <= 0) + { + radMsgLog (PRI_HIGH, "FTP-ERROR: No files match rule %s", rule->src); + return ERROR; + } + + cmndLength = 0; + + if (strlen(id->directory) == 0) + { + cmndLength += sprintf (&FTPCmndLine[cmndLength], "ftp://%s/", + id->host); + } + else + { + cmndLength += sprintf (&FTPCmndLine[cmndLength], "ftp://%s/%s/", + id->host, id->directory); + } + + hdrLength = cmndLength; + + sprintf(loginStr, "%s:%s", id->user, id->pass); + + // Grab last transmission time marker: + // wvutilsReadMarkerFile returns 0 if the file does not exist: + lastUpdate = wvutilsReadMarkerFile(markerFile); + + // Loop through all files in the list: + for (index = 0; index < numFiles; index ++) + { + // Skip meta files: + if (! strncmp(namelist[index]->d_name, ".", 8) || + ! strncmp(namelist[index]->d_name, "..", 8)) + { + free(namelist[index]); + continue; + } + + // Get entry's information: + if (strncmp(dirnameStr, ".", 2) != 0) + { + sprintf(tempRule, "%s/%s", dirnameStr, namelist[index]->d_name); + } + else + { + // In this directory: + sprintf(tempRule, "%s", namelist[index]->d_name); + } + + memset(&statbuf, 0, sizeof(statbuf)); + if (stat(tempRule, &statbuf) == -1) + { + free(namelist[index]); + continue; + } + + if (statbuf.st_mtime <= lastUpdate) + { + // nothing new: + wvutilsLogEvent(PRI_STATUS, "FTP-STATUS: %s is not new, skipping it", + tempRule); + free(namelist[index]); + continue; + } + + // send it now: + cmndLength = hdrLength; + cmndLength += sprintf(&FTPCmndLine[cmndLength], "%s", tempRule); + + // Log the command before executing it: + for (logindex = 0; logindex < cmndLength; logindex += RADMSGLOG_MAX_LENGTH) + { + wvstrncpy(logBfr, &FTPCmndLine[logindex], RADMSGLOG_MAX_LENGTH); + if (strlen(logBfr) > 0) + { + wvutilsLogEvent(PRI_STATUS, "FTP-URL: %s", logBfr); + } + } + + // Execute the curl transaction: + sendFile = fopen(tempRule, "rb"); + if (sendFile == NULL) + { + sprintf(logBfr, "FTP-ERROR: failed to open %s", tempRule); + radMsgLog (PRI_HIGH, "%s", logBfr); + statusUpdateMessage(logBfr); + free(namelist[index]); + continue; + } + + // Set curl URL, login and FILE* each time through: + curl_easy_setopt(id->curlHandle, CURLOPT_URL, FTPCmndLine); + curl_easy_setopt(id->curlHandle, CURLOPT_USERPWD, loginStr); + curl_easy_setopt(id->curlHandle, CURLOPT_READDATA, sendFile); + + res = curl_easy_perform(id->curlHandle); + if (res != CURLE_OK) + { + sprintf(logBfr, "FTP-ERROR: curl_easy_perform failed: %s", curlError); + radMsgLog (PRI_HIGH, "%s", logBfr); + statusUpdateMessage(logBfr); + statusIncrementStat(FTP_STATS_CONNECT_ERRORS); + } + else + { + wvutilsLogEvent(PRI_STATUS, "FTP-SUCCESS: %s", tempRule); + retVal ++; + } + + fclose(sendFile); + free(namelist[index]); + } + + free(namelist); + return retVal; +} + + +// API: +int ftpUtilsInit (FTP_DATA* data) +{ + FTP_RULE_ID rule; + FTP_ID newId; + int i; + char conftype[WVIEW_STRING1_SIZE]; + int iValue; + const char* source; + int interval; + FileStates state = FS_HOST; + + newId = (FTP_ID)data; + radListReset (&newId->rules); + newId->expiry = 0; + + if (wvconfigInit(FALSE) == ERROR) + { + radMsgLog (PRI_CATASTROPHIC, "wvconfigInit failed!"); + return ERROR; + } + + // Is the ftp daemon enabled? + iValue = wvconfigGetBooleanValue(configItem_ENABLE_FTP); + if (iValue == ERROR || iValue == 0) + { + wvconfigExit (); + radMsgLog (PRI_STATUS, "ftp daemon disabled - exiting..."); + return ERROR_ABORT; + } + + // get the wview verbosity setting + if (wvutilsSetVerbosity (WV_VERBOSE_WVIEWFTPD) == ERROR) + { + radMsgLog (PRI_CATASTROPHIC, "wvutilsSetVerbosity failed!"); + wvconfigExit (); + return ERROR_ABORT; + } + + + source = wvconfigGetStringValue(configItemFTP_HOST); + if (source == NULL) + { + // No host defined - bail: + radMsgLog (PRI_CATASTROPHIC, "wvconfigGetStringValue %s failed!", + configItemFTP_HOST); + wvconfigExit(); + return ERROR; + } + wvstrncpy (newId->host, source, sizeof(newId->host)); + radMsgLog (PRI_STATUS, "FTP-CONFIG: using remote host: %s", newId->host); + + source = wvconfigGetStringValue(configItemFTP_USERNAME); + if (source == NULL) + { + // No username defined - bail: + radMsgLog (PRI_CATASTROPHIC, "wvconfigGetStringValue %s failed!", + configItemFTP_USERNAME); + wvconfigExit(); + return ERROR; + } + wvstrncpy (newId->user, source, sizeof(newId->user)); + + source = wvconfigGetStringValue(configItemFTP_PASSWD); + if (source == NULL) + { + // No passwd defined - bail: + radMsgLog (PRI_CATASTROPHIC, "wvconfigGetStringValue %s failed!", + configItemFTP_PASSWD); + wvconfigExit(); + return ERROR; + } + wvstrncpy (newId->pass, source, sizeof(newId->pass)); + + source = wvconfigGetStringValue(configItemFTP_REMOTE_DIRECTORY); + if (source == NULL) + { + // No dir defined - bail: + radMsgLog (PRI_CATASTROPHIC, "wvconfigGetStringValue %s failed!", + configItemFTP_REMOTE_DIRECTORY); + wvconfigExit(); + return ERROR; + } + wvstrncpy (newId->directory, source, sizeof(newId->directory)); + radMsgLog (PRI_STATUS, "FTP-CONFIG: using remote directory: %s", newId->directory); + + // Get passive mode setting: + iValue = wvconfigGetBooleanValue(configItemFTP_USE_PASSIVE); + if (iValue == ERROR || iValue == 1) + { + // Default to passive mode: + newId->IsPassive = TRUE; + radMsgLog (PRI_STATUS, "FTP-CONFIG: using EPSV mode for transfers"); + } + else + { + newId->IsPassive = FALSE; + radMsgLog (PRI_STATUS, "FTP-CONFIG: not using EPSV mode for transfers"); + } + + // Get default interval: + iValue = wvconfigGetINTValue(configItemFTP_INTERVAL); + if (iValue == ERROR || iValue == 0) + { + // Default to 5 minutes: + newId->interval = 5; + radMsgLog (PRI_STATUS, "FTP-CONFIG: using default 5 minute interval for transfers"); + } + else + { + newId->interval = iValue; + radMsgLog (PRI_STATUS, "FTP-CONFIG: using %d minute interval for transfers", iValue); + } + + for (i = 1; i <= FTP_MAX_RULES; i ++) + { + rule = (FTP_RULE_ID)malloc (sizeof (*rule)); + if (rule == NULL) + { + for (rule = (FTP_RULE_ID)radListRemoveFirst (&newId->rules); + rule != NULL; + rule = (FTP_RULE_ID)radListRemoveFirst (&newId->rules)) + { + free (rule); + } + + return ERROR; + } + + sprintf (conftype, "FTP_RULE_%1.1d_SOURCE", i); + source = wvconfigGetStringValue(conftype); + if (source == NULL) + { + // No source given - continue: + radMsgLog (PRI_MEDIUM, "wvconfigGetStringValue %s failed!", + conftype); + free (rule); + continue; + } + + if (strlen(source) == 0) + { + // No source or interval given - continue: + free (rule); + continue; + } + else + { + wvstrncpy (rule->src, source, sizeof(rule->src)); + radListAddToEnd (&newId->rules, (NODE_PTR)rule); + } + } + + wvconfigExit(); + + curl_global_init(CURL_GLOBAL_ALL); + + + // Create curl handle here for all files: + newId->curlHandle = curl_easy_init(); + if (newId->curlHandle == NULL) + { + radMsgLog (PRI_HIGH, "FTP-ERROR: failed to initialize curl!"); + statusUpdateMessage("failed to initialize curl"); + return ERROR; + } + + // Setup curl options that are not file specific: + + // Make sure libcurl does NOT use SIG_ALARM: + curl_easy_setopt(newId->curlHandle, CURLOPT_NOSIGNAL, 1L); + + curl_easy_setopt(newId->curlHandle, CURLOPT_ERRORBUFFER, curlError); + curl_easy_setopt(newId->curlHandle, CURLOPT_UPLOAD, OPTION_TRUE); + curl_easy_setopt(newId->curlHandle, CURLOPT_TCP_NODELAY, OPTION_TRUE); + if (newId->IsPassive) + { + curl_easy_setopt(newId->curlHandle, CURLOPT_FTP_USE_EPSV, OPTION_TRUE); + } + else + { + curl_easy_setopt(newId->curlHandle, CURLOPT_FTP_USE_EPSV, OPTION_FALSE); + } + + // Set overall timeout for a file: + curl_easy_setopt(newId->curlHandle, CURLOPT_TIMEOUT, 20L); + + // Set the connection timeout: + curl_easy_setopt(newId->curlHandle, CURLOPT_CONNECTTIMEOUT, 15L); + + // Set server FTP response timeout: + curl_easy_setopt(newId->curlHandle, CURLOPT_FTP_RESPONSE_TIMEOUT, 10L); + + // Set the duration in seconds for the curl handle's DNS cache before flush: + // (7 days) + curl_easy_setopt(newId->curlHandle, CURLOPT_DNS_CACHE_TIMEOUT, 604800L); + + curl_easy_setopt(newId->curlHandle, CURLOPT_NOPROGRESS, OPTION_TRUE); + curl_easy_setopt(newId->curlHandle, CURLOPT_FTP_CREATE_MISSING_DIRS, OPTION_TRUE); + + radMsgLog (PRI_MEDIUM, "FTP: INIT: %d rules added", + radListGetNumberOfNodes (&newId->rules)); + + return OK; +} + +int ftpUtilsSendFiles (FTP_ID id, char *workdir) +{ + FTP_RULE_ID rule; + int retVal, numRules = 0, numFiles = 0; + char markerFName[_MAX_PATH]; + time_t updateTime = time(NULL); + + sprintf (markerFName, "%s/%s", WVIEW_RUN_DIR, FTP_MARKER_FILE); + + if (id->expiry > updateTime) + { + // not our time yet: + return 0; + } + + if (chdir(workdir) == -1) + { + radMsgLog (PRI_HIGH, "FTP: chdir failed: %s", strerror(errno)); + return ERROR; + } + + for (rule = (FTP_RULE_ID)radListGetFirst (&id->rules); + rule != NULL; + rule = (FTP_RULE_ID)radListGetNext (&id->rules, (NODE_PTR)rule)) + { + if (strlen(rule->src) == 0) + { + // skip this one, it is empty: + continue; + } + + wvutilsLogEvent(PRI_STATUS, "FTP-RULE: checking for new %s", rule->src); + + retVal = SendRule(id, rule, markerFName); + if (retVal > 0) + { + numFiles += retVal; + } + + numRules ++; + } + + // First time through? + if (id->expiry == 0) + { + id->expiry = updateTime; + } + + while (id->expiry <= updateTime) + { + id->expiry += (id->interval * 60); + } + + if (numRules == 0) + { + // nothing to do! + return 0; + } + + + // Update status: + id->rulesSent += numRules; + statusUpdateStat(FTP_STATS_RULES_SENT, id->rulesSent); + + // Mark the last update time: + wvutilsWriteMarkerFile(markerFName, updateTime); + + wvutilsLogEvent(PRI_STATUS, "FTP-DONE: sent %d files", numFiles); + return numRules; +} + + +void ftpUtilsExit (FTP_ID id) +{ + FTP_RULE_ID rule; + + curl_easy_cleanup(id->curlHandle); + curl_global_cleanup(); + + for (rule = (FTP_RULE_ID)radListRemoveFirst (&id->rules); + rule != NULL; + rule = (FTP_RULE_ID)radListRemoveFirst (&id->rules)) + { + free (rule); + } +} + + diff --git a/ftp/ftpUtils.h b/ftp/ftpUtils.h new file mode 100755 index 0000000..a07dcc8 --- /dev/null +++ b/ftp/ftpUtils.h @@ -0,0 +1,124 @@ +#ifndef INC_ftputilsh +#define INC_ftputilsh +/*--------------------------------------------------------------------------- + + FILENAME: + ftpUtils.h + + PURPOSE: + Provide the ftp utility definitions. + + REVISION HISTORY: + Date Engineer Revision Remarks + 01/08/04 M.S. Teel 0 Original + + NOTES: + + + LICENSE: + Copyright (c) 2004, Mark S. Teel (mark@teel.ws) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +/* ... System include files +*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* ... Library include files +*/ +#include +#include +#include +#include +#include +#include +#include + +/* ... Local include files +*/ +#include +#include +#include + + + +/* ... API definitions +*/ + +/* !!!!!!!!!!!!!!!!!! HIDDEN, NOT FOR API USE !!!!!!!!!!!!!!!!!! +*/ + +#define FTP_MAX_RULES 10 +#define FTP_MAX_CMND_LENGTH 512 +#define OPTION_TRUE 1L +#define OPTION_FALSE 0L +#define FTP_MAX_PATH 256 + +typedef enum +{ + FTP_STATS_RULES_DEFINED = 0, + FTP_STATS_RULES_SENT, + FTP_STATS_CONNECT_ERRORS +} FTP_STATS; + + +typedef enum +{ + FS_HOST = 1, + FS_USER = 2, + FS_PASS = 3, + FS_DIRECTORY = 4, + FS_BINARY = 5, + FS_ARGS = 6, + FS_RULE = 7 +} FileStates; + +typedef struct +{ + NODE node; + char src[128]; +}*FTP_RULE_ID; + + +typedef struct +{ + RADLIST rules; + CURL* curlHandle; + int rulesSent; + char host[96]; + char user[64]; + char pass[64]; + char directory[256]; + int IsPassive; + int interval; + time_t expiry; +}FTP_DATA, *FTP_ID; + + +/* !!!!!!!!!!!!!!!!!!!! END HIDDEN SECTION !!!!!!!!!!!!!!!!!!!!! +*/ + + + +/* ... API function prototypes +*/ +extern int ftpUtilsInit (FTP_DATA* data); + +// ... process the rules list; returns number of rules successfully sent +extern int ftpUtilsSendFiles (FTP_ID id, char *workdir); + +extern void ftpUtilsExit (FTP_ID); + + +#endif diff --git a/ftp/wviewftp.debug.sh b/ftp/wviewftp.debug.sh new file mode 100644 index 0000000..1a17eb5 --- /dev/null +++ b/ftp/wviewftp.debug.sh @@ -0,0 +1,68 @@ +#!/bin/bash +################################################################################ +# wviewftp.debug.sh +# +# Author: Oscar Barlow +# Date: Dec 16, 2008 +# +# Description: This shell script logs the parameters sent to the ftp process +# by wview. It logs the ftp command that wview would use to ftp your files. +# After logging the information, it attempts to execute the ftp process. +# This script appends to the log file so each time the ftp process is called by +# wview, the information is logged. To debug the ftp process, copy the ftp +# command from the log file to a shell prompt and see what errors you get. +# +# To use the script, follow these steps: +# +# 1. create a directory under root called wview +# 2. move this file to that directory. Make sure the file is executable +# chmod +x wviewftp.debug.sh +# 3. modify the LOGFILE and wviewVAR variables below: +# +LOGFILE=/root/wview/wview_ftp_debug.txt +wviewVAR=/usr/local/var/wview +wviewFTP_BINARY=/usr/bin/tnftp +# +# 4. modify your wviewftp.conf file and have wview execute this script for +# the ftp process. Example below: +# ftp_binary /root/wview/wviewftp.debug.sh +# +# After you have resolved the issue, you can change the myDEBUG variable to +# false and the script won't log the next time wview calls the script. +# +myDEBUG=True +# +################################################################################ +# +# +if [[ $myDEBUG = "True" ]] +then + echo start $(date) >> ${LOGFILE} + pwd >> ${LOGFILE} + echo parm1=$1 >> ${LOGFILE} + echo parm2=$2 >> ${LOGFILE} + echo parm3=$3 >> ${LOGFILE} + echo parm4=$4 >> ${LOGFILE} + echo parm5=$5 >> ${LOGFILE} + echo parm6=$6 >> ${LOGFILE} + echo parm7=$7 >> ${LOGFILE} + echo parm8=$8 >> ${LOGFILE} + echo parm9=$9 >> ${LOGFILE} + echo parm10=${10} >> ${LOGFILE} + echo parm11=${11} >> ${LOGFILE} + echo parm12=${12} >> ${LOGFILE} + echo parm13=${13} >> ${LOGFILE} + echo parm14=${14} >> ${LOGFILE} + echo parm15=${15} >> ${LOGFILE} + echo FTP COMMAND BELOW + echo "${wviewFTP_BINARY} ${1} $2 $3 $4 $5 $6 $7 $8 $9 ${10} ${11} ${12} ${13} $(14) ${15}" >> ${LOGFILE} +fi + +cd ${wviewVAR} +${wviewFTP_BINARY} ${1} $2 $3 $4 $5 $6 $7 $8 $9 ${10} ${11} ${12} ${13} ${14} ${15} +cd + +if [[ $myDEBUG = "True" ]] +then + echo end $(date) >> ${LOGFILE} +fi diff --git a/htmlgenerator/Makefile.am b/htmlgenerator/Makefile.am new file mode 100755 index 0000000..d7afc13 --- /dev/null +++ b/htmlgenerator/Makefile.am @@ -0,0 +1,74 @@ +# Makefile - htmlgend + +# files to include in the distro but not build + +#define the executable to be built +bin_PROGRAMS = htmlgend + +# define include directories +INCLUDES = \ + -I$(top_srcdir)/common \ + -I$(prefix)/include \ + -D_GNU_SOURCE \ + -DWV_CONFIG_DIR=\"$(sysconfdir)/wview\" \ + -DWV_RUN_DIR=\"$(localstatedir)/wview\" \ + -DBUILD_HTMLGEND + +# define the sources +htmlgend_SOURCES = \ + $(top_srcdir)/common/sensor.c \ + $(top_srcdir)/common/wvutils.c \ + $(top_srcdir)/common/wvconfig.c \ + $(top_srcdir)/common/status.c \ + $(top_srcdir)/common/lunarCycle.c \ + $(top_srcdir)/common/sunTimes.c \ + $(top_srcdir)/common/dbsqlite.c \ + $(top_srcdir)/common/dbsqliteHistory.c \ + $(top_srcdir)/common/dbsqliteHiLow.c \ + $(top_srcdir)/common/dbsqliteNOAA.c \ + $(top_srcdir)/common/windAverage.c \ + $(top_srcdir)/common/emailAlerts.c \ + $(top_srcdir)/htmlgenerator/html.c \ + $(top_srcdir)/htmlgenerator/htmlStates.c \ + $(top_srcdir)/htmlgenerator/htmlMgr.c \ + $(top_srcdir)/htmlgenerator/htmlGenerate.c \ + $(top_srcdir)/htmlgenerator/noaaGenerate.c \ + $(top_srcdir)/htmlgenerator/arcrecGenerate.c \ + $(top_srcdir)/htmlgenerator/htmlUtils.c \ + $(top_srcdir)/htmlgenerator/glbucket.c \ + $(top_srcdir)/htmlgenerator/glchart.c \ + $(top_srcdir)/htmlgenerator/glmultichart.c \ + $(top_srcdir)/htmlgenerator/images.c \ + $(top_srcdir)/htmlgenerator/images-user.c \ + $(top_srcdir)/common/sensor.h \ + $(top_srcdir)/common/datadefs.h \ + $(top_srcdir)/common/dbsqlite.h \ + $(top_srcdir)/common/services.h \ + $(top_srcdir)/common/sysdefs.h \ + $(top_srcdir)/common/wvconfig.h \ + $(top_srcdir)/common/status.h \ + $(top_srcdir)/common/windAverage.h \ + $(top_srcdir)/common/emailAlerts.h \ + $(top_srcdir)/common/beaufort.h \ + $(top_srcdir)/htmlgenerator/glbucket.h \ + $(top_srcdir)/htmlgenerator/glchart.h \ + $(top_srcdir)/htmlgenerator/glmultichart.h \ + $(top_srcdir)/htmlgenerator/htmlGenerate.h \ + $(top_srcdir)/htmlgenerator/arcrecGenerate.h \ + $(top_srcdir)/htmlgenerator/html.h \ + $(top_srcdir)/htmlgenerator/htmlMgr.h \ + $(top_srcdir)/htmlgenerator/images.h \ + $(top_srcdir)/htmlgenerator/images-user.h \ + $(top_srcdir)/htmlgenerator/noaaGenerate.h + + +# define libraries +htmlgend_LDADD = + +# define library directories +htmlgend_LDFLAGS = -L$(prefix)/lib -L$(prefix)/usr/lib -L/usr/lib + +if CROSSCOMPILE +htmlgend_LDFLAGS += $(prefix)/lib/crt1.o $(prefix)/lib/crti.o $(prefix)/lib/crtn.o +endif + diff --git a/htmlgenerator/arcrecGenerate.c b/htmlgenerator/arcrecGenerate.c new file mode 100644 index 0000000..7a688f1 --- /dev/null +++ b/htmlgenerator/arcrecGenerate.c @@ -0,0 +1,317 @@ +/*--------------------------------------------------------------------------- + + FILENAME: + arcrecGenerate.c + + PURPOSE: + Provide the wview archive record browser generator utilities. + + REVISION HISTORY: + Date Engineer Revision Remarks + 07/05/2005 M.S. Teel 0 Original + 04/12/2008 W. Krenn 1 metric adaptions + + NOTES: + + + LICENSE: + Copyright (c) 2005, Mark S. Teel (mark@teel.ws) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +/* ... System include files +*/ + +/* ... Library include files +*/ + +/* ... Local include files +*/ +#include + + +/* ... global memory declarations +*/ + +/* ... global memory referenced +*/ + +/* ... static (local) memory declarations +*/ +static ARCREC_WORK arWork; + + +/* ... local functions +*/ +static time_t decrementTime (time_t timeval, int days) +{ + time_t retval = timeval; + + retval -= (days * WV_SECONDS_IN_DAY); + + return retval; +} + +static time_t setTo2000 (void) +{ + time_t retVal; + struct tm loctime; + + memset (&loctime, 0, sizeof (loctime)); + loctime.tm_year = 100; + loctime.tm_mday = 1; + + retVal = mktime (&loctime); + return retVal; +} + + +static char arcrecFileName[_MAX_PATH]; +static char *buildArcFilename (time_t timeval) +{ + sprintf (arcrecFileName, "%s/Archive/ARC-%4.4d-%2.2d-%2.2d.txt", + arWork.htmlPath, + wvutilsGetYear(timeval), + wvutilsGetMonth(timeval), + wvutilsGetDay(timeval)); + + return arcrecFileName; +} + +static char *buildArcFilenameFromRecord (ARCHIVE_PKT* record) +{ + sprintf (arcrecFileName, "%s/Archive/ARC-%4.4d-%2.2d-%2.2d.txt", + arWork.htmlPath, + wvutilsGetYear(record->dateTime), + wvutilsGetMonth(record->dateTime), + wvutilsGetDay(record->dateTime)); + + return arcrecFileName; +} + +static char *buildArcStartTime (time_t timeval) +{ + static char arcrecStart[64]; + + sprintf (arcrecStart, "%4.4d%2.2d%2.2d", + wvutilsGetYear(timeval), + wvutilsGetMonth(timeval), + wvutilsGetDay(timeval)); + + return arcrecStart; +} + +static int arFileExists (char *store, time_t val) +{ + char *arFilename = buildArcFilename (val); + struct stat fileStatus; + + wvstrncpy (store, arFilename, _MAX_PATH); + if (stat(arFilename, &fileStatus) == 0) + { + // does exist + return TRUE; + } + else + { + return FALSE; + } +} + +static int arSameDay (time_t temptime, time_t timenow) +{ + struct tm temploc, nowloc; + + localtime_r (&temptime, &temploc); + localtime_r (&timenow, &nowloc); + + if (temploc.tm_year != nowloc.tm_year || + temploc.tm_mon != nowloc.tm_mon || + temploc.tm_mday != nowloc.tm_mday) + { + return FALSE; + } + else + { + return TRUE; + } +} + +static void arcrecWriteHeader (FILE *file) +{ + int retVal; + + // write out a human readable header for the data + retVal = fwrite(arWork.header, 1, strlen(arWork.header), file); + + return; +} + + +int arcrecGenerateInit +( + char *htmlPath, + int daysToKeep, + int isMetric, + int arcInterval +) +{ + time_t timenow = time (NULL); + time_t startTime, temptime; + char *filename, *fgetsRetVal, temp[_MAX_PATH]; + struct stat fileData; + int count, length = 0, daysWritten = 0; + FILE *hdrFile; + + memset (&arWork, 0, sizeof (arWork)); + + wvstrncpy (arWork.htmlPath, htmlPath, _MAX_PATH); + arWork.daysToKeep = daysToKeep; + + if (daysToKeep == -1) + { + // we are disabled... + return OK; + } + + sprintf (temp, "%s/%s", WVIEW_CONFIG_DIR, ARCREC_HEADER_FILENAME); + if (stat (temp, &fileData) != 0) + { + radMsgLog (PRI_STATUS, "ARCREC: cannot locate header file %s - using English default...", + temp); + + // write out the English default to our buffer + memset (arWork.header, 0, ARCREC_HEADER_MAX_LENGTH); + count = sprintf (arWork.header, "--Timestamp---\tTemp\tChill\tHIndex\tHumid\tDewpt\tWind\tHiWind\tWindDir\tRain\tBarom\tSolar\tET\tUV\n"); + sprintf (&arWork.header[count], "--------------\t----\t-----\t------\t-----\t-----\t----\t------\t-------\t----\t-----\t-----\t--\t--\n"); + } + else + { + radMsgLog (PRI_STATUS, "ARCREC: using header file %s ...", + temp); + + // read the file into our header buffer + hdrFile = fopen (temp, "r"); + if (hdrFile == NULL) + { + radMsgLog (PRI_STATUS, "ARCREC: failed to open header file %s - using English default...", + temp); + memset (arWork.header, 0, ARCREC_HEADER_MAX_LENGTH); + count = sprintf (arWork.header, "--Timestamp---\tTemp\tChill\tHIndex\tHumid\tDewpt\tWind\tHiWind\tWindDir\tRain\tBarom\tSolar\tET\tUV\n"); + sprintf (&arWork.header[count], "--------------\t----\t-----\t------\t-----\t-----\t----\t------\t-------\t----\t-----\t-----\t--\t--\n"); + } + else + { + while ((length < ARCREC_HEADER_MAX_LENGTH - 1) && !feof (hdrFile)) + { + fgetsRetVal = fgets(&arWork.header[length], ARCREC_HEADER_MAX_LENGTH - (length+1), hdrFile); + length = strlen (arWork.header); + } + + fclose (hdrFile); + } + } + + // do we need to purge any files? + if (daysToKeep > 0) + { + // perhaps, get our starting point + startTime = temptime = decrementTime (timenow, daysToKeep); + startTime += WV_SECONDS_IN_DAY; + + // walk backwards in time until the file does not exist, deleting + // the old files + while (arFileExists (temp, temptime)) + { + // delete that bad boy! + unlink (temp); + + temptime -= WV_SECONDS_IN_DAY; + } + } + else + { + startTime = setTo2000(); + } + + // now, let's generate files for the last "daysToKeep" days (if needed), + // including today - if daysToKeep is 0, we must generate all of them + if (daysToKeep == 0) + { + radMsgLog (PRI_STATUS, "ARCREC: saving ALL daily archive reports ..."); + } + else + { + radMsgLog (PRI_STATUS, "ARCREC: saving %d daily archive reports ...", + daysToKeep); + } + + // we include the current time in the loop to do the partial for today + for (temptime = startTime; temptime <= timenow; temptime += WV_SECONDS_IN_DAY) + { + if (arFileExists(temp, temptime) && !arSameDay(temptime, timenow)) + { + daysWritten ++; + continue; + } + + filename = temp; + + // call the dbf utility to write out a day's records + if (dbsqliteWriteDailyArchiveReport (filename, + temptime, + isMetric, + arcInterval, + arcrecWriteHeader) + == OK) + { + daysWritten ++; + } + } + + radMsgLog (PRI_STATUS, "ARCREC: %d daily archive reports available", + daysWritten); + + return OK; +} + +int arcrecGenerate (ARCHIVE_PKT* record, int isMetric) +{ + char *temp; + int retVal; + time_t timenow; + char tempname[_MAX_PATH]; + + if (arWork.daysToKeep == -1) + { + // we are disabled + return OK; + } + + // build the filename + temp = buildArcFilenameFromRecord (record); + + retVal = dbsqliteUpdateDailyArchiveReport(temp, record, arcrecWriteHeader, isMetric); + + // we need to do some purging? + if (retVal == 1 && arWork.daysToKeep > 0) + { + // yes! + timenow = time (NULL); + timenow -= (arWork.daysToKeep * WV_SECONDS_IN_DAY); + if (arFileExists (tempname, timenow)) + { + // delete that bad boy! + unlink (tempname); + } + + return OK; + } + else + { + return retVal; + } +} diff --git a/htmlgenerator/arcrecGenerate.h b/htmlgenerator/arcrecGenerate.h new file mode 100644 index 0000000..cde0f47 --- /dev/null +++ b/htmlgenerator/arcrecGenerate.h @@ -0,0 +1,93 @@ +#ifndef INC_arcrecgenerateh +#define INC_arcrecgenerateh +/*--------------------------------------------------------------------------- + + FILENAME: + arcrecGenerate.h + + PURPOSE: + Provide the wview archive record browser generator definitions. + + REVISION HISTORY: + Date Engineer Revision Remarks + 07/05/2005 M.S. Teel 0 Original + 04/12/2008 W. Krenn 1 metric adaptions + + NOTES: + + + LICENSE: + Copyright (c) 2005, Mark S. Teel (mark@teel.ws) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +/* ... System include files +*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* ... Library include files +*/ +#include +#include +#include +#include + +/* ... Local include files +*/ +#include +#include +#include + + + +/* ... API definitions +*/ + +#define ARCREC_HEADER_FILENAME "arcrec-header.conf" + + +/* !!!!!!!!!!!!!!!!!! HIDDEN, NOT FOR API USE !!!!!!!!!!!!!!!!!! +*/ + +#define ARCREC_HEADER_MAX_LENGTH 1024 + +typedef struct +{ + char htmlPath [_MAX_PATH]; + int daysToKeep; + char header[ARCREC_HEADER_MAX_LENGTH]; +}__attribute__ ((packed)) ARCREC_WORK, *ARCREC_ID; + + + +/* !!!!!!!!!!!!!!!!!!!! END HIDDEN SECTION !!!!!!!!!!!!!!!!!!!!! +*/ + + +/* ... API function prototypes +*/ + +extern int arcrecGenerateInit +( + char *htmlPath, + int daysToKeep, + int isMetric, + int arcInterval +); + +extern int arcrecGenerate (ARCHIVE_PKT* record, int isMetric); + + +#endif + diff --git a/htmlgenerator/glbucket.c b/htmlgenerator/glbucket.c new file mode 100644 index 0000000..44e1c66 --- /dev/null +++ b/htmlgenerator/glbucket.c @@ -0,0 +1,679 @@ +/*--------------------------------------------------------------------------- + + FILENAME: + glbucket.c + + PURPOSE: + Provide the graphics lib utilities. + + REVISION HISTORY: + Date Engineer Revision Remarks + 09/05/03 M.S. Teel 0 Original + 02/03/07 Randy Miller 1 Add alternate units on right + side of bucket and at bottom + with label + 05/15/08 Werner Krenn 2 Add some metric units for dualUnits + + NOTES: + + + LICENSE: + Copyright (c) 2004, Mark S. Teel (mark@teel.ws) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +/* ... System include files +*/ +#include +#include +#include +#include +#include +#include +#include +#include + +/* ... Library include files +*/ +#include +#include +#include "gd.h" +#include "gdfonts.h" +#include "gdfontt.h" +#include "gdfontmb.h" +#include "gdfontg.h" +#include "gdfontl.h" + + +/* ... Local include files +*/ +#include + + +/* ... global memory declarations +*/ + +/* ... global memory referenced +*/ + +/* ... static (local) memory declarations +*/ +#define MAX_STEP_MULTIPLIER 9 + +static BUCKET nonReentrantBucket; + +static double stepSizeMultipliers[MAX_STEP_MULTIPLIER] = + { + 2.0, 5.0, 10.0, 20.0, 50.0, 100.0, 200.0, 500.0, 1000.0 + }; + + +static int createImage (BUCKET_ID id) +{ + if (id->height < ( 4 * gdFontMediumBold->h)) + { + return -1; + } + + if (id->im) + { + gdImageDestroy (id->im); + } + + id->im = gdImageCreate (id->width, id->height); + if (id->im == NULL) + { + return -1; + } + + // ... allocate our colors + id->bgcolor = gdImageColorAllocateAlpha (id->im, + GLB_RED(id->bgcolor), + GLB_GREEN(id->bgcolor), + GLB_BLUE(id->bgcolor), + GLB_ALPHA(id->bgcolor)); + + if (id->isTransparent) + { + gdImageColorTransparent (id->im, id->bgcolor); + id->titleBGcolor = id->bgcolor; + } + else + { + id->titleBGcolor = gdImageColorAllocateAlpha (id->im, + GLB_RED(id->titleBGcolor), + GLB_GREEN(id->titleBGcolor), + GLB_BLUE(id->titleBGcolor), + GLB_ALPHA(id->titleBGcolor)); + } + + id->bucketcolor = gdImageColorAllocateAlpha (id->im, + GLB_RED(id->bucketcolor), + GLB_GREEN(id->bucketcolor), + GLB_BLUE(id->bucketcolor), + GLB_ALPHA(id->bucketcolor)); + id->contentcolor = gdImageColorAllocateAlpha (id->im, + GLB_RED(id->contentcolor), + GLB_GREEN(id->contentcolor), + GLB_BLUE(id->contentcolor), + GLB_ALPHA(id->contentcolor)); + id->highcolor = gdImageColorAllocateAlpha (id->im, + GLB_RED(id->highcolor), + GLB_GREEN(id->highcolor), + GLB_BLUE(id->highcolor), + GLB_ALPHA(id->highcolor)); + id->lowcolor = gdImageColorAllocateAlpha (id->im, + GLB_RED(id->lowcolor), + GLB_GREEN(id->lowcolor), + GLB_BLUE(id->lowcolor), + GLB_ALPHA(id->lowcolor)); + id->titleFGcolor = gdImageColorAllocateAlpha (id->im, + GLB_RED(id->titleFGcolor), + GLB_GREEN(id->titleFGcolor), + GLB_BLUE(id->titleFGcolor), + GLB_ALPHA(id->titleFGcolor)); + id->textcolor = gdImageColorAllocateAlpha (id->im, + GLB_RED(id->textcolor), + GLB_GREEN(id->textcolor), + GLB_BLUE(id->textcolor), + GLB_ALPHA(id->textcolor)); + + return 0; +} + + +static void drawBucket (BUCKET_ID id) +{ + int i, ylabelwidth = 0; + double units, step, hashwidth; + double minR, stepR; + int numhash, x, y; + char text[64][32]; + char textR[64][32]; + char ylabelfmt[24]; + char ylabelfmtR[24]; + + /* Calculate the maximum number of hash marks and the units per hash */ + hashwidth = (5 * gdFontSmall->h)/4; + numhash = (id->imby - id->imty)/hashwidth; + if (numhash > 64) + numhash = 64; + + units = (id->max - id->min)/id->stepSize; + step = id->stepSize; + i = 0; + while ((int)units > numhash && i < MAX_STEP_MULTIPLIER) + { + step = id->stepSize * stepSizeMultipliers[i]; + units = (id->max - id->min)/step; + i ++; + } + + id->stepSize = step; + numhash = (int)units; + if ((id->min + (id->stepSize * numhash)) < id->max) + { + numhash ++; + } + + if (numhash == 0) + { + hashwidth = 0.0; + id->max = id->min; + } + else + { + hashwidth = (double)(id->imby - id->imty)/(double)numhash; + id->max = id->min + (id->stepSize * numhash); + } + + if (id->max - id->min == 0) + id->pixelconstant = 0; + else + id->pixelconstant = ((double)(id->imby - id->imty))/(id->max - id->min); + + if (numhash) + numhash ++; + if (numhash > 64) + numhash = 64; + + // bucket scales + // build labels and calculate the maximum label width + sprintf (ylabelfmt, "%%.%1.1df", id->decPlaces-1); + for (i = 0; i < numhash; i ++) + { + sprintf (text[i], ylabelfmt, id->min + (i * id->stepSize)); + // now fill the right hand array with alternate scale @Rcm + if ( id->isDualUnits ) + { + textR[i][0] = ' '; + if (strcmp(id->units,"hPa") == 0) + { + minR = wvutilsConvertHPAToINHG (id->min); + stepR = wvutilsConvertHPAToINHG (id->stepSize); + sprintf (textR[i], "%.2f", minR + (i * stepR)); + } + else if (strcmp(id->units,"inHg") == 0) + { + minR = wvutilsConvertINHGToHPA (id->min); + stepR = wvutilsConvertINHGToHPA (id->stepSize); + sprintf (textR[i], "%.0f", minR + (i * stepR)); + } + else if (strcmp(id->units,"F") == 0) + { + minR = wvutilsConvertFToC (id->min); + stepR = ((id->stepSize) * (5.0/9.0)); + sprintf (textR[i], "%.1f", minR + (i * stepR)); + } + else if (strcmp(id->units,"C") == 0) + { + minR = wvutilsConvertCToF (id->min); + stepR = ((id->stepSize) * (9.0/5.0)); + sprintf (textR[i], "%.1f", minR + (i * stepR)); + } + else if ((strcmp(id->units,"inches") == 0) || + (strcmp(id->units,"in/hour") == 0) || + (strcmp(id->units,"in/hr") == 0)) + { + minR = wvutilsConvertINToMM (id->min); + stepR = wvutilsConvertINToMM (id->stepSize); + if (wvutilsConvertINToMM (id->max) >= 100) + sprintf (textR[i], "%.0f", minR + (i * stepR)); + else + sprintf (textR[i], "%.1f", minR + (i * stepR)); + } + else if ((strcmp(id->units,"mm") == 0) || + (strcmp(id->units,"mm/hr") == 0) || + (strcmp(id->units,"mm/hour") == 0) || + (strcmp(id->units,"mm/h") == 0)) + { + minR = wvutilsConvertMMToIN (id->min); + stepR = wvutilsConvertMMToIN (id->stepSize); + if (wvutilsConvertMMToIN (id->max) >= 100) + sprintf (textR[i], "%.0f", minR + (i * stepR)); + else + sprintf (textR[i], "%.1f", minR + (i * stepR)); + } + else if (strcmp(id->units,"cm") == 0) + { + minR = wvutilsConvertCMToIN (id->min); + stepR = wvutilsConvertCMToIN (id->stepSize); + sprintf (textR[i], "%.1f", minR + (i * stepR)); + if (wvutilsConvertCMToIN (id->max) >= 100) + sprintf (textR[i], "%.0f", minR + (i * stepR)); + else + sprintf (textR[i], "%.1f", minR + (i * stepR)); + } + + else + { + // all else: eg '%' or watts/m^2 (hum/ rad) + // same value both sides + minR = id->min * 25.4; + stepR = id->stepSize * 25.4; + sprintf (textR[i], "%s", text[i]); + } + + if (strlen (text[i]) > ylabelwidth) + ylabelwidth = strlen (text[i])+2; + } + else + { + if (strlen (text[i]) > ylabelwidth) + ylabelwidth = strlen (text[i]); + } + } + + // ... now we know the bucket area, draw it + gdImageFilledRectangle (id->im, + id->imtx - GLB_CONTENTS_OFFSET, + id->imty, + id->imbx + GLB_CONTENTS_OFFSET, + id->imby + GLB_CONTENTS_OFFSET, + id->bucketcolor); + + // ... open up the contents area + gdImageFilledRectangle (id->im, + id->imtx, id->imty - 1, + id->imbx, id->imby - 1, + id->bgcolor); + + // ... draw the lips + gdImageSetThickness (id->im, GLB_CONTENTS_OFFSET); + gdImageLine (id->im, id->imtx-1, id->imty, + id->imtx-8, id->imty, id->bucketcolor); + gdImageLine (id->im, id->imbx+1, id->imty, + id->imbx+8, id->imty, id->bucketcolor); + + // ... draw the ticks and labels + gdImageSetThickness (id->im, 2); + for (i = 0; i < numhash; i ++) + { + // left side + x = id->imtx - (GLB_CONTENTS_OFFSET - 1); + y = (id->imby - (i * hashwidth)) + 1; + + gdImageLine (id->im, + x, + y, + x - 3, + y, + id->bucketcolor); + + y -= gdFontSmall->h/2; + gdImageString (id->im, + gdFontSmall, + (x - 5) - (strlen(text[i]) * gdFontSmall->h/2), + y, + (uint8_t *)text[i], + id->textcolor); + + // right side + x = id->imbx + (GLB_CONTENTS_OFFSET - 1); + y = (id->imby - (i * hashwidth)) + 1; + + gdImageLine (id->im, + x, + y, + x + 3, + y, + id->bucketcolor); + if ( id->isDualUnits ) + { + y -= gdFontSmall->h/2; + gdImageString (id->im, + gdFontSmall, + x + 4 + gdFontSmall->h/2, + y, + (uint8_t *)textR[i], + id->textcolor); + } + else + { + y -= gdFontSmall->h/2; + gdImageString (id->im, + gdFontSmall, + x + 4 + gdFontSmall->h/2, + y, + (uint8_t *)text[i], + id->textcolor); + } + } + + // ... finally, draw units and date + sprintf (ylabelfmt, "%%.%1.1df %%s", id->decPlaces); // generate the format string with a variable number of decimal places + sprintf (text[0], ylabelfmt, id-> value, id->units); // build the string with readig and units + + gdImageString (id->im, + gdFontMediumBold, + ((id->width - ((gdFontMediumBold->h/2) * (1+strlen (text[0]))))/2), + (id->height - (gdFontMediumBold->h*3)) + 4, + (uint8_t *)text[0], + id->textcolor); + + // -- Bottom of bucket display -- + // now next line down, draw the alt units @rcm + if ( id->isDualUnits) + { + if (strcmp(id->units,"F") == 0) + { + id->valueM = wvutilsConvertFToC (id->value); + sprintf (text[0], "%.1f C", id->valueM); + } + else if (strcmp(id->units,"C") == 0) + { + id->valueM = wvutilsConvertCToF (id->value); + sprintf (text[0], "%.0f F", id->valueM); + } + else if (strcmp(id->units,"inches") == 0) + { + id->valueM = wvutilsConvertINToMM (id->value); + sprintf (text[0], "%.1f mm", id->valueM); + } + else if ((strcmp(id->units,"in/hour") == 0) || (strcmp(id->units,"in/hr") == 0)) + { + id->valueM = wvutilsConvertINToMM (id->value); + sprintf (text[0], "%.1f mm/h", id->valueM); + } + else if ((strcmp(id->units,"mm/hr") == 0) || + (strcmp(id->units,"mm/hour") == 0) || + (strcmp(id->units,"mm/h") == 0)) + { + id->valueM = wvutilsConvertMMToIN (id->value); + sprintf (text[0], "%.2f in/hr", id->valueM); + } + else if (strcmp(id->units,"hPa") == 0) + { + id->valueM = wvutilsConvertHPAToINHG (id->value); + sprintf (text[0], "%2.3f inHg", id->valueM); + } + else if (strcmp(id->units,"inHg") == 0) + { + id->valueM = wvutilsConvertINHGToHPA (id->value); + sprintf (text[0], "%4.0f hPa", id->valueM); + } + else if (strcmp(id->units,"mm") == 0) + { + id->valueM = wvutilsConvertMMToIN (id->value); + sprintf (text[0], "%.2f inches", id->valueM); + } + else if (strcmp(id->units,"cm") == 0) + { + id->valueM = wvutilsConvertCMToIN (id->value); + sprintf (text[0], "%.2f inches", id->valueM); + } + else + { + // no conversion no second display + id->valueM = id->value; + text[0][0] = 0; + } + gdImageString (id->im, + gdFontMediumBold, + ((id->width - ((gdFontMediumBold->h/2) * (1+strlen (text[0]))))/2), + (id->height - (gdFontMediumBold->h*3)) + 15, + (uint8_t *)text[0], + id->textcolor); + } + gdImageString (id->im, + gdFontSmall, + ((id->width - ((gdFontSmall->h/2) * strlen (id->datetime)))/2) - 2, + id->height - (gdFontMediumBold->h - 0), + (uint8_t *)id->datetime, + id->textcolor); +} + +static void drawTitle (BUCKET_ID id) +{ + gdImageFilledRectangle (id->im, + 0, 0, + id->width, gdFontMediumBold->h, + id->titleBGcolor); + + gdImageString (id->im, + gdFontMediumBold, + ((id->width - ((gdFontMediumBold->h/2) * strlen (id->title)))/2) - 3, + 0, + (uint8_t *)id->title, + id->titleFGcolor); +} + +static void drawContents (BUCKET_ID id) +{ + int y; + + y = (int)(id->pixelconstant * (id->value - id->min)); + + // ... draw the contents area + gdImageFilledRectangle (id->im, + id->imtx, + id->imby - y, + id->imbx, + id->imby, + id->contentcolor); + + // ... draw high and low + if (id->high != GLB_HILOW_NONE) + { + y = id->pixelconstant * (id->high - id->min); + gdImageLine (id->im, + id->imtx, + id->imby - y, + id->imbx, + id->imby - y, + id->highcolor); + } + + if (id->low != GLB_HILOW_NONE) + { + y = id->pixelconstant * (id->low - id->min); + gdImageLine (id->im, + id->imtx, + id->imby - y, + id->imbx, + id->imby - y, + id->lowcolor); + } +} + + +/* ... methods +*/ + +BUCKET_ID bucketCreate (int width, int height, int bucketWidth, char *title) +{ + BUCKET_ID newId; + + if ((5 * gdFontSmall->h) + (2 * GLB_CONTENTS_OFFSET) + bucketWidth > width) + { + return NULL; + } + + newId = (BUCKET_ID)&nonReentrantBucket; + + memset (newId, 0, sizeof (*newId)); + + newId->width = width; + newId->height = height; + newId->bucketWidth = bucketWidth; + wvstrncpy (newId->title, title, sizeof(newId->title)); + + // ... now set the contents coords + newId->imtx = (newId->width - newId->bucketWidth)/2; + newId->imbx = (newId->width - 1) - newId->imtx; + newId->imty = 2 * gdFontMediumBold->h; + newId->imby = newId->height - (3 * gdFontMediumBold->h); + + newId->decPlaces = 0; + newId->bgcolor = GLB_DFLT_BG; + newId->bucketcolor = GLB_DFLT_BUCKET; + newId->contentcolor = GLB_DFLT_CONTENT; + newId->highcolor = GLB_DFLT_HIGH; + newId->lowcolor = GLB_DFLT_LOW; + newId->titleFGcolor = GLB_DFLT_TITLEFG; + newId->titleBGcolor = GLB_DFLT_TITLEBG; + newId->textcolor = GLB_DFLT_TEXT; + + return newId; +} + +void bucketSetScale (BUCKET_ID id, double min, double max, double step) +{ + id->min = min; + id->max = max; + id->stepSize = step; + + return; +} + +void bucketSetValues (BUCKET_ID id, double high, double low, double value) +{ + id->high = high; + id->low = low; + id->value = value; + + if ((id->high != GLB_HILOW_NONE) && (id->high > id->max)) + id->max = id->high; + + if ((id->low != GLB_HILOW_NONE) && (id->low < id->min)) + id->min = id->low; + + if (value < id->min) + id->min = value - (0.1 * value); + + if (value > id->max) + id->max = value + (0.1 * value); + + return; +} + +void bucketSetUnits (BUCKET_ID id, char *units) +{ + wvstrncpy (id->units, units, sizeof(id->units)); + return; +} + +void bucketSetDecimalPlaces (BUCKET_ID id, int decPlaces) +{ + id->decPlaces = decPlaces; + return; +} + +void bucketSetDateTime (BUCKET_ID id, char *datetime) +{ + wvstrncpy (id->datetime, datetime, sizeof(id->datetime)); + return; +} + +void bucketSetBGColor (BUCKET_ID id, int color) +{ + id->bgcolor = color; + return; +} + +void bucketSetBucketColor (BUCKET_ID id, int color) +{ + id->bucketcolor = color; + return; +} + +void bucketSetContentColor (BUCKET_ID id, int color) +{ + id->contentcolor = color; + return; +} + +void bucketSetHighLowColors (BUCKET_ID id, int hcolor, int lcolor) +{ + id->highcolor = hcolor; + id->lowcolor = lcolor; + return; +} + +void bucketSetTitleColors (BUCKET_ID id, int fg, int bg) +{ + id->titleFGcolor = fg; + id->titleBGcolor = bg; + return; +} + +void bucketSetTextColor (BUCKET_ID id, int color) +{ + id->textcolor = color; + return; +} + +void bucketSetTransparency (BUCKET_ID id, int isTransparent) +{ + id->isTransparent = isTransparent; + return; +} + +void bucketSetDualUnits (BUCKET_ID id, int isDualUnits) +{ + id->isDualUnits = isDualUnits; + return; +} + +int bucketRender (BUCKET_ID id) +{ + if (createImage (id) == -1) + { + return -1; + } + + drawTitle (id); + drawBucket (id); + drawContents (id); + + return 0; +} + +int bucketSave (BUCKET_ID id, char *name) +{ + FILE *out; + + if ((out = fopen (name, "wb")) != NULL) + { + gdImagePng (id->im, out); + fflush (out); + fclose (out); + return 0; + } + else + { + return -1; + } +} + +void bucketDestroy (BUCKET_ID id) +{ + if (id->im) + gdImageDestroy (id->im); + + return; +} + diff --git a/htmlgenerator/glbucket.h b/htmlgenerator/glbucket.h new file mode 100644 index 0000000..8eae7c7 --- /dev/null +++ b/htmlgenerator/glbucket.h @@ -0,0 +1,105 @@ +#ifndef INC_glbucketh +#define INC_glbucketh +/*--------------------------------------------------------------------------- + + FILENAME: + glbucket.h + + PURPOSE: + Provide the graphics lib bucket definitions. + + REVISION HISTORY: + Date Engineer Revision Remarks + 09/05/03 M.S. Teel 0 Original + + NOTES: + + + LICENSE: + Copyright (c) 2004, Mark S. Teel (mark@teel.ws) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +#include "gd.h" + + +#define GLB_RED(x) ((x >> 24) & 0xFF) +#define GLB_GREEN(x) ((x >> 16) & 0xFF) +#define GLB_BLUE(x) ((x >> 8) & 0xFF) +#define GLB_ALPHA(x) ((x) & 0xFF) + +#define GLB_CONTENTS_OFFSET 4 + +#define GLB_HILOW_NONE (double)(-1000.0) + +#define GLB_DFLT_BG 0xF0F0F0 +#define GLB_DFLT_BUCKET 0x000000 +#define GLB_DFLT_CONTENT 0x808080 +#define GLB_DFLT_HIGH 0xFF0000 +#define GLB_DFLT_LOW 0x0000FF +#define GLB_DFLT_TITLEFG 0x000000 +#define GLB_DFLT_TITLEBG 0xE0E0E0 +#define GLB_DFLT_TEXT 0x000000 + + +typedef struct +{ + gdImagePtr im; + int width; + int height; + int bucketWidth; + int imtx; + int imty; + int imbx; + int imby; + double pixelconstant; + double min; + double max; + double high; + double low; + double value; + double valueM; + double stepSize; + int decPlaces; + char title[64]; + char units[64]; + char datetime[64]; + int bgcolor; + int bucketcolor; + int contentcolor; + int highcolor; + int lowcolor; + int titleFGcolor; + int titleBGcolor; + int textcolor; + int isTransparent; + int isDualUnits; + int isMetric; +} BUCKET, *BUCKET_ID; + + +// ... API prototypes + +extern BUCKET_ID bucketCreate (int width, int height, int bucketWidth, char *title); +extern void bucketSetScale (BUCKET_ID id, double min, double max, double step); +extern void bucketSetValues (BUCKET_ID id, double high, double low, double value); +extern void bucketSetUnits (BUCKET_ID id, char *units); +extern void bucketSetDecimalPlaces (BUCKET_ID id, int decimalPlaces); +extern void bucketSetDateTime (BUCKET_ID id, char *datetime); +extern void bucketSetBGColor (BUCKET_ID id, int color); +extern void bucketSetBucketColor (BUCKET_ID id, int color); +extern void bucketSetContentColor (BUCKET_ID id, int color); +extern void bucketSetHighLowColors (BUCKET_ID id, int hcolor, int lcolor); +extern void bucketSetTitleColors (BUCKET_ID id, int fg, int bg); +extern void bucketSetTextColor (BUCKET_ID id, int color); +extern void bucketSetTransparency (BUCKET_ID id, int isTransparent); +extern int bucketRender (BUCKET_ID id); +extern int bucketSave (BUCKET_ID id, char *filename); +extern void bucketDestroy (BUCKET_ID id); + +#endif + + diff --git a/htmlgenerator/glchart.c b/htmlgenerator/glchart.c new file mode 100755 index 0000000..91b596f --- /dev/null +++ b/htmlgenerator/glchart.c @@ -0,0 +1,874 @@ +/*--------------------------------------------------------------------------- + + FILENAME: + glchart.c + + PURPOSE: + Provide the graphics lib chart utilities. + + REVISION HISTORY: + Date Engineer Revision Remarks + 09/05/03 M.S. Teel 0 Original + 01/02/05 M.S. Teel 1 Improve labeling and + chart limits calc + 05/15/08 Werner Krenn 2 Dual Units + + NOTES: + + + LICENSE: + Copyright (c) 2004, Mark S. Teel (mark@teel.ws) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +/* ... System include files +*/ +#include +#include +#include +#include +#include +#include +#include +#include + +/* ... Library include files +*/ +#include +#include +#include "gd.h" +#include "gdfonts.h" +#include "gdfontt.h" +#include "gdfontmb.h" +#include "gdfontg.h" +#include "gdfontl.h" + + +/* ... Local include files +*/ +#include + + +/* ... global memory declarations +*/ + +/* ... global memory referenced +*/ + +/* ... static (local) memory declarations +*/ +#define MAX_STEP_MULTIPLIER 9 + +static CHART nonReentrantChart; + +static double stepSizeMultipliers[MAX_STEP_MULTIPLIER] = + { + 2.0, 5.0, 10.0, 20.0, 50.0, 100.0, 200.0, 500.0, 1000.0 + }; + + + +static int createImage (CHART_ID id) +{ + if (id->height < (10 * gdFontMediumBold->h)) + { + return -1; + } + + if (id->im) + { + gdImageDestroy (id->im); + } + + id->im = gdImageCreate (id->width, id->height); + if (id->im == NULL) + { + return -1; + } + + + // ... allocate our colors + id->bgcolor = gdImageColorAllocateAlpha (id->im, + GLC_RED(id->bgcolor), + GLC_GREEN(id->bgcolor), + GLC_BLUE(id->bgcolor), + GLC_ALPHA(id->bgcolor)); + if (id->isTransparent) + { + gdImageColorTransparent (id->im, id->bgcolor); + id->chartcolor = id->bgcolor; + id->titleBGcolor = id->bgcolor; + } + else + { + id->chartcolor = gdImageColorAllocateAlpha (id->im, + GLC_RED(id->chartcolor), + GLC_GREEN(id->chartcolor), + GLC_BLUE(id->chartcolor), + GLC_ALPHA(id->chartcolor)); + id->titleBGcolor = gdImageColorAllocateAlpha (id->im, + GLC_RED(id->titleBGcolor), + GLC_GREEN(id->titleBGcolor), + GLC_BLUE(id->titleBGcolor), + GLC_ALPHA(id->titleBGcolor)); + } + + id->gridcolor = gdImageColorAllocateAlpha (id->im, + GLC_RED(id->gridcolor), + GLC_GREEN(id->gridcolor), + GLC_BLUE(id->gridcolor), + GLC_ALPHA(id->gridcolor)); + id->linecolor = gdImageColorAllocateAlpha (id->im, + GLC_RED(id->linecolor), + GLC_GREEN(id->linecolor), + GLC_BLUE(id->linecolor), + GLC_ALPHA(id->linecolor)); + id->titleFGcolor = gdImageColorAllocateAlpha (id->im, + GLC_RED(id->titleFGcolor), + GLC_GREEN(id->titleFGcolor), + GLC_BLUE(id->titleFGcolor), + GLC_ALPHA(id->titleFGcolor)); + id->textcolor = gdImageColorAllocateAlpha (id->im, + GLC_RED(id->textcolor), + GLC_GREEN(id->textcolor), + GLC_BLUE(id->textcolor), + GLC_ALPHA(id->textcolor)); + + return 0; +} + + +static void drawGrid (CHART_ID id) +{ + register int i; + int xlabelwidth = 0, ylabelwidth = 0; + double units, step; + double minR, stepR; + int dataPoints, xnumhash, xhashindexlen; + double hashwidth, xhashwidth; + int numhash, x, y, temp1, temp2; + char text[MAX_NUMHASH][WVIEW_STRING1_SIZE], ylabelfmt[32]; + char textR[MAX_NUMHASH][WVIEW_STRING1_SIZE], ylabelfmtR[32]; + + // build labels and calculate the maximum x-label width + for (i = 0; i < id->numpoints; i ++) + { + if (strlen (id->pointnames[i]) > xlabelwidth) + xlabelwidth = strlen (id->pointnames[i]); + } + + // calculate the maximum number of hash marks and the units per hash + // for the x-axis + xhashwidth = (xlabelwidth*gdFontSmall->h)/2; + dataPoints = id->numpoints - 1; + + // see if a suggestion was given + if (id->xnumhashes != 0) + { + xnumhash = id->xnumhashes; + } + else + { + xnumhash = (id->imbx - id->imtx)/xhashwidth; + + // try to normalize the number of hashmarks + // try with the endpoint removed + for (i = 1; i < dataPoints/2; i ++) + { + if (dataPoints % i != 0) + continue; + + if (dataPoints/i <= xnumhash) + { + xnumhash = dataPoints/i; + break; + } + } + } + + if (strlen(id->DualUnit) > 0) + { + id->imbx -= (gdFontMediumBold->h); + if ((strcmp(id->DualUnit,"inHg") == 0) || + (strcmp(id->DualUnit,"inches") == 0) || + (strcmp(id->DualUnit,"in/day") == 0) || + (strcmp(id->DualUnit,"cm/day") == 0) || + (strcmp(id->DualUnit,"in/hour") == 0) || + (strcmp(id->DualUnit,"in/week") == 0)) + { + id->imbx -= 7; + } + } + + xhashwidth = (double)(id->imbx - id->imtx)/(double)xnumhash; + id->pointpixels = (double)(id->imbx - id->imtx)/(double)dataPoints; + xhashindexlen = dataPoints/xnumhash; + + + // calculate the maximum number of hash marks and the units per hash + // for the y-axis + hashwidth = (gdFontSmall->h * 3)/2; + + numhash = (id->imby - id->imty)/hashwidth; + if (numhash > MAX_NUMHASH) + { + numhash = MAX_NUMHASH; + } + + units = (id->max - id->min)/id->ystepSize; + step = id->ystepSize; + i = 0; + while ((int)units > numhash && i < MAX_STEP_MULTIPLIER) + { + step = id->ystepSize * stepSizeMultipliers[i]; + units = (id->max - id->min)/step; + i ++; + } + + id->ystepSize = step; + numhash = (int)units; + + temp1 = (int)(id->max * 10000); + temp2 = (int)((id->min + (id->ystepSize * numhash)) * 10000); + temp2 ++; + + if (temp2 < temp1) + { + numhash ++; + } + + hashwidth = (double)(id->imby - id->imty)/(double)numhash; + id->max = id->min + (id->ystepSize * numhash); + id->ypixelconstant = (double)(id->imby - id->imty)/(double)(id->max - id->min); + numhash ++; + if (numhash > 64) + numhash = 64; + + // build labels and calculate the maximum y-label width + sprintf (ylabelfmt, "%%.%1.1df", id->ydecPlaces); + for (i = 0; i < numhash; i ++) + { + sprintf (text[i], ylabelfmt, id->min + (i * id->ystepSize)); + + // now fill the right hand array with alternate scale @Rcm + if (strlen(id->DualUnit) > 0) + { + textR[i][0] = ' '; + if (strcmp(id->DualUnit,"inHg") == 0) + { // if hPa then change to inHg value + minR = wvutilsConvertHPAToINHG (id->min); + stepR = wvutilsConvertHPAToINHG (id->ystepSize); + sprintf (textR[i], "%.1f", minR + (i * stepR)); + } + else if (strcmp(id->DualUnit,"hPa") == 0) + { // if inHg then change to hPa value + minR = wvutilsConvertINHGToHPA (id->min); + stepR = wvutilsConvertINHGToHPA (id->ystepSize); + sprintf (textR[i], "%.0f", minR + (i * stepR)); + } + else if (strcmp(id->DualUnit,"C") == 0) + { + minR = wvutilsConvertFToC (id->min); + stepR = ((id->ystepSize) * (5.0/9.0)); + sprintf (textR[i], "%.1f", minR + (i * stepR)); + } + else if (strcmp(id->DualUnit,"F") == 0) + { + minR = wvutilsConvertCToF (id->min); + stepR = ((id->ystepSize) * (9.0/5.0)); + sprintf (textR[i], "%.1f", minR + (i * stepR)); + } + else if ((strcmp(id->DualUnit,"mm") == 0) || + (strcmp(id->DualUnit,"mm/h") == 0)) + { + minR = wvutilsConvertINToMM (id->min); + stepR = wvutilsConvertINToMM (id->ystepSize); + if (wvutilsConvertINToMM (id->max) >= 100) + sprintf (textR[i], "%.0f", minR + (i * stepR)); + else + sprintf (textR[i], "%.1f", minR + (i * stepR)); + } + else if ((strcmp(id->DualUnit,"inches") == 0) || + (strcmp(id->DualUnit,"in/hour") == 0) || + (strcmp(id->DualUnit,"in/hr") == 0)) + { + minR = wvutilsConvertMMToIN (id->min); + stepR = wvutilsConvertMMToIN (id->ystepSize); + if (wvutilsConvertMMToIN (id->max) >= 100) + sprintf (textR[i], "%.1f", minR + (i * stepR)); + else + sprintf (textR[i], "%.2f", minR + (i * stepR)); + } + else if (((strcmp(id->DualUnit,"inches") == 0) && (strcmp(id->units,"cm") == 0)) || + (strcmp(id->DualUnit,"in/day") == 0) || (strcmp(id->DualUnit,"in/week") == 0)) + { + minR = wvutilsConvertCMToIN (id->min); + stepR = wvutilsConvertCMToIN (id->ystepSize); + sprintf (textR[i], "%.1f", minR + (i * stepR)); + } + else if (strcmp(id->DualUnit,"km/h") == 0) + { + if (!strcmp(id->units, "mph")) + { + minR = wvutilsConvertMPHToKPH (id->min); + stepR = wvutilsConvertMPHToKPH (id->ystepSize); + sprintf (textR[i], "%.0f", minR + (i * stepR)); + } + else if (!strcmp(id->units, "m/s")) + { + minR = wvutilsConvertMPSToKPH (id->min); + stepR = wvutilsConvertMPSToKPH (id->ystepSize); + sprintf (textR[i], "%.0f", minR + (i * stepR)); + } + else if (!strcmp(id->units, "knots")) + { + minR = wvutilsConvertKnotsToKPH (id->min); + stepR = wvutilsConvertKnotsToKPH (id->ystepSize); + sprintf (textR[i], "%.0f", minR + (i * stepR)); + } + } + else if (strcmp(id->DualUnit,"mph") == 0) + { + if (!strcmp(id->units, "km/h")) + { + minR = wvutilsConvertKPHToMPH (id->min); + stepR = wvutilsConvertKPHToMPH (id->ystepSize); + sprintf (textR[i], "%.0f", minR + (i * stepR)); + } + else if (!strcmp(id->units, "m/s")) + { + minR = wvutilsConvertMPSToMPH (id->min); + stepR = wvutilsConvertMPSToMPH (id->ystepSize); + sprintf (textR[i], "%.0f", minR + (i * stepR)); + } + else if (!strcmp(id->units, "knots")) + { + minR = wvutilsConvertKnotsToMPH (id->min); + stepR = wvutilsConvertKnotsToMPH (id->ystepSize); + sprintf (textR[i], "%.0f", minR + (i * stepR)); + } + } + else if (strcmp(id->DualUnit,"m/s") == 0) + { + if (!strcmp(id->units, "km/h")) + { + minR = wvutilsConvertKPHToMPS (id->min); + stepR = wvutilsConvertKPHToMPS (id->ystepSize); + sprintf (textR[i], "%.0f", minR + (i * stepR)); + } + else if (!strcmp(id->units, "mph")) + { + minR = wvutilsConvertMPHToMPS (id->min); + stepR = wvutilsConvertMPHToMPS (id->ystepSize); + sprintf (textR[i], "%.0f", minR + (i * stepR)); + } + else if (!strcmp(id->units, "knots")) + { + minR = wvutilsConvertKnotsToMPS (id->min); + stepR = wvutilsConvertKnotsToMPS (id->ystepSize); + sprintf (textR[i], "%.0f", minR + (i * stepR)); + } + } + else if (strcmp(id->DualUnit,"knots") == 0) + { + if (!strcmp(id->units, "km/h")) + { + minR = wvutilsConvertKPHToKnots (id->min); + stepR = wvutilsConvertKPHToKnots (id->ystepSize); + sprintf (textR[i], "%.0f", minR + (i * stepR)); + } + else if (!strcmp(id->units, "mph")) + { + minR = wvutilsConvertMPHToKnots (id->min); + stepR = wvutilsConvertMPHToKnots (id->ystepSize); + sprintf (textR[i], "%.0f", minR + (i * stepR)); + } + else if (!strcmp(id->units, "m/s")) + { + minR = wvutilsConvertMPSToKnots (id->min); + stepR = wvutilsConvertMPSToKnots (id->ystepSize); + sprintf (textR[i], "%.0f", minR + (i * stepR)); + } + } + // all else: eg '%' or watts/m^2 (hum/rad) + // same value both sides + else + { + wvstrncpy (textR[i], text[i], sizeof(textR[i])); + } + if (strlen (text[i]) > ylabelwidth) + ylabelwidth = strlen (text[i])+2; + } + else + { + if (strlen (text[i]) > ylabelwidth) + ylabelwidth = strlen (text[i]); + } + } + + // now we know the chart area, draw it + gdImageFilledRectangle (id->im, + id->imtx - GLC_CONTENTS_OFFSET, + id->imty - GLC_CONTENTS_OFFSET, + id->imbx + GLC_CONTENTS_OFFSET, + id->imby + GLC_CONTENTS_OFFSET, + id->chartcolor); + + gdImageRectangle (id->im, + id->imtx, + id->imty, + id->imbx, + id->imby, + id->gridcolor); + + // draw the y-axis + gdImageSetThickness (id->im, 1); + for (i = 0; i < numhash; i ++) + { + y = id->imby - (i * hashwidth); + + gdImageLine (id->im, + id->imtx, + y, + id->imbx, + y, + id->gridcolor); + + y -= gdFontSmall->h/2; + gdImageString (id->im, + gdFontSmall, + (id->imtx - 5) - (strlen(text[i]) * gdFontSmall->h/2), + y, + (uint8_t *)text[i], + id->textcolor); + + if (strlen(id->DualUnit) > 0) + { + // right side + x = id->imbx + (GLC_CONTENTS_OFFSET - 2); + + gdImageString (id->im, + gdFontSmall, + x + gdFontSmall->h/2, + y, + (uint8_t *)textR[i], + id->textcolor); + } + } + + // draw the x-axis + gdImageSetThickness (id->im, 1); + + for (i = 0; i <= xnumhash; i ++) // one more for right-most + { + int dataPoint = i * xhashindexlen; + + x = id->imtx + (i * xhashwidth); + + gdImageLine (id->im, + x, + id->imty, + x, + id->imby, + id->gridcolor); + + if (strlen(id->pointnames[dataPoint]) < 2) + x -= gdFontSmall->h/4; + + gdImageString (id->im, + gdFontSmall, + x - ((gdFontSmall->h/2) * + (strlen(id->pointnames[dataPoint])/2)), + (id->height - (3 * gdFontMediumBold->h)) + 4, + (uint8_t *)id->pointnames[dataPoint], + id->textcolor); + } + + + // finally, draw date + gdImageString (id->im, + gdFontSmall, + ((id->width - ((gdFontSmall->h/2) * strlen (id->datetime)))/2) - 2, + id->height - (gdFontMediumBold->h + 2), + (uint8_t *)id->datetime, + id->textcolor); +} + +static void drawTitle (CHART_ID id) +{ + gdImageFilledRectangle (id->im, + 0, 0, + id->width, gdFontMediumBold->h, + id->titleBGcolor); + + if (strlen (id->DualUnit) > 0) + { + gdImageString (id->im, + gdFontMediumBold, + ((id->width - ((gdFontMediumBold->h/2) * strlen (id->title)))/2) - 3, + 2, + (uint8_t *)id->title, + id->linecolor); + + gdImageString (id->im, + gdFontMediumBold, + id->width - (((gdFontMediumBold->h/2)+2) * strlen (id->DualUnit)), + 0, + (uint8_t *)id->DualUnit, + id->titleFGcolor); + + gdImageString (id->im, + gdFontMediumBold, + 2, + 0, + (uint8_t *)id->units, + id->titleFGcolor); + } + else + { + gdImageString (id->im, + gdFontMediumBold, + 2, + 0, + (uint8_t *)id->title, + id->linecolor); + + gdImageString (id->im, + gdFontMediumBold, + id->width - (((gdFontMediumBold->h/2)+2) * strlen (id->units)), + 0, + (uint8_t *)id->units, + id->titleFGcolor); + } +} + +static void drawLine (CHART_ID id) +{ + register int i; + int x1, x2, y1, y2; + + gdImageSetThickness (id->im, 2); + for (i = 0; i < id->numpoints - 1; i ++) + { + if (id->valueset[i] <= ARCHIVE_VALUE_NULL || id->valueset[i+1] <= ARCHIVE_VALUE_NULL) + { + continue; + } + + x1 = id->imtx + (int)((double)i * id->pointpixels); + x2 = id->imtx + (int)((double)(i+1) * id->pointpixels); + y1 = id->imby - (int)(id->ypixelconstant * (id->valueset[i] - id->min)); + y2 = id->imby - (int)(id->ypixelconstant * (id->valueset[i+1] - id->min)); + gdImageLine (id->im, + x1, + y1, + x2, + y2, + id->linecolor); + } +} + + +static void drawBars (CHART_ID id) +{ + register int i; + int x1, x2, y1, y2; + + for (i = 0; i < id->numpoints - 1; i ++) + { + if (id->valueset[i] <= ARCHIVE_VALUE_NULL) + { + continue; + } + + x1 = id->imtx + (int)((double)i * id->pointpixels); + x2 = id->imtx + (int)((double)(i+1) * id->pointpixels); + x1 += 1; + x2 -= 1; + y1 = id->imby - (int)(id->ypixelconstant * (id->valueset[i] - id->min)); + y2 = id->imby; + gdImageFilledRectangle (id->im, + x1, y1, + x2, y2, + id->linecolor); + } +} + +static void drawScatter (CHART_ID id) +{ + register int i; + int x1, y1; + + gdImageSetThickness (id->im, 2); + for (i = 0; i < id->numpoints; i ++) + { + if (id->valueset[i] <= ARCHIVE_VALUE_NULL) + { + continue; + } + + x1 = id->imtx + (int)((double)i * id->pointpixels); + y1 = id->imby - (int)(id->ypixelconstant * (id->valueset[i] - id->min)); + gdImageFilledEllipse (id->im, + x1, + y1, + 2, + 2, + id->linecolor); + } +} + + +static void normalizeMinMax (CHART_ID id, double newMIN, double newMAX) +{ + register double temp, factor = 1.0; + register int i; + + // compute factor based on number of decimal places defined + // (avoid use of pow function) + for (i = 0; i < id->ydecPlaces; i ++) + { + factor *= 10.0; + } + + // determine chart min + id->min = newMIN; + id->min *= factor; + + // make sure we don't lop off decimal places "below" since we are + // computing a MIN here + temp = (int)id->min; + if (temp - id->min > 0) + temp -= 1.0; + id->min = temp; + while (((int)id->min % 5) != 0) + { + id->min -= 1.0; + } + id->min /= factor; + + // determine chart max + id->max = newMAX; + id->max *= factor; + id->max *= 10; + id->max = (int)id->max; + id->max /= 10; + + // make sure we don't lop off decimal places "above" since we are + // computing a MAX here + temp = (int)id->max; + if (id->max - temp > 0) + temp += 1.0; + id->max = temp; + while (((int)id->max % 5) != 0) + { + id->max += 1.0; + } + id->max /= factor; + + return; +} + + + +/* ... API methods +*/ + +CHART_ID chartCreate +( + int width, + int height, + char *title, + char *units, + CHART_TYPE chartType +) +{ + register CHART_ID newId; + + newId = (CHART_ID)&nonReentrantChart; + + memset (newId, 0, sizeof (*newId)); + + newId->chartType = chartType; + newId->width = width; + newId->height = height; + wvstrncpy (newId->title, title, sizeof(newId->title)); + wvstrncpy (newId->units, units, sizeof(newId->units)); + + // ... now set the contents coords + newId->imtx = 5 * gdFontSmall->h/2; + // ... SSM: move the right edge of the graph 5 pixels left to make + // ... room for last label + newId->imbx = (newId->width - 1) - gdFontMediumBold->h - 5; + newId->imty = 2 * gdFontMediumBold->h; + newId->imby = newId->height - (3 * gdFontMediumBold->h); + + newId->bgcolor = GLC_DFLT_BG; + newId->chartcolor = GLC_DFLT_CHART; + newId->gridcolor = GLC_DFLT_GRID; + newId->linecolor = GLC_DFLT_LINE; + newId->titleFGcolor = GLC_DFLT_TITLEFG; + newId->titleBGcolor = GLC_DFLT_TITLEBG; + newId->textcolor = GLC_DFLT_TEXT; + + return newId; +} + +void chartSetXScale (CHART_ID id, int decPlaces) +{ + id->xdecPlaces = decPlaces; + + return; +} + +void chartSetXHashes (CHART_ID id, int numHashes) +{ + id->xnumhashes = numHashes; + + return; +} + + +void chartSetDualUnits (CHART_ID id) +{ + htmlutilsSetDualUnits (id->isMetric, id->units, id->DualUnit); + return; +} + + +void chartSetYScale (CHART_ID id, double min, double max, double step, int decPlaces) +{ + id->min = min; + id->max = max; + id->ystepSize = step; + id->ydecPlaces = decPlaces; + + normalizeMinMax (id, min, max); + return; +} + +void chartAddPoint (CHART_ID id, double value, char *name) +{ + if (id->numpoints >= MAX_GRAPH_POINTS) + { + return; + } + + id->valueset[id->numpoints] = value; + wvstrncpy (id->pointnames[id->numpoints], name, 16); + id->numpoints ++; + + if (value <= ARCHIVE_VALUE_NULL) + { + return; + } + + if (value < id->min) + normalizeMinMax (id, value, id->max); + else if (value > id->max) + normalizeMinMax (id, id->min, value); + + return; +} + +void chartSetDateTime (CHART_ID id, char *datetime) +{ + wvstrncpy (id->datetime, datetime, sizeof(id->datetime)); + return; +} + +void chartSetBGColor (CHART_ID id, int color) +{ + id->bgcolor = color; + return; +} + +void chartSetChartColor (CHART_ID id, int color) +{ + id->chartcolor = color; + return; +} + +void chartSetGridColor (CHART_ID id, int color) +{ + id->gridcolor = color; + return; +} + +void chartSetLineColor (CHART_ID id, int color) +{ + id->linecolor = color; + return; +} + +void chartSetTitleColors (CHART_ID id, int fg, int bg) +{ + id->titleFGcolor = fg; + id->titleBGcolor = bg; + return; +} + +void chartSetTextColor (CHART_ID id, int color) +{ + id->textcolor = color; + return; +} + +void chartSetTransparency (CHART_ID id, int isTransparent) +{ + id->isTransparent = isTransparent; + return; +} + +int chartRender (CHART_ID id) +{ + if (createImage (id) == -1) + { + return -1; + } + + drawTitle (id); + drawGrid (id); + + switch (id->chartType) + { + case CHART_TYPE_BAR: + drawBars(id); + break; + case CHART_TYPE_LINE: + drawLine(id); + break; + case CHART_TYPE_SCATTER: + drawScatter(id); + break; + } + + return 0; +} + +int chartSave (CHART_ID id, char *name) +{ + FILE *out; + + if ((out = fopen (name, "wb")) != NULL) + { + gdImagePng (id->im, out); + fclose (out); + return 0; + } + else + { + return -1; + } +} + +void chartDestroy (CHART_ID id) +{ + if (id->im) + gdImageDestroy (id->im); + + return; +} diff --git a/htmlgenerator/glchart.h b/htmlgenerator/glchart.h new file mode 100644 index 0000000..428b64d --- /dev/null +++ b/htmlgenerator/glchart.h @@ -0,0 +1,129 @@ +#ifndef INC_glcharth +#define INC_glcharth +/*--------------------------------------------------------------------------- + + FILENAME: + glchart.h + + PURPOSE: + Provide the graphics lib chart definitions. + + REVISION HISTORY: + Date Engineer Revision Remarks + 09/05/03 M.S. Teel 0 Original + 05/15/08 Werner Krenn 1 dualUnits + 05/05/09 D. Pickett 2 Add scatter charts + + NOTES: + + + LICENSE: + Copyright (c) 2004, Mark S. Teel (mark@teel.ws) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +#include "gd.h" +#include + + + +#define GLC_RED(x) ((x >> 24) & 0xFF) +#define GLC_GREEN(x) ((x >> 16) & 0xFF) +#define GLC_BLUE(x) ((x >> 8 ) & 0xFF) +#define GLC_ALPHA(x) ((x) & 0xFF) + +#define GLC_DFLT_BG 0xF0F0F000 +#define GLC_DFLT_CHART 0x0000F000 +#define GLC_DFLT_GRID 0xA0A00000 +#define GLC_DFLT_LINE 0x80808000 +#define GLC_DFLT_TITLEFG 0x00000000 +#define GLC_DFLT_TITLEBG 0xE0E0E000 +#define GLC_DFLT_TEXT 0x00000000 + +#define GLC_CONTENTS_OFFSET 4 + +#define MAX_GRAPH_POINTS 1024 +#define MAX_NUMHASH 64 + + +typedef enum +{ + CHART_TYPE_LINE, + CHART_TYPE_BAR, + CHART_TYPE_SCATTER +} CHART_TYPE; + +typedef struct +{ + gdImagePtr im; + CHART_TYPE chartType; + int width; + int height; + char title[64]; + char units[16]; + char datetime[64]; + int xdecPlaces; + int xnumhashes; + int ydecPlaces; + double ystepSize; + double ypixelconstant; + double min; + double max; + int imtx; + int imty; + int imbx; + int imby; + double xmax; + int numpoints; + double pointpixels; + char pointnames[MAX_GRAPH_POINTS][16]; + double valueset[MAX_GRAPH_POINTS]; + int bgcolor; + int chartcolor; + int gridcolor; + int linecolor; + int titleFGcolor; + int titleBGcolor; + int textcolor; + int isTransparent; + int isMetric; + char DualUnit[16]; +} CHART, *CHART_ID; + + +// ... API prototypes +// ... NOTE: for the sake of optimization, these routines have been +// ... rendered "one chart at a time". This is how they were being +// ... used anyway, but each chartCreate call uses the same static +// ... buffer, thus overwriting the previous contents + +extern CHART_ID chartCreate +( + int width, + int height, + char *title, + char *units, + CHART_TYPE chartType +); +extern void chartSetXScale (CHART_ID id, int decPlaces); +extern void chartSetXHashes (CHART_ID id, int numHashes); +extern void chartSetYScale (CHART_ID id, double min, double max, double step, int decPlaces); +extern void chartAddPoint (CHART_ID id, double value, char *name); +extern void chartSetDateTime (CHART_ID id, char *datetime); +extern void chartSetBGColor (CHART_ID id, int color); +extern void chartSetChartColor (CHART_ID id, int color); +extern void chartSetGridColor (CHART_ID id, int color); +extern void chartSetLineColor (CHART_ID id, int color); +extern void chartSetTitleColors (CHART_ID id, int fg, int bg); +extern void chartSetTextColor (CHART_ID id, int color); +extern void chartSetTransparency (CHART_ID id, int isTransparent); +extern void chartSetDualUnits (CHART_ID id); +extern int chartRender (CHART_ID id); +extern int chartSave (CHART_ID id, char *filename); +extern void chartDestroy (CHART_ID id); + + +#endif diff --git a/htmlgenerator/glmultichart.c b/htmlgenerator/glmultichart.c new file mode 100644 index 0000000..637becd --- /dev/null +++ b/htmlgenerator/glmultichart.c @@ -0,0 +1,823 @@ +/*--------------------------------------------------------------------------- + + FILENAME: + glmultichart.c + + PURPOSE: + Provide the graphics lib multiple plot chart utilities. + + REVISION HISTORY: + Date Engineer Revision Remarks + 02/06/05 M.S. Teel 0 Original + + NOTES: + + + LICENSE: + Copyright (c) 2004, Mark S. Teel (mark@teel.ws) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +/* ... System include files +*/ +#include +#include +#include +#include +#include +#include +#include +#include + +/* ... Library include files +*/ +#include +#include +#include "gd.h" +#include "gdfonts.h" +#include "gdfontt.h" +#include "gdfontmb.h" +#include "gdfontg.h" +#include "gdfontl.h" + + +/* ... Local include files +*/ +#include "glmultichart.h" + + +/* ... global memory declarations +*/ + +/* ... global memory referenced +*/ + +/* ... static (local) memory declarations +*/ +#define MAX_STEP_MULTIPLIER 9 + +static MULTICHART nonReentrantMultiChart; + +static double stepSizeMultipliers[MAX_STEP_MULTIPLIER] = + { + 2.0, 5.0, 10.0, 20.0, 50.0, 100.0, 200.0, 500.0, 1000.0 + }; + + + +static int createImage (MULTICHART_ID id) +{ + int i; + + if (id->height < (10 * gdFontMediumBold->h)) + { + return -1; + } + + if (id->im) + { + gdImageDestroy (id->im); + } + + id->im = gdImageCreate (id->width, id->height); + if (id->im == NULL) + { + return -1; + } + + // ... allocate our colors + id->bgcolor = gdImageColorAllocateAlpha (id->im, + GLC_RED(id->bgcolor), + GLC_GREEN(id->bgcolor), + GLC_BLUE(id->bgcolor), + GLC_ALPHA(id->bgcolor)); + + if (id->isTransparent) + { + gdImageColorTransparent (id->im, id->bgcolor); + id->chartcolor = id->bgcolor; + id->titleBGcolor = id->bgcolor; + } + else + { + id->chartcolor = gdImageColorAllocateAlpha (id->im, + GLC_RED(id->chartcolor), + GLC_GREEN(id->chartcolor), + GLC_BLUE(id->chartcolor), + GLC_ALPHA(id->chartcolor)); + id->titleBGcolor = gdImageColorAllocateAlpha (id->im, + GLC_RED(id->titleBGcolor), + GLC_GREEN(id->titleBGcolor), + GLC_BLUE(id->titleBGcolor), + GLC_ALPHA(id->titleBGcolor)); + } + + id->gridcolor = gdImageColorAllocateAlpha (id->im, + GLC_RED(id->gridcolor), + GLC_GREEN(id->gridcolor), + GLC_BLUE(id->gridcolor), + GLC_ALPHA(id->gridcolor)); + id->titleFGcolor = gdImageColorAllocateAlpha (id->im, + GLC_RED(id->titleFGcolor), + GLC_GREEN(id->titleFGcolor), + GLC_BLUE(id->titleFGcolor), + GLC_ALPHA(id->titleFGcolor)); + id->textcolor = gdImageColorAllocateAlpha (id->im, + GLC_RED(id->textcolor), + GLC_GREEN(id->textcolor), + GLC_BLUE(id->textcolor), + GLC_ALPHA(id->textcolor)); + + for (i = 0; i < id->numdatasets; i ++) + { + id->dataset[i].lineColor = + gdImageColorAllocateAlpha (id->im, + GLC_RED(id->dataset[i].lineColor), + GLC_GREEN(id->dataset[i].lineColor), + GLC_BLUE(id->dataset[i].lineColor), + GLC_ALPHA(id->dataset[i].lineColor)); + } + + return 0; +} + + +static void drawGrid (MULTICHART_ID id) +{ + register int i; + int xlabelwidth = 0, ylabelwidth = 0; + double units, step; + double minR, stepR; + int dataPoints, xnumhash, xhashindexlen; + double hashwidth, xhashwidth; + int numhash, x, y; + char text[MAX_NUMHASH][WVIEW_STRING1_SIZE], ylabelfmt[32]; + char textR[MAX_NUMHASH][WVIEW_STRING1_SIZE], ylabelfmtR[32]; + + // ... calculate the maximum number of hash marks and the units per hash + // ... for the x-axis + // build labels and calculate the maximum x-label width + for (i = 0; i < id->numpoints; i ++) + { + if (strlen (id->pointnames[i]) > xlabelwidth) + xlabelwidth = strlen (id->pointnames[i]); + } + + xhashwidth = (xlabelwidth*gdFontSmall->h)/2; + dataPoints = id->numpoints - 1; + + + if (strlen (id->DualUnit) > 0) + { + // ((gdFontSmall->h/2) * strlen (id->DualUnit)))/2) - 2 + id->imbx -= (gdFontMediumBold->h); + if ((strcmp(id->DualUnit,"inches") == 0) || + (strcmp(id->DualUnit,"knots") == 0) || + (strcmp(id->DualUnit,"m/s") == 0)) + id->imbx -= 7; + } + + + // see if a suggestion was given + if (id->xnumhashes != 0) + { + xnumhash = id->xnumhashes; + } + else + { + xnumhash = (id->imbx - id->imtx)/xhashwidth; + + // try to normalize the number of hashmarks + // try with the endpoint removed + for (i = 1; i < dataPoints/2; i ++) + { + if (dataPoints % i != 0) + continue; + + if (dataPoints/i <= xnumhash) + { + xnumhash = dataPoints/i; + break; + } + } + } + + xhashwidth = (double)(id->imbx - id->imtx)/(double)xnumhash; + id->pointpixels = (double)(id->imbx - id->imtx)/(double)dataPoints; + xhashindexlen = dataPoints/xnumhash; + + + // ... calculate the maximum number of hash marks and the units per hash + // ... for the y-axis + hashwidth = (gdFontSmall->h * 3)/2; + numhash = (id->imby - id->imty)/hashwidth; + if (numhash > 64) + numhash = 64; + + units = (id->max - id->min)/id->ystepSize; + step = id->ystepSize; + i = 0; + while ((int)units > numhash && i < MAX_STEP_MULTIPLIER) + { + step = id->ystepSize * stepSizeMultipliers[i]; + units = (id->max - id->min)/step; + i ++; + } + + id->ystepSize = step; + numhash = (int)units; + if ((id->min + (id->ystepSize * numhash)) < id->max) + { + numhash ++; + } + + hashwidth = (double)(id->imby - id->imty)/(double)numhash; + id->max = id->min + (id->ystepSize * numhash); + id->ypixelconstant = (double)(id->imby - id->imty)/(double)(id->max - id->min); + numhash ++; + if (numhash > MAX_NUMHASH) + { + numhash = MAX_NUMHASH; + } + + // build labels and calculate the maximum y-label width + sprintf (ylabelfmt, "%%.%1.1df", id->ydecPlaces); + for (i = 0; i < numhash; i ++) + { + sprintf (text[i], ylabelfmt, id->min + (i * id->ystepSize)); + + // now fill the right hand array with alternate scale @Rcm + if (strlen(id->DualUnit) > 0) + { + textR[i][0] = ' '; + if (strcmp(id->DualUnit,"C") == 0) + { + minR = wvutilsConvertFToC (id->min); + stepR = ((id->ystepSize) * (5.0/9.0)); + sprintf (textR[i], "%.1f", minR + (i * stepR)); + } + else if (strcmp(id->DualUnit,"F") == 0) + { + minR = wvutilsConvertCToF (id->min); + stepR = ((id->ystepSize) * (9.0/5.0)); + sprintf (textR[i], "%.1f", minR + (i * stepR)); + } + else if (strcmp(id->DualUnit,"mm") == 0) + { + minR = wvutilsConvertINToMM (id->min); + stepR = wvutilsConvertINToMM (id->ystepSize); + if (wvutilsConvertINToMM (id->max) >= 100) + sprintf (textR[i], "%.0f", minR + (i * stepR)); + else + sprintf (textR[i], "%.1f", minR + (i * stepR)); + } + else if (strcmp(id->DualUnit,"inches") == 0) + { + minR = wvutilsConvertMMToIN (id->min); + stepR = wvutilsConvertMMToIN (id->ystepSize); + if (wvutilsConvertMMToIN (id->max) >= 100) + sprintf (textR[i], "%.0f", minR + (i * stepR)); + else + sprintf (textR[i], "%.1f", minR + (i * stepR)); + } + else if (strcmp(id->DualUnit,"km/h") == 0) + { + if (!strcmp(id->units, "mph")) + { + minR = wvutilsConvertMPHToKPH (id->min); + stepR = wvutilsConvertMPHToKPH (id->ystepSize); + sprintf (textR[i], "%.0f", minR + (i * stepR)); + } + else if (!strcmp(id->units, "m/s")) + { + minR = wvutilsConvertMPSToKPH (id->min); + stepR = wvutilsConvertMPSToKPH (id->ystepSize); + sprintf (textR[i], "%.0f", minR + (i * stepR)); + } + else if (!strcmp(id->units, "knots")) + { + minR = wvutilsConvertKnotsToKPH (id->min); + stepR = wvutilsConvertKnotsToKPH (id->ystepSize); + sprintf (textR[i], "%.0f", minR + (i * stepR)); + } + } + else if (strcmp(id->DualUnit,"mph") == 0) + { + if (!strcmp(id->units, "km/h")) + { + minR = wvutilsConvertKPHToMPH (id->min); + stepR = wvutilsConvertKPHToMPH (id->ystepSize); + sprintf (textR[i], "%.0f", minR + (i * stepR)); + } + else if (!strcmp(id->units, "m/s")) + { + minR = wvutilsConvertMPSToMPH (id->min); + stepR = wvutilsConvertMPSToMPH (id->ystepSize); + sprintf (textR[i], "%.0f", minR + (i * stepR)); + } + else if (!strcmp(id->units, "knots")) + { + minR = wvutilsConvertKnotsToMPH (id->min); + stepR = wvutilsConvertKnotsToMPH (id->ystepSize); + sprintf (textR[i], "%.0f", minR + (i * stepR)); + } + } + else if (strcmp(id->DualUnit,"m/s") == 0) + { + if (!strcmp(id->units, "km/h")) + { + minR = wvutilsConvertKPHToMPS (id->min); + stepR = wvutilsConvertKPHToMPS (id->ystepSize); + sprintf (textR[i], "%.0f", minR + (i * stepR)); + } + else if (!strcmp(id->units, "mph")) + { + minR = wvutilsConvertMPHToMPS (id->min); + stepR = wvutilsConvertMPHToMPS (id->ystepSize); + sprintf (textR[i], "%.0f", minR + (i * stepR)); + } + else if (!strcmp(id->units, "knots")) + { + minR = wvutilsConvertKnotsToMPS (id->min); + stepR = wvutilsConvertKnotsToMPS (id->ystepSize); + sprintf (textR[i], "%.0f", minR + (i * stepR)); + } + } + else if (strcmp(id->DualUnit,"knots") == 0) + { + if (!strcmp(id->units, "km/h")) + { + minR = wvutilsConvertKPHToKnots (id->min); + stepR = wvutilsConvertKPHToKnots (id->ystepSize); + sprintf (textR[i], "%.0f", minR + (i * stepR)); + } + else if (!strcmp(id->units, "mph")) + { + minR = wvutilsConvertMPHToKnots (id->min); + stepR = wvutilsConvertMPHToKnots (id->ystepSize); + sprintf (textR[i], "%.0f", minR + (i * stepR)); + } + else if (!strcmp(id->units, "m/s")) + { + minR = wvutilsConvertMPSToKnots (id->min); + stepR = wvutilsConvertMPSToKnots (id->ystepSize); + sprintf (textR[i], "%.0f", minR + (i * stepR)); + } + } + else //all else: eg '%' or watts/m^2 (hum/ rad) + { // same value both sides + minR = id->min * 25.4; + stepR = id->ystepSize * 25.4; + sprintf (textR[i], "%s", text[i]); + } + if (strlen (text[i]) > ylabelwidth) + ylabelwidth = strlen (text[i])+2; + } + else + { + + if (strlen (text[i]) > ylabelwidth) + ylabelwidth = strlen (text[i]); + } + } + + // ... now we know the chart area, draw it + gdImageFilledRectangle (id->im, + id->imtx - GLC_CONTENTS_OFFSET, + id->imty - GLC_CONTENTS_OFFSET, + id->imbx + GLC_CONTENTS_OFFSET, + id->imby + GLC_CONTENTS_OFFSET, + id->chartcolor); + + gdImageRectangle (id->im, + id->imtx, + id->imty, + id->imbx, + id->imby, + id->gridcolor); + + // ... draw the y-axis + gdImageSetThickness (id->im, 1); + for (i = 0; i < numhash; i ++) + { + y = id->imby - (i * hashwidth); + + gdImageLine (id->im, + id->imtx, + y, + id->imbx, + y, + id->gridcolor); + + y -= gdFontSmall->h/2; + gdImageString (id->im, + gdFontSmall, + (id->imtx - 5) - (strlen(text[i]) * gdFontSmall->h/2), + y, + (uint8_t *)text[i], + id->textcolor); + + if (strlen(id->DualUnit) > 0 ) + { + // right side + x = id->imbx + (GLC_CONTENTS_OFFSET - 2); + + gdImageString (id->im, + gdFontSmall, + x + gdFontSmall->h/2, + y, + (uint8_t *)textR[i], + id->textcolor); + } + } + + // ... draw the x-axis + gdImageSetThickness (id->im, 1); + + for (i = 0; i <= xnumhash; i ++) // one more for right-most + { + int dataPoint = i * xhashindexlen; + + x = id->imtx + (i * xhashwidth); + + gdImageLine (id->im, + x, + id->imty, + x, + id->imby, + id->gridcolor); + + if (strlen(id->pointnames[dataPoint]) < 2) + x -= gdFontSmall->h/4; + + gdImageString (id->im, + gdFontSmall, + x - ((gdFontSmall->h/2) * + (strlen(id->pointnames[dataPoint])/2)), + (id->height - (3 * gdFontMediumBold->h)) + 4, + (uint8_t *)id->pointnames[dataPoint], + id->textcolor); + } + + + // ... finally, draw date + gdImageString (id->im, + gdFontSmall, + ((id->width - ((gdFontSmall->h/2) * strlen (id->datetime)))/2) - 2, + id->height - (gdFontMediumBold->h + 2), + (uint8_t *)id->datetime, + id->textcolor); +} + +static void drawTitle (MULTICHART_ID id) +{ + int i, totalLen = 0; + char xtitle[WVIEW_STRING1_SIZE]; + + gdImageFilledRectangle (id->im, + 0, 0, + id->width, gdFontMediumBold->h, + id->titleBGcolor); + + if (strlen(id->DualUnit) != 0) + { + for (i = 0; i < id->numdatasets; i ++) + { + totalLen += 2; + totalLen += (((gdFontMediumBold->h/2)+2) * strlen (id->dataset[i].legend)); + } + totalLen = (id->width - totalLen) - 3; + totalLen /= 2; + + for (i = 0; i < id->numdatasets; i ++) + { + totalLen += 2; + gdImageString (id->im, + gdFontMediumBold, + totalLen, + 0, + (uint8_t *)id->dataset[i].legend, + id->dataset[i].lineColor); + + totalLen += (((gdFontMediumBold->h/2)+2) * strlen (id->dataset[i].legend)); + } + + gdImageString (id->im, + gdFontMediumBold, + id->width - (((gdFontMediumBold->h/2)+2) * strlen (id->DualUnit)), + 0, + (uint8_t *)id->DualUnit, + id->titleFGcolor); + + gdImageString (id->im, + gdFontMediumBold, + 2, + 0, + (uint8_t *)id->units, + id->titleFGcolor); + } + else + { + for (i = 0; i < id->numdatasets; i ++) + { + totalLen += 2; + gdImageString (id->im, + gdFontMediumBold, + totalLen, + 0, + (uint8_t *)id->dataset[i].legend, + id->dataset[i].lineColor); + + totalLen += (((gdFontMediumBold->h/2)+2) * strlen (id->dataset[i].legend)); + } + + gdImageString (id->im, + gdFontMediumBold, + id->width - (((gdFontMediumBold->h/2)+2) * strlen (id->title)), + 0, + (uint8_t *)id->title, + id->titleFGcolor); + + } +} + +static void drawLines (MULTICHART_ID id) +{ + register int i, j; + int x1, x2, y1, y2; + + gdImageSetThickness (id->im, 2); + + for (i = 0; i < id->numpoints - 1; i ++) + { + for (j = 0; j < id->numdatasets; j ++) + { + if (id->dataset[j].valueset[i] <= ARCHIVE_VALUE_NULL || + id->dataset[j].valueset[i+1] <= ARCHIVE_VALUE_NULL) + { + continue; + } + + x1 = id->imtx + (int)((double)i * id->pointpixels); + x2 = id->imtx + (int)((double)(i+1) * id->pointpixels); + y1 = id->imby - (int)(id->ypixelconstant * (id->dataset[j].valueset[i] - id->min)); + y2 = id->imby - (int)(id->ypixelconstant * (id->dataset[j].valueset[i+1] - id->min)); + gdImageLine (id->im, + x1, + y1, + x2, + y2, + id->dataset[j].lineColor); + } + } +} + + +static void normalizeMinMax (MULTICHART_ID id, double newMIN, double newMAX) +{ + register double temp, factor = 1.0; + register int i; + + // compute factor based on number of decimal places defined + // (avoid use of pow function) + for (i = 0; i < id->ydecPlaces; i ++) + { + factor *= 10.0; + } + + // determine chart min + id->min = newMIN; + id->min *= factor; + // make sure we don't lop off decimal places "below" since we are + // computing a MIN here + temp = (int)id->min; + if (temp - id->min > 0) + temp -= 1.0; + id->min = temp; + while (((int)id->min % 5) != 0) + { + id->min -= 1.0; + } + id->min /= factor; + + // determine chart max + id->max = newMAX; + id->max *= factor; + + // make sure we don't lop off decimal places "above" since we are + // computing a MAX here + temp = (int)id->max; + if (id->max - temp > 0) + temp += 1.0; + id->max = temp; + while (((int)id->max % 5) != 0) + { + id->max += 1.0; + } + id->max /= factor; +} + + + +/* ... API methods +*/ + +MULTICHART_ID multiChartCreate +( + int width, + int height, + char *units, + int numDataSets, + char *legends[] +) +{ + int i; + register MULTICHART_ID newId; + + newId = &nonReentrantMultiChart; + + memset (newId, 0, sizeof (*newId)); + + if (numDataSets > MC_MAX_DATA_SETS) + numDataSets = MC_MAX_DATA_SETS; + newId->numdatasets = numDataSets; + + for (i = 0; i < numDataSets; i ++) + { + wvstrncpy (newId->dataset[i].legend, legends[i], sizeof(newId->dataset[i].legend)); + } + + newId->width = width; + newId->height = height; + wvstrncpy (newId->units, units, sizeof(newId->units)); + + // ... now set the contents coords + newId->imtx = 5 * gdFontSmall->h/2; + // ... SSM: move the right edge of the graph 5 pixels left to make + // ... room for last label + newId->imbx = (newId->width - 1) - gdFontMediumBold->h - 5; + newId->imty = 2 * gdFontMediumBold->h; + newId->imby = newId->height - (3 * gdFontMediumBold->h); + + newId->bgcolor = GLC_DFLT_BG; + newId->chartcolor = GLC_DFLT_CHART; + newId->gridcolor = GLC_DFLT_GRID; + newId->titleFGcolor = GLC_DFLT_TITLEFG; + newId->titleBGcolor = GLC_DFLT_TITLEBG; + newId->textcolor = GLC_DFLT_TEXT; + + return newId; +} + +void multiChartSetXScale (MULTICHART_ID id, int decPlaces) +{ + id->xdecPlaces = decPlaces; + + return; +} + +void multiChartSetXHashes (MULTICHART_ID id, int numHashes) +{ + id->xnumhashes = numHashes; + + return; +} + +void multiChartSetYScale (MULTICHART_ID id, double min, double max, double step, int decPlaces) +{ + id->min = min; + id->max = max; + id->ystepSize = step; + id->ydecPlaces = decPlaces; + + normalizeMinMax (id, min, max); + return; +} + +void multiChartAddPoint (MULTICHART_ID id, double *values, char *name) +{ + int i; + + if (id->numpoints >= MAX_GRAPH_POINTS) + { + return; + } + + for (i = 0; i < id->numdatasets; i ++) + { + id->dataset[i].valueset[id->numpoints] = values[i]; + + if (values[i] > ARCHIVE_VALUE_NULL) + { + if (values[i] < id->min) + normalizeMinMax (id, values[i], id->max); + else if (values[i] > id->max) + normalizeMinMax (id, id->min, values[i]); + } + } + + wvstrncpy (id->pointnames[id->numpoints], name, sizeof(id->pointnames[id->numpoints])); + id->numpoints ++; + + return; +} + +void multiChartSetDateTime (MULTICHART_ID id, char *datetime) +{ + wvstrncpy (id->datetime, datetime, sizeof(id->datetime)); + return; +} + +void multiChartSetBGColor (MULTICHART_ID id, int color) +{ + id->bgcolor = color; + return; +} + +void multiChartSetChartColor (MULTICHART_ID id, int color) +{ + id->chartcolor = color; + return; +} + +void multiChartSetGridColor (MULTICHART_ID id, int color) +{ + id->gridcolor = color; + return; +} + +void multiChartSetLineColor (MULTICHART_ID id, int dataset, int color) +{ + if (dataset >= MC_MAX_DATA_SETS) + return; + + id->dataset[dataset].lineColor = color; + return; +} + +void multiChartSetTitleColors (MULTICHART_ID id, int fg, int bg) +{ + id->titleFGcolor = fg; + id->titleBGcolor = bg; + return; +} + +void multiChartSetTextColor (MULTICHART_ID id, int color) +{ + id->textcolor = color; + return; +} + +void multiChartSetTransparency (MULTICHART_ID id, int isTransparent) +{ + id->isTransparent = isTransparent; + return; +} + +void multiChartSetDualUnits (MULTICHART_ID id) +{ + htmlutilsSetDualUnits (id->isMetric, id->units, id->DualUnit); + return; +} + +int multiChartRender (MULTICHART_ID id) +{ + if (createImage (id) == -1) + { + return -1; + } + + drawTitle (id); + drawGrid (id); + drawLines (id); + + return 0; +} + +int multiChartSave (MULTICHART_ID id, char *name) +{ + FILE *out; + + if ((out = fopen (name, "wb")) != NULL) + { + gdImagePng (id->im, out); + fclose (out); + return 0; + } + else + { + return -1; + } +} + +void multiChartDestroy (MULTICHART_ID id) +{ + if (id->im) + gdImageDestroy (id->im); + + return; +} + diff --git a/htmlgenerator/glmultichart.h b/htmlgenerator/glmultichart.h new file mode 100644 index 0000000..955153f --- /dev/null +++ b/htmlgenerator/glmultichart.h @@ -0,0 +1,107 @@ +#ifndef INC_glmulticharth +#define INC_glmulticharth +/*--------------------------------------------------------------------------- + + FILENAME: + glmultichart.h + + PURPOSE: + Provide the graphics lib miltiple plot chart definitions. + + REVISION HISTORY: + Date Engineer Revision Remarks + 02/06/05 M.S. Teel 0 Original + + NOTES: + + + LICENSE: + Copyright (c) 2004, Mark S. Teel (mark@teel.ws) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +#include "glchart.h" + +#define MC_MAX_DATA_SETS 4 + + +typedef struct +{ + char legend[16]; + int lineColor; + double valueset[MAX_GRAPH_POINTS]; +} MC_DATASET; + +typedef struct +{ + gdImagePtr im; + int isBarChart; + int width; + int height; + char title[64]; + char units[16]; + char datetime[64]; + int xdecPlaces; + int xnumhashes; + int ydecPlaces; + double ystepSize; + double ypixelconstant; + double min; + double max; + int imtx; + int imty; + int imbx; + int imby; + double xmax; + int numpoints; + int numdatasets; + MC_DATASET dataset[MC_MAX_DATA_SETS]; + char pointnames[MAX_GRAPH_POINTS][16]; + double pointpixels; + int bgcolor; + int chartcolor; + int gridcolor; + int titleFGcolor; + int titleBGcolor; + int textcolor; + int isTransparent; + int isMetric; + char DualUnit[16]; +} MULTICHART, *MULTICHART_ID; + + +// ... API prototypes +// ... NOTE: for the sake of optimization, these routines have been +// ... rendered "one chart at a time". This is how they were being +// ... used anyway, but each multiChartCreate call uses the same static +// ... buffer, thus overwriting the previous contents + +extern MULTICHART_ID multiChartCreate +( + int width, + int height, + char *units, + int numDataSets, + char *legends[] +); +extern void multiChartSetXScale (MULTICHART_ID id, int decPlaces); +extern void multiChartSetXHashes (MULTICHART_ID id, int numHashes); +extern void multiChartSetYScale (MULTICHART_ID id, double min, double max, double step, int decPlaces); +extern void multiChartAddPoint (MULTICHART_ID id, double *values, char *name); +extern void multiChartSetDateTime (MULTICHART_ID id, char *datetime); +extern void multiChartSetBGColor (MULTICHART_ID id, int color); +extern void multiChartSetChartColor (MULTICHART_ID id, int color); +extern void multiChartSetGridColor (MULTICHART_ID id, int color); +extern void multiChartSetLineColor (MULTICHART_ID id, int dataset, int color); +extern void multiChartSetTitleColors (MULTICHART_ID id, int fg, int bg); +extern void multiChartSetTextColor (MULTICHART_ID id, int color); +extern void multiChartSetTransparency (MULTICHART_ID id, int isTransparent); +extern int multiChartRender (MULTICHART_ID id); +extern int multiChartSave (MULTICHART_ID id, char *filename); +extern void multiChartDestroy (MULTICHART_ID id); + + +#endif diff --git a/htmlgenerator/html.c b/htmlgenerator/html.c new file mode 100755 index 0000000..7ef053e --- /dev/null +++ b/htmlgenerator/html.c @@ -0,0 +1,931 @@ +/*--------------------------------------------------------------------------- + + FILENAME: + html.c + + PURPOSE: + Provide the wview html generator entry point. + + REVISION HISTORY: + Date Engineer Revision Remarks + 08/27/03 M.S. Teel 0 Original + + NOTES: + + + LICENSE: + Copyright (c) 2004, Mark S. Teel (mark@teel.ws) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +/* ... System include files +*/ +#include +#include +#include +#include +#include +#include +#include +#include + +/* ... Library include files +*/ +#include + +/* ... Local include files +*/ +#include +#include + + +/* ... global memory declarations +*/ + +/* ... global memory referenced +*/ + +/* ... static (local) memory declarations +*/ +static HTML_WORK htmlWork; + +static char* htmlStatusLabels[STATUS_STATS_MAX] = +{ + "Images defined", + "Templates defined", + "Images generated", + "Templates generated" +}; + +/* ... methods +*/ +/* ... system initialization +*/ +static int htmlSysInit (HTML_WORK *work) +{ + char devPath[256]; + struct stat fileData; + + /* ... check for our daemon's pid file, don't run if it isn't there + */ + sprintf (devPath, "%s/%s", WVIEW_RUN_DIR, WVD_LOCK_FILE_NAME); + if (stat (devPath, &fileData) != 0) + { + radMsgLogInit (PROC_NAME_HTML, FALSE, TRUE); + radMsgLog (PRI_CATASTROPHIC, "wviewd process not running - aborting!"); + radMsgLogExit (); + return ERROR; + } + + sprintf (work->pidFile, "%s/%s", WVIEW_RUN_DIR, HTML_LOCK_FILE_NAME); + sprintf (work->indicateFile, "%s/%s", WVIEW_RUN_DIR, HTML_INDICATE_FILE_NAME); + sprintf (work->fifoFile, "%s/dev/%s", WVIEW_RUN_DIR, PROC_NAME_HTML); + sprintf (work->statusFile, "%s/%s", WVIEW_STATUS_DIRECTORY, HTML_STATUS_FILE_NAME); + sprintf (work->daemonQname, "%s/dev/%s", WVIEW_RUN_DIR, PROC_NAME_DAEMON); + + + /* ... check for our html template directory and bail if it is not there + */ + sprintf (devPath, "%s/html", WVIEW_CONFIG_DIR); + if (stat (devPath, &fileData) != 0) + { + radMsgLogInit (PROC_NAME_HTML, FALSE, TRUE); + radMsgLog (PRI_CATASTROPHIC, "HTML template directory %s is missing...aborting!", + devPath); + radMsgLogExit (); + return ERROR; + } + + /* ... check for our pid file, don't run if it is there + */ + if (stat (work->pidFile, &fileData) == 0) + { + radMsgLogInit (PROC_NAME_HTML, FALSE, TRUE); + radMsgLog (PRI_CATASTROPHIC, "lock file %s exists, older copy may be running - aborting!", + work->pidFile); + radMsgLogExit (); + return ERROR; + } + + /* ... check for our indicator file, delete it if it is there + */ + if (stat (work->indicateFile, &fileData) == 0) + { + unlink(work->indicateFile); + } + + /* ... create our device directory if it is not there + */ + sprintf (devPath, "%s/dev", WVIEW_RUN_DIR); + if (stat (devPath, &fileData) != 0) + { + if (mkdir (devPath, 0755) != 0) + { + radMsgLogInit (PROC_NAME_HTML, FALSE, TRUE); + radMsgLog (PRI_CATASTROPHIC, "Cannot create device dir: %s...", + devPath); + radMsgLogExit (); + return ERROR; + } + } + + /* ... create our image directory if it is not there + */ + sprintf (devPath, "%s/img", WVIEW_RUN_DIR); + if (stat (devPath, &fileData) != 0) + { + if (mkdir (devPath, 0755) != 0) + { + radMsgLogInit (PROC_NAME_HTML, FALSE, TRUE); + radMsgLog (PRI_CATASTROPHIC, "Cannot create image dir: %s...", + devPath); + radMsgLogExit (); + return ERROR; + } + } + + return 0; +} + +/* ... system exit +*/ +static int htmlSysExit (HTML_WORK *work) +{ + struct stat fileData; + + /* ... delete our pid file + */ + if (stat (work->pidFile, &fileData) == 0) + { + unlink (work->pidFile); + } + if (stat (work->indicateFile, &fileData) == 0) + { + unlink (work->indicateFile); + } + + return 0; +} + +static void defaultSigHandler (int signum) +{ + int retVal; + + switch (signum) + { + case SIGHUP: + // user wants us to reload some config files: + retVal = wvutilsToggleVerbosity (); + radMsgLog (PRI_STATUS, "htmlgend: SIGHUP - toggling log verbosity %s", + ((retVal == 0) ? "OFF" : "ON")); + radMsgLog (PRI_STATUS, "htmlgend: SIGHUP - " + "re-reading image/html config files"); + if (htmlWork.mgrId != NULL) + { + htmlmgrReReadImageFiles (htmlWork.mgrId, WVIEW_CONFIG_DIR); + } + radProcessSignalCatch(signum, defaultSigHandler); + break; + + case SIGBUS: + case SIGFPE: + case SIGSEGV: + case SIGXFSZ: + case SIGSYS: + // unrecoverable radProcessSignalCatch- we must exit right now! + radMsgLog (PRI_CATASTROPHIC, + "htmlgend: recv unrecoverable signal %d: aborting!", + signum); + abort (); + + case SIGCHLD: + wvutilsWaitForChildren(); + radProcessSignalCatch(signum, defaultSigHandler); + break; + + default: + // we can allow the process to exit normally: + if (htmlWork.exiting) + { + radProcessSignalCatch(signum, defaultSigHandler); + return; + } + + radMsgLog (PRI_CATASTROPHIC, "htmlgend: recv signal %d: exiting!", signum); + + htmlWork.exiting = TRUE; + radProcessSetExitFlag (); + + radProcessSignalCatch(signum, defaultSigHandler); + break; + } + + return; +} + +static void msgHandler +( + char *srcQueueName, + UINT msgType, + void *msg, + UINT length, + void *userData +) +{ + STIM stim; + + if (msgType == WVIEW_MSG_TYPE_POLL) + { + WVIEW_MSG_POLL* pPoll = (WVIEW_MSG_POLL*)msg; + wvutilsSendPMONPollResponse(pPoll->mask, PMON_PROCESS_HTMLGEND); + return; + } + else if (msgType == WVIEW_MSG_TYPE_SHUTDOWN) + { + radMsgLog (PRI_HIGH, "htmlgend: received shutdown from wviewd"); + htmlWork.exiting = TRUE; + return; + } + + stim.type = STIM_QMSG; + stim.srcQueueName = srcQueueName; + stim.msgType = msgType; + stim.msg = msg; + stim.length = length; + + radStatesProcess(htmlWork.stateMachine, &stim); + + return; +} + +static void evtHandler +( + UINT eventsRx, + UINT rxData, + void *userData +) +{ + STIM stim; + + stim.type = STIM_EVENT; + stim.eventsRx = eventsRx; + stim.eventData = rxData; + + radStatesProcess(htmlWork.stateMachine, &stim); + + return; +} + +static void timerHandler (void *parm) +{ + STIM stim; + struct timeval timenow; + uint32_t netOffset; + + // get current time + gettimeofday(&timenow, NULL); + + // bump our expected next generation time + htmlWork.nextGenerationTime.tv_sec += (htmlWork.timerInterval / 1000L); + + // compute the next timer period + netOffset = (htmlWork.nextGenerationTime.tv_sec - timenow.tv_sec) * 1000L; + netOffset += ((htmlWork.nextGenerationTime.tv_usec - timenow.tv_usec) / 1000L); + + radProcessTimerStart(htmlWork.timer, netOffset); + + memset(&stim, 0, sizeof (stim)); + + stim.type = STIM_TIMER; + stim.timerNumber = TIMER_GENERATE; + + radStatesProcess(htmlWork.stateMachine, &stim); + + return; +} + +static void rxtimerHandler (void *parm) +{ + STIM stim; + + memset(&stim, 0, sizeof (stim)); + + stim.type = STIM_TIMER; + stim.timerNumber = TIMER_RX_PACKETS; + + radStatesProcess(htmlWork.stateMachine, &stim); + + return; +} + +static void noaaTimerHandler (void *parm) +{ + noaaGenerate(htmlWork.noaaId, time(NULL)); + return; +} + + +/* ... the main entry point for the html process +*/ +int main (int argc, char *argv[]) +{ + void (*alarmHandler)(int); + STIM stim; + int i, verbose; + int iValue; + double dValue; + const char* sValue; + char dateFmt[WVIEW_STRING1_SIZE]; + FILE *pidfile; + int isMetricUnits; + int runAsDaemon = TRUE; + + if (argc > 1) + { + if (!strcmp(argv[1], "-f")) + { + runAsDaemon = FALSE; + } + } + + memset (&htmlWork, 0, sizeof (htmlWork)); + + /* ... initialize some system stuff first + */ + if (htmlSysInit (&htmlWork) == -1) + { + radMsgLogInit (PROC_NAME_HTML, TRUE, TRUE); + radMsgLog (PRI_CATASTROPHIC, "system init failed!"); + radMsgLogExit (); + exit (1); + } + + + /* ... call the global radlib system init function + */ + if (radSystemInit (WVIEW_SYSTEM_ID) == ERROR) + { + radMsgLogInit (PROC_NAME_HTML, TRUE, TRUE); + radMsgLog (PRI_CATASTROPHIC, "radSystemInit failed!"); + radMsgLogExit (); + exit (1); + } + + + /* ... call the radlib process init function + */ + if (radProcessInit (PROC_NAME_HTML, + htmlWork.fifoFile, + PROC_NUM_TIMERS_HTML, + runAsDaemon, // TRUE for daemon + msgHandler, + evtHandler, + NULL) + == ERROR) + { + printf ("radProcessInit failed: %s", PROC_NAME_HTML); + radSystemExit (WVIEW_SYSTEM_ID); + exit (1); + } + + htmlWork.myPid = getpid (); + pidfile = fopen (htmlWork.pidFile, "w"); + if (pidfile == NULL) + { + radMsgLog (PRI_CATASTROPHIC, "lock file create failed!"); + radProcessExit (); + radSystemExit (WVIEW_SYSTEM_ID); + exit (1); + } + fprintf (pidfile, "%d", getpid ()); + fclose (pidfile); + + + alarmHandler = radProcessSignalGetHandler (SIGALRM); + radProcessSignalCatchAll (defaultSigHandler); + radProcessSignalCatch (SIGALRM, alarmHandler); + radProcessSignalRelease(SIGABRT); + + + // ... get our configuration values + if (wvconfigInit(FALSE) == ERROR) + { + radMsgLog (PRI_CATASTROPHIC, "wvconfigInit failed!"); + htmlSysExit (&htmlWork); + radProcessExit (); + radSystemExit (WVIEW_SYSTEM_ID); + exit (1); + } + + // Is the htmlgend daemon enabled? + iValue = wvconfigGetBooleanValue(configItem_ENABLE_HTMLGEN); + if (iValue == ERROR || iValue == 0) + { + wvconfigExit (); + radMsgLog (PRI_CATASTROPHIC, "htmlgend daemon is NOT enabled - exiting..."); + htmlSysExit (&htmlWork); + radProcessExit (); + radSystemExit (WVIEW_SYSTEM_ID); + exit (1); + } + + // get the wview verbosity setting + if (wvutilsSetVerbosity (WV_VERBOSE_HTMLGEND) == ERROR) + { + wvconfigExit (); + radMsgLog (PRI_CATASTROPHIC, "wvutilsSetVerbosity failed!"); + htmlSysExit (&htmlWork); + radProcessExit (); + radSystemExit (WVIEW_SYSTEM_ID); + exit (1); + } + + + // get the metric units flag first + iValue = wvconfigGetBooleanValue(configItem_HTMLGEN_METRIC_UNITS); + if (iValue == ERROR) + { + htmlWork.isMetricUnits = 0; + } + else + { + htmlWork.isMetricUnits = iValue; + } + + if (htmlWork.isMetricUnits) + { + radMsgLog (PRI_STATUS, "!! configured for metric units/conversion !!"); + + // try to get the CM/MM rain setting here too... + iValue = wvconfigGetBooleanValue(configItem_HTMLGEN_METRIC_USE_RAIN_MM); + if (iValue == ERROR) + { + // just assume MM units + wvutilsSetRainIsMM(TRUE); + } + else + { + wvutilsSetRainIsMM(iValue); + } + if (wvutilsGetRainIsMM()) + radMsgLog (PRI_STATUS, "!! Rain units will be mm !!"); + else + radMsgLog (PRI_STATUS, "!! Rain units will be cm !!"); + } + + // get the archive browser files-to-keep value + iValue = wvconfigGetINTValue(configItem_HTMLGEN_ARCHIVE_BROWSER_FILES_TO_KEEP); + htmlWork.arcrecDaysToKeep = iValue; + + sValue = wvconfigGetStringValue(configItem_HTMLGEN_MPHASE_INCREASE); + if (sValue == NULL) + { + wvstrncpy (htmlWork.mphaseIncrease, "Waxing", sizeof(htmlWork.mphaseIncrease)); + } + else + { + wvstrncpy (htmlWork.mphaseIncrease, sValue, sizeof(htmlWork.mphaseIncrease)); + } + + sValue = wvconfigGetStringValue(configItem_HTMLGEN_MPHASE_DECREASE); + if (sValue == NULL) + { + wvstrncpy (htmlWork.mphaseDecrease, "Waning", sizeof(htmlWork.mphaseDecrease)); + } + else + { + wvstrncpy (htmlWork.mphaseDecrease, sValue, sizeof(htmlWork.mphaseDecrease)); + } + + sValue = wvconfigGetStringValue(configItem_HTMLGEN_MPHASE_FULL); + if (sValue == NULL) + { + wvstrncpy (htmlWork.mphaseFull, "Full", sizeof(htmlWork.mphaseFull)); + } + else + { + wvstrncpy (htmlWork.mphaseFull, sValue, sizeof(htmlWork.mphaseFull)); + } + + sValue = wvconfigGetStringValue(configItem_HTMLGEN_LOCAL_RADAR_URL); + if (sValue == NULL) + { + radMsgLog (PRI_STATUS, "Set your default radar image - using default"); + wvstrncpy (htmlWork.radarURL, + "http://www.srh.noaa.gov/radar/images/DS.p19r0/SI.kfws/latest.gif", + sizeof(htmlWork.radarURL)); + } + else + { + wvstrncpy (htmlWork.radarURL, sValue, sizeof(htmlWork.radarURL)); + } + + sValue = wvconfigGetStringValue(configItem_HTMLGEN_LOCAL_FORECAST_URL); + if (sValue == NULL) + { + radMsgLog (PRI_STATUS, "Set your default forecast URL - using default"); + wvstrncpy (htmlWork.forecastURL, + "http://www.wunderground.com/cgi-bin/findweather/getForecast?query=76233", + sizeof(htmlWork.forecastURL)); + } + else + { + wvstrncpy (htmlWork.forecastURL, sValue, sizeof(htmlWork.forecastURL)); + } + + sValue = wvconfigGetStringValue(configItem_HTMLGEN_STATION_NAME); + if (sValue == NULL) + { + radMsgLog (PRI_STATUS, "Set your station name - using 'WVIEW_STATION'"); + wvstrncpy (htmlWork.stationName, "WVIEW_STATION", sizeof(htmlWork.stationName)); + } + else + { + wvstrncpy (htmlWork.stationName, sValue, sizeof(htmlWork.stationName)); + } + + sValue = wvconfigGetStringValue(configItem_HTMLGEN_STATION_CITY); + if (sValue == NULL) + { + radMsgLog (PRI_STATUS, "Set your station city - using 'WVIEW_CITY'"); + wvstrncpy (htmlWork.stationCity, "WVIEW_CITY", sizeof(htmlWork.stationCity)); + } + else + { + wvstrncpy (htmlWork.stationCity, sValue, sizeof(htmlWork.stationCity)); + } + + sValue = wvconfigGetStringValue(configItem_HTMLGEN_STATION_STATE); + if (sValue == NULL) + { + radMsgLog (PRI_STATUS, "Set your station state - using 'WVIEW_STATE'"); + wvstrncpy (htmlWork.stationState, "WVIEW_STATE", sizeof(htmlWork.stationState)); + } + else + { + wvstrncpy (htmlWork.stationState, sValue, sizeof(htmlWork.stationState)); + } + + sValue = wvconfigGetStringValue(configItem_HTMLGEN_IMAGE_PATH); + if (sValue == NULL) + { + radMsgLog (PRI_CATASTROPHIC, "HTMLGEN_IMAGE_PATH not found!"); + wvconfigExit (); + htmlSysExit (&htmlWork); + radProcessExit (); + radSystemExit (WVIEW_SYSTEM_ID); + exit (1); + } + else + { + wvstrncpy (htmlWork.imagePath, sValue, sizeof(htmlWork.imagePath)); + radMsgLog (PRI_STATUS, "generating to %s", htmlWork.imagePath); + } + + sValue = wvconfigGetStringValue(configItem_HTMLGEN_HTML_PATH); + if (sValue == NULL) + { + radMsgLog (PRI_CATASTROPHIC, "HTMLGEN_HTML_PATH not found!"); + wvconfigExit (); + htmlSysExit (&htmlWork); + radProcessExit (); + radSystemExit (WVIEW_SYSTEM_ID); + exit (1); + } + else + { + wvstrncpy (htmlWork.htmlPath, sValue, sizeof(htmlWork.htmlPath)); + radMsgLog (PRI_STATUS, "templates at %s", htmlWork.htmlPath); + } + + iValue = wvconfigGetINTValue(configItem_HTMLGEN_START_OFFSET); + if (iValue < 0) + { + radMsgLog (PRI_CATASTROPHIC, "HTMLGEN_START_OFFSET not found - defaulting to 0"); + htmlWork.startOffset = 0; + } + else + { + htmlWork.startOffset = iValue; + } + + iValue = wvconfigGetINTValue(configItem_HTMLGEN_GENERATE_INTERVAL); + if (iValue <= 0) + { + radMsgLog (PRI_CATASTROPHIC, "HTMLGEN_GENERATE_INTERVAL not found - defaulting to 1"); + htmlWork.timerInterval = 60000; + } + else + { + htmlWork.timerInterval = 60000 * iValue; + } + + iValue = wvconfigGetBooleanValue(configItem_HTMLGEN_EXTENDED_DATA); + if (iValue >= 0) + { + htmlWork.isExtendedData = iValue; + } + + sValue = wvconfigGetStringValue(configItem_HTMLGEN_DATE_FORMAT); + if (sValue == NULL) + { + if (htmlWork.isMetricUnits) + { + wvstrncpy (htmlWork.dateFormat, "%Y%m%d %R", sizeof(htmlWork.dateFormat)); + } + else + { + wvstrncpy (htmlWork.dateFormat, "%m/%d/%Y %R", sizeof(htmlWork.dateFormat)); + } + } + else + { + wvstrncpy (dateFmt, sValue, WVIEW_STRING1_SIZE); + for (i = 0; i < WVIEW_STRING1_SIZE; i ++) + { + if (dateFmt[i] == 0) + { + break; + } + if (dateFmt[i] == '_') + { + dateFmt[i] = ' '; + } + } + wvstrncpy (htmlWork.dateFormat, dateFmt, WVIEW_STRING1_SIZE); + } + + // Are we displaying dual units?.. + iValue = wvconfigGetBooleanValue(configItem_HTMLGEN_DUAL_UNITS); + if (iValue >= 0) + { + htmlWork.isDualUnits = iValue; + if (htmlWork.isDualUnits) + radMsgLog (PRI_STATUS, "!! Dual units will be displayed !!"); + } + else + { + // just assume single units + htmlWork.isDualUnits = 0; + } + + // Get the default wind units: + sValue = wvconfigGetStringValue(configItem_HTMLGEN_WIND_UNITS); + if (sValue == NULL) + { + radMsgLog (PRI_MEDIUM, "HTMLGEN_WIND_UNITS not found, defaulting to MPH..."); + htmlWork.windUnits = HTML_WINDUNITS_MPH; + wvutilsSetWindUnits(HTML_WINDUNITS_MPH); + } + else if (!strncmp(sValue, "mph", 3)) + { + // mph + htmlWork.windUnits = HTML_WINDUNITS_MPH; + wvutilsSetWindUnits(HTML_WINDUNITS_MPH); + radMsgLog (PRI_STATUS, "Using mph as default wind units"); + } + else if (!strncmp(sValue, "m/s", 3)) + { + // m/s + htmlWork.windUnits = HTML_WINDUNITS_MS; + wvutilsSetWindUnits(HTML_WINDUNITS_MS); + radMsgLog (PRI_STATUS, "Using m/s as default wind units"); + } + else if (!strncmp(sValue, "knots", 5)) + { + // knots + htmlWork.windUnits = HTML_WINDUNITS_KNOTS; + wvutilsSetWindUnits(HTML_WINDUNITS_KNOTS); + radMsgLog (PRI_STATUS, "Using knots as default wind units"); + } + else if (!strncmp(sValue, "km/h", 4)) + { + // km/h + htmlWork.windUnits = HTML_WINDUNITS_KMH; + wvutilsSetWindUnits(HTML_WINDUNITS_KMH); + radMsgLog (PRI_STATUS, "Using km/h as default wind units"); + } + else + { + // default to mph: + htmlWork.windUnits = HTML_WINDUNITS_MPH; + wvutilsSetWindUnits(HTML_WINDUNITS_MPH); + radMsgLog (PRI_STATUS, "DB value not recognized: using mph as default wind units"); + } + + wvconfigExit (); + + if (statusInit(htmlWork.statusFile, htmlStatusLabels) == ERROR) + { + radMsgLog (PRI_HIGH, "statusInit failed"); + htmlSysExit (&htmlWork); + radProcessExit (); + radSystemExit (WVIEW_SYSTEM_ID); + exit (1); + } + statusUpdate(STATUS_BOOTING); + + + // ... Initialize the generator: + htmlGenerateInit (); + + // ... Initialize the state machine: + htmlWork.stateMachine = radStatesInit (&htmlWork); + if (htmlWork.stateMachine == NULL) + { + statusUpdateMessage("radStatesInit failed"); + statusUpdate(STATUS_ERROR); + radMsgLog (PRI_HIGH, "radStatesInit failed"); + htmlSysExit (&htmlWork); + radProcessExit (); + radSystemExit (WVIEW_SYSTEM_ID); + exit (1); + } + + if (radStatesAddHandler (htmlWork.stateMachine, HTML_STATE_IDLE, + htmlIdleState) + == ERROR) + { + radMsgLog (PRI_HIGH, "radStatesAddHandler failed"); + statusUpdateMessage("radStatesAddHandler failed"); + statusUpdate(STATUS_ERROR); + radStatesExit (htmlWork.stateMachine); + htmlSysExit (&htmlWork); + radProcessExit (); + radSystemExit (WVIEW_SYSTEM_ID); + exit (1); + } + if (radStatesAddHandler (htmlWork.stateMachine, HTML_STATE_STATION_INFO, + htmlStationInfoState) + == ERROR) + { + radMsgLog (PRI_HIGH, "radStatesAddHandler failed"); + statusUpdateMessage("radStatesAddHandler failed"); + statusUpdate(STATUS_ERROR); + radStatesExit (htmlWork.stateMachine); + htmlSysExit (&htmlWork); + radProcessExit (); + radSystemExit (WVIEW_SYSTEM_ID); + exit (1); + } + if (radStatesAddHandler (htmlWork.stateMachine, HTML_STATE_RUN, + htmlRunState) + == ERROR) + { + radMsgLog (PRI_HIGH, "radStatesAddHandler failed"); + statusUpdateMessage("radStatesAddHandler failed"); + statusUpdate(STATUS_ERROR); + radStatesExit (htmlWork.stateMachine); + htmlSysExit (&htmlWork); + radProcessExit (); + radSystemExit (WVIEW_SYSTEM_ID); + exit (1); + } + if (radStatesAddHandler (htmlWork.stateMachine, HTML_STATE_DATA, + htmlDataState) + == ERROR) + { + radMsgLog (PRI_HIGH, "radStatesAddHandler failed"); + statusUpdateMessage("radStatesAddHandler failed"); + statusUpdate(STATUS_ERROR); + radStatesExit (htmlWork.stateMachine); + htmlSysExit (&htmlWork); + radProcessExit (); + radSystemExit (WVIEW_SYSTEM_ID); + exit (1); + } + if (radStatesAddHandler (htmlWork.stateMachine, HTML_STATE_ERROR, + htmlErrorState) + == ERROR) + { + radMsgLog (PRI_HIGH, "radStatesAddHandler failed"); + statusUpdateMessage("radStatesAddHandler failed"); + statusUpdate(STATUS_ERROR); + radStatesExit (htmlWork.stateMachine); + htmlSysExit (&htmlWork); + radProcessExit (); + radSystemExit (WVIEW_SYSTEM_ID); + exit (1); + } + + radStatesSetState (htmlWork.stateMachine, HTML_STATE_IDLE); + + + htmlWork.timer = radTimerCreate (NULL, timerHandler, NULL); + if (htmlWork.timer == NULL) + { + radMsgLog (PRI_HIGH, "radTimerCreate failed"); + statusUpdateMessage("radTimerCreate failed"); + statusUpdate(STATUS_ERROR); + radStatesExit (htmlWork.stateMachine); + htmlSysExit (&htmlWork); + radProcessExit (); + radSystemExit (WVIEW_SYSTEM_ID); + exit (1); + } + + htmlWork.rxTimer = radTimerCreate (NULL, rxtimerHandler, NULL); + if (htmlWork.rxTimer == NULL) + { + radMsgLog (PRI_HIGH, "radTimerCreate 2 failed"); + statusUpdateMessage("radTimerCreate 2 failed"); + statusUpdate(STATUS_ERROR); + radTimerDelete (htmlWork.timer); + radStatesExit (htmlWork.stateMachine); + htmlSysExit (&htmlWork); + radProcessExit (); + radSystemExit (WVIEW_SYSTEM_ID); + exit (1); + } + + htmlWork.noaaTimer = radTimerCreate (NULL, noaaTimerHandler, NULL); + if (htmlWork.noaaTimer == NULL) + { + radMsgLog (PRI_HIGH, "radTimerCreate 3 failed"); + statusUpdateMessage("radTimerCreate 3 failed"); + statusUpdate(STATUS_ERROR); + radTimerDelete (htmlWork.rxTimer); + radTimerDelete (htmlWork.timer); + radStatesExit (htmlWork.stateMachine); + htmlSysExit (&htmlWork); + radProcessExit (); + radSystemExit (WVIEW_SYSTEM_ID); + exit (1); + } + + // wait a bit here before continuing + radUtilsSleep (500); + + // register with the radlib message router + if (radMsgRouterInit (WVIEW_RUN_DIR) == ERROR) + { + radMsgLog (PRI_HIGH, "radMsgRouterInit failed!"); + statusUpdateMessage("radMsgRouterInit failed"); + statusUpdate(STATUS_ERROR); + radTimerDelete (htmlWork.noaaTimer); + radTimerDelete (htmlWork.rxTimer); + radTimerDelete (htmlWork.timer); + radStatesExit (htmlWork.stateMachine); + htmlSysExit (&htmlWork); + radProcessExit (); + radSystemExit (WVIEW_SYSTEM_ID); + exit (1); + } + + // enable message reception from the radlib router for worker requests + radMsgRouterMessageRegister (WVIEW_MSG_TYPE_STATION_INFO); + radMsgRouterMessageRegister (WVIEW_MSG_TYPE_HILOW_DATA); + radMsgRouterMessageRegister (WVIEW_MSG_TYPE_LOOP_DATA); + radMsgRouterMessageRegister (WVIEW_MSG_TYPE_ARCHIVE_NOTIFY); + + // enable message reception from the radlib router for POLL msgs + radMsgRouterMessageRegister (WVIEW_MSG_TYPE_POLL); + + // enable message reception from the radlib router for SHUTDOWN msgs + radMsgRouterMessageRegister (WVIEW_MSG_TYPE_SHUTDOWN); + + + radMsgLog (PRI_STATUS, "running..."); + + + // dummy up a stimulus to get the state machine running + stim.type = STIM_DUMMY; + radStatesProcess (htmlWork.stateMachine, &stim); + + + while (! htmlWork.exiting) + { + // wait on timers, events, file descriptors, msgs + if (radProcessWait(0) == ERROR) + { + htmlWork.exiting = TRUE; + } + } + + + statusUpdateMessage("exiting normally"); + radMsgLog (PRI_STATUS, "exiting normally..."); + statusUpdate(STATUS_SHUTDOWN); + + radMsgRouterExit (); + radTimerDelete (htmlWork.noaaTimer); + radTimerDelete (htmlWork.rxTimer); + radTimerDelete (htmlWork.timer); + radStatesExit (htmlWork.stateMachine); + + if (htmlWork.mgrId != NULL) + htmlmgrExit (htmlWork.mgrId); + + dbsqliteHiLowExit(); + dbsqliteNOAAExit(); + dbsqliteArchiveExit(); + htmlSysExit (&htmlWork); + + radProcessExit (); + radSystemExit (WVIEW_SYSTEM_ID); + exit (0); +} + diff --git a/htmlgenerator/html.h b/htmlgenerator/html.h new file mode 100755 index 0000000..ff733ae --- /dev/null +++ b/htmlgenerator/html.h @@ -0,0 +1,150 @@ +#ifndef INC_htmlh +#define INC_htmlh +/*--------------------------------------------------------------------------- + + FILENAME: + html.h + + PURPOSE: + Provide the wview HTML generator definitions. + + REVISION HISTORY: + Date Engineer Revision Remarks + 08/27/03 M.S. Teel 0 Original + + NOTES: + + + LICENSE: + Copyright (c) 2004, Mark S. Teel (mark@teel.ws) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +/* ... System include files +*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* ... Library include files +*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* ... Local include files +*/ +#include +#include +#include +#include +#include +#include +#include +#include + + +/* !!!!!!!!!!!!!!!!!! HIDDEN, NOT FOR API USE !!!!!!!!!!!!!!!!!! +*/ +#define TIMER_GENERATE 1 +#define TIMER_RX_PACKETS 2 + +#define HTML_NOAA_UPDATE_DELAY 30000 // 30 secs + +#define HTML_RX_PACKETS_TIMEOUT 60000 // 60 secs + + +typedef enum +{ + HTML_STATS_IMAGES_DEFINED = 0, + HTML_STATS_TEMPLATES_DEFINED, + HTML_STATS_IMAGES_GENERATED, + HTML_STATS_TEMPLATES_GENERATED +} HTML_STATS; + +typedef struct +{ + pid_t myPid; + char pidFile[WVIEW_STRING2_SIZE]; + char indicateFile[WVIEW_STRING2_SIZE]; + char fifoFile[WVIEW_STRING2_SIZE]; + char statusFile[WVIEW_STRING2_SIZE]; + char daemonQname[WVIEW_STRING2_SIZE]; + char configFile[WVIEW_STRING2_SIZE]; + int archiveInterval; + int isMetricUnits; + HTML_WUNITS windUnits; + char imagePath[WVIEW_STRING2_SIZE]; + char htmlPath[WVIEW_STRING2_SIZE]; + int isExtendedData; + STATES_ID stateMachine; + char stationName[WVIEW_STRING1_SIZE]; + char stationCity[WVIEW_STRING1_SIZE]; + char stationState[WVIEW_STRING1_SIZE]; + int arcrecDaysToKeep; + char mphaseIncrease[32]; + char mphaseDecrease[32]; + char mphaseFull[32]; + char radarURL[WVIEW_STRING2_SIZE]; + char forecastURL[WVIEW_STRING2_SIZE]; + TIMER_ID timer; + TIMER_ID rxTimer; + TIMER_ID noaaTimer; + struct timeval nextGenerationTime; + int timerInterval; + int startOffset; + time_t LastArchiveDateTime; + int histLastHour; + int histLastDay; + HTML_MGR_ID mgrId; + NOAA_ID noaaId; + int numDataReceived; + int exiting; + char dateFormat[WVIEW_STRING1_SIZE]; + int isDualUnits; +} HTML_WORK; + + +typedef enum +{ + HTML_STATE_IDLE = 1, + HTML_STATE_STATION_INFO, + HTML_STATE_RUN, + HTML_STATE_DATA, + HTML_STATE_ERROR +} HTML_STATES; + + +extern int htmlIdleState (int state, void *stimulus, void *data); +extern int htmlStationInfoState (int state, void *stimulus, void *data); +extern int htmlRunState (int state, void *stimulus, void *data); +extern int htmlDataState (int state, void *stimulus, void *data); +extern int htmlErrorState (int state, void *stimulus, void *data); + + +/* !!!!!!!!!!!!!!!!!!!! END HIDDEN SECTION !!!!!!!!!!!!!!!!!!!!! +*/ + + + +/* ... API function prototypes +*/ + +#endif diff --git a/htmlgenerator/htmlGenerate.c b/htmlgenerator/htmlGenerate.c new file mode 100755 index 0000000..5f4adc2 --- /dev/null +++ b/htmlgenerator/htmlGenerate.c @@ -0,0 +1,6221 @@ +/*--------------------------------------------------------------------------- + + FILENAME: + htmlGenerate.c + + PURPOSE: + Provide the wview html generator utilities. + + REVISION HISTORY: + Date Engineer Revision Remarks + 08/30/03 M.S. Teel 0 Original + 06/26/2005 S. Pacenka 1 Add metric support to + the temp dial + 02/16/2008 M.B. Clark 2 Added ability to customize + color/size of plots using + graphics.conf settings + 03/24/2008 W. Krenn 3 metric adaptations + 12/01/2009 M. Hornsby 4 Add Moon Rise and Set + + NOTES: + This is by far the ugliest code in the wview source. Shortcuts are taken + to optimize image and HTML generation. + + LICENSE: + Copyright (c) 2004, Mark S. Teel (mark@teel.ws) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +/* ... System include files +*/ +#include + +/* ... Library include files +*/ +#include "gd.h" +#include "gdfonts.h" +#include "gdfontt.h" +#include "gdfontmb.h" +#include "gdfontg.h" +#include "gdfontl.h" +#include +#include + +/* ... Local include files +*/ +#include +#include +#include +#include +#include +#include + + +/* ... global memory declarations +*/ + +/* ... global memory referenced +*/ + +/* ... static (local) memory declarations +*/ +#define _DEBUG_GENERATION FALSE + +static TEXT_SEARCH_ID tagSearchEngine; + +static PLOT_PREFS plotPrefs; + +enum ConfigIds +{ + CFG_ID_BUCKET_TRANSPARENT = 0, + CFG_ID_BUCKET_BG_COLOR, + CFG_ID_BUCKET_FG_COLOR, + CFG_ID_BUCKET_CONTENT_COLOR, + CFG_ID_BUCKET_HIGH_COLOR, + CFG_ID_BUCKET_LOW_COLOR, + CFG_ID_BUCKET_TITLE_BG_COLOR, + CFG_ID_BUCKET_TITLE_FG_COLOR, + CFG_ID_BUCKET_TEXT_COLOR, + CFG_ID_BUCKET_TRANSPARENT_COLOR, + CFG_ID_BUCKET_IMAGE_WIDTH , + CFG_ID_BUCKET_IMAGE_HEIGHT, + CFG_ID_BUCKET_WIDTH, + + CFG_ID_CHART_TRANSPARENT, + CFG_ID_CHART_IMAGE_BG_COLOR, + CFG_ID_CHART_GRAPH_BG_COLOR, + CFG_ID_CHART_GRID_COLOR, + CFG_ID_CHART_FIRST_LINE_COLOR, + CFG_ID_CHART_SECOND_LINE_COLOR, + CFG_ID_CHART_THIRD_LINE_COLOR, + CFG_ID_CHART_FOURTH_LINE_COLOR, + CFG_ID_CHART_TITLE_BG_COLOR, + CFG_ID_CHART_TITLE_FG_COLOR, + CFG_ID_CHART_TEXT_COLOR, + CFG_ID_CHART_WIDTH, + CFG_ID_CHART_HEIGHT, + + CFG_ID_BAR_IMAGE_BG_COLOR, + CFG_ID_BAR_GRAPH_BG_COLOR , + CFG_ID_BAR_BAR_COLOR, + CFG_ID_BAR_TITLE_BG_COLOR, + CFG_ID_BAR_TITLE_FG_COLOR, + CFG_ID_BAR_GRID_COLOR, + CFG_ID_BAR_TEXT_COLOR, + CFG_ID_BAR_WIDTH, + CFG_ID_BAR_HEIGHT, + + CFG_ID_DIAL_TRANSPARENT, + CFG_ID_DIAL_BG_COLOR, + CFG_ID_DIAL_IMAGE_BG_COLOR, + CFG_ID_DIAL_CENTER_COLOR, + CFG_ID_DIAL_CENTER_TEXT_COLOR, + CFG_ID_DIAL_CENTER_HIGH_COLOR, + CFG_ID_DIAL_POINTER_COLOR, + CFG_ID_DIAL_POINTER_OUTLINE_COLOR, + CFG_ID_DIAL_HIGH_COLOR, + CFG_ID_DIAL_LOW_COLOR, + CFG_ID_DIAL_APP_COLOR, + CFG_ID_DIAL_TEXT_COLOR, + CFG_ID_DIAL_IMAGE_WIDTH, + CFG_ID_DIAL_DIAMETER, + CFG_ID_DIAL_CTR_DIAMETER, + + CFG_ID_MULTICHART_FIRST_LINE_COLOR, + CFG_ID_MULTICHART_SECOND_LINE_COLOR, + CFG_ID_MULTICHART_THIRD_LINE_COLOR, + CFG_ID_MULTICHART_FOURTH_LINE_COLOR +}; + + +static char *configIDs[] = + { + "BUCKET_TRANSPARENT", // Background transparent? + "BUCKET_BG_COLOR", // Background color of image + "BUCKET_FG_COLOR", // Foreground color (lines) + "BUCKET_CONTENT_COLOR", // Color fill for bucket + "BUCKET_HIGH_COLOR", // Color of line for high value + "BUCKET_LOW_COLOR", // Color or line for low value + "BUCKET_TITLE_BG_COLOR", // Background color for title + "BUCKET_TITLE_FG_COLOR", // Foreground (text) color for title + "BUCKET_TEXT_COLOR", // Color of text labels + "BUCKET_TRANSPARENT_COLOR", // Not implemented yet + "BUCKET_IMAGE_WIDTH", // Width of entire image + "BUCKET_IMAGE_HEIGHT", // Height of enitre image + "BUCKET_WIDTH", // Width of bucket itself + + "CHART_TRANSPARENT", // Background transparent? + "CHART_IMAGE_BG_COLOR", // Background color of image + "CHART_GRAPH_BG_COLOR", // Background color of plot + "CHART_GRID_COLOR", // Color of grid lines + "CHART_FIRST_LINE_COLOR", // Color of first line + "CHART_SECOND_LINE_COLOR", // Color of second line + "CHART_THIRD_LINE_COLOR", // Color of third line + "CHART_FOURTH_LINE_COLOR", // Color of fourth line + "CHART_TITLE_BG_COLOR", // Background color for title + "CHART_TITLE_FG_COLOR", // Foreground color for title + "CHART_TEXT_COLOR", // Color of text labels + "CHART_WIDTH", // Width of entire image + "CHART_HEIGHT", // Height of entire image + + "BAR_IMAGE_BG_COLOR", // Background color of image + "BAR_GRAPH_BG_COLOR", // Background color of plot + "BAR_BAR_COLOR", // Color of bars + "BAR_TITLE_BG_COLOR", // Background color of title + "BAR_TITLE_FG_COLOR", // Foreground color of title + "BAR_GRID_COLOR", // Color of grid lines + "BAR_TEXT_COLOR", // Color of text labels + "BAR_WIDTH", // Width of entire image + "BAR_HEIGHT", // Height of entire image + + "DIAL_TRANSPARENT", // Background transparent? + "DIAL_BG_COLOR", // Background color of dial + "DIAL_IMAGE_BG_COLOR", // Background color of image (transparent) + "DIAL_CENTER_COLOR", // Color of circle in center of image + "DIAL_CENTER_TEXT_COLOR", // Text color in image center (title) + "DIAL_CENTER_HIGH_COLOR", // Text color for high value + "DIAL_POINTER_COLOR", // Pointer color + "DIAL_POINTER_OUTLINE_COLOR", // Color of pointer outline + "DIAL_HIGH_COLOR", // Color of high-value tick + "DIAL_LOW_COLOR", // Color of low-value tick + "DIAL_APP_COLOR", // + "DIAL_TEXT_COLOR", // Color of labels + "DIAL_IMAGE_WIDTH", // Width of image + "DIAL_DIAMETER", // Diameter of dial + "DIAL_CTR_DIAMETER", // Diameter of center circle + + "MULTICHART_FIRST_LINE_COLOR", // Color of first line + "MULTICHART_SECOND_LINE_COLOR", // Color of second line + "MULTICHART_THIRD_LINE_COLOR", // Color of third line + "MULTICHART_FOURTH_LINE_COLOR" // Color of forth line + }; + +// ... define the HTML data tags +// ... "computeTag" below depends on the order of these strings! +static char *dataTags[] = + { + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", // 10 + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", // 20 + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", // 30 + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", // 40 + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", // 50 + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", // 60 + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", // 70 + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", // 80 + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", // 90 + "", + "", + "", + "", + "", + + "", + "", + "", + "", + "", // 100 + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", // 110 + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", // 120 + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", // 130 + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", // 140 + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", // 150 + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", // 160 + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", // 170 + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", // 180 + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", // 190 + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", // 200 + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", // 210 + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", // 220 + "", + + "", + "", + "", + "", + "", + "", + "", + "", + "", // 230 + "", + "", + + "", + "", + "", + "", + "", + "", + + "", + "", // 240 + "", + + "", + "", + "", + "", + + "", + "", + "", + "", + "", // 250 + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", // 260 + + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", // 270 + "", + "", + "", + "", + "", + + "", + "", + "", + "", + "", // 280 + "", + + "", + "", + "", + "", + "", + "", + "", + "", + "", // 290 + "", + "", + "", + "", + "", + + "", + "", + "", + "", + "", // 300 + "", + + "", + "", + "", + "", + "", + "", + "", + "", + "", // 310 + "", + "", + "", + "", + "", + "", + "", + "", + "", + + "", // 320 + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", // 330 + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", // 340 + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", // 350 + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", // 360 + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", // 370 + "", + "", + "", + "", + + NULL + }; + +static char BPTrendLabels[4] = + { + '-', + '~', + '+', + ' ' + }; + +static char *monthLabel[13] = + { + "UNK", + "JAN", + "FEB", + "MAR", + "APR", + "MAY", + "JUN", + "JUL", + "AUG", + "SEP", + "OCT", + "NOV", + "DEC" + }; + +static char winddir[16][4] = +{ + "N", + "NNE", + "NE", + "ENE", + "E", + "ESE", + "SE", + "SSE", + "S", + "SSW", + "SW", + "WSW", + "W", + "WNW", + "NW", + "NNW" +}; + +static char tendency[5][14] = +{ + "Clear", + "Partly Cloudy", + "Cloudy", + "Rain", + "Unknown" +}; + +static char *buildTimeTag (int16_t timeval) +{ + static char ret[16]; + + if (timeval < 0) + { + sprintf (ret, "--:--"); + } + else + { + sprintf (ret, "%2.2d:%2.2d", + EXTRACT_PACKED_HOUR(timeval), + EXTRACT_PACKED_MINUTE(timeval)); + } + return ret; +} + +char *buildWindDirString (int dir) +{ + if (dir < 0 || dir > 15) + { + return "---"; + } + else + { + return winddir[dir]; + } +} + +char *buildTendencyString (int tend) +{ + int text; + + switch (tend) + { + case 0x0c: // Clear + text = 0; + break; + case 0x06: // Partly Cloudy + text = 1; + break; + case 0x02: // Cloudy + text = 2; + break; + case 0x03: // Rain + text = 3; + break; + default: // Unknown + text = 4; + } + + return tendency[text]; +} + +static char *makeduration (float x) +{ + int hour, min, sec; + static char duration[32]; + + hour = (int)( x / 3600 ); + x -= (hour * 3600); + min = (int)( x / 60 ); + sec = x - ( 60 * min ); + sprintf (duration, "%d:%2.2d:%2.2d", hour, min, sec); + + return duration; + +} + +static char* getBattStatus(uint8_t status) +{ + static char batteryStatus[16]; + + if (status == 0) + sprintf(batteryStatus, "LOW"); + else if (status == 1) + sprintf(batteryStatus, "OK"); + else + sprintf(batteryStatus, "UNKNOWN"); + return batteryStatus; +} + +static void computeTag (HTML_MGR_ID id, char *tag, int len, char *store) +{ + time_t ntime; + struct tm loctime; + char temp[SEARCH_TEXT_MAX]; + int tagIndex, tempInt, tempInt1; + float tempfloat; + SENSOR_STORE *sensors = &id->hilowStore; + + // First, find the index: + strncpy(temp, tag, len); + temp[len] = 0; + + if (radtextsearchFind(tagSearchEngine, temp, &tagIndex) == ERROR) + { + store[0] = 0; + return; + } + + // now "tagIndex" is the index for the matched tag: + switch (tagIndex) + { + case 0: + if (id->isMetricUnits) + strcpy (store, " C"); + else + strcpy (store, " F"); + break; + case 1: + strcpy (store, " %"); + break; + case 2: + sprintf (store, " %s", wvutilsGetWindUnitLabel()); + break; + case 3: + if (id->isMetricUnits) + strcpy (store, " mb"); + else + strcpy (store, " in"); + break; + case 4: + if (id->isMetricUnits) + { + if (wvutilsGetRainIsMM()) + strcpy (store, " mm/h"); + else + strcpy (store, " cm/h"); + } + else + strcpy (store, " in/hr"); + break; + case 5: + if (id->isMetricUnits) + { + if (wvutilsGetRainIsMM()) + strcpy (store, " mm"); + else + strcpy (store, " cm"); + } + else + strcpy (store, " in"); + break; + case 6: + wvstrncpy (store, id->stationCity, HTML_MAX_LINE_LENGTH); + break; + case 7: + wvstrncpy (store, id->stationState, HTML_MAX_LINE_LENGTH); + break; + case 8: + ntime = time(NULL); + localtime_r (&ntime, &loctime); + strftime(store, 256, id->dateFormat, &loctime); + break; + case 9: + ntime = time (NULL); + localtime_r (&ntime, &loctime); + sprintf (store, "%2.2d:%2.2d:%2.2d", + loctime.tm_hour, + loctime.tm_min, + loctime.tm_sec); + break; + case 10: + wvstrncpy (store, buildTimeTag (id->sunrise), HTML_MAX_LINE_LENGTH); + break; + case 11: + wvstrncpy (store, buildTimeTag (id->sunset), HTML_MAX_LINE_LENGTH); + break; + case 12: + if (id->isMetricUnits) + { + sprintf (store, "%.1f", + wvutilsConvertFToC(id->loopStore.outTemp)); + } + else + { + sprintf (store, "%.1f", id->loopStore.outTemp); + } + break; + case 13: + if (id->isMetricUnits) + { + sprintf (store, "%.1f", + wvutilsConvertFToC(id->loopStore.windchill)); + } + else + { + sprintf (store, "%.1f", id->loopStore.windchill); + } + break; + case 14: + sprintf (store, "%d", id->loopStore.outHumidity); + break; + case 15: + if (id->isMetricUnits) + { + sprintf (store, "%.1f", + wvutilsConvertFToC(id->loopStore.heatindex)); + } + else + { + sprintf (store, "%.1f", id->loopStore.heatindex); + } + break; + case 16: + tempfloat = (float)id->loopStore.windDir + 11.24; + tempfloat /= 22.5; + tempInt = (int)tempfloat; + tempInt %= 16; + sprintf (store, "%s", buildWindDirString(tempInt)); + break; + case 17: + sprintf (store, + "%.1f", + wvutilsGetWindSpeed(id->loopStore.windSpeedF)); + break; + case 18: + if (id->isMetricUnits) + { + sprintf (store, "%.1f", + wvutilsConvertFToC(id->loopStore.dewpoint)); + } + else + { + sprintf (store, "%.1f", id->loopStore.dewpoint); + } + break; + case 19: + if (id->isMetricUnits) + { + sprintf (store, "%.1f", + wvutilsConvertINHGToHPA(id->loopStore.barometer)); + } + else + { + sprintf (store, "%.3f", id->loopStore.barometer); + } + break; + case 20: + if (id->isMetricUnits) + { + sprintf (store, "%.2f", + wvutilsConvertRainINToMetric(id->loopStore.rainRate)); + } + else + { + sprintf (store, "%.2f", id->loopStore.rainRate); + } + break; + case 21: + if (id->isMetricUnits) + { + strcpy (store, + wvutilsPrintFloat(wvutilsConvertRainINToMetric(id->loopStore.dayRain), 2)); + } + else + { + strcpy (store, wvutilsPrintFloat(id->loopStore.dayRain, 2)); + } + break; + case 22: + if (id->isMetricUnits) + { + strcpy (store, + wvutilsPrintFloat(wvutilsConvertRainINToMetric(id->loopStore.monthRain), 2)); + } + else + { + strcpy (store, wvutilsPrintFloat(id->loopStore.monthRain, 2)); + } + break; + case 23: + if (id->isMetricUnits) + { + strcpy (store, + wvutilsPrintFloat(wvutilsConvertRainINToMetric(id->loopStore.stormRain), 2)); + } + else + { + strcpy (store, wvutilsPrintFloat(id->loopStore.stormRain, 2)); + } + break; + case 24: + if (id->isMetricUnits) + { + strcpy (store, + wvutilsPrintFloat(wvutilsConvertRainINToMetric(id->loopStore.yearRain), 2)); + } + else + { + sprintf (store, "%s", + wvutilsPrintFloat(id->loopStore.yearRain, 2)); + } + break; + case 25: + if (id->isMetricUnits) + { + sprintf (store, "%.1f", + wvutilsConvertFToC(sensorGetDailyHigh(sensors->sensor, SENSOR_OUTTEMP))); + } + else + { + sprintf (store, "%.1f", sensorGetDailyHigh(sensors->sensor, SENSOR_OUTTEMP)); + } + break; + case 26: + strcpy (store, sensorGetDailyHighTime(sensors->sensor, SENSOR_OUTTEMP, temp)); + break; + case 27: + if (id->isMetricUnits) + { + sprintf (store, "%.1f", + wvutilsConvertFToC(sensorGetDailyLow(sensors->sensor, SENSOR_OUTTEMP))); + } + else + { + sprintf (store, "%.1f", sensorGetDailyLow(sensors->sensor, SENSOR_OUTTEMP)); + } + break; + case 28: + strcpy (store, sensorGetDailyLowTime(sensors->sensor, SENSOR_OUTTEMP, temp)); + break; + case 29: + sprintf (store, "%d", (int)sensorGetDailyHigh(sensors->sensor, SENSOR_OUTHUMID)); + break; + case 30: + strcpy (store, sensorGetDailyHighTime(sensors->sensor, SENSOR_OUTHUMID, temp)); + break; + case 31: + sprintf (store, "%d", (int)sensorGetDailyLow(sensors->sensor, SENSOR_OUTHUMID)); + break; + case 32: + strcpy (store, sensorGetDailyLowTime(sensors->sensor, SENSOR_OUTHUMID, temp)); + break; + case 33: + if (id->isMetricUnits) + { + sprintf (store, "%.1f", + wvutilsConvertFToC(sensorGetDailyHigh(sensors->sensor, SENSOR_DEWPOINT))); + } + else + { + sprintf (store, "%.1f", sensorGetDailyHigh(sensors->sensor, SENSOR_DEWPOINT)); + } + break; + case 34: + strcpy (store, sensorGetDailyHighTime(sensors->sensor, SENSOR_DEWPOINT, temp)); + break; + case 35: + if (id->isMetricUnits) + { + sprintf (store, "%.1f", + wvutilsConvertFToC(sensorGetDailyLow(sensors->sensor, SENSOR_DEWPOINT))); + } + else + { + sprintf (store, "%.1f", sensorGetDailyLow(sensors->sensor, SENSOR_DEWPOINT)); + } + break; + case 36: + strcpy (store, sensorGetDailyLowTime(sensors->sensor, SENSOR_DEWPOINT, temp)); + break; + case 37: + sprintf (store, + "%.1f", + wvutilsGetWindSpeed(sensorGetDailyHigh(sensors->sensor, SENSOR_WGUST)) + ); + break; + case 38: + strcpy (store, sensorGetDailyHighTime(sensors->sensor, SENSOR_WGUST, temp)); + break; + case 39: + if (id->isMetricUnits) + { + sprintf (store, "%.1f", + wvutilsConvertINHGToHPA(sensorGetDailyHigh(sensors->sensor, SENSOR_BP))); + } + else + { + sprintf (store, "%.3f", + sensorGetDailyHigh(sensors->sensor, SENSOR_BP)); + } + break; + case 40: + strcpy (store, sensorGetDailyHighTime(sensors->sensor, SENSOR_BP, temp)); + break; + case 41: + if (id->isMetricUnits) + { + sprintf (store, "%.1f", + wvutilsConvertINHGToHPA(sensorGetDailyLow(sensors->sensor, SENSOR_BP))); + } + else + { + sprintf (store, "%.3f", + sensorGetDailyLow(sensors->sensor, SENSOR_BP)); + } + break; + case 42: + strcpy (store, sensorGetDailyLowTime(sensors->sensor, SENSOR_BP, temp)); + break; + case 43: + if (id->isMetricUnits) + { + sprintf (store, "%.2f", + wvutilsConvertRainINToMetric(sensorGetDailyHigh(sensors->sensor, SENSOR_RAINRATE))); + } + else + { + sprintf (store, "%.2f", sensorGetDailyHigh(sensors->sensor, SENSOR_RAINRATE)); + } + break; + case 44: + if (sensorGetDailyHigh(sensors->sensor, SENSOR_RAINRATE) < 0.01) + strcpy (store, "-----"); + else + strcpy (store, sensorGetDailyHighTime(sensors->sensor, SENSOR_RAINRATE, temp)); + break; + case 45: + if (id->isMetricUnits) + { + sprintf (store, "%.1f", + wvutilsConvertFToC(sensorGetDailyLow(sensors->sensor, SENSOR_WCHILL))); + } + else + { + sprintf (store, "%.1f", sensorGetDailyLow(sensors->sensor, SENSOR_WCHILL)); + } + break; + case 46: + strcpy (store, sensorGetDailyLowTime(sensors->sensor, SENSOR_WCHILL, temp)); + break; + case 47: + if (id->isMetricUnits) + { + sprintf (store, "%.1f", + wvutilsConvertFToC(sensorGetDailyHigh(sensors->sensor, SENSOR_HINDEX))); + } + else + { + sprintf (store, "%.1f", sensorGetDailyHigh(sensors->sensor, SENSOR_HINDEX)); + } + break; + case 48: + strcpy (store, sensorGetDailyHighTime(sensors->sensor, SENSOR_HINDEX, temp)); + break; + case 49: + if (id->isMetricUnits) + { + sprintf (store, "%.1f", + wvutilsConvertFToC(sensorGetHigh(&sensors->sensor[STF_MONTH][SENSOR_OUTTEMP]))); + } + else + { + sprintf (store, "%.1f", sensorGetHigh(&sensors->sensor[STF_MONTH][SENSOR_OUTTEMP])); + } + break; + case 50: + if (id->isMetricUnits) + { + sprintf (store, "%.1f", + wvutilsConvertFToC(sensorGetLow(&sensors->sensor[STF_MONTH][SENSOR_OUTTEMP]))); + } + else + { + sprintf (store, "%.1f", sensorGetLow(&sensors->sensor[STF_MONTH][SENSOR_OUTTEMP])); + } + break; + case 51: + sprintf (store, "%d", (int)sensorGetHigh(&sensors->sensor[STF_MONTH][SENSOR_OUTHUMID])); + break; + case 52: + sprintf (store, "%d", (int)sensorGetLow(&sensors->sensor[STF_MONTH][SENSOR_OUTHUMID])); + break; + case 53: + if (id->isMetricUnits) + { + sprintf (store, "%.1f", + wvutilsConvertFToC(sensorGetHigh(&sensors->sensor[STF_MONTH][SENSOR_DEWPOINT]))); + } + else + { + sprintf (store, "%.1f", sensorGetHigh(&sensors->sensor[STF_MONTH][SENSOR_DEWPOINT])); + } + break; + case 54: + if (id->isMetricUnits) + { + sprintf (store, "%.1f", + wvutilsConvertFToC(sensorGetLow(&sensors->sensor[STF_MONTH][SENSOR_DEWPOINT]))); + } + else + { + sprintf (store, "%.1f", sensorGetLow(&sensors->sensor[STF_MONTH][SENSOR_DEWPOINT])); + } + break; + case 55: + sprintf (store, + "%.1f", + wvutilsGetWindSpeed(sensorGetHigh(&sensors->sensor[STF_MONTH][SENSOR_WGUST])) + ); + break; + case 56: + if (id->isMetricUnits) + { + sprintf (store, "%.1f", + wvutilsConvertINHGToHPA(sensorGetHigh(&sensors->sensor[STF_MONTH][SENSOR_BP]))); + } + else + { + sprintf (store, "%.3f", + sensorGetHigh(&sensors->sensor[STF_MONTH][SENSOR_BP])); + } + break; + case 57: + if (id->isMetricUnits) + { + sprintf (store, "%.1f", + wvutilsConvertINHGToHPA(sensorGetLow(&sensors->sensor[STF_MONTH][SENSOR_BP]))); + } + else + { + sprintf (store, "%.3f", + sensorGetLow(&sensors->sensor[STF_MONTH][SENSOR_BP])); + } + break; + case 58: + if (id->isMetricUnits) + { + sprintf (store, "%.1f", + wvutilsConvertFToC(sensorGetLow(&sensors->sensor[STF_MONTH][SENSOR_WCHILL]))); + } + else + { + sprintf (store, "%.1f", sensorGetLow(&sensors->sensor[STF_MONTH][SENSOR_WCHILL])); + } + break; + case 59: + if (id->isMetricUnits) + { + sprintf (store, "%.1f", + wvutilsConvertFToC(sensorGetHigh(&sensors->sensor[STF_MONTH][SENSOR_HINDEX]))); + } + else + { + sprintf (store, "%.1f", sensorGetHigh(&sensors->sensor[STF_MONTH][SENSOR_HINDEX])); + } + break; + case 60: + if (id->isMetricUnits) + { + sprintf (store, "%.2f", + wvutilsConvertRainINToMetric(sensorGetHigh(&sensors->sensor[STF_MONTH][SENSOR_RAINRATE]))); + } + else + { + sprintf (store, "%.2f", sensorGetHigh(&sensors->sensor[STF_MONTH][SENSOR_RAINRATE])); + } + break; + case 61: + if (id->isMetricUnits) + { + sprintf (store, "%.1f", + wvutilsConvertFToC(sensorGetHigh(&sensors->sensor[STF_YEAR][SENSOR_OUTTEMP]))); + } + else + { + sprintf (store, "%.1f", sensorGetHigh(&sensors->sensor[STF_YEAR][SENSOR_OUTTEMP])); + } + break; + case 62: + if (id->isMetricUnits) + { + sprintf (store, "%.1f", + wvutilsConvertFToC(sensorGetLow(&sensors->sensor[STF_YEAR][SENSOR_OUTTEMP]))); + } + else + { + sprintf (store, "%.1f", sensorGetLow(&sensors->sensor[STF_YEAR][SENSOR_OUTTEMP])); + } + break; + case 63: + sprintf (store, "%d", (int)sensorGetHigh(&sensors->sensor[STF_YEAR][SENSOR_OUTHUMID])); + break; + case 64: + sprintf (store, "%d", (int)sensorGetLow(&sensors->sensor[STF_YEAR][SENSOR_OUTHUMID])); + break; + case 65: + if (id->isMetricUnits) + { + sprintf (store, "%.1f", + wvutilsConvertFToC(sensorGetHigh(&sensors->sensor[STF_YEAR][SENSOR_DEWPOINT]))); + } + else + { + sprintf (store, "%.1f", sensorGetHigh(&sensors->sensor[STF_YEAR][SENSOR_DEWPOINT])); + } + break; + case 66: + if (id->isMetricUnits) + { + sprintf (store, "%.1f", + wvutilsConvertFToC(sensorGetLow(&sensors->sensor[STF_YEAR][SENSOR_DEWPOINT]))); + } + else + { + sprintf (store, "%.1f", sensorGetLow(&sensors->sensor[STF_YEAR][SENSOR_DEWPOINT])); + } + break; + case 67: + sprintf (store, + "%.1f", + wvutilsGetWindSpeed(sensorGetHigh(&sensors->sensor[STF_YEAR][SENSOR_WGUST])) + ); + break; + case 68: + if (id->isMetricUnits) + { + sprintf (store, "%.1f", + wvutilsConvertINHGToHPA(sensorGetHigh(&sensors->sensor[STF_YEAR][SENSOR_BP]))); + } + else + { + sprintf (store, "%.3f", + sensorGetHigh(&sensors->sensor[STF_YEAR][SENSOR_BP])); + } + break; + case 69: + if (id->isMetricUnits) + { + sprintf (store, "%.1f", + wvutilsConvertINHGToHPA(sensorGetLow(&sensors->sensor[STF_YEAR][SENSOR_BP]))); + } + else + { + sprintf (store, "%.3f", + sensorGetLow(&sensors->sensor[STF_YEAR][SENSOR_BP])); + } + break; + case 70: + if (id->isMetricUnits) + { + sprintf (store, "%.1f", + wvutilsConvertFToC(sensorGetLow(&sensors->sensor[STF_YEAR][SENSOR_WCHILL]))); + } + else + { + sprintf (store, "%.1f", sensorGetLow(&sensors->sensor[STF_YEAR][SENSOR_WCHILL])); + } + break; + case 71: + if (id->isMetricUnits) + { + sprintf (store, "%.1f", + wvutilsConvertFToC(sensorGetHigh(&sensors->sensor[STF_YEAR][SENSOR_HINDEX]))); + } + else + { + sprintf (store, "%.1f", sensorGetHigh(&sensors->sensor[STF_YEAR][SENSOR_HINDEX])); + } + break; + case 72: + if (id->isMetricUnits) + { + sprintf (store, "%.2f", + wvutilsConvertRainINToMetric(sensorGetHigh(&sensors->sensor[STF_YEAR][SENSOR_RAINRATE]))); + } + else + { + sprintf (store, "%.2f", sensorGetHigh(&sensors->sensor[STF_YEAR][SENSOR_RAINRATE])); + } + break; + case 73: + sprintf (store, "%s", globalWviewVersionStr); + break; + case 74: + sprintf (store, "%s", radSystemGetUpTimeSTR (WVIEW_SYSTEM_ID)); + break; + case 75: + sprintf (store, "%.1f", (float)id->loopStore.UV); + break; + case 76: + if (id->isMetricUnits) + { + strcpy (store, + wvutilsPrintFloat(wvutilsConvertRainINToMetric(id->loopStore.dayET), 3)); + } + else + { + strcpy (store, wvutilsPrintFloat(id->loopStore.dayET, 3)); + } + break; + case 77: + sprintf (store, "%.0f", (float)id->loopStore.radiation); + break; + case 78: + if (id->isMetricUnits) + { + sprintf (store, "%.1f", + wvutilsConvertFToC(id->loopStore.extraTemp[0])); + } + else + { + sprintf (store, "%.0f", id->loopStore.extraTemp[0]); + } + break; + case 79: + if (id->isMetricUnits) + { + sprintf (store, "%.1f", + wvutilsConvertFToC(id->loopStore.extraTemp[1])); + } + else + { + sprintf (store, "%.0f", id->loopStore.extraTemp[1]); + } + break; + case 80: + if (id->isMetricUnits) + { + sprintf (store, "%.1f", + wvutilsConvertFToC(id->loopStore.extraTemp[2])); + } + else + { + sprintf (store, "%.0f", id->loopStore.extraTemp[2]); + } + break; + case 81: + if (id->isMetricUnits) + { + sprintf (store, "%.1f", + wvutilsConvertFToC(id->loopStore.soilTemp1)); + } + else + { + sprintf (store, "%.0f", id->loopStore.soilTemp1); + } + break; + case 82: + if (id->isMetricUnits) + { + sprintf (store, "%.1f", + wvutilsConvertFToC(id->loopStore.soilTemp2)); + } + else + { + sprintf (store, "%.0f", id->loopStore.soilTemp2); + } + break; + case 83: + if (id->isMetricUnits) + { + sprintf (store, "%.1f", + wvutilsConvertFToC(id->loopStore.soilTemp3)); + } + else + { + sprintf (store, "%.0f", id->loopStore.soilTemp3); + } + break; + case 84: + if (id->isMetricUnits) + { + sprintf (store, "%.1f", + wvutilsConvertFToC(id->loopStore.soilTemp4)); + } + else + { + sprintf (store, "%.0f", id->loopStore.soilTemp4); + } + break; + case 85: + if (id->isMetricUnits) + { + sprintf (store, "%.1f", + wvutilsConvertFToC(id->loopStore.leafTemp1)); + } + else + { + sprintf (store, "%.0f", id->loopStore.leafTemp1); + } + break; + case 86: + if (id->isMetricUnits) + { + sprintf (store, "%.1f", + wvutilsConvertFToC(id->loopStore.leafTemp2)); + } + else + { + sprintf (store, "%.0f", id->loopStore.leafTemp2); + } + break; + case 87: + sprintf (store, "%d", (int)id->loopStore.extraHumidity[0]); + break; + case 88: + sprintf (store, "%d", (int)id->loopStore.extraHumidity[1]); + break; + case 89: + if (id->loopStore.forecastRule <= HTML_MAX_FCAST_RULE && + id->ForecastRuleText[id->loopStore.forecastRule] != NULL) + wvstrncpy (store, + id->ForecastRuleText[id->loopStore.forecastRule], + HTML_MAX_LINE_LENGTH); + else + store[0] = 0; + break; + case 90: + // copy html for icon image + tempInt = id->loopStore.forecastIcon; + if (tempInt > 0 && + tempInt <= VP_FCAST_ICON_MAX && + id->ForecastIconFile[tempInt] != NULL) + { + sprintf (store, "", id->ForecastIconFile[tempInt]); + } + else + store[0] = 0; + break; + case 91: + if (id->isMetricUnits) + sprintf (store, "%d m", (int)wvutilsConvertFeetToMeters((int)id->stationElevation)); + else + sprintf (store, "%d ft", (int)id->stationElevation); + break; + case 92: + sprintf (store, "%3.1f %c", + ((float)abs((int)id->stationLatitude))/10.0, + ((id->stationLatitude < 0) ? 'S' : 'N')); + break; + case 93: + sprintf (store, "%3.1f %c", + ((float)abs((int)id->stationLongitude))/10.0, + ((id->stationLongitude < 0) ? 'W' : 'E')); + break; + case 94: + if (id->isMetricUnits) + { + sprintf (store, "%.1f", + wvutilsConvertFToC(id->loopStore.inTemp)); + } + else + { + sprintf (store, "%.1f", id->loopStore.inTemp); + } + break; + case 95: + sprintf (store, "%d", id->loopStore.inHumidity); + break; + case 96: + if (id->isMetricUnits) + { + sprintf (store, "%.2f", + wvutilsConvertRainINToMetric(sensorGetCumulative(&sensors->sensor[STF_HOUR][SENSOR_RAIN]))); + } + else + { + sprintf (store, "%.2f", sensorGetCumulative(&sensors->sensor[STF_HOUR][SENSOR_RAIN])); + } + break; + case 97: + if (id->isMetricUnits) + { + sprintf (store, "%.1f km", + wvutilsConvertMilesToKilometers(sensorGetWindRun(STF_HOUR, &sensors->sensor[STF_HOUR][SENSOR_WSPEED]))); + } + else + { + sprintf (store, "%.1f miles", sensorGetWindRun(STF_HOUR, &sensors->sensor[STF_HOUR][SENSOR_WSPEED])); + } + break; + case 98: + if (id->isMetricUnits) + { + sprintf (store, "%.1f", + wvutilsConvertFToC(sensorGetAvg(&sensors->sensor[STF_HOUR][SENSOR_OUTTEMP]))); + } + else + { + sprintf (store, "%.1f", sensorGetAvg(&sensors->sensor[STF_HOUR][SENSOR_OUTTEMP])); + } + break; + case 99: + sprintf (store, + "%.1f", + wvutilsGetWindSpeed(sensorGetAvg(&sensors->sensor[STF_HOUR][SENSOR_WSPEED])) + ); + break; + case 100: + sprintf (store, "%d", windAverageCompute(&sensors->wind[STF_HOUR])); + break; + case 101: + sprintf (store, "%.0f", sensorGetAvg(&sensors->sensor[STF_HOUR][SENSOR_OUTHUMID])); + break; + case 102: + if (id->isMetricUnits) + { + sprintf (store, "%.1f", + wvutilsConvertFToC(sensorGetAvg(&sensors->sensor[STF_HOUR][SENSOR_DEWPOINT]))); + } + else + { + sprintf (store, "%.1f", sensorGetAvg(&sensors->sensor[STF_HOUR][SENSOR_DEWPOINT])); + } + break; + case 103: + if (id->isMetricUnits) + { + sprintf (store, "%.1f", + wvutilsConvertINHGToHPA(sensorGetAvg(&sensors->sensor[STF_HOUR][SENSOR_BP]))); + } + else + { + sprintf (store, "%.3f", + sensorGetAvg(&sensors->sensor[STF_HOUR][SENSOR_BP])); + } + break; + case 104: + if (id->isMetricUnits) + { + sprintf (store, "%.1f", + wvutilsConvertDeltaFToC(id->hilowStore.hourchangetemp)); + } + else + { + sprintf (store, "%.1f", id->hilowStore.hourchangetemp); + } + break; + case 105: + sprintf (store, + "%.1f", + wvutilsGetWindSpeed(id->hilowStore.hourchangewind)); + break; + case 106: + sprintf (store, "%d", id->hilowStore.hourchangewinddir); + break; + case 107: + sprintf (store, "%d", id->hilowStore.hourchangehumid); + break; + case 108: + if (id->isMetricUnits) + { + sprintf (store, "%.1f", + wvutilsConvertDeltaFToC(id->hilowStore.hourchangedewpt)); + } + else + { + sprintf (store, "%.1f", id->hilowStore.hourchangedewpt); + } + break; + case 109: + if (id->isMetricUnits) + { + sprintf (store, "%.1f", + wvutilsConvertINHGToHPA(id->hilowStore.hourchangebarom)); + } + else + { + sprintf (store, "%.3f", + id->hilowStore.hourchangebarom); + } + break; + case 110: + if (id->isMetricUnits) + { + sprintf (store, "%.1f km", + wvutilsConvertMilesToKilometers(sensorGetWindRun(STF_DAY, &sensors->sensor[STF_DAY][SENSOR_WSPEED]))); + } + else + { + sprintf (store, "%.1f miles", sensorGetWindRun(STF_DAY, &sensors->sensor[STF_DAY][SENSOR_WSPEED])); + } + break; + case 111: + if (id->isMetricUnits) + { + sprintf (store, "%.1f", + wvutilsConvertFToC(sensorGetAvg(&sensors->sensor[STF_DAY][SENSOR_OUTTEMP]))); + } + else + { + sprintf (store, "%.1f", sensorGetAvg(&sensors->sensor[STF_DAY][SENSOR_OUTTEMP])); + } + break; + case 112: + sprintf (store, + "%.1f", + wvutilsGetWindSpeed(sensorGetAvg(&sensors->sensor[STF_DAY][SENSOR_WSPEED]))); + break; + case 113: + sprintf (store, "%d", windAverageCompute(&sensors->wind[STF_DAY])); + break; + case 114: + sprintf (store, "%.0f", sensorGetAvg(&sensors->sensor[STF_DAY][SENSOR_OUTHUMID])); + break; + case 115: + if (id->isMetricUnits) + { + sprintf (store, "%.1f", + wvutilsConvertFToC(sensorGetAvg(&sensors->sensor[STF_DAY][SENSOR_DEWPOINT]))); + } + else + { + sprintf (store, "%.1f", sensorGetAvg(&sensors->sensor[STF_DAY][SENSOR_DEWPOINT])); + } + break; + case 116: + if (id->isMetricUnits) + { + sprintf (store, "%.1f", + wvutilsConvertINHGToHPA(sensorGetAvg(&sensors->sensor[STF_DAY][SENSOR_BP]))); + } + else + { + sprintf (store, "%.3f", + sensorGetAvg(&sensors->sensor[STF_DAY][SENSOR_BP])); + } + break; + case 117: + if (id->isMetricUnits) + { + sprintf (store, "%.1f", + wvutilsConvertDeltaFToC(id->hilowStore.daychangetemp)); + } + else + { + sprintf (store, "%.1f", id->hilowStore.daychangetemp); + } + break; + case 118: + sprintf (store, + "%.1f", + wvutilsGetWindSpeed(id->hilowStore.daychangewind)); + break; + case 119: + sprintf (store, "%d", id->hilowStore.daychangewinddir); + break; + case 120: + sprintf (store, "%d", id->hilowStore.daychangehumid); + break; + case 121: + if (id->isMetricUnits) + { + sprintf (store, "%.1f", + wvutilsConvertDeltaFToC(id->hilowStore.daychangedewpt)); + } + else + { + sprintf (store, "%.1f", id->hilowStore.daychangedewpt); + } + break; + case 122: + if (id->isMetricUnits) + { + sprintf (store, "%.1f", + wvutilsConvertINHGToHPA(id->hilowStore.daychangebarom)); + } + else + { + sprintf (store, "%.3f", + id->hilowStore.daychangebarom); + } + break; + case 123: + if (id->isMetricUnits) + { + sprintf (store, "%.1f km", + wvutilsConvertMilesToKilometers(sensorGetWindRun(STF_WEEK, &sensors->sensor[STF_WEEK][SENSOR_WSPEED]))); + } + else + { + sprintf (store, "%.1f miles", sensorGetWindRun(STF_WEEK, &sensors->sensor[STF_WEEK][SENSOR_WSPEED])); + } + break; + case 124: + if (id->isMetricUnits) + { + sprintf (store, "%.1f", + wvutilsConvertFToC(sensorGetAvg(&sensors->sensor[STF_WEEK][SENSOR_OUTTEMP]))); + } + else + { + sprintf (store, "%.1f", sensorGetAvg(&sensors->sensor[STF_WEEK][SENSOR_OUTTEMP])); + } + break; + case 125: + sprintf (store, + "%.1f", + wvutilsGetWindSpeed(sensorGetAvg(&sensors->sensor[STF_WEEK][SENSOR_WSPEED]))); + break; + case 126: + sprintf (store, "%d", windAverageCompute(&sensors->wind[STF_WEEK])); + break; + case 127: + sprintf (store, "%.0f", sensorGetAvg(&sensors->sensor[STF_WEEK][SENSOR_OUTHUMID])); + break; + case 128: + if (id->isMetricUnits) + { + sprintf (store, "%.1f", + wvutilsConvertFToC(sensorGetAvg(&sensors->sensor[STF_WEEK][SENSOR_DEWPOINT]))); + } + else + { + sprintf (store, "%.1f", sensorGetAvg(&sensors->sensor[STF_WEEK][SENSOR_DEWPOINT])); + } + break; + case 129: + if (id->isMetricUnits) + { + sprintf (store, "%.1f", + wvutilsConvertINHGToHPA(sensorGetAvg(&sensors->sensor[STF_WEEK][SENSOR_BP]))); + } + else + { + sprintf (store, "%.3f", + sensorGetAvg(&sensors->sensor[STF_WEEK][SENSOR_BP])); + } + break; + case 130: + if (id->isMetricUnits) + { + sprintf (store, "%.1f", + wvutilsConvertDeltaFToC(id->hilowStore.weekchangetemp)); + } + else + { + sprintf (store, "%.1f", id->hilowStore.weekchangetemp); + } + break; + case 131: + sprintf (store, + "%.1f", + wvutilsGetWindSpeed(id->hilowStore.weekchangewind)); + break; + case 132: + sprintf (store, "%d", id->hilowStore.weekchangewinddir); + break; + case 133: + sprintf (store, "%d", id->hilowStore.weekchangehumid); + break; + case 134: + if (id->isMetricUnits) + { + sprintf (store, "%.1f", + wvutilsConvertDeltaFToC(id->hilowStore.weekchangedewpt)); + } + else + { + sprintf (store, "%.1f", id->hilowStore.weekchangedewpt); + } + break; + case 135: + if (id->isMetricUnits) + { + sprintf (store, "%.1f", + wvutilsConvertINHGToHPA(id->hilowStore.weekchangebarom)); + } + else + { + sprintf (store, "%.3f", + id->hilowStore.weekchangebarom); + } + break; + case 136: + if (id->isMetricUnits) + { + sprintf (store, "%.1f km", + wvutilsConvertMilesToKilometers(sensorGetWindRun(STF_MONTH, &sensors->sensor[STF_MONTH][SENSOR_WSPEED]))); + } + else + { + sprintf (store, "%.1f miles", sensorGetWindRun(STF_MONTH, &sensors->sensor[STF_MONTH][SENSOR_WSPEED])); + } + break; + case 137: + if (id->isMetricUnits) + { + sprintf (store, "%.1f", + wvutilsConvertFToC(sensorGetAvg(&sensors->sensor[STF_MONTH][SENSOR_OUTTEMP]))); + } + else + { + sprintf (store, "%.1f", sensorGetAvg(&sensors->sensor[STF_MONTH][SENSOR_OUTTEMP])); + } + break; + case 138: + sprintf (store, + "%.1f", + wvutilsGetWindSpeed(sensorGetAvg(&sensors->sensor[STF_MONTH][SENSOR_WSPEED]))); + break; + case 139: + sprintf (store, "%d", windAverageCompute(&sensors->wind[STF_MONTH])); + break; + case 140: + sprintf (store, "%.0f", sensorGetAvg(&sensors->sensor[STF_MONTH][SENSOR_OUTHUMID])); + break; + case 141: + if (id->isMetricUnits) + { + sprintf (store, "%.1f", + wvutilsConvertFToC(sensorGetAvg(&sensors->sensor[STF_MONTH][SENSOR_DEWPOINT]))); + } + else + { + sprintf (store, "%.1f", sensorGetAvg(&sensors->sensor[STF_MONTH][SENSOR_DEWPOINT])); + } + break; + case 142: + if (id->isMetricUnits) + { + sprintf (store, "%.1f", + wvutilsConvertINHGToHPA(sensorGetAvg(&sensors->sensor[STF_MONTH][SENSOR_BP]))); + } + else + { + sprintf (store, "%.3f", + sensorGetAvg(&sensors->sensor[STF_MONTH][SENSOR_BP])); + } + break; + case 143: + strcpy (store, sensorGetHighDate(&sensors->sensor[STF_MONTH][SENSOR_OUTTEMP], + temp, id->dateFormat)); + break; + case 144: + strcpy (store, sensorGetLowDate(&sensors->sensor[STF_MONTH][SENSOR_OUTTEMP], + temp, id->dateFormat)); + break; + case 145: + strcpy (store, sensorGetLowDate(&sensors->sensor[STF_MONTH][SENSOR_WCHILL], + temp, id->dateFormat)); + break; + case 146: + strcpy (store, sensorGetHighDate(&sensors->sensor[STF_MONTH][SENSOR_HINDEX], + temp, id->dateFormat)); + break; + case 147: + strcpy (store, sensorGetHighDate(&sensors->sensor[STF_MONTH][SENSOR_OUTHUMID], + temp, id->dateFormat)); + break; + case 148: + strcpy (store, sensorGetLowDate(&sensors->sensor[STF_MONTH][SENSOR_OUTHUMID], + temp, id->dateFormat)); + break; + case 149: + strcpy (store, sensorGetHighDate(&sensors->sensor[STF_MONTH][SENSOR_DEWPOINT], + temp, id->dateFormat)); + break; + case 150: + strcpy (store, sensorGetLowDate(&sensors->sensor[STF_MONTH][SENSOR_DEWPOINT], + temp, id->dateFormat)); + break; + case 151: + strcpy (store, sensorGetHighDate(&sensors->sensor[STF_MONTH][SENSOR_BP], + temp, id->dateFormat)); + break; + case 152: + strcpy (store, sensorGetLowDate(&sensors->sensor[STF_MONTH][SENSOR_BP], + temp, id->dateFormat)); + break; + case 153: + strcpy (store, sensorGetHighDate(&sensors->sensor[STF_MONTH][SENSOR_WSPEED], + temp, id->dateFormat)); + break; + case 154: + strcpy (store, sensorGetHighDate(&sensors->sensor[STF_MONTH][SENSOR_WGUST], + temp, id->dateFormat)); + break; + case 155: + strcpy (store, sensorGetHighDate(&sensors->sensor[STF_MONTH][SENSOR_RAINRATE], + temp, id->dateFormat)); + break; + case 156: + if (id->isMetricUnits) + { + sprintf (store, "%.1f km", + wvutilsConvertMilesToKilometers(sensorGetWindRun(STF_YEAR, &sensors->sensor[STF_YEAR][SENSOR_WSPEED]))); + } + else + { + sprintf (store, "%.1f miles", sensorGetWindRun(STF_YEAR, &sensors->sensor[STF_YEAR][SENSOR_WSPEED])); + } + break; + case 157: + if (id->isMetricUnits) + { + sprintf (store, "%.1f", + wvutilsConvertFToC(sensorGetAvg(&sensors->sensor[STF_YEAR][SENSOR_OUTTEMP]))); + } + else + { + sprintf (store, "%.1f", sensorGetAvg(&sensors->sensor[STF_YEAR][SENSOR_OUTTEMP])); + } + break; + case 158: + sprintf (store, + "%.1f", + wvutilsGetWindSpeed(sensorGetAvg(&sensors->sensor[STF_YEAR][SENSOR_WSPEED]))); + break; + case 159: + sprintf (store, "%d", windAverageCompute(&sensors->wind[STF_YEAR])); + break; + case 160: + sprintf (store, "%.0f", sensorGetAvg(&sensors->sensor[STF_YEAR][SENSOR_OUTHUMID])); + break; + case 161: + if (id->isMetricUnits) + { + sprintf (store, "%.1f", + wvutilsConvertFToC(sensorGetAvg(&sensors->sensor[STF_YEAR][SENSOR_DEWPOINT]))); + } + else + { + sprintf (store, "%.1f", sensorGetAvg(&sensors->sensor[STF_YEAR][SENSOR_DEWPOINT])); + } + break; + case 162: + if (id->isMetricUnits) + { + sprintf (store, "%.1f", + wvutilsConvertINHGToHPA(sensorGetAvg(&sensors->sensor[STF_YEAR][SENSOR_BP]))); + } + else + { + sprintf (store, "%.3f", + sensorGetAvg(&sensors->sensor[STF_YEAR][SENSOR_BP])); + } + break; + case 163: + strcpy (store, sensorGetHighDate(&sensors->sensor[STF_YEAR][SENSOR_OUTTEMP], + temp, id->dateFormat)); + break; + case 164: + strcpy (store, sensorGetLowDate(&sensors->sensor[STF_YEAR][SENSOR_OUTTEMP], + temp, id->dateFormat)); + break; + case 165: + strcpy (store, sensorGetLowDate(&sensors->sensor[STF_YEAR][SENSOR_WCHILL], + temp, id->dateFormat)); + break; + case 166: + strcpy (store, sensorGetHighDate(&sensors->sensor[STF_YEAR][SENSOR_HINDEX], + temp, id->dateFormat)); + break; + case 167: + strcpy (store, sensorGetHighDate(&sensors->sensor[STF_YEAR][SENSOR_OUTHUMID], + temp, id->dateFormat)); + break; + case 168: + strcpy (store, sensorGetLowDate(&sensors->sensor[STF_YEAR][SENSOR_OUTHUMID], + temp, id->dateFormat)); + break; + case 169: + strcpy (store, sensorGetHighDate(&sensors->sensor[STF_YEAR][SENSOR_DEWPOINT], + temp, id->dateFormat)); + break; + case 170: + strcpy (store, sensorGetLowDate(&sensors->sensor[STF_YEAR][SENSOR_DEWPOINT], + temp, id->dateFormat)); + break; + case 171: + strcpy (store, sensorGetHighDate(&sensors->sensor[STF_YEAR][SENSOR_BP], + temp, id->dateFormat)); + break; + case 172: + strcpy (store, sensorGetLowDate(&sensors->sensor[STF_YEAR][SENSOR_BP], + temp, id->dateFormat)); + break; + case 173: + strcpy (store, sensorGetHighDate(&sensors->sensor[STF_YEAR][SENSOR_WSPEED], + temp, id->dateFormat)); + break; + case 174: + strcpy (store, sensorGetHighDate(&sensors->sensor[STF_YEAR][SENSOR_WGUST], + temp, id->dateFormat)); + break; + case 175: + strcpy (store, sensorGetHighDate(&sensors->sensor[STF_YEAR][SENSOR_RAINRATE], + temp, id->dateFormat)); + break; + case 176: + sprintf (store, "%d", id->loopStore.windDir); + break; + case 181: + sprintf (store, "%.0f", sensorGetDailyHigh(sensors->sensor, SENSOR_SOLRAD)); + break; + case 182: + strcpy (store, sensorGetDailyHighTime(sensors->sensor, SENSOR_SOLRAD, temp)); + break; + case 183: + sprintf (store, "%.0f", sensorGetHigh(&sensors->sensor[STF_MONTH][SENSOR_SOLRAD])); + break; + case 184: + sprintf (store, "%.0f", sensorGetHigh(&sensors->sensor[STF_YEAR][SENSOR_SOLRAD])); + break; + case 185: + sprintf (store, "%.1f", sensorGetDailyHigh(sensors->sensor, SENSOR_UV)); + break; + case 186: + strcpy (store, sensorGetDailyHighTime(sensors->sensor, SENSOR_UV, temp)); + break; + case 187: + sprintf (store, "%.1f", sensorGetHigh(&sensors->sensor[STF_MONTH][SENSOR_UV])); + break; + case 188: + sprintf (store, "%.1f", sensorGetHigh(&sensors->sensor[STF_YEAR][SENSOR_UV])); + break; + case 189: + sprintf (store, "%s", lunarPhaseGet (id->mphaseIncrease, + id->mphaseDecrease, + id->mphaseFull)); + break; + case 190: + sprintf (store, " kg/m^3"); + break; + case 191: + sprintf (store, "%.3f", + wvutilsCalculateAirDensity (id->loopStore.outTemp, + id->loopStore.barometer, + id->loopStore.dewpoint)); + break; + case 192: + if (id->isMetricUnits) + strcpy (store, " m"); + else + strcpy (store, " ft"); + break; + case 193: + tempfloat = id->loopStore.outTemp; + tempfloat -= id->loopStore.dewpoint; + tempfloat *= 228; + if (id->isMetricUnits) + { + tempfloat = wvutilsConvertFeetToMeters (tempfloat); + } + sprintf (store, "%.0f", tempfloat); + break; + case 194: + sprintf (store, "%.0f", (float)id->loopStore.soilMoist1); + break; + case 195: + sprintf (store, "%.0f", (float)id->loopStore.soilMoist2); + break; + case 196: + sprintf (store, "%.0f", (float)id->loopStore.leafWet1); + break; + case 197: + sprintf (store, "%.0f", (float)id->loopStore.leafWet2); + break; + case 198: + // day high wind direction + tempfloat = sensorGetDailyWhenHigh(sensors->sensor, SENSOR_WGUST) + 11.24; + tempfloat /= 22.5; + tempInt = (int)tempfloat; + tempInt %= 16; + sprintf (store, "%s", buildWindDirString(tempInt)); + break; + case 199: + // baromtrend + sprintf (store, "%c", BPTrendLabels[id->baromTrendIndicator]); + break; + case 200: + // dailyRainMM + sprintf (store, "%.1f", + wvutilsConvertINToMM(sensorGetCumulative(&id->hilowStore.sensor[STF_DAY][SENSOR_RAIN]))); + break; + case 201: + // stationDateMetric + ntime = time (NULL); + localtime_r (&ntime, &loctime); + sprintf (store, "%4.4d%2.2d%2.2d", + loctime.tm_year+1900, + loctime.tm_mon+1, + loctime.tm_mday); + break; + case 202: + // middayTime + strcpy (store, buildTimeTag (id->midday)); + break; + case 203: + // dayLength + strcpy (store, buildTimeTag (id->dayLength)); + break; + case 204: + // civilriseTime + strcpy (store, buildTimeTag (id->civilrise)); + break; + case 205: + // civilsetTime + strcpy (store, buildTimeTag (id->civilset)); + break; + case 206: + // astroriseTime + strcpy (store, buildTimeTag (id->astrorise)); + break; + case 207: + // astrosetTime + strcpy (store, buildTimeTag (id->astroset)); + break; + case 208: + // stormStart + if (id->loopStore.stormStart == (time_t)0) + { + strcpy (store, "-------- -----"); + break; + } + else + { + time_t Time = (time_t)id->loopStore.stormStart; + localtime_r (&Time, &loctime); + strftime(store, 64, id->dateFormat, &loctime); + tempInt = strlen(store); + snprintf(&store[tempInt], 64, " %2.2d:%2.2d", + loctime.tm_hour, loctime.tm_min); + break; + } + case 209: + // forecastIconFile + tempInt = id->loopStore.forecastIcon; + if (tempInt > 0 && + tempInt <= VP_FCAST_ICON_MAX && + id->ForecastIconFile[tempInt] != NULL) + { + sprintf (store, "%s", id->ForecastIconFile[tempInt]); + } + else + store[0] = 0; + break; + case 210: + // rainSeasonStart (month) + sprintf (store, "%s", monthLabel[id->loopStore.yearRainMonth]); + break; + case 211: + // intervalAvgWindChill + if (id->isMetricUnits) + { + sprintf (store, "%.1f", + wvutilsConvertFToC(id->loopStore.intervalAvgWCHILL)); + } + else + { + sprintf (store, "%.1f", id->loopStore.intervalAvgWCHILL); + } + break; + case 212: + // intervalAvgWindSpeed + sprintf (store, + "%.1f", + wvutilsGetWindSpeed(id->loopStore.intervalAvgWSPEEDF)); + break; + case 213: + // stationPressure + if (id->isMetricUnits) + { + sprintf (store, "%.1f", + wvutilsConvertINHGToHPA(id->loopStore.stationPressure)); + } + else + { + sprintf (store, "%.3f", + id->loopStore.stationPressure); + } + break; + case 214: + // altimeter + if (id->isMetricUnits) + { + sprintf (store, "%.1f", + wvutilsConvertINHGToHPA(id->loopStore.altimeter)); + } + else + { + sprintf (store, "%.3f", + id->loopStore.altimeter); + } + break; + + case 215: + // localRadarURL + wvstrncpy (store, id->radarURL, HTML_MAX_LINE_LENGTH); + break; + + case 216: + // localForecastURL + wvstrncpy (store, id->forecastURL, HTML_MAX_LINE_LENGTH); + break; + + case 217: + // windGustSpeed - "Current" wind gust speed + tempfloat = sensorGetHigh(&sensors->sensor[STF_INTERVAL][SENSOR_WGUST]); + if (tempfloat <= 0) + { + tempfloat = 0; + } + sprintf (store, + "%.1f", + wvutilsGetWindSpeed(tempfloat)); + break; + + case 218: + // windGustDirectionDegrees - "Current" wind gust direction + sprintf (store, "%.0f", sensorGetWhenHigh(&sensors->sensor[STF_INTERVAL][SENSOR_WGUST])); + break; + + case 219: + // windBeaufortScale + sprintf (store, "%s", wvutilsConvertToBeaufortScale((int)id->loopStore.windSpeedF)); + break; + + case 220: + // intervalAvgBeaufortScale + sprintf (store, "%s", wvutilsConvertToBeaufortScale((int)id->loopStore.intervalAvgWSPEEDF)); + break; + + case 221: + // station type + strcpy (store, id->stationType); + break; + + case 222: + // "" + if (id->isMetricUnits) + { + sprintf (store, "%.3f", + wvutilsConvertRainINToMetric((float)id->loopStore.wxt510Hail)); + } + else + { + sprintf (store, "%.2f",id->loopStore.wxt510Hail); + } + break; + + case 223: + // "" + if (id->isMetricUnits) + { + sprintf (store, "%.3f", + wvutilsConvertRainINToMetric((float)id->loopStore.wxt510Hailrate)); + } + else + { + sprintf (store, "%.2f",id->loopStore.wxt510Hailrate); + } + break; + + case 224: + // "" + if (id->isMetricUnits) + { + tempfloat = (float)(id->loopStore.wxt510HeatingTemp); + if (tempfloat != 0) + { + sprintf (store, "%.1f", + wvutilsConvertFToC(tempfloat)); + } + else + { + sprintf (store, "%.1f", 0.0); + } + } + else + { + sprintf (store, "%.1f", id->loopStore.wxt510HeatingTemp); + } + break; + + case 225: + // "" + sprintf (store, "%.1f",id->loopStore.wxt510HeatingVoltage); + break; + + case 226: + // "" + sprintf (store, "%.1f",id->loopStore.wxt510SupplyVoltage); + break; + + case 227: + // "" + sprintf (store, "%.3f",id->loopStore.wxt510ReferenceVoltage); + break; + + case 228: + // """ + wvstrncpy (store, + makeduration(id->loopStore.wxt510RainDuration), + HTML_MAX_LINE_LENGTH); + break; + + case 229: + // """ + if (id->isMetricUnits) + { + sprintf (store, "%.3f", + wvutilsConvertRainINToMetric((float)id->loopStore.wxt510RainPeakRate)); + } + else + { + sprintf (store, "%.2f",id->loopStore.wxt510RainPeakRate); + } + break; + + case 230: + // "" + wvstrncpy (store, + makeduration(id->loopStore.wxt510HailDuration), + HTML_MAX_LINE_LENGTH); + break; + + case 231: + // "" + if (id->isMetricUnits) + { + sprintf (store, "%.3f", + wvutilsConvertRainINToMetric((float)id->loopStore.wxt510HailPeakRate)); + } + else + { + sprintf (store, "%.2f",id->loopStore.wxt510HailPeakRate); + } + break; + + case 232: + // "" + if (id->isMetricUnits) + { + sprintf (store, "%.3f", + wvutilsConvertRainINToMetric((float)id->loopStore.wxt510Rain)); + } + else + { + sprintf (store, "%.3f",id->loopStore.wxt510Rain); + } + break; + + case 233: + // "", uint16_t rxCheckPercent; 0 - 100 + sprintf (store, "%.1f", (float)id->loopStore.rxCheckPercent); + break; + + case 234: + // "", uint16_t tenMinuteAvgWindSpeed; mph + if (id->isMetricUnits) + { + sprintf (store, "%.1f", + wvutilsConvertMPHToKPH((float)id->loopStore.tenMinuteAvgWindSpeed)); + } + else + { + sprintf (store, "%d", id->loopStore.tenMinuteAvgWindSpeed); + } + break; + + case 237: + // "", uint16_t txBatteryStatus; VP only + sprintf (store, "%2.2x", id->loopStore.txBatteryStatus); + break; + + case 238: + // "", uint16_t consBatteryVoltage; VP only + tempfloat = (((float)id->loopStore.consBatteryVoltage * 300)/512)/100; + sprintf (store, "%.2f", tempfloat); + break; + + case 239: + ntime = time (NULL); + // "", + localtime_r (&ntime, &loctime); + sprintf (store, "%2.2d:%2.2d", + loctime.tm_hour, + loctime.tm_min); + break; + + case 240: + // "", + tempInt = ((id->loopStore.wxt510RainDuration)/60); + sprintf (store, "%d", tempInt); + break; + + case 241: + // "", + tempInt = ((id->loopStore.wxt510HailDuration)/60); + sprintf (store, "%d", tempInt); + break; + + + case 242: // case 222: + // wmr918WindBatteryStatus + sprintf (store, "%d", (int)id->loopStore.wmr918WindBatteryStatus); + break; + + case 243: + // wmr918RainBatteryStatus + sprintf (store, "%d", (int)id->loopStore.wmr918RainBatteryStatus); + break; + + case 244: + // wmr918OutTempBatteryStatus + sprintf (store, "%d", (int)id->loopStore.wmr918OutTempBatteryStatus); + break; + + case 245: + // wmr918InTempBatteryStatus + sprintf (store, "%d", (int)id->loopStore.wmr918InTempBatteryStatus); + break; + + // ###### Begin EXTRA Wind ####################### + case 246: + // "" + tempfloat = wvutilsConvertMPHToMPS (id->loopStore.windSpeedF); + sprintf (store, "%.1f", tempfloat); + break; + + case 247: + // "" + tempfloat = (float)sensorGetHigh(&sensors->sensor[STF_INTERVAL][SENSOR_WGUST]); + if (tempfloat < 0) + { + tempfloat = id->loopStore.windSpeedF; + } + tempfloat = wvutilsConvertMPHToMPS (tempfloat); + sprintf (store, "%.1f", tempfloat); + break; + + case 248: + // + tempfloat = wvutilsConvertMPHToMPS (id->loopStore.intervalAvgWSPEEDF); + sprintf (store, "%.1f", tempfloat); + break; + + case 249: + // "" + tempfloat = wvutilsConvertMPHToMPS ((float)id->loopStore.tenMinuteAvgWindSpeed); + sprintf (store, "%.1f", tempfloat); + break; + + case 250: + // + tempfloat = wvutilsConvertMPHToMPS ((float)sensorGetDailyHigh(sensors->sensor, SENSOR_WGUST)); + sprintf (store, "%.1f", tempfloat); + break; + + case 251: + // + tempfloat = wvutilsConvertMPHToMPS ((float)sensorGetHigh(&sensors->sensor[STF_MONTH][SENSOR_WGUST])); + sprintf (store, "%.1f", tempfloat); + break; + + case 252: + // + tempfloat = wvutilsConvertMPHToMPS ((float)sensorGetHigh(&sensors->sensor[STF_YEAR][SENSOR_WGUST])); + sprintf (store, "%.1f", tempfloat); + break; + + case 253: + // + tempfloat = wvutilsConvertMPHToMPS ((float)sensorGetAvg(&sensors->sensor[STF_HOUR][SENSOR_WSPEED])); + sprintf (store, "%.1f", tempfloat); + break; + + case 254: + // + tempfloat = wvutilsConvertMPHToMPS ((float)sensorGetAvg(&sensors->sensor[STF_DAY][SENSOR_WSPEED])); + sprintf (store, "%.1f", tempfloat); + break; + + case 255: + // + tempfloat = wvutilsConvertMPHToMPS ((float)sensorGetAvg(&sensors->sensor[STF_WEEK][SENSOR_WSPEED])); + sprintf (store, "%.1f", tempfloat); + break; + + case 256: + // + tempfloat = wvutilsConvertMPHToMPS ((float)sensorGetAvg(&sensors->sensor[STF_MONTH][SENSOR_WSPEED])); + sprintf (store, "%.1f", tempfloat); + break; + + case 257: + // + tempfloat = wvutilsConvertMPHToMPS ((float)sensorGetAvg(&sensors->sensor[STF_YEAR][SENSOR_WSPEED])); + sprintf (store, "%.1f", tempfloat); + break; + + case 258: + // + tempfloat = wvutilsConvertMPHToMPS ((float)id->hilowStore.hourchangewind); + sprintf (store, "%.1f", tempfloat); + break; + + case 259: + // + tempfloat = wvutilsConvertMPHToMPS ((float)id->hilowStore.daychangewind); + sprintf (store, "%.1f", tempfloat); + break; + + case 260: + // + tempfloat = wvutilsConvertMPHToMPS ((float)id->hilowStore.weekchangewind * 0.447027); + sprintf (store, "%.1f", tempfloat); + break; + + case 261: + // "", + tempfloat = wvutilsConvertMPHToKnots (id->loopStore.windSpeedF); + sprintf (store, "%.1f", tempfloat); + break; + + case 262: + // "", + tempfloat = sensorGetHigh(&sensors->sensor[STF_INTERVAL][SENSOR_WGUST]); + if (tempfloat < 0) + { + tempfloat = (id->loopStore.windSpeedF); + } + tempfloat = wvutilsConvertMPHToKnots (tempfloat); + sprintf (store, "%.1f", tempfloat); + break; + + case 263: + // + tempfloat = wvutilsConvertMPHToKnots (id->loopStore.intervalAvgWSPEEDF); + sprintf (store, "%.1f", tempfloat); + break; + + case 264: + // "" + tempfloat = wvutilsConvertMPHToKnots ((float)(id->loopStore.tenMinuteAvgWindSpeed)); + sprintf (store, "%.1f", tempfloat); + break; + + case 265: + // + tempfloat = wvutilsConvertMPHToKnots ((float)sensorGetDailyHigh(sensors->sensor, SENSOR_WGUST)); + sprintf (store, "%.1f", tempfloat); + break; + + case 266: + // + tempfloat = wvutilsConvertMPHToKnots ((float)sensorGetHigh(&sensors->sensor[STF_MONTH][SENSOR_WGUST])); + sprintf (store, "%.1f", tempfloat); + break; + + case 267: + // + tempfloat = wvutilsConvertMPHToKnots ((float)sensorGetHigh(&sensors->sensor[STF_YEAR][SENSOR_WGUST])); + sprintf (store, "%.1f", tempfloat); + break; + + case 268: + // + tempfloat = wvutilsConvertMPHToKnots ((float)sensorGetAvg(&sensors->sensor[STF_HOUR][SENSOR_WSPEED])); + sprintf (store, "%.1f", tempfloat); + break; + + case 269: + // + tempfloat = wvutilsConvertMPHToKnots ((float)sensorGetAvg(&sensors->sensor[STF_DAY][SENSOR_WSPEED])); + sprintf (store, "%.1f", tempfloat); + break; + + case 270: + // + tempfloat = wvutilsConvertMPHToKnots ((float)sensorGetAvg(&sensors->sensor[STF_WEEK][SENSOR_WSPEED])); + sprintf (store, "%.1f", tempfloat); + break; + + case 271: + // + tempfloat = wvutilsConvertMPHToKnots ((float)sensorGetAvg(&sensors->sensor[STF_MONTH][SENSOR_WSPEED])); + sprintf (store, "%.1f", tempfloat); + break; + + case 272: + // + tempfloat = wvutilsConvertMPHToKnots ((float)sensorGetAvg(&sensors->sensor[STF_YEAR][SENSOR_WSPEED])); + sprintf (store, "%.1f", tempfloat); + break; + + case 273: + // + tempfloat = wvutilsConvertMPHToKnots ((float)(id->hilowStore.hourchangewind)); + sprintf (store, "%.1f", tempfloat); + break; + + case 274: + // + tempfloat = wvutilsConvertMPHToKnots ((float)(id->hilowStore.daychangewind)); + sprintf (store, "%.1f", tempfloat); + break; + + case 275: + // + tempfloat = wvutilsConvertMPHToKnots ((float)(id->hilowStore.weekchangewind)); + sprintf (store, "%.1f", tempfloat); + break; + + case 276: + // "", + sprintf (store, "%d", (int)id->loopStore.wmr918Humid3); + break; + case 277: + // "", + if (id->isMetricUnits) + { + sprintf (store, "%.1f", wvutilsConvertFToC((float)id->loopStore.wmr918Pool)); + } + else + { + sprintf (store, "%.0f", (float)id->loopStore.wmr918Pool); + } + break; + case 278: + // "", + sprintf (store, "%d", (int)id->loopStore.wmr918poolTempBatteryStatus); + break; + case 279: + // "", + sprintf (store, "%d", (int)id->loopStore.wmr918extra1BatteryStatus); + break; + case 280: + // "", + sprintf (store, "%d", (int)id->loopStore.wmr918extra2BatteryStatus); + break; + case 281: + // "", + sprintf (store, "%d", (int)id->loopStore.wmr918extra3BatteryStatus); + break; + case 282: + // "", + if (id->isMetricUnits) + { + sprintf (store, "%.1f", + wvutilsConvertFToC(sensorGetHigh(&sensors->sensor[STF_ALL][SENSOR_OUTTEMP]))); + } + else + { + sprintf (store, "%.1f", sensorGetHigh(&sensors->sensor[STF_ALL][SENSOR_OUTTEMP])); + } + break; + case 283: + // "", + if (id->isMetricUnits) + { + sprintf (store, "%.1f", + wvutilsConvertFToC(sensorGetLow(&sensors->sensor[STF_ALL][SENSOR_OUTTEMP]))); + } + else + { + sprintf (store, "%.1f", sensorGetLow(&sensors->sensor[STF_ALL][SENSOR_OUTTEMP])); + } + break; + case 284: + // "", + sprintf (store, "%d", (int)sensorGetHigh(&sensors->sensor[STF_ALL][SENSOR_OUTHUMID])); + break; + case 285: + // "", + sprintf (store, "%d", (int)sensorGetLow(&sensors->sensor[STF_ALL][SENSOR_OUTHUMID])); + break; + case 286: + // "", + if (id->isMetricUnits) + { + sprintf (store, "%.1f", + wvutilsConvertFToC(sensorGetHigh(&sensors->sensor[STF_ALL][SENSOR_DEWPOINT]))); + } + else + { + sprintf (store, "%.1f", sensorGetHigh(&sensors->sensor[STF_ALL][SENSOR_DEWPOINT])); + } + break; + case 287: + // "", + if (id->isMetricUnits) + { + sprintf (store, "%.1f", + wvutilsConvertFToC(sensorGetLow(&sensors->sensor[STF_ALL][SENSOR_DEWPOINT]))); + } + else + { + sprintf (store, "%.1f", sensorGetLow(&sensors->sensor[STF_ALL][SENSOR_DEWPOINT])); + } + break; + case 288: + // "", + sprintf (store, + "%.1f", + wvutilsGetWindSpeed(sensorGetHigh(&sensors->sensor[STF_ALL][SENSOR_WGUST]))); + break; + case 289: + // "", + if (id->isMetricUnits) + { + sprintf (store, "%.1f", + wvutilsConvertINHGToHPA(sensorGetHigh(&sensors->sensor[STF_ALL][SENSOR_BP]))); + } + else + { + sprintf (store, "%.3f", + sensorGetHigh(&sensors->sensor[STF_ALL][SENSOR_BP])); + } + break; + case 290: + // "", + if (id->isMetricUnits) + { + sprintf (store, "%.1f", + wvutilsConvertINHGToHPA(sensorGetLow(&sensors->sensor[STF_ALL][SENSOR_BP]))); + } + else + { + sprintf (store, "%.3f", + sensorGetLow(&sensors->sensor[STF_ALL][SENSOR_BP])); + } + break; + case 291: + // "", + if (id->isMetricUnits) + { + sprintf (store, "%.1f", + wvutilsConvertFToC(sensorGetLow(&sensors->sensor[STF_ALL][SENSOR_WCHILL]))); + } + else + { + sprintf (store, "%.1f", sensorGetLow(&sensors->sensor[STF_ALL][SENSOR_WCHILL])); + } + break; + case 292: + // "", + if (id->isMetricUnits) + { + sprintf (store, "%.1f", + wvutilsConvertFToC(sensorGetHigh(&sensors->sensor[STF_ALL][SENSOR_HINDEX]))); + } + else + { + sprintf (store, "%.1f", sensorGetHigh(&sensors->sensor[STF_ALL][SENSOR_HINDEX])); + } + break; + case 293: + // "", + if (id->isMetricUnits) + { + sprintf (store, "%.2f", + wvutilsConvertRainINToMetric(sensorGetHigh(&sensors->sensor[STF_ALL][SENSOR_RAINRATE]))); + } + else + { + sprintf (store, "%.2f", sensorGetHigh(&sensors->sensor[STF_ALL][SENSOR_RAINRATE])); + } + break; + case 294: + // "", + sprintf (store, "%.0f", sensorGetHigh(&sensors->sensor[STF_ALL][SENSOR_SOLRAD])); + break; + case 295: + // "", + sprintf (store, "%.1f", sensorGetHigh(&sensors->sensor[STF_ALL][SENSOR_UV])); + break; + case 296: + // "", + if (id->isMetricUnits) + { + sprintf (store, "%.1f", + wvutilsConvertFToC(sensorGetAvg(&sensors->sensor[STF_ALL][SENSOR_OUTTEMP]))); + } + else + { + sprintf (store, "%.1f", sensorGetAvg(&sensors->sensor[STF_ALL][SENSOR_OUTTEMP])); + } + break; + case 297: + // "", + sprintf (store, + "%.1f", + wvutilsGetWindSpeed(sensorGetAvg(&sensors->sensor[STF_ALL][SENSOR_WSPEED]))); + break; + case 298: + // "", + sprintf (store, "%d", windAverageCompute(&sensors->wind[STF_ALL])); + break; + case 299: + // "", + sprintf (store, "%.0f", sensorGetAvg(&sensors->sensor[STF_ALL][SENSOR_OUTHUMID])); + break; + case 300: + // "", + if (id->isMetricUnits) + { + sprintf (store, "%.1f", + wvutilsConvertFToC(sensorGetAvg(&sensors->sensor[STF_ALL][SENSOR_DEWPOINT]))); + } + else + { + sprintf (store, "%.1f", sensorGetAvg(&sensors->sensor[STF_ALL][SENSOR_DEWPOINT])); + } + break; + case 301: + // "", + if (id->isMetricUnits) + { + sprintf (store, "%.1f", + wvutilsConvertINHGToHPA(sensorGetAvg(&sensors->sensor[STF_ALL][SENSOR_BP]))); + } + else + { + sprintf (store, "%.3f", + sensorGetAvg(&sensors->sensor[STF_ALL][SENSOR_BP])); + } + break; + case 302: + // "", + strcpy (store, sensorGetHighDate(&sensors->sensor[STF_ALL][SENSOR_OUTTEMP], + temp, id->dateFormat)); + break; + case 303: + // "", + strcpy (store, sensorGetLowDate(&sensors->sensor[STF_ALL][SENSOR_OUTTEMP], + temp, id->dateFormat)); + break; + case 304: + // "", + strcpy (store, sensorGetLowDate(&sensors->sensor[STF_ALL][SENSOR_WCHILL], + temp, id->dateFormat)); + break; + case 305: + // "", + strcpy (store, sensorGetHighDate(&sensors->sensor[STF_ALL][SENSOR_HINDEX], + temp, id->dateFormat)); + break; + case 306: + // "", + strcpy (store, sensorGetHighDate(&sensors->sensor[STF_ALL][SENSOR_OUTHUMID], + temp, id->dateFormat)); + break; + case 307: + // "", + strcpy (store, sensorGetLowDate(&sensors->sensor[STF_ALL][SENSOR_OUTHUMID], + temp, id->dateFormat)); + break; + case 308: + // "", + strcpy (store, sensorGetHighDate(&sensors->sensor[STF_ALL][SENSOR_DEWPOINT], + temp, id->dateFormat)); + break; + case 309: + // "", + strcpy (store, sensorGetLowDate(&sensors->sensor[STF_ALL][SENSOR_DEWPOINT], + temp, id->dateFormat)); + break; + case 310: + // "", + strcpy (store, sensorGetHighDate(&sensors->sensor[STF_ALL][SENSOR_BP], + temp, id->dateFormat)); + break; + case 311: + // "", + strcpy (store, sensorGetLowDate(&sensors->sensor[STF_ALL][SENSOR_BP], + temp, id->dateFormat)); + break; + case 312: + // "", + strcpy (store, sensorGetHighDate(&sensors->sensor[STF_ALL][SENSOR_WSPEED], + temp, id->dateFormat)); + break; + case 313: + // "", + strcpy (store, sensorGetHighDate(&sensors->sensor[STF_ALL][SENSOR_WGUST], + temp, id->dateFormat)); + break; + case 314: + // "", + strcpy (store, sensorGetHighDate(&sensors->sensor[STF_ALL][SENSOR_RAINRATE], + temp, id->dateFormat)); + break; + case 315: + // "", + sprintf (store, "%s", buildTendencyString((int)id->loopStore.wmr918Tendency)); + break; + case 316: + // "", + wvstrncpy (store, id->stationName, HTML_MAX_LINE_LENGTH); + break; + case 317: + // "", + if (id->moonrise == -2 && id->moonset == -2) + sprintf (store, "Down all day"); + else if (id->moonrise == -1 && id->moonset == -1) + sprintf (store, "Up all day"); + else if (id->moonrise > 0) + sprintf (store , "%2.2d:%2.2d", id->moonrise/100, id->moonrise%100); + else + sprintf (store, "--:--"); // No Moon Rise + break; + case 318: + // "", + if (id->moonrise == -2 && id->moonset == -2) + sprintf (store, "Down all day"); + else if (id->moonrise == -1 && id->moonset == -1) + sprintf (store, "Up all day"); + else if (id->moonset > 0) + sprintf (store, "%2.2d:%2.2d", id->moonset/100, id->moonset%100); + else + sprintf (store, "--:--"); // No Moon Set + break; + case 319: + // "", + if (id->isMetricUnits) + { + sprintf (store, "%.1f", + wvutilsConvertFToC(wvutilsCalculateApparentTemp(id->loopStore.outTemp, + id->loopStore.windSpeedF, + id->loopStore.outHumidity))); + + } + else + { + sprintf (store, "%.1f", + wvutilsCalculateApparentTemp(id->loopStore.outTemp, id->loopStore.windSpeedF, + id->loopStore.outHumidity)); + } + break; + case 320: + // "", + if (id->isMetricUnits) + { + sprintf (store, "%.1f", wvutilsConvertFToC(id->loopStore.extraTemp[0])); + + } + else + { + sprintf (store, "%.1f", id->loopStore.extraTemp[0]); + } + break; + case 321: + // "", + if (id->isMetricUnits) + { + sprintf (store, "%.1f", wvutilsConvertFToC(id->loopStore.extraTemp[1])); + + } + else + { + sprintf (store, "%.1f", id->loopStore.extraTemp[1]); + } + break; + case 322: + // "", + if (id->isMetricUnits) + { + sprintf (store, "%.1f", wvutilsConvertFToC(id->loopStore.extraTemp[2])); + + } + else + { + sprintf (store, "%.1f", id->loopStore.extraTemp[2]); + } + break; + case 323: + // "", + if (id->isMetricUnits) + { + sprintf (store, "%.1f", wvutilsConvertFToC(id->loopStore.extraTemp[3])); + + } + else + { + sprintf (store, "%.1f", id->loopStore.extraTemp[3]); + } + break; + case 324: + // "", + if (id->isMetricUnits) + { + sprintf (store, "%.1f", wvutilsConvertFToC(id->loopStore.extraTemp[4])); + + } + else + { + sprintf (store, "%.1f", id->loopStore.extraTemp[4]); + } + break; + case 325: + // "", + if (id->isMetricUnits) + { + sprintf (store, "%.1f", wvutilsConvertFToC(id->loopStore.extraTemp[5])); + + } + else + { + sprintf (store, "%.1f", id->loopStore.extraTemp[5]); + } + break; + case 326: + // "", + if (id->isMetricUnits) + { + sprintf (store, "%.1f", wvutilsConvertFToC(id->loopStore.extraTemp[6])); + + } + else + { + sprintf (store, "%.1f", id->loopStore.extraTemp[6]); + } + break; + case 327: + // "", + if (id->isMetricUnits) + { + sprintf (store, "%.1f", wvutilsConvertFToC(id->loopStore.extraTemp[7])); + + } + else + { + sprintf (store, "%.1f", id->loopStore.extraTemp[7]); + } + break; + case 328: + // "", + if (id->isMetricUnits) + { + sprintf (store, "%.1f", wvutilsConvertFToC(id->loopStore.extraTemp[8])); + + } + else + { + sprintf (store, "%.1f", id->loopStore.extraTemp[8]); + } + break; + case 329: + // "", + if (id->isMetricUnits) + { + sprintf (store, "%.1f", wvutilsConvertFToC(id->loopStore.extraTemp[9])); + + } + else + { + sprintf (store, "%.1f", id->loopStore.extraTemp[9]); + } + break; + case 330: + // "", + if (id->isMetricUnits) + { + sprintf (store, "%.1f", wvutilsConvertFToC(id->loopStore.extraTemp[10])); + + } + else + { + sprintf (store, "%.1f", id->loopStore.extraTemp[10]); + } + break; + case 331: + // "", + if (id->isMetricUnits) + { + sprintf (store, "%.1f", wvutilsConvertFToC(id->loopStore.extraTemp[11])); + + } + else + { + sprintf (store, "%.1f", id->loopStore.extraTemp[11]); + } + break; + case 332: + // "", + if (id->isMetricUnits) + { + sprintf (store, "%.1f", wvutilsConvertFToC(id->loopStore.extraTemp[12])); + + } + else + { + sprintf (store, "%.1f", id->loopStore.extraTemp[12]); + } + break; + case 333: + // "", + if (id->isMetricUnits) + { + sprintf (store, "%.1f", wvutilsConvertFToC(id->loopStore.extraTemp[13])); + + } + else + { + sprintf (store, "%.1f", id->loopStore.extraTemp[13]); + } + break; + case 334: + // "", + if (id->isMetricUnits) + { + sprintf (store, "%.1f", wvutilsConvertFToC(id->loopStore.extraTemp[14])); + + } + else + { + sprintf (store, "%.1f", id->loopStore.extraTemp[14]); + } + break; + case 335: + // "", + if (id->isMetricUnits) + { + sprintf (store, "%.1f", wvutilsConvertFToC(id->loopStore.extraTemp[15])); + + } + else + { + sprintf (store, "%.1f", id->loopStore.extraTemp[15]); + } + break; + case 336: + // "", + sprintf (store, "%d", id->loopStore.extraHumidity[0]); + break; + case 337: + // "", + sprintf (store, "%d", id->loopStore.extraHumidity[1]); + break; + case 338: + // "", + sprintf (store, "%d", id->loopStore.extraHumidity[2]); + break; + case 339: + // "", + sprintf (store, "%d", id->loopStore.extraHumidity[3]); + break; + case 340: + // "", + sprintf (store, "%d", id->loopStore.extraHumidity[4]); + break; + case 341: + // "", + sprintf (store, "%d", id->loopStore.extraHumidity[5]); + break; + case 342: + // "", + sprintf (store, "%d", id->loopStore.extraHumidity[6]); + break; + case 343: + // "", + sprintf (store, "%d", id->loopStore.extraHumidity[7]); + break; + case 344: + // "", + sprintf (store, "%d", id->loopStore.extraHumidity[8]); + break; + case 345: + // "", + sprintf (store, "%d", id->loopStore.extraHumidity[9]); + break; + case 346: + // "", + sprintf (store, "%d", id->loopStore.extraHumidity[10]); + break; + case 347: + // "", + sprintf (store, "%d", id->loopStore.extraHumidity[11]); + break; + case 348: + // "", + sprintf (store, "%d", id->loopStore.extraHumidity[12]); + break; + case 349: + // "", + sprintf (store, "%d", id->loopStore.extraHumidity[13]); + break; + case 350: + // "", + sprintf (store, "%d", id->loopStore.extraHumidity[14]); + break; + case 351: + // "", + sprintf (store, "%d", id->loopStore.extraHumidity[15]); + break; + case 352: + // "", + sprintf (store, "%s", getBattStatus(id->loopStore.extraTempBatteryStatus[0])); + break; + case 353: + // "", + sprintf (store, "%s", getBattStatus(id->loopStore.extraTempBatteryStatus[1])); + break; + case 354: + // "", + sprintf (store, "%s", getBattStatus(id->loopStore.extraTempBatteryStatus[2])); + break; + case 355: + // "", + sprintf (store, "%s", getBattStatus(id->loopStore.extraTempBatteryStatus[3])); + break; + case 356: + // "", + sprintf (store, "%s", getBattStatus(id->loopStore.extraTempBatteryStatus[4])); + break; + case 357: + // "", + sprintf (store, "%s", getBattStatus(id->loopStore.extraTempBatteryStatus[5])); + break; + case 358: + // "", + sprintf (store, "%s", getBattStatus(id->loopStore.extraTempBatteryStatus[6])); + break; + case 359: + // "", + sprintf (store, "%s", getBattStatus(id->loopStore.extraTempBatteryStatus[7])); + break; + case 360: + // "", + sprintf (store, "%s", getBattStatus(id->loopStore.extraTempBatteryStatus[8])); + break; + case 361: + // "", + sprintf (store, "%s", getBattStatus(id->loopStore.extraTempBatteryStatus[9])); + break; + case 362: + // "", + sprintf (store, "%s", getBattStatus(id->loopStore.extraTempBatteryStatus[10])); + break; + case 363: + // "", + sprintf (store, "%s", getBattStatus(id->loopStore.extraTempBatteryStatus[11])); + break; + case 364: + // "", + sprintf (store, "%s", getBattStatus(id->loopStore.extraTempBatteryStatus[12])); + break; + case 365: + // "", + sprintf (store, "%s", getBattStatus(id->loopStore.extraTempBatteryStatus[13])); + break; + case 366: + // "", + sprintf (store, "%s", getBattStatus(id->loopStore.extraTempBatteryStatus[14])); + break; + case 367: + // "", + sprintf (store, "%s", getBattStatus(id->loopStore.extraTempBatteryStatus[15])); + break; + case 368: + // "", + sprintf (store, "%s", getBattStatus(id->loopStore.windBatteryStatus)); + break; + case 369: + // "", + sprintf (store, "%s", getBattStatus(id->loopStore.outTempBatteryStatus)); + break; + case 370: + // "", + sprintf (store, "%s", getBattStatus(id->loopStore.consoleBatteryStatus)); + break; + case 371: + // "", + sprintf (store, "%s", getBattStatus(id->loopStore.uvBatteryStatus)); + break; + case 372: + // "", + sprintf (store, "%s", getBattStatus(id->loopStore.solarBatteryStatus)); + break; + case 373: + // "", + sprintf (store, "%s", getBattStatus(id->loopStore.rainBatteryStatus)); + break; + case 374: + // "", + if (id->isMetricUnits) + { + sprintf (store, "%.1f", + wvutilsConvertFToC(wvutilsCalculateWetBulbTemp(id->loopStore.outTemp, + id->loopStore.outHumidity, + id->loopStore.barometer))); + } + else + { + sprintf (store, "%.1f", + wvutilsCalculateWetBulbTemp(id->loopStore.outTemp, id->loopStore.outHumidity, + id->loopStore.barometer)); + } + break; + default: + store[0] = 0; + } + + return; +} + +static int replaceDataTags (HTML_MGR_ID id, char *oldline, char *newline) +{ + register int i, j, k; + int len, taglen, found; + char temp[HTML_MAX_LINE_LENGTH]; + + newline[0] = 0; + + for (i = 0, j = 0; oldline[i] != 0; i ++) + { + if (!strncmp (&oldline[i], " Installation Configuration for Vantage Pro(2) Console" +echo "" +echo "--> To reset historical highs and lows, use the Vantage Pro Console" +echo "" +echo "--> Note: values in parenthesis are used if you hit enter at a prompt..." +echo "" +echo "################################################################################" +echo "" + +echo "" +echo "-------------------------------------------------------------" +echo "--NOTE-- ** Serial and USB data loggers are both considered serial -" +echo "--NOTE-- examples of this are: /dev/ttyS0, /dev/ttyUSB0, /dev/cuaa0, etc." +echo "--NOTE-- ** If your Vantage Pro is connected to a terminal or serial server," +echo "--NOTE-- you should enter the host and port in the format host:port -" +echo "--NOTE-- examples of this are: 10.10.10.10:3001, xyplex1:2102, etc." +echo "Serial port or host:port the VP console is connected to (i.e. /dev/ttyS0, mss1:3001)" +echo -n "(/dev/ttyS0): " +read INVAL +if [ "" != "$INVAL" ]; then + DEVNAME=$INVAL +fi + +echo "" +echo "-------------------------------------------------------------" +echo "Desired archive interval (minutes) (5,10,15,30,60)" +echo -n "(5): " +read INVAL +if [ "" != "$INVAL" ]; then + ARCINT=$INVAL +fi + +echo "" +echo "-------------------------------------------------------------" +echo "Station elevation (feet) ((-2000) - (15000))" +echo -n "(0): " +read INVAL +if [ "" != "$INVAL" ]; then + ELEVATION=$INVAL +fi + +echo "" +echo "-------------------------------------------------------------" +echo "Station latitude (tenths of degrees) ((-900) - (900), (-) => South)" +echo -n "(0): " +read INVAL +if [ "" != "$INVAL" ]; then + LATITUDE=$INVAL +fi + +echo "" +echo "-------------------------------------------------------------" +echo "Station longitude (tenths of degrees) ((-1799) - (1800), (-) => West)" +echo -n "(0): " +read INVAL +if [ "" != "$INVAL" ]; then + LONGITUDE=$INVAL +fi + +echo "" +echo "-------------------------------------------------------------" +echo "Rain season start (month) (1 - 12)" +echo -n "(1): " +read INVAL +if [ "" != "$INVAL" ]; then + RAINSTART=$INVAL +fi + +echo "" +echo "-------------------------------------------------------------" +echo "Year-to-Date Rain (inches) (0.00 - 200.00)" +echo -n "(0.00): " +read INVAL +if [ "" != "$INVAL" ]; then + RAINTODATE=$INVAL +fi + +echo "" +echo "-------------------------------------------------------------" +echo "Year-to-Date ET (inches) (0.00 - 200.00)" +echo -n "(0.00): " +read INVAL +if [ "" != "$INVAL" ]; then + ETTODATE=$INVAL +fi + +echo "" +echo "" +echo "-------------------------------------------------------------" +echo "Let's review your answers:" +echo "" +echo "VP Console Device: $DEVNAME" +echo "Archive Interval: $ARCINT (minutes)" +echo "Station Elevation: $ELEVATION (feet)" +echo "Station Latitude: $LATITUDE (tenths of a degree)" +echo "Station Longitude: $LONGITUDE (tenths of a degree)" +echo "Rain Season Start Month: $RAINSTART" +echo "Year-to-Date Rain: $RAINTODATE" +echo "Year-to-Date ET: $ETTODATE" +echo "-------------------------------------------------------------" +echo "" +echo "WARNING: All archive records will be erased on the VP console!" +echo "Do you want to proceed with these values? (y/n)" +echo -n "(n): " +read INVAL +if [ "y" = "$INVAL" ]; then + echo "" + echo "Configuring with new values (takes some time, be patient) ..." + echo "" +else + echo "" + echo "Aborting Vantage Pro Configuration ..." + exit 4 +fi + +# OK, here we go ... + +# 1) Set the elevation +$VPCONFIG_BIN $DEVNAME setelevation $ELEVATION +if [ "$?" != "0" ]; then + echo "set elevation failed!" + exit 7; +fi + +sleep 1 + +# 2) Set the rain season start +$VPCONFIG_BIN $DEVNAME setrainseasonstart $RAINSTART +if [ "$?" != "0" ]; then + echo "set rain season start failed!" + exit 7 +fi + +sleep 1 + +# 3) Set the rain year-to-date +$VPCONFIG_BIN $DEVNAME setyeartodaterain $RAINTODATE +if [ "$?" != "0" ]; then + echo "set year-to-date rain failed!" + exit 7 +fi + +sleep 1 + +# 4) Set the ET year-to-date +$VPCONFIG_BIN $DEVNAME setyeartodateET $ETTODATE +if [ "$?" != "0" ]; then + echo "set year-to-date ET failed!" + exit 7 +fi + +sleep 1 + +# 5) Set the archive interval +$VPCONFIG_BIN $DEVNAME setinterval $ARCINT +if [ "$?" != "0" ]; then + echo "set Archive Interval failed!" + exit 7 +fi + +sleep 5 + +# 6) Set the LAT/LONG +$VPCONFIG_BIN $DEVNAME setlatlong $LATITUDE $LONGITUDE +if [ "$?" != "0" ]; then + echo "set LAT/LONG failed!" + exit 7 +fi + + +echo "" +echo -n "Please wait - allowing VP console to digest new settings " +sleep 2 +echo -n "." +sleep 2 +echo -n "." +sleep 2 +echo -n "." +sleep 2 +echo -n "." +sleep 2 +echo -n "." +echo "" +echo "" + +$VPCONFIG_BIN $DEVNAME show + +echo "" +echo "################################################################################" +echo "It is advisable to wait $ARCINT minutes before starting wview for the" +echo "first time so the Vantage Pro can become consistent with the new settings..." +echo "" +echo "In the meantime, be sure you have deleted any archive files in" +echo "/var/wview/archive if you have changed the archive interval..." +echo "" +echo "Vantage Pro configuration complete!" +echo "" +echo "################################################################################" +exit 0 + diff --git a/stations/VantagePro/vproInterface.c b/stations/VantagePro/vproInterface.c new file mode 100755 index 0000000..65e844f --- /dev/null +++ b/stations/VantagePro/vproInterface.c @@ -0,0 +1,2938 @@ +/*--------------------------------------------------------------------------- + + FILENAME: + vproInterface.c + + PURPOSE: + Provide the non-medium-specific interface utilities. + + REVISION HISTORY: + Date Engineer Revision Remarks + 06/06/2005 M.S. Teel 0 Original + 04/12/2008 W. Krenn 1 vpifGetRainCollectorSize + + NOTES: + + + LICENSE: + Copyright (c) 2005, Mark S. Teel (mark@teel.ws) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +/* ... System include files +*/ + +/* ... Library include files +*/ + +/* ... Local include files +*/ +#include +#include + +/* ... global memory declarations +*/ + +/* ... local memory +*/ + +#ifdef WORDS_BIGENDIAN +#define SHORT_SWAP(x) (((x << 8) & 0xFF00) | ((x >> 8) & 0x00FF)) +#else +#define SHORT_SWAP(x) (x) +#endif + +static VP_IF_DATA vpWorkData; +static WV_ACCUM_ID vp12HourTempAvg; + +static void portConfig (int fd); +static void (*ArchiveIndicator) (ARCHIVE_PKT *newRecord); + + +////////////****////**** S T A T I O N A P I ****////****//////////// +///// Must be provided by each supported wview station interface ////// + +// station-supplied init function +// -- Can Be Asynchronous - event indication required -- +// +// MUST: +// - set the 'stationGeneratesArchives' flag in WVIEWD_WORK: +// if the station generates archive records (TRUE) or they should be +// generated automatically by the daemon from the sensor readings (FALSE) +// - Initialize the 'stationData' store for station work area +// - Initialize the interface medium +// - do initial LOOP acquisition +// - do any catch-up on archive records if there is a data logger +// - 'work->runningFlag' can be used for start up synchronization but should +// not be modified by the station interface code +// - indicate init is done by sending the STATION_INIT_COMPLETE_EVENT event to +// this process (radProcessEventsSend (NULL, STATION_INIT_COMPLETE_EVENT, 0)) +// +// OPTIONAL: +// - Initialize a state machine or any other construct required for the +// station interface - these should be stored in the 'stationData' store +// +// 'archiveIndication' - indication callback used to pass back an archive record +// generated as a result of 'stationGetArchive' being called; should receive a +// NULL pointer for 'newRecord' if no record available; only used if +// 'stationGeneratesArchives' flag is set to TRUE by the station interface +// +// Returns: OK or ERROR +// +int stationInit +( + WVIEWD_WORK *work, + void (*archiveIndication)(ARCHIVE_PKT* newRecord) +) +{ + STIM stim; + ARCHIVE_PKT newestRecord; + time_t nowTime = time(NULL) - (WV_SECONDS_IN_HOUR * 12); + ARCHIVE_PKT recordStore; + + memset (&vpWorkData, 0, sizeof(vpWorkData)); + vpWorkData.sampleRain = -1; + vpWorkData.sampleET = -1; + + // save the archive indication callback + ArchiveIndicator = archiveIndication; + + // set our work data pointer + work->stationData = &vpWorkData; + + // This is now read from configuration... + // work->stationGeneratesArchives = TRUE; + + + // initialize the medium abstraction based on user configuration + if (!strcmp (work->stationInterface, "serial")) + { + if (serialMediumInit (&work->medium, portConfig, O_RDWR | O_NOCTTY | O_NDELAY) == ERROR) + { + radMsgLog (PRI_HIGH, "stationInit: serial MediumInit failed"); + return ERROR; + } + } + else if (!strcmp (work->stationInterface, "ethernet")) + { + if (ethernetMediumInit (&work->medium, work->stationHost, work->stationPort) + == ERROR) + { + radMsgLog (PRI_HIGH, "stationInit: ethernet MediumInit failed"); + return ERROR; + } + } + else + { + radMsgLog (PRI_HIGH, "stationInit: medium %s not supported", + work->stationInterface); + return ERROR; + } + + // initialize the VP interface using the media specific routine + if ((*(work->medium.init)) (&work->medium, work->stationDevice) == ERROR) + { + radMsgLog (PRI_HIGH, "stationInit: medium setup failed"); + return ERROR; + } + + vpifWakeupConsole (work); + vpifWakeupConsole (work); + + +#ifndef _VP_CONFIG_ONLY + if (!strcmp (work->stationInterface, "serial")) + { + radMsgLog (PRI_STATUS, "Vantage Pro on %s opened ...", + work->stationDevice); + } + else if (!strcmp (work->stationInterface, "ethernet")) + { + radMsgLog (PRI_STATUS, "Vantage Pro on %s:%d opened ...", + work->stationHost, work->stationPort); + } + + // get VP-specific configuration + if (stationGetConfigValueBoolean (work, + configItem_STATION_DO_RXCHECK, + &vpWorkData.doRXCheck) + == ERROR) + { + // just default to disabled + radMsgLog(PRI_MEDIUM, "stationInit: can't retrieve rxcheck configuration - defaulting to OFF"); + vpWorkData.doRXCheck = 0; + } + else + { + radMsgLog(PRI_MEDIUM, "stationInit: VP rxcheck is %s", + ((vpWorkData.doRXCheck) ? "ENABLED" : "DISABLED")); + } + + // This must be done here so dmpafter will work: + work->archiveDateTime = dbsqliteArchiveGetNewestTime(&newestRecord); + if ((int)work->archiveDateTime == ERROR) + { + work->archiveDateTime = time(NULL) - WV_SECONDS_IN_MONTH; + radMsgLog (PRI_STATUS, "stationInit: no archive records found in database!"); + } + + // now initialize the state machine + vpWorkData.stateMachine = radStatesInit (work); + if (vpWorkData.stateMachine == NULL) + { + radMsgLog (PRI_HIGH, "stationInit: radStatesInit failed"); + (*(work->medium.exit)) (&work->medium); + return ERROR; + } + + if (radStatesAddHandler (vpWorkData.stateMachine, + VPRO_STATE_STARTPROC, + vproStartProcState) + == ERROR) + { + radMsgLog (PRI_HIGH, "stationInit: radStatesAddHandler failed"); + radStatesExit (vpWorkData.stateMachine); + (*(work->medium.exit)) (&work->medium); + return ERROR; + } + if (radStatesAddHandler (vpWorkData.stateMachine, + VPRO_STATE_RUN, + vproRunState) + == ERROR) + { + radMsgLog (PRI_HIGH, "stationInit: radStatesAddHandler failed"); + radStatesExit (vpWorkData.stateMachine); + (*(work->medium.exit)) (&work->medium); + return ERROR; + } + if (radStatesAddHandler (vpWorkData.stateMachine, + VPRO_STATE_DMPAFT_RQST, + vproDumpAfterState) + == ERROR) + { + radMsgLog (PRI_HIGH, "stationInit: radStatesAddHandler failed"); + radStatesExit (vpWorkData.stateMachine); + (*(work->medium.exit)) (&work->medium); + return ERROR; + } + if (radStatesAddHandler (vpWorkData.stateMachine, + VPRO_STATE_DMPAFT_ACK, + vproDumpAfterAckState) + == ERROR) + { + radMsgLog (PRI_HIGH, "stationInit: radStatesAddHandler failed"); + radStatesExit (vpWorkData.stateMachine); + (*(work->medium.exit)) (&work->medium); + return ERROR; + } + if (radStatesAddHandler (vpWorkData.stateMachine, + VPRO_STATE_RECV_ARCH, + vproReceiveArchiveState) + == ERROR) + { + radMsgLog (PRI_HIGH, "stationInit: radStatesAddHandler failed"); + radStatesExit (vpWorkData.stateMachine); + (*(work->medium.exit)) (&work->medium); + return ERROR; + } + if (radStatesAddHandler (vpWorkData.stateMachine, + VPRO_STATE_LOOP_RQST, + vproLoopState) + == ERROR) + { + radMsgLog (PRI_HIGH, "stationInit: radStatesAddHandler failed"); + radStatesExit (vpWorkData.stateMachine); + (*(work->medium.exit)) (&work->medium); + return ERROR; + } + if (radStatesAddHandler (vpWorkData.stateMachine, + VPRO_STATE_READ_RECOVER, + vproReadRecoverState) + == ERROR) + { + radMsgLog (PRI_HIGH, "stationInit: radStatesAddHandler failed"); + radStatesExit (vpWorkData.stateMachine); + (*(work->medium.exit)) (&work->medium); + return ERROR; + } + if (radStatesAddHandler (vpWorkData.stateMachine, + VPRO_STATE_ERROR, + vproErrorState) + == ERROR) + { + radMsgLog (PRI_HIGH, "stationInit: radStatesAddHandler failed"); + radStatesExit (vpWorkData.stateMachine); + (*(work->medium.exit)) (&work->medium); + return ERROR; + } + + radStatesSetState (vpWorkData.stateMachine, VPRO_STATE_STARTPROC); + + // Initialize the 12-hour temp accumulator: + vp12HourTempAvg = sensorAccumInit(60 * 12); + + // Load data for the last 12 hours: + while ((nowTime = dbsqliteArchiveGetNextRecord(nowTime, &recordStore)) != ERROR) + { + sensorAccumAddSample(vp12HourTempAvg, + recordStore.dateTime, + recordStore.value[DATA_INDEX_outTemp]); + } + + radMsgLog (PRI_STATUS, "Starting station interface: VantagePro"); + + // dummy up a stimulus to get the state machine running + stim.type = STIM_DUMMY; + radStatesProcess (vpWorkData.stateMachine, &stim); + +#endif + + return OK; +} + +// station-supplied exit function +// +// Returns: N/A +// +void stationExit (WVIEWD_WORK *work) +{ + radStatesExit (vpWorkData.stateMachine); + (*(work->medium.exit)) (&work->medium); + + return; +} + +// station-supplied function to retrieve positional info (lat, long, elev) - +// should populate 'work' fields: latitude, longitude, elevation +// -- Synchronous -- +// +// - If station does not store these parameters, they can be retrieved from the +// wview.conf file (see daemon.c for example conf file use) - user must choose +// station type "Generic" when running the wviewconfig script +// +// Returns: OK or ERROR +// +int stationGetPosition (WVIEWD_WORK *work) +{ + // wakeup the console + if (vpifWakeupConsole (work) == ERROR) + { + radMsgLog (PRI_HIGH, "stationGetPosition: WAKEUP failed"); + return ERROR; + } + + // get the station latitude and longitude and elevation + if (vpifGetLatandLong (work) == ERROR) + { + radMsgLog (PRI_HIGH, "stationGetPosition: failed"); + return ERROR; + } + else + { + vpWorkData.elevation = work->elevation; + + radMsgLog (PRI_STATUS, "station location: elevation: %d feet", + work->elevation); + + radMsgLog (PRI_STATUS, "station location: latitude: %3.1f %c longitude: %3.1f %c", + (float)abs(work->latitude)/10.0, + ((work->latitude < 0) ? 'S' : 'N'), + (float)abs(work->longitude)/10.0, + ((work->longitude < 0) ? 'W' : 'E')); + + return OK; + } +} + +// station-supplied function to indicate a time sync should be performed if the +// station maintains time, otherwise may be safely ignored +// -- Can Be Asynchronous -- +// +// Returns: OK or ERROR +// +int stationSyncTime (WVIEWD_WORK *work) +{ + // let state machine do it when convenient + vpWorkData.timeSyncFlag = TRUE; + + return OK; +} + +// station-supplied function to indicate sensor readings should be performed - +// should populate 'work' struct: loopPkt (see datadefs.h for minimum field reqs) +// -- Can Be Asynchronous -- +// +// - indicate readings are complete by sending the STATION_LOOP_COMPLETE_EVENT +// event to this process (radProcessEventsSend (NULL, STATION_LOOP_COMPLETE_EVENT, 0)) +// +// Returns: OK or ERROR +// +int stationGetReadings (WVIEWD_WORK *work) +{ + STIM stim; + + stim.type = VP_STIM_READINGS; + + // use the VP_STIM_READINGS stim to kick off the state machine + radStatesProcess (vpWorkData.stateMachine, &stim); + + return OK; +} + +// station-supplied function to indicate an archive record should be generated - +// MUST populate an ARCHIVE_PKT struct and indicate it to 'archiveIndication' +// function passed into 'stationInit' +// -- Asynchronous - callback indication required -- +// +// Returns: OK or ERROR +// +// Note: 'archiveIndication' should receive a NULL pointer for the newRecord if +// no record is available +// Note: This function will only be invoked by the wview daemon if the +// 'stationInit' function set the 'stationGeneratesArchives' to TRUE +// +int stationGetArchive (WVIEWD_WORK *work) +{ + STIM stim; + + stim.type = VP_STIM_ARCHIVE; + + // use the VP_STIM_ARCHIVE stim to kick off the state machine + radStatesProcess (vpWorkData.stateMachine, &stim); + + return OK; +} + +// station-supplied function to indicate data is available on the station +// interface medium (serial or ethernet) - +// It is the responsibility of the station interface to read the data from the +// medium and process appropriately. The data does not have to be read within +// the context of this function, but may be used to stimulate a state machine. +// -- Synchronous -- +// +// Returns: N/A +// +void stationDataIndicate (WVIEWD_WORK *work) +{ + STIM stim; + + stim.type = STIM_IO; + + radStatesProcess (vpWorkData.stateMachine, &stim); + return; +} + +// station-supplied function to receive IPM messages - any message received by +// the generic station message handler which is not recognized will be passed +// to the station-specific code through this function. +// It is the responsibility of the station interface to process the message +// appropriately (or ignore it). +// -- Synchronous -- +// +// Returns: N/A +// +void stationMessageIndicate (WVIEWD_WORK *work, int msgType, void *msg) +{ + // N/A + return; +} + +// station-supplied function to indicate the interface timer has expired - +// It is the responsibility of the station interface to start/stop the interface +// timer as needed for the particular station requirements. +// The station interface timer is specified by the 'ifTimer' member of the +// WVIEWD_WORK structure. No other timers in that structure should be manipulated +// in any way by the station interface code. +// -- Synchronous -- +// +// Returns: N/A +// +void stationIFTimerExpiry (WVIEWD_WORK *work) +{ + STIM stim; + + stim.type = STIM_TIMER; + + radStatesProcess (vpWorkData.stateMachine, &stim); + + return; +} + +////////////****//// S T A T I O N A P I E N D ////****//////////// + + +/* ... ----- static (local) methods ----- +*/ +static void portConfig (int fd) +{ + struct termios port, tty, old; + int portstatus; + + memset (&port, 0, sizeof(port)); + + // Serial control options: + port.c_cflag &= ~PARENB; // No parity + port.c_cflag &= ~CSTOPB; // One stop bit + port.c_cflag &= ~CSIZE; // Character size mask + port.c_cflag |= CS8; // Character size 8 bits + port.c_cflag |= CREAD; // Enable Receiver + port.c_cflag &= ~HUPCL; // No "hangup" + port.c_cflag |= CLOCAL; // Ignore modem control lines + + cfsetispeed (&port, B19200); + cfsetospeed (&port, B19200); + + // Serial local options: + // Raw input = clear ICANON, ECHO, ECHOE, and ISIG + // Disable misc other local features = clear FLUSHO, NOFLSH, TOSTOP, PENDIN, and IEXTEN + // So we actually clear all flags in adtio.c_lflag + port.c_lflag = 0; + + // Serial input options: + // Disable parity check = clear INPCK, PARMRK, and ISTRIP + // Disable software flow control = clear IXON, IXOFF, and IXANY + // Disable any translation of CR and LF = clear INLCR, IGNCR, and ICRNL + // Ignore break condition on input = set IGNBRK + // Ignore parity errors just in case = set IGNPAR; + // So we can clear all flags except IGNBRK and IGNPAR + port.c_iflag = IGNBRK|IGNPAR; + + // Serial output options: + // Raw output should disable all other output options + port.c_oflag &= ~OPOST; + + port.c_cc[VTIME] = 10; // timer 1s + port.c_cc[VMIN] = 0; // blocking read until 1 char + + tcsetattr (fd, TCSANOW, &port); + tcflush(fd, TCIOFLUSH); + + return; +} + + +#ifndef _VP_CONFIG_ONLY + +#ifdef WORDS_BIGENDIAN +static void swapArchiveRecord (ARCHIVE_RECORD *rec) +{ + rec->date = SHORT_SWAP(rec->date); + rec->time = SHORT_SWAP(rec->time); + rec->outTemp = SHORT_SWAP(rec->outTemp); + rec->highOutTemp = SHORT_SWAP(rec->highOutTemp); + rec->lowOutTemp = SHORT_SWAP(rec->lowOutTemp); + rec->rain = SHORT_SWAP(rec->rain); + rec->highRainRate = SHORT_SWAP(rec->highRainRate); + rec->barometer = SHORT_SWAP(rec->barometer); + rec->radiation = SHORT_SWAP(rec->radiation); + rec->windSamples = SHORT_SWAP(rec->windSamples); + rec->inTemp = SHORT_SWAP(rec->inTemp); + + return; +} +#else +static void swapArchiveRecord (ARCHIVE_RECORD *rec) +{ + return; +} +#endif + + +// Calculate the "e ^ (-mgh/RT)" term for pressure conversions: +static double calcPressureTerm(float tempF, float elevationFT) +{ + double exponent; + double elevMeters = (double)wvutilsConvertFeetToMeters(elevationFT); + double tempKelvin = (double)wvutilsConvertFToC(tempF) + 273.15; + + // e ^ -elevMeters/(tempK * 29.263) + exponent = (-elevMeters); + + // degrees Kelvin (T) + exponent /= (tempKelvin * 29.263); + + // e ^ (-mgh/RT) + exponent = exp(exponent); + + return exponent; +} + +// calculate station pressure from sea level pressure (using 12-hour temp avg): +static float vpConvertSLPToSP(float SLP, float tempF, float elevationFT) +{ + double SP, PT; + + // Formula used: SP = SLP * PressureTerm + // compute PressureTerm: + PT = calcPressureTerm (tempF, elevationFT); + SP = SLP * PT; + + return (float)SP; +} + + +static void convertToArchivePkt(WVIEWD_WORK *work, ARCHIVE_RECORD* newRecord, ARCHIVE_PKT* archivePkt) +{ + struct tm bknTime; + Data_Indices index; + + // create the time_t time for the record: + bknTime.tm_year = EXTRACT_PACKED_YEAR(newRecord->date) - 1900; + bknTime.tm_mon = EXTRACT_PACKED_MONTH(newRecord->date) - 1; + bknTime.tm_mday = EXTRACT_PACKED_DAY(newRecord->date); + bknTime.tm_hour = EXTRACT_PACKED_HOUR(newRecord->time); + bknTime.tm_min = EXTRACT_PACKED_MINUTE(newRecord->time); + bknTime.tm_sec = 0; + bknTime.tm_isdst = -1; + + archivePkt->dateTime = (int32_t)mktime(&bknTime); + archivePkt->usUnits = 1; + archivePkt->interval = work->archiveInterval; + + // Set all values to NULL by default: + for (index = DATA_INDEX_barometer; index < DATA_INDEX_MAX; index ++) + { + archivePkt->value[index] = ARCHIVE_VALUE_NULL; + } + + // Set the values we can: + if (-1500 < newRecord->outTemp && newRecord->outTemp < 1500) + archivePkt->value[DATA_INDEX_outTemp] = (float)newRecord->outTemp/10.0; + if (1000 < newRecord->barometer && newRecord->barometer < 40000) + { + archivePkt->value[DATA_INDEX_barometer] = (float)newRecord->barometer/1000.0; + archivePkt->value[DATA_INDEX_pressure] + = vpConvertSLPToSP((float)archivePkt->value[DATA_INDEX_barometer], + sensorAccumGetAverage(vp12HourTempAvg), + (float)vpWorkData.elevation); + archivePkt->value[DATA_INDEX_altimeter] + = wvutilsConvertSPToAltimeter((float)archivePkt->value[DATA_INDEX_pressure], + (float)vpWorkData.elevation); + } + if (newRecord->inTemp < 2000) + archivePkt->value[DATA_INDEX_inTemp] = (float)newRecord->inTemp/10.0; + if (newRecord->inHumidity <= 100) + archivePkt->value[DATA_INDEX_inHumidity] = (float)newRecord->inHumidity; + if (newRecord->outHumidity <= 100) + archivePkt->value[DATA_INDEX_outHumidity] = (float)newRecord->outHumidity; + if (newRecord->avgWindSpeed <= 250) + archivePkt->value[DATA_INDEX_windSpeed] = (float)newRecord->avgWindSpeed; + if (newRecord->prevWindDir < 16) + archivePkt->value[DATA_INDEX_windDir] = (float)newRecord->prevWindDir * 22.5; + if (newRecord->highWindSpeed <= 250) + archivePkt->value[DATA_INDEX_windGust] = (float)newRecord->highWindSpeed; + if (newRecord->highWindDir < 16) + archivePkt->value[DATA_INDEX_windGustDir] = (float)newRecord->highWindDir * 22.5; + archivePkt->value[DATA_INDEX_rainRate] + = (float)newRecord->highRainRate/vpWorkData.rainTicksPerInch; + archivePkt->value[DATA_INDEX_rain] + = ((float)(newRecord->rain & 0xFFF))/vpWorkData.rainTicksPerInch; + if (archivePkt->value[DATA_INDEX_outTemp] != ARCHIVE_VALUE_NULL && + archivePkt->value[DATA_INDEX_outHumidity] != ARCHIVE_VALUE_NULL) + { + archivePkt->value[DATA_INDEX_dewpoint] + = wvutilsCalculateDewpoint ((float)archivePkt->value[DATA_INDEX_outTemp], + (float)archivePkt->value[DATA_INDEX_outHumidity]); + archivePkt->value[DATA_INDEX_heatindex] + = wvutilsCalculateHeatIndex ((float)archivePkt->value[DATA_INDEX_outTemp], + (float)archivePkt->value[DATA_INDEX_outHumidity]); + } + if (archivePkt->value[DATA_INDEX_outTemp] != ARCHIVE_VALUE_NULL && + archivePkt->value[DATA_INDEX_windSpeed] != ARCHIVE_VALUE_NULL) + { + archivePkt->value[DATA_INDEX_windchill] + = wvutilsCalculateWindChill ((float)archivePkt->value[DATA_INDEX_outTemp], + (float)archivePkt->value[DATA_INDEX_windSpeed]); + } + if (newRecord->ET != 0xFF) + archivePkt->value[DATA_INDEX_ET] = (float)newRecord->ET/1000.0; + if ((uint16_t)newRecord->radiation != 0x7FFF && + (uint16_t)newRecord->radiation != 0xFFFF && + (float)newRecord->radiation >= 0 && + (float)newRecord->radiation <= 1800) + { + archivePkt->value[DATA_INDEX_radiation] = (float)newRecord->radiation; + } + if (newRecord->UV != 0xFF) + { + archivePkt->value[DATA_INDEX_UV] = (float)newRecord->UV/10.0; + } + if (newRecord->extraTemp1 != 0xFF) + { + archivePkt->value[DATA_INDEX_extraTemp1] = (float)(newRecord->extraTemp1 - 90); + } + if (newRecord->extraTemp2 != 0xFF) + { + archivePkt->value[DATA_INDEX_extraTemp2] = (float)(newRecord->extraTemp2 - 90); + } + if (newRecord->extraTemp3 != 0xFF) + { + archivePkt->value[DATA_INDEX_extraTemp3] = (float)(newRecord->extraTemp3 - 90); + } + if (newRecord->soilTemp1 != 0xFF) + { + archivePkt->value[DATA_INDEX_soilTemp1] = (float)(newRecord->soilTemp1 - 90); + } + if (newRecord->soilTemp2 != 0xFF) + { + archivePkt->value[DATA_INDEX_soilTemp2] = (float)(newRecord->soilTemp2 - 90); + } + if (newRecord->soilTemp3 != 0xFF) + { + archivePkt->value[DATA_INDEX_soilTemp3] = (float)(newRecord->soilTemp3 - 90); + } + if (newRecord->soilTemp4 != 0xFF) + { + archivePkt->value[DATA_INDEX_soilTemp4] = (float)(newRecord->soilTemp4 - 90); + } + if (newRecord->leafTemp1 != 0xFF) + { + archivePkt->value[DATA_INDEX_leafTemp1] = (float)(newRecord->leafTemp1 - 90); + } + if (newRecord->leafTemp2 != 0xFF) + { + archivePkt->value[DATA_INDEX_leafTemp2] = (float)(newRecord->leafTemp2 - 90); + } + if (newRecord->extraHumid1 != 0xFF) + { + archivePkt->value[DATA_INDEX_extraHumid1] = (float)newRecord->extraHumid1; + } + if (newRecord->extraHumid2 != 0xFF) + { + archivePkt->value[DATA_INDEX_extraHumid2] = (float)newRecord->extraHumid2; + } + if (newRecord->soilMoist1 != 0xFF) + { + archivePkt->value[DATA_INDEX_soilMoist1] = (float)newRecord->soilMoist1; + } + if (newRecord->soilMoist2 != 0xFF) + { + archivePkt->value[DATA_INDEX_soilMoist2] = (float)newRecord->soilMoist2; + } + if (newRecord->soilMoist3 != 0xFF) + { + archivePkt->value[DATA_INDEX_soilMoist3] = (float)newRecord->soilMoist3; + } + if (newRecord->soilMoist4 != 0xFF) + { + archivePkt->value[DATA_INDEX_soilMoist4] = (float)newRecord->soilMoist4; + } + if (newRecord->leafWet1 != 0xFF) + { + archivePkt->value[DATA_INDEX_leafWet1] = (float)newRecord->leafWet1; + } + if (newRecord->leafWet2 != 0xFF) + { + archivePkt->value[DATA_INDEX_leafWet2] = (float)newRecord->leafWet2; + } + + if (work->loopPkt.rxCheckPercent != 0xFFFF) + archivePkt->value[DATA_INDEX_rxCheckPercent] = (float)work->loopPkt.rxCheckPercent; + archivePkt->value[DATA_INDEX_txBatteryStatus] = (float)work->loopPkt.txBatteryStatus; + archivePkt->value[DATA_INDEX_consBatteryVoltage] = (float)work->loopPkt.consBatteryVoltage; + + return; +} + +static void storeLoopPkt (WVIEWD_WORK *work, LOOP_DATA *src) +{ + LOOP_PKT* dest = &(work->loopPkt); + uint16_t tempshort; + int intTemp; + + // Clear optional data: + stationClearLoopData(work); + + + // do this magic to track rainfall and ET per LOOP sample + intTemp = (int)SHORT_SWAP(src->dayRain); + + if (vpWorkData.sampleRain == -1 && intTemp != 0xFFFF && intTemp != 0x7FFF) + { + vpWorkData.sampleRain = intTemp; + dest->sampleRain = 0.0; + } + else if (intTemp != 0xFFFF && intTemp != 0x7FFF) + { + if (intTemp - vpWorkData.sampleRain >= 0) + { + dest->sampleRain = intTemp - vpWorkData.sampleRain; + dest->sampleRain /= vpWorkData.rainTicksPerInch; + } + else + { + // we had a day rollover, all rain must have come in this sample + dest->sampleRain = intTemp; + dest->sampleRain /= vpWorkData.rainTicksPerInch; + } + vpWorkData.sampleRain = intTemp; + } + else + { + dest->sampleRain = 0.0; + } + + intTemp = (int)SHORT_SWAP(src->dayET); + if (vpWorkData.sampleET == -1 && intTemp != 0xFFFF && intTemp != 0x7FFF) + { + vpWorkData.sampleET = intTemp; + dest->sampleET = 0.0; + } + else if (intTemp != 0xFFFF && intTemp != 0x7FFF) + { + if (intTemp - vpWorkData.sampleET >= 0) + { + dest->sampleET = intTemp - vpWorkData.sampleET; + dest->sampleET /= 1000.0; + } + else + { + dest->sampleET = intTemp; + dest->sampleET /= 1000.0; + } + vpWorkData.sampleET = intTemp; + } + else + { + dest->sampleET = 0.0; + } + + + // store the RX Check value here + dest->rxCheckPercent = vpWorkData.rxCheckPercent; + + src->outTemp = SHORT_SWAP(src->outTemp); + if (src->outTemp != 0x7FFF) + dest->outTemp = (float)src->outTemp/10.0; + + // Pressure: + if (src->barometer != 0xFFFF) + { + // VP produces sea level pressure (SLP): + tempshort = SHORT_SWAP(src->barometer); + dest->barometer = (float)tempshort/1000.0; + + // Apply calibration here so the computed values reflect it: + dest->barometer *= work->calMBarometer; + dest->barometer += work->calCBarometer; + + // calculate station pressure: + dest->stationPressure = vpConvertSLPToSP(dest->barometer, + sensorAccumGetAverage(vp12HourTempAvg), + (float)vpWorkData.elevation); + + // now calculate altimeter: + dest->altimeter = wvutilsConvertSPToAltimeter(dest->stationPressure, + (float)vpWorkData.elevation); + } + + src->inTemp = SHORT_SWAP(src->inTemp); + if (src->inTemp != 0x7FFF) + dest->inTemp = (float)src->inTemp/10.0; + if (src->inHumidity != 0xFF) + dest->inHumidity = src->inHumidity; + if (src->outHumidity != 0xFF) + dest->outHumidity = src->outHumidity; + if (src->windSpeed != 0xFF) + { + dest->windSpeedF = (float)src->windSpeed; + dest->windGustF = (float)src->windSpeed; + } + tempshort = SHORT_SWAP(src->windDir); + if (tempshort != 0xFFFF && tempshort != 0x7FFF) + { + dest->windDir = tempshort; + dest->windGustDir = tempshort; + } + tempshort = SHORT_SWAP(src->rainRate); + if (tempshort != 0xFFFF && tempshort != 0x7FFF) + { + dest->rainRate = (float)tempshort/vpWorkData.rainTicksPerInch; + } + if (src->UV != 0xFF) + dest->UV = (float)src->UV/10; + else + dest->UV = -1; + + tempshort = SHORT_SWAP(src->radiation); + if (tempshort != 0x7FFF && tempshort != 0xFFFF && tempshort <= 1800) + dest->radiation = tempshort; + else + dest->radiation = 0xFFFF; + + if (src->tenMinuteAvgWindSpeed != 0xFF) + dest->tenMinuteAvgWindSpeed = src->tenMinuteAvgWindSpeed; + + dest->forecastIcon = src->forecastIcon; + dest->forecastRule = src->forecastRule; + dest->txBatteryStatus = src->txBatteryStatus; + dest->consBatteryVoltage = SHORT_SWAP(src->consBatteryVoltage); + dest->extraTemp[0] = (float)(src->extraTemp1 - 90); + dest->extraTemp[1] = (float)(src->extraTemp2 - 90); + dest->extraTemp[2] = (float)(src->extraTemp3 - 90); + dest->soilTemp1 = (float)(src->soilTemp1 - 90); + dest->soilTemp2 = (float)(src->soilTemp2 - 90); + dest->soilTemp3 = (float)(src->soilTemp3 - 90); + dest->soilTemp4 = (float)(src->soilTemp4 - 90); + dest->leafTemp1 = (float)(src->leafTemp1 - 90); + dest->leafTemp2 = (float)(src->leafTemp2 - 90); + dest->extraHumidity[0] = src->extraHumid1; + dest->extraHumidity[1] = src->extraHumid2; + dest->soilMoist1 = src->soilMoist1; + dest->soilMoist2 = src->soilMoist2; + if (src->leafWet1 != 0xFF) + dest->leafWet1 = src->leafWet1; + if (src->leafWet2 != 0xFF) + dest->leafWet2 = src->leafWet2; + + return; +} + +static int processArchivePage (WVIEWD_WORK *work, ARCHIVE_PAGE *page) +{ + int i, start, tempInt, numNewRecords = 0; + ARCHIVE_RECORD* newRecord; + ARCHIVE_PKT archivePkt; + float tempf; + uint16_t date, ntime, tempRainBits; + + start = vpWorkData.archiveRecOffset; + vpWorkData.archiveRecOffset = 0; + + for (i = start; i < 5; i ++) + { + // ... swap shorts for big endian before we get started... + swapArchiveRecord (&page->record[i]); + + date = INSERT_PACKED_DATE(wvutilsGetYear(work->archiveDateTime), + wvutilsGetMonth(work->archiveDateTime), + wvutilsGetDay(work->archiveDateTime)); + ntime = (100 * wvutilsGetHour(work->archiveDateTime)) + + wvutilsGetMin(work->archiveDateTime); + + if ((page->record[i].date < date) || + (page->record[i].date == date && page->record[i].time <= ntime) || + (page->record[i].date == 0xFFFF) || + (page->record[i].time == 0xFFFF)) + { + // ... this is an old record or uninitialized, skip it + continue; + } + + numNewRecords ++; + + // save the high wind speed + work->loopPkt.windGustF = (float)page->record[i].highWindSpeed; + + // clear the retry flag here + vpWorkData.archiveRetryFlag = FALSE; + + newRecord = &page->record[i]; + + // Convert to the wview internal archive format: + convertToArchivePkt(work, newRecord, &archivePkt); + + // Calibrate archive record contents: + archivePkt.value[DATA_INDEX_barometer] *= work->calMBarometer; + archivePkt.value[DATA_INDEX_barometer] += work->calCBarometer; + + archivePkt.value[DATA_INDEX_inTemp] *= work->calMInTemp; + archivePkt.value[DATA_INDEX_inTemp] += work->calCInTemp; + + archivePkt.value[DATA_INDEX_outTemp] *= work->calMOutTemp; + archivePkt.value[DATA_INDEX_outTemp] += work->calCOutTemp; + + archivePkt.value[DATA_INDEX_inHumidity] *= work->calMInHumidity; + archivePkt.value[DATA_INDEX_inHumidity] += work->calCInHumidity; + if (archivePkt.value[DATA_INDEX_inHumidity] > 100) + { + archivePkt.value[DATA_INDEX_inHumidity] = 100; + } + + archivePkt.value[DATA_INDEX_outHumidity] *= work->calMOutHumidity; + archivePkt.value[DATA_INDEX_outHumidity] += work->calCOutHumidity; + if (archivePkt.value[DATA_INDEX_outHumidity] > 100) + { + archivePkt.value[DATA_INDEX_outHumidity] = 100; + } + + archivePkt.value[DATA_INDEX_windSpeed] *= work->calMWindSpeed; + archivePkt.value[DATA_INDEX_windSpeed] += work->calCWindSpeed; + + if (archivePkt.value[DATA_INDEX_windDir] > ARCHIVE_VALUE_NULL) + { + archivePkt.value[DATA_INDEX_windDir] *= work->calMWindDir; + archivePkt.value[DATA_INDEX_windDir] += work->calCWindDir; + if (archivePkt.value[DATA_INDEX_windDir] < 0) + { + archivePkt.value[DATA_INDEX_windDir] += 360; + } + if (archivePkt.value[DATA_INDEX_windDir] > 360) + { + archivePkt.value[DATA_INDEX_windDir] -= 360; + } + } + archivePkt.value[DATA_INDEX_rain] *= work->calMRain; + archivePkt.value[DATA_INDEX_rain] += work->calCRain; + if (archivePkt.value[DATA_INDEX_rain] < 0) + { + archivePkt.value[DATA_INDEX_rain] = 0; + } + + archivePkt.value[DATA_INDEX_rainRate] *= work->calMRainRate; + archivePkt.value[DATA_INDEX_rainRate] += work->calCRainRate; + if (archivePkt.value[DATA_INDEX_rainRate] < 0) + { + archivePkt.value[DATA_INDEX_rainRate] = 0; + } + + // Add for the 12-hour temp average: + sensorAccumAddSample(vp12HourTempAvg, archivePkt.dateTime, archivePkt.value[DATA_INDEX_outTemp]); + + // indicate through the station API: + (*ArchiveIndicator) (&archivePkt); + + // If not running yet, add to HILOW database: + if (!work->runningFlag) + { + dbsqliteHiLowStoreArchive(&archivePkt); + } + else + { + // Add all but cumulative: + dbsqliteHiLowUpdateArchive(&archivePkt); + } + } + + vpWorkData.archiveCurrentPage ++; + return numNewRecords; +} +#endif + + +static uint16_t genCRC (void *data, int length) +{ + uint8_t *ptr = (uint8_t *)data; + uint16_t crc = 0; + register int i; + + for (i = 0; i < length; i ++) + { + crc = crc_table[(crc >> 8) ^ ptr[i]] ^ (crc << 8); + } + + crc = ((crc >> 8) & 0xFF) | ((crc << 8) & 0xFF00); + return crc; +} + +static int readWithCRC (WVIEWD_WORK *work, void *bfr, int len, int msTimeout) +{ + int retVal, index = 0; + uint8_t *ptr = (uint8_t *)bfr; + uint16_t crc = 0; + + retVal = (*work->medium.read) (&work->medium, bfr, len, msTimeout); + if (retVal != len) + { + return ERROR; + } + + for (index = 0; index < len; index ++) + { + crc = crc_table [(crc >> 8) ^ ptr[index]] ^ (crc << 8); + } + + return (crc == 0) ? (len) : (ERROR); +} + +static int writeWithCRC (WVIEWD_WORK *work, void *buffer, int length) +{ + int retVal; + int wrerrno = 0; + uint16_t crc; + + crc = SHORT_SWAP(genCRC (buffer, length)); + + retVal = (*work->medium.write) (&work->medium, buffer, length); + if (retVal != length) + { + if (retVal == -1) + { + wrerrno = errno; + } + + return ERROR; + } + retVal = (*work->medium.write) (&work->medium, &crc, 2); + if (retVal != 2) + { + if (retVal == -1) + { + wrerrno = errno; + } + + return ERROR; + } + + /* ... now wait for the TX buffer to empty out (this blocks) + */ + (*work->medium.txdrain) (&work->medium); + + return length; +} + +static int wakeupConsole (WVIEWD_WORK *work) +{ + int retVal; + uint8_t bfr[32]; + int retries = 4; + + while (retries > 0) + { + bfr[0] = VP_CR; + (*work->medium.flush) (&work->medium, WV_QUEUE_INPUT); + retVal = (*work->medium.write) (&work->medium, bfr, 1); + if (retVal != 1) + { + radMsgLog (PRI_MEDIUM, "wakeupConsole: Write ERROR!"); + return ERROR; + } + + memset (bfr, 0, sizeof (bfr)); + + // If this is a Weatherlink IP interface, allow more time for the wakeup: + if (work->stationIsWLIP) + { + retVal = (*work->medium.read) (&work->medium, bfr, 2, 2000); + } + else if (retries == 4) + { + // Don't wait long on the first try for serial interfaces; + // This is often ignored by the console: + retVal = (*work->medium.read) (&work->medium, bfr, 2, 500); + } + else + { + // Wait longer after first try: + retVal = (*work->medium.read) (&work->medium, bfr, 2, 1200); + } + + if (retVal == 2 && bfr[0] == VP_LF && bfr[1] == VP_CR) + { + // good stuff: + return OK; + } + else + { + // radMsgLog (PRI_MEDIUM, "wakeupConsole: bad read: retVal=%d", retVal); + } + + retries -= 1; + } + + // If here, try a restart (if the medium supports it): + radMsgLog (PRI_MEDIUM, "wakeupConsole: failed"); + (*work->medium.restart) (&work->medium); + return ERROR; +} + + + +/* ... ----- API methods ----- +*/ +void vpifIndicateStationUp (void) +{ + radProcessEventsSend (NULL, STATION_INIT_COMPLETE_EVENT, 0); + return; +} + +void vpifIndicateLoopDone (void) +{ + radProcessEventsSend (NULL, STATION_LOOP_COMPLETE_EVENT, 0); + return; +} + +void vpifFlush (WVIEWD_WORK *work) +{ + (*work->medium.flush) (&work->medium, WV_QUEUE_INPUT); + return; +} + +int vpifWakeupConsole (WVIEWD_WORK *work) +{ + vpifFlush (work); + + return wakeupConsole (work); +} + +int vpifSynchronizeConsoleClock (WVIEWD_WORK *work) +{ + time_t ntime; + struct tm locTime; + int gmtOffset; + + /* ... wakeup the console + */ + if (vpifWakeupConsole (work) == ERROR) + { + radMsgLog (PRI_HIGH, "vpifSynchronizeConsoleClock: WAKEUP1 failed"); + return ERROR; + } + + /* ... set the console GMT offset + */ + if (vpifSetGMTOffset (work, &gmtOffset) == ERROR) + { + radMsgLog (PRI_HIGH, "vpifSynchronizeConsoleClock: GMT failed"); + return ERROR; + } + + /* ... wakeup the console + */ + if (vpifWakeupConsole (work) == ERROR) + { + radMsgLog (PRI_HIGH, "vpifSynchronizeConsoleClock: WAKEUP2 failed"); + return ERROR; + } + + /* ... set the console date and time + */ + ntime = time (NULL); + localtime_r (&ntime, &locTime); + + if (vpifSetTime (work, + locTime.tm_year + 1900, + locTime.tm_mon + 1, + locTime.tm_mday, + locTime.tm_hour, + locTime.tm_min, + locTime.tm_sec) + == ERROR) + { + radMsgLog (PRI_HIGH, "vpifSynchronizeConsoleClock: SETTIME failed"); + return ERROR; + } + + radMsgLog (PRI_STATUS, "station time synchronized to: %2.2d-%2.2d-%4.4d %2.2d:%2.2d:%2.2d", + locTime.tm_mon + 1, locTime.tm_mday, locTime.tm_year + 1900, + locTime.tm_hour, locTime.tm_min, locTime.tm_sec); + radMsgLog (PRI_STATUS, "station GMT offset synchronized to: %d hours, %d minutes", + gmtOffset/60, gmtOffset%60); + + // the VP console is cranky after a time reset + radUtilsSleep (1500); + + return OK; +} + +int vpifGetTime +( + WVIEWD_WORK *work, + uint16_t *year, + uint16_t *month, + uint16_t *day, + uint16_t *hour, + uint16_t *minute, + uint16_t *second +) +{ + int retVal, len; + uint8_t bfr[32]; + + strcpy ((char *)bfr, "GETTIME"); + len = strlen ((char *)bfr); + bfr[len] = VP_LF; + + retVal = (*work->medium.write) (&work->medium, bfr, len+1); + if (retVal != len+1) + { + return ERROR; + } + + retVal = (*work->medium.read) (&work->medium, bfr, 1, 1000); + if (retVal != 1) + { + return ERROR; + } + if (bfr[0] != VP_ACK) + { + return ERROR; + } + + memset (bfr, 0, sizeof (bfr)); + retVal = readWithCRC (work, bfr, 8, 1000); + if (retVal != 8) + { + return ERROR; + } + + *second = bfr[0]; + *minute = bfr[1]; + *hour = bfr[2]; + *day = bfr[3]; + *month = bfr[4]; + *year = bfr[5] + 1900; + + return OK; +} + +int vpifGetLatandLong +( + WVIEWD_WORK *work +) +{ + int retVal, len; + int16_t tmp[16]; + uint8_t *bfr = (uint8_t *)tmp; + + strcpy ((char *)bfr, "EEBRD 0B 2"); + len = strlen ((char *)bfr); + bfr[len] = VP_LF; + + if ((*work->medium.write) (&work->medium, bfr, len + 1) != len + 1) + { + return ERROR; + } + + if (vpifGetAck (work, 2000) == ERROR) + { + (*work->medium.flush) (&work->medium, WV_QUEUE_INPUT); + return ERROR; + } + + memset (bfr, 0, sizeof (tmp)); + retVal = readWithCRC (work, bfr, 4, 2000); + if (retVal != 4) + { + return ERROR; + } + + work->latitude = SHORT_SWAP(tmp[0]); + + strcpy ((char *)bfr, "EEBRD 0D 2"); + len = strlen ((char *)bfr); + bfr[len] = VP_LF; + + if ((*work->medium.write) (&work->medium, bfr, len + 1) != len + 1) + { + return ERROR; + } + + if (vpifGetAck (work, 2000) == ERROR) + { + (*work->medium.flush) (&work->medium, WV_QUEUE_INPUT); + return ERROR; + } + + memset (bfr, 0, sizeof (tmp)); + retVal = readWithCRC (work, bfr, 4, 2000); + if (retVal != 4) + { + return ERROR; + } + + work->longitude = SHORT_SWAP(tmp[0]); + + strcpy ((char *)bfr, "EEBRD 0F 2"); + len = strlen ((char *)bfr); + bfr[len] = VP_LF; + + if ((*work->medium.write) (&work->medium, bfr, len + 1) != len + 1) + { + return ERROR; + } + + if (vpifGetAck (work, 2000) == ERROR) + { + (*work->medium.flush) (&work->medium, WV_QUEUE_INPUT); + return ERROR; + } + + memset (bfr, 0, sizeof (tmp)); + retVal = readWithCRC (work, bfr, 4, 2000); + if (retVal != 4) + { + return ERROR; + } + + work->elevation = SHORT_SWAP(tmp[0]); + + return OK; +} + +int vpifGetArchiveInterval (WVIEWD_WORK *work) +{ + uint16_t temp[32]; // short align + uint8_t *ptr = (uint8_t *)temp; + int len, retVal; + ARCHIVE_INTERVAL *interval = (ARCHIVE_INTERVAL *)temp; + + strcpy ((char *)ptr, "EEBRD 2D 1"); + len = strlen ((char *)ptr); + ptr[len] = VP_LF; + + if ((*work->medium.write) (&work->medium, ptr, len + 1) != len + 1) + { + return ERROR; + } + + if (vpifGetAck (work, 3000) == ERROR) + { + (*work->medium.flush) (&work->medium, WV_QUEUE_INPUT); + return ERROR; + } + + retVal = readWithCRC (work, interval, sizeof (ARCHIVE_INTERVAL), 3000); + if (retVal != sizeof (ARCHIVE_INTERVAL)) + { + (*work->medium.flush) (&work->medium, WV_QUEUE_INPUT); + return ERROR; + } + + work->archiveInterval = interval->interval; + return OK; +} + + +int vpifGetRainCollectorSize (WVIEWD_WORK *work) +{ + int retVal, len; + int16_t tmp[16]; + uint8_t *bfr = (uint8_t *)tmp; + float tempfloat; + + strcpy ((char *)bfr, "EEBRD 2B 1"); + len = strlen ((char *)bfr); + bfr[len] = VP_LF; + + if ((*work->medium.write) (&work->medium, bfr, len + 1) != len + 1) + { + return ERROR; + } + + if (vpifGetAck (work, 2000) == ERROR) + { + (*work->medium.flush) (&work->medium, WV_QUEUE_INPUT); + return ERROR; + } + + memset (bfr, 0, sizeof (tmp)); + retVal = readWithCRC (work, bfr, 3, 2000); + if (retVal != 3) + { + return ERROR; + } + + retVal = bfr[0] & 0x30; + + if (retVal == 0x10) + { + vpWorkData.rainTicksPerInch = 127; // 0.2 mm = 0.007874 in + vpWorkData.RainCollectorType = 0x2000; + } + else if (retVal == 0x20) + { + vpWorkData.rainTicksPerInch = 254; // 0.1 mm = 0.003937 in + vpWorkData.RainCollectorType = 0x6000; + } + else + { + vpWorkData.rainTicksPerInch = 100; // 0.01 in = 0.254 mm + vpWorkData.RainCollectorType = 0x1000; + } + + return OK; +} + + +int vpifSetTime +( + WVIEWD_WORK *work, + uint16_t year, + uint16_t month, + uint16_t day, + uint16_t hour, + uint16_t minute, + uint16_t second +) +{ + int retVal, len; + uint16_t usbfr[16]; + uint8_t *bfr = (uint8_t *)usbfr; + uint16_t *crc; + + strcpy ((char *)bfr, "SETTIME"); + len = strlen ((char *)bfr); + bfr[len] = VP_LF; + + retVal = (*work->medium.write) (&work->medium, bfr, len+1); + if (retVal != len+1) + { + return ERROR; + } + + if (vpifGetAck (work, 2000) == ERROR) + { + (*work->medium.flush) (&work->medium, WV_QUEUE_INPUT); + return ERROR; + } + + bfr[0] = second; + bfr[1] = minute; + bfr[2] = hour; + bfr[3] = day; + bfr[4] = month; + bfr[5] = year - 1900; + crc = (uint16_t *)&bfr[6]; + *crc = SHORT_SWAP(genCRC (bfr, 6)); + + retVal = (*work->medium.write) (&work->medium, bfr, 8); + if (retVal != 8) + { + return ERROR; + } + + if (vpifGetAck (work, 2000) == ERROR) + { + (*work->medium.flush) (&work->medium, WV_QUEUE_INPUT); + return ERROR; + } + + return OK; +} + +int vpifSetGMTOffset +( + WVIEWD_WORK *work, + int *save +) +{ + int retVal, len, gmtOffsetHours, gmtOffsetMinutes; + int16_t tmp[16]; + uint8_t *bfr = (uint8_t *)tmp; + time_t nowtime = time (NULL); + struct tm bknTime; + long gmtMinsEast; + + localtime_r (&nowtime, &bknTime); + +#ifdef HAVE_STRUCT_TM_TM_ZONE + gmtMinsEast = bknTime.tm_gmtoff/60; +#else + gmtMinsEast = -(timezone/60); +#endif + + gmtOffsetHours = gmtMinsEast/60; + gmtOffsetMinutes = gmtMinsEast%60; + *save = gmtMinsEast; + + strcpy ((char *)bfr, "EEBWR 12 5"); + len = strlen ((char *)bfr); + bfr[len] = VP_LF; + + if ((*work->medium.write) (&work->medium, bfr, len + 1) != len + 1) + { + return ERROR; + } + + if (vpifGetAck (work, 2000) == ERROR) + { + (*work->medium.flush) (&work->medium, WV_QUEUE_INPUT); + return ERROR; + } + + memset (bfr, 0, 8); + + // set dst to manual and turn it off (it is reflected in the GMT offset) + bfr[0] = 1; + bfr[1] = 0; + + // set the GMT offset + tmp[1] = SHORT_SWAP((int16_t)((gmtOffsetHours * 100) + gmtOffsetMinutes)); + + // make sure the VP uses the GMT offset + bfr[4] = 1; + + retVal = writeWithCRC (work, bfr, 5); + if (retVal != 5) + { + return ERROR; + } + + return OK; +} + +/* ... this guy reads/parses msgs and updates the internal data stores; + ... returns OK or ERROR (except when ACK expected, then returns + ... ACK, NAK, or ERROR) +*/ +int vpifReadMessage (WVIEWD_WORK *work, int expectACK) +{ + uint16_t temp[VP_BYTE_LENGTH_MAX/2]; // short align + uint8_t *chPtr = (uint8_t *)temp; + ARCHIVE_PAGE *arcRec = (ARCHIVE_PAGE *)temp; + DMPAFT_HDR *dmphdr = (DMPAFT_HDR *)temp; + LOOP_DATA *loop = (LOOP_DATA *)temp; + int retVal; + + memset (temp, 0, sizeof(temp)); + switch (vpWorkData.reqMsgType) + { + case SER_MSG_ACK: + retVal = (*work->medium.read) (&work->medium, chPtr, 1, 1000); + if (retVal != 1) + { + return ERROR; + } + if (chPtr[0] == VP_ACK) + { + return VP_ACK; + } + else if (chPtr[0] == VP_NAK) + { + return VP_NAK; + } + else + { + return ERROR; + } + +#ifndef _VP_CONFIG_ONLY + case SER_MSG_DMPAFT_HDR: + if (expectACK) + { + if (vpifGetAck (work, 1000) == ERROR) + { + (*work->medium.flush) (&work->medium, WV_QUEUE_INPUT); + return ERROR; + } + } + retVal = readWithCRC (work, dmphdr, sizeof (DMPAFT_HDR), 3000); + if (retVal != sizeof (DMPAFT_HDR)) + { + (*work->medium.flush) (&work->medium, WV_QUEUE_INPUT); + return ERROR; + } + + vpWorkData.archivePages = SHORT_SWAP(dmphdr->pages); + vpWorkData.archiveRecOffset = SHORT_SWAP(dmphdr->firstRecIndex); + return OK; + + case SER_MSG_ARCHIVE: + retVal = readWithCRC (work, arcRec, sizeof (ARCHIVE_PAGE), 5000); + if (retVal != sizeof (ARCHIVE_PAGE)) + { + (*work->medium.flush) (&work->medium, WV_QUEUE_INPUT); + return ERROR; + } + + /* ... send ACK before processing to speed up download + */ + if (vpWorkData.archiveCurrentPage < vpWorkData.archivePages - 1) + { + if (vpifSendAck (work) == ERROR) + { + return ERROR; + } + } + + return (processArchivePage (work, arcRec)); + + case SER_MSG_LOOP: + if (expectACK) + { + if (vpifGetAck (work, 1000) == ERROR) + { + (*work->medium.flush) (&work->medium, WV_QUEUE_INPUT); + return ERROR; + } + } + retVal = readWithCRC (work, loop, sizeof (LOOP_DATA), 5000); + if (retVal != sizeof (LOOP_DATA)) + { + (*work->medium.flush) (&work->medium, WV_QUEUE_INPUT); + return ERROR; + } + + /* ... store in IPM format + */ + storeLoopPkt (work, loop); + + return OK; +#endif + + case SER_MSG_NONE: + default: + /* ... just flush the receive buffer, we're not expecting anything! + */ + (*work->medium.flush) (&work->medium, WV_QUEUE_INPUT); + return OK; + } +} + +int vpifGetAck (WVIEWD_WORK *work, int msWait) +{ + uint8_t temp[4]; + + if ((*work->medium.read) (&work->medium, temp, 1, msWait) != 1) + { + (*work->medium.flush) (&work->medium, WV_QUEUE_INPUT); + return ERROR; + } + + // this "flushing" of CR/LF's takes care of many problems with terminal + // servers who want to echo them or attach them to the end of packets - + // while not bothering cleaner true serial interfaces... + while (temp[0] == VP_LF || temp[0] == VP_CR) + { + // extra LF/CR, discard and read again + if ((*work->medium.read) (&work->medium, temp, 1, msWait) != 1) + { + (*work->medium.flush) (&work->medium, WV_QUEUE_INPUT); + return ERROR; + } + } + + if (temp[0] != VP_ACK) + { + (*work->medium.flush) (&work->medium, WV_QUEUE_INPUT); + return ERROR; + } + + return OK; +} + + +/* ... send msgs to the console; + ... will set the "reqMsgType" of the work area appropriately; + ... returns OK or ERROR +*/ +int vpifSendAck (WVIEWD_WORK *work) +{ + uint16_t temp[32]; // short align + uint8_t *ptr = (uint8_t *)temp; + + ptr[0] = VP_ACK; + + if ((*work->medium.write) (&work->medium, ptr, 1) != 1) + { + return ERROR; + } + + return OK; +} + +int vpifSendNak (WVIEWD_WORK *work) +{ + uint16_t temp[32]; // short align + uint8_t *ptr = (uint8_t *)temp; + + ptr[0] = VP_NAK; + + if ((*work->medium.write) (&work->medium, ptr, 1) != 1) + { + return ERROR; + } + + return OK; +} + +int vpifSendCancel (WVIEWD_WORK *work) +{ + uint16_t temp[32]; // short align + uint8_t *ptr = (uint8_t *)temp; + + ptr[0] = VP_CANCEL; + + if ((*work->medium.write) (&work->medium, ptr, 1) != 1) + { + return ERROR; + } + + return OK; +} + +int vpifSendDumpAfterRqst (WVIEWD_WORK *work) +{ + uint16_t temp[32]; // short align + uint8_t *ptr = (uint8_t *)temp; + int len; + + strcpy ((char *)ptr, "DMPAFT"); + len = strlen ((char *)ptr); + ptr[len] = VP_LF; + + if ((*work->medium.write) (&work->medium, ptr, len + 1) != len + 1) + { + return ERROR; + } + + vpWorkData.reqMsgType = SER_MSG_ACK; + return OK; +} + +int vpifSendDumpDateTimeRqst (WVIEWD_WORK *work) +{ + uint16_t temp[32]; // short align + uint16_t date, ntime; + + date = INSERT_PACKED_DATE(wvutilsGetYear(work->archiveDateTime), + wvutilsGetMonth(work->archiveDateTime), + wvutilsGetDay(work->archiveDateTime)); + ntime = (100 * wvutilsGetHour(work->archiveDateTime)) + + wvutilsGetMin(work->archiveDateTime); + + temp[0] = SHORT_SWAP(date); + temp[1] = SHORT_SWAP(ntime); + temp[2] = SHORT_SWAP(genCRC (temp, 4)); + + if ((*work->medium.write) (&work->medium, temp, 6) != 6) + { + return ERROR; + } + + vpWorkData.reqMsgType = SER_MSG_DMPAFT_HDR; + return OK; +} + +int vpifSendLoopRqst (WVIEWD_WORK *work, int number) +{ + uint16_t temp[32]; // short align + uint8_t *ptr = (uint8_t *)temp; + int len; + + sprintf ((char *)ptr, "LOOP %d", number); + len = strlen ((char *)ptr); + ptr[len] = VP_LF; + + if ((*work->medium.write) (&work->medium, ptr, len + 1) != len + 1) + { + return ERROR; + } + + vpWorkData.reqMsgType = SER_MSG_LOOP; + return OK; +} + +int vpifGetRXCheck (WVIEWD_WORK *work) +{ + int i, len, retVal, done; + uint16_t temp[VP_BYTE_LENGTH_MAX/2]; // short align + uint8_t *ptr = (uint8_t *)temp; + char *token; + int rxGood, rxMiss, rxCRC, tempint; + + vpWorkData.rxCheck[0] = 0; + strcpy ((char *)ptr, "RXCHECK"); + len = strlen ((char *)ptr); + ptr[len] = VP_CR; + + if ((*work->medium.write) (&work->medium, ptr, len + 1) != len + 1) + { + return ERROR; + } + + // lose the OK + if ((*work->medium.read) (&work->medium, ptr, 6, 2000) != 6) + { + (*work->medium.flush) (&work->medium, WV_QUEUE_INPUT); + return ERROR; + } + + memset (ptr, 0, 64); + for (i = 0, done = 0; (done == 0) && (i < 63); i ++) + { + retVal = (*work->medium.read) (&work->medium, &ptr[i], 1, 2000); + if (retVal != 1) + { + return ERROR; + } + + if (ptr[i] == VP_CR) + done = 1; + } + + if (!done || i < 3) + { + return ERROR; + } + + i -= 2; // lose the and + ptr[i] = 0; + + strcpy (vpWorkData.rxCheck, (char *)ptr); + + token = strtok ((char *)ptr, " "); + if (token == NULL) + { + return ERROR; + } + rxGood = atoi (token); + + token = strtok (NULL, " "); + if (token == NULL) + { + return ERROR; + } + rxMiss = atoi (token); + + // toss re-syncs + token = strtok (NULL, " "); + if (token == NULL) + { + return ERROR; + } + // toss good-in-a-row + token = strtok (NULL, " "); + if (token == NULL) + { + return ERROR; + } + + token = strtok (NULL, " "); + if (token == NULL) + { + return ERROR; + } + rxCRC = atoi (token); + if (rxCRC < 0) + rxCRC += 65536; + + // now we have something to work with + tempint = rxGood - vpWorkData.rxCheckGood; + vpWorkData.rxCheckGood = rxGood; + if (tempint < 0) + { + // must have rolled over the counts or manually reset + vpWorkData.rxCheckMissed = rxMiss; + vpWorkData.rxCheckCRC = rxCRC; + return OK; + } + else + { + rxGood = tempint; + } + tempint = rxMiss - vpWorkData.rxCheckMissed; + vpWorkData.rxCheckMissed = rxMiss; + if (tempint < 0) + { + // rolled over + vpWorkData.rxCheckCRC = rxCRC; + return OK; + } + else + { + rxMiss = tempint; + } + tempint = rxCRC - vpWorkData.rxCheckCRC; + vpWorkData.rxCheckCRC = rxCRC; + if (tempint < 0) + { + // rolled over + return OK; + } + else + { + rxCRC = tempint; + } + + // finally calculate the percentage + tempint = rxGood + rxMiss + rxCRC; + if (tempint > 0) + { + vpWorkData.rxCheckPercent = (100 * rxGood)/tempint; + } + else + { + vpWorkData.rxCheckPercent = 100; + } + +//radMsgLog(PRI_MEDIUM, "RXCHECK: %s", vpWorkData.rxCheck); + return OK; +} + + +//////// methods for vpconfig //////// +#ifdef _VP_CONFIG_ONLY + +int vpconfigGetArchiveInterval +( + WVIEWD_WORK *work +) +{ + int retVal, len; + uint16_t temp[VP_BYTE_LENGTH_MAX/2]; // short align + uint8_t *ptr = (uint8_t *)temp; + ARCHIVE_INTERVAL *interval = (ARCHIVE_INTERVAL *)temp; + + strcpy ((char *)ptr, "EEBRD 2D 1"); + len = strlen ((char *)ptr); + ptr[len] = VP_LF; + + if ((*work->medium.write) (&work->medium, ptr, len + 1) != len + 1) + { + return ERROR; + } + + if (vpifGetAck (work, 2000) == ERROR) + { + (*work->medium.flush) (&work->medium, WV_QUEUE_INPUT); + return ERROR; + } + retVal = readWithCRC (work, + interval, + sizeof (ARCHIVE_INTERVAL), + 2000); + if (retVal != sizeof (ARCHIVE_INTERVAL)) + { + (*work->medium.flush) (&work->medium, WV_QUEUE_INPUT); + return ERROR; + } + + work->archiveInterval = interval->interval; + + return OK; +} + +int vpconfigGetFWVersion +( + WVIEWD_WORK *work +) +{ + int i, retVal, len, done = 0; + uint16_t temp[VP_BYTE_LENGTH_MAX/2]; // short align + uint8_t *indexPtr, *ptr = (uint8_t *)temp; + + strcpy ((char *)ptr, "VER"); + len = strlen ((char *)ptr); + ptr[len] = VP_LF; + + if ((*work->medium.write) (&work->medium, ptr, len + 1) != len + 1) + { + return ERROR; + } + + // lose the OK + if ((*work->medium.read) (&work->medium, ptr, 6, 2000) != 6) + { + (*work->medium.flush) (&work->medium, WV_QUEUE_INPUT); + return ERROR; + } + + indexPtr = ptr; + memset (indexPtr, 0, 32); + while (!done) + { + retVal = (*work->medium.read) (&work->medium, indexPtr, 1, 2000); + if (retVal != 1) + { + done = 1; + } + + if (*indexPtr == VP_CR) + done = 1; + + indexPtr ++; + } + + len = indexPtr - ptr; + len -= 2; // lose the and + ptr[len] = 0; + + +#if 0 +printf ("@!@ GetRXCheck: "); +for (i = 0; i < len; i ++) + printf ("%2.2X, ", ptr[i]); +printf ("\n len = %d\n", len); +#endif + + strcpy (vpWorkData.fwVersion, (char *)ptr); + + return OK; +} + +int vpconfigGetRainSeasonStart +( + WVIEWD_WORK *work +) +{ + int retVal, len; + int16_t tmp[16]; + uint8_t *bfr = (uint8_t *)tmp; + + strcpy ((char *)bfr, "EEBRD 2C 1"); + len = strlen ((char *)bfr); + bfr[len] = VP_LF; + + if ((*work->medium.write) (&work->medium, bfr, len + 1) != len + 1) + { + return ERROR; + } + + if (vpifGetAck (work, 2000) == ERROR) + { + (*work->medium.flush) (&work->medium, WV_QUEUE_INPUT); + return ERROR; + } + + memset (bfr, 0, sizeof (tmp)); + retVal = readWithCRC (work, bfr, 3, 2000); + if (retVal != 3) + { + return ERROR; + } + + vpWorkData.rainSeasonStart = bfr[0]; + + return OK; +} + +int vpconfigGetWindDirectionCal +( + WVIEWD_WORK *work +) +{ + int retVal, len; + int16_t tmp[16]; + uint8_t *bfr = (uint8_t *)tmp; + + strcpy ((char *)bfr, "EEBRD 4D 2"); + len = strlen ((char *)bfr); + bfr[len] = VP_LF; + + if ((*work->medium.write) (&work->medium, bfr, len + 1) != len + 1) + { + return ERROR; + } + + if (vpifGetAck (work, 2000) == ERROR) + { + (*work->medium.flush) (&work->medium, WV_QUEUE_INPUT); + return ERROR; + } + + memset (bfr, 0, sizeof (tmp)); + retVal = readWithCRC (work, bfr, 4, 2000); + if (retVal != 4) + { + return ERROR; + } + + vpWorkData.windDirectionCal = SHORT_SWAP(tmp[0]); + + return OK; +} + +int vpconfigGetTransmitters +( + WVIEWD_WORK *work +) +{ + int retVal, len; + uint16_t tmp[16]; + uint8_t *bfr = (uint8_t *)tmp; + + strcpy ((char *)bfr, "EEBRD 17 12"); + len = strlen ((char *)bfr); + bfr[len] = VP_LF; + + if ((*work->medium.write) (&work->medium, bfr, len + 1) != len + 1) + { + return ERROR; + } + + if (vpifGetAck (work, 2000) == ERROR) + { + (*work->medium.flush) (&work->medium, WV_QUEUE_INPUT); + return ERROR; + } + + memset (bfr, 0, sizeof (tmp)); + retVal = readWithCRC (work, bfr, 20, 2000); + if (retVal != 20) + { + return ERROR; + } + + vpWorkData.listenChannels = bfr[0]; + vpWorkData.retransmitChannel = bfr[1]; + memcpy(vpWorkData.transmitterType, bfr + 2, 16); + + return OK; +} + +int vpconfigGetRainCollectorSize +( + WVIEWD_WORK *work +) +{ + int retVal, len; + uint16_t tmp[16]; + uint8_t *bfr = (uint8_t *)tmp; + + strcpy ((char *)bfr, "EEBRD 2B 1"); + len = strlen ((char *)bfr); + bfr[len] = VP_LF; + + if ((*work->medium.write) (&work->medium, bfr, len + 1) != len + 1) + { + return ERROR; + } + + if (vpifGetAck (work, 2000) == ERROR) + { + (*work->medium.flush) (&work->medium, WV_QUEUE_INPUT); + return ERROR; + } + + memset (bfr, 0, sizeof (tmp)); + retVal = readWithCRC (work, bfr, 3, 2000); + if (retVal != 3) + { + return ERROR; + } + + vpWorkData.rainCollectorSize = (bfr[0] >> 4) & 0x3; + + return OK; +} + +int vpconfigGetWindCupSize +( + WVIEWD_WORK *work +) +{ + int retVal, len; + uint16_t tmp[16]; + uint8_t *bfr = (uint8_t *)tmp; + + strcpy ((char *)bfr, "EEBRD 2B 1"); + len = strlen ((char *)bfr); + bfr[len] = VP_LF; + + if ((*work->medium.write) (&work->medium, bfr, len + 1) != len + 1) + { + return ERROR; + } + + if (vpifGetAck (work, 2000) == ERROR) + { + (*work->medium.flush) (&work->medium, WV_QUEUE_INPUT); + return ERROR; + } + + memset (bfr, 0, sizeof (tmp)); + retVal = readWithCRC (work, bfr, 3, 2000); + if (retVal != 3) + { + return ERROR; + } + + vpWorkData.windCupSize = (bfr[0] >> 3) & 0x1; + + return OK; +} + +int vpconfigSetInterval +( + WVIEWD_WORK *work, + int interval +) +{ + int i, retVal, len, done = 0; + uint16_t usbfr[16]; + uint8_t *indexPtr, *bfr = (uint8_t *)usbfr; + + sprintf ((char *)bfr, "SETPER %d", interval); + len = strlen ((char *)bfr); + bfr[len] = VP_LF; + + retVal = (*work->medium.write) (&work->medium, bfr, len+1); + if (retVal != len+1) + { + return ERROR; + } + + // lose the + if ((*work->medium.read) (&work->medium, bfr, 2, 5000) != 2) + { + (*work->medium.flush) (&work->medium, WV_QUEUE_INPUT); + return ERROR; + } + + indexPtr = bfr; + memset (indexPtr, 0, 32); + while (!done) + { + retVal = (*work->medium.read) (&work->medium, indexPtr, 1, 2000); + if (retVal != 1) + { + done = 1; + } + + indexPtr ++; + } + + len = (indexPtr-1) - bfr; + len -= 2; // lose the and + bfr[len] = 0; + + if (strncmp ((char *)bfr, "OK", 2)) + { + return ERROR; + } + + sleep (1); + + sprintf ((char *)bfr, "NEWSETUP"); + len = strlen ((char *)bfr); + bfr[len] = VP_LF; + + retVal = (*work->medium.write) (&work->medium, bfr, len+1); + if (retVal != len+1) + { + return ERROR; + } + + if (vpifGetAck (work, 5000) == ERROR) + { + (*work->medium.flush) (&work->medium, WV_QUEUE_INPUT); + return ERROR; + } + + return OK; +} + +int vpconfigClearArchiveMemory +( + WVIEWD_WORK *work +) +{ + int retVal, len; + uint16_t usbfr[16]; + uint8_t *bfr = (uint8_t *)usbfr; + + sprintf ((char *)bfr, "CLRLOG"); + len = strlen ((char *)bfr); + bfr[len] = VP_LF; + + retVal = (*work->medium.write) (&work->medium, bfr, len+1); + if (retVal != len+1) + { + return ERROR; + } + + if (vpifGetAck (work, 2000) == ERROR) + { + (*work->medium.flush) (&work->medium, WV_QUEUE_INPUT); + return ERROR; + } + + return OK; +} + +int vpconfigSetElevation +( + WVIEWD_WORK *work, + int value +) +{ + int retVal, len, done = 0; + uint16_t usbfr[16]; + uint8_t *indexPtr, *bfr = (uint8_t *)usbfr; + + sprintf ((char *)bfr, "BAR=0 %d", value); + len = strlen ((char *)bfr); + bfr[len] = VP_CR; + bfr[len+1] = VP_LF; + + retVal = (*work->medium.write) (&work->medium, bfr, len+2); + if (retVal != len+2) + { + return ERROR; + } + + // lose the + if ((*work->medium.read) (&work->medium, bfr, 2, 5000) != 2) + { + (*work->medium.flush) (&work->medium, WV_QUEUE_INPUT); + return ERROR; + } + + indexPtr = bfr; + memset (indexPtr, 0, 32); + while (!done) + { + retVal = (*work->medium.read) (&work->medium, indexPtr, 1, 5000); + if (retVal != 1) + { + done = 1; + } + + if (*indexPtr == VP_CR) + done = 1; + + indexPtr ++; + } + + len = indexPtr - bfr; + len -= 2; // lose the and + bfr[len] = 0; + + if (strncmp ((char *)bfr, "OK", 2)) + { + return ERROR; + } + + return OK; +} + +int vpconfigSetGain +( + WVIEWD_WORK *work, + int on +) +{ + int i, retVal, len, done = 0; + uint16_t temp[VP_BYTE_LENGTH_MAX/2]; // short align + uint8_t *indexPtr, *ptr = (uint8_t *)temp; + + sprintf ((char *)ptr, "GAIN %d", on); + len = strlen ((char *)ptr); + ptr[len] = VP_LF; + + if ((*work->medium.write) (&work->medium, ptr, len + 1) != len + 1) + { + return ERROR; + } + + // lose the + if ((*work->medium.read) (&work->medium, ptr, 2, 2000) != 2) + { + (*work->medium.flush) (&work->medium, WV_QUEUE_INPUT); + return ERROR; + } + + indexPtr = ptr; + memset (indexPtr, 0, 32); + while (!done) + { + retVal = (*work->medium.read) (&work->medium, indexPtr, 1, 2000); + if (retVal != 1) + { + done = 1; + } + + if (*indexPtr == VP_CR) + done = 1; + + indexPtr ++; + } + + len = indexPtr - ptr; + len -= 2; // lose the and + ptr[len] = 0; + + if (!strncmp ((char *)ptr, "OK", 2)) + return OK; + else + return ERROR; + + return OK; +} + +int vpconfigSetLatandLong +( + WVIEWD_WORK *work, + int latitude, + int longitude +) +{ + int retVal, len; + int16_t tmp[16], lat1, long1; + uint8_t *bfr = (uint8_t *)tmp; + + strcpy ((char *)bfr, "EEBWR 0B 4"); + len = strlen ((char *)bfr); + bfr[len] = VP_LF; + + if ((*work->medium.write) (&work->medium, bfr, len + 1) != len + 1) + { + return ERROR; + } + + if (vpifGetAck (work, 2000) == ERROR) + { + (*work->medium.flush) (&work->medium, WV_QUEUE_INPUT); + return ERROR; + } + + lat1 = (int16_t)latitude; + long1 = (int16_t)longitude; + memset (bfr, 0, 4); + tmp[0] = SHORT_SWAP(lat1); + tmp[1] = SHORT_SWAP(long1); + retVal = writeWithCRC (work, bfr, 4); + if (retVal != 4) + { + return ERROR; + } + + sleep (3); + + sprintf ((char *)bfr, "NEWSETUP"); + len = strlen ((char *)bfr); + bfr[len] = VP_LF; + + retVal = (*work->medium.write) (&work->medium, bfr, len+1); + if (retVal != len+1) + { + return ERROR; + } + + if (vpifGetAck (work, 5000) == ERROR) + { + (*work->medium.flush) (&work->medium, WV_QUEUE_INPUT); + return ERROR; + } + + return OK; +} + +int vpconfigSetRainSeasonStart +( + WVIEWD_WORK *work, + int startMonth +) +{ + int retVal, len; + int16_t tmp[16]; + uint8_t *bfr = (uint8_t *)tmp; + + strcpy ((char *)bfr, "EEBWR 2C 1"); + len = strlen ((char *)bfr); + bfr[len] = VP_LF; + + if ((*work->medium.write) (&work->medium, bfr, len + 1) != len + 1) + { + return ERROR; + } + + if (vpifGetAck (work, 2000) == ERROR) + { + (*work->medium.flush) (&work->medium, WV_QUEUE_INPUT); + return ERROR; + } + + memset (bfr, 0, sizeof (tmp)); + bfr[0] = startMonth; + retVal = writeWithCRC (work, bfr, 1); + if (retVal != 1) + { + return ERROR; + } + + return OK; +} + +int vpconfigSetRainYearToDate +( + WVIEWD_WORK *work, + float rainAmount +) +{ + int retVal, len; + int16_t tmp[16]; + uint8_t *bfr = (uint8_t *)tmp; + + rainAmount *=1000; + rainAmount = (int)rainAmount; + rainAmount += 1; + rainAmount /= 10; + + retVal = (int)(rainAmount); // rain collector = 0.01 inches + sprintf ((char *)bfr, "PUTRAIN %d", retVal); + len = strlen ((char *)bfr); + bfr[len] = VP_LF; + + if ((*work->medium.write) (&work->medium, bfr, len + 1) != len + 1) + { + return ERROR; + } + + if (vpifGetAck (work, 2000) == ERROR) + { + (*work->medium.flush) (&work->medium, WV_QUEUE_INPUT); + return ERROR; + } + + return OK; +} + +int vpconfigSetETYearToDate +( + WVIEWD_WORK *work, + float etAmount +) +{ + int retVal, len; + int16_t tmp[16]; + uint8_t *bfr = (uint8_t *)tmp; + + etAmount *=1000; + etAmount = (int)etAmount; + etAmount += 1; + etAmount /= 10; + + retVal = (int)etAmount; // rain collector = 0.01 inches + sprintf ((char *)bfr, "PUTET %d", retVal); + len = strlen ((char *)bfr); + bfr[len] = VP_LF; + + if ((*work->medium.write) (&work->medium, bfr, len + 1) != len + 1) + { + return ERROR; + } + + if (vpifGetAck (work, 2000) == ERROR) + { + (*work->medium.flush) (&work->medium, WV_QUEUE_INPUT); + return ERROR; + } + + return OK; +} + +int vpconfigSetWindDirectionCal +( + WVIEWD_WORK *work, + int offset +) +{ + int retVal, len; + int16_t tmp[16]; + uint8_t *bfr = (uint8_t *)tmp; + + strcpy ((char *)bfr, "EEBWR 4D 2"); + len = strlen ((char *)bfr); + bfr[len] = VP_LF; + + if ((*work->medium.write) (&work->medium, bfr, len + 1) != len + 1) + { + return ERROR; + } + + if (vpifGetAck (work, 2000) == ERROR) + { + (*work->medium.flush) (&work->medium, WV_QUEUE_INPUT); + return ERROR; + } + + memset (bfr, 0, sizeof (tmp)); + tmp[0] = SHORT_SWAP((int16_t) offset); + retVal = writeWithCRC (work, bfr, 2); + if (retVal != 2) + { + return ERROR; + } + + return OK; +} + +int vpconfigSetSensor +( + WVIEWD_WORK *work, + int channel, + VPRO_SENSOR_TYPES type +) +{ + int retVal, len, i, temperature_id = 0, humidity_id = 1; + uint16_t tmp[16]; + uint8_t *bfr = (uint8_t *)tmp; + + // Fetch existing transmitter information. + if (vpconfigGetTransmitters(work) != OK) + { + return ERROR; + } + + // Set or clear the USETX bit corresponding to the channel. + if (type == VPRO_SENSOR_NONE) + { + vpWorkData.listenChannels &= ~((uint8_t) 1 << (channel-1)); + } + else + { + vpWorkData.listenChannels |= ((uint8_t) 1 << (channel-1)); + } + + // There can be only one ISS. + if (type == VPRO_SENSOR_ISS) + { + for (i = 0; i < 8; i++) + { + if (channel == i+1) + { + continue; + } + + if ((vpWorkData.transmitterType[i*2] & 0xF) == VPRO_SENSOR_ISS) + { + // Downgrade to temperature/humidity sensor. + vpWorkData.transmitterType[i*2] = VPRO_SENSOR_TEMP_HUM; + } + } + } + + // Assign the sensor type on this channel. + vpWorkData.transmitterType[(channel-1)*2] = type; + + // Extra temperature/humidity sensors must be sequential. + for (i = 0; i < 8; i ++) + { + if (vpWorkData.transmitterType[i*2] == VPRO_SENSOR_TEMP) + { + vpWorkData.transmitterType[i*2+1] = temperature_id++; + } + else if (vpWorkData.transmitterType[i*2] == VPRO_SENSOR_HUM) + { + vpWorkData.transmitterType[i*2+1] = humidity_id++ << 4; + } + else if (vpWorkData.transmitterType[i*2] == VPRO_SENSOR_TEMP_HUM) + { + vpWorkData.transmitterType[i*2+1] = humidity_id++ << 4 | temperature_id++; + } + } + + // Send the new transmitter setup to the console. + strcpy ((char *)bfr, "EEBWR 17 12"); + len = strlen ((char *)bfr); + bfr[len] = VP_LF; + + if ((*work->medium.write) (&work->medium, bfr, len + 1) != len + 1) + { + return ERROR; + } + + if (vpifGetAck (work, 2000) == ERROR) + { + (*work->medium.flush) (&work->medium, WV_QUEUE_INPUT); + return ERROR; + } + + memset (bfr, 0, sizeof (tmp)); + bfr[0] = vpWorkData.listenChannels; + bfr[1] = vpWorkData.retransmitChannel; + memcpy(bfr + 2, vpWorkData.transmitterType, 16); + + retVal = writeWithCRC (work, bfr, 18); + if (retVal != 18) + { + return ERROR; + } + + sleep (1); + + // Setting the retransmit channel requires clearing the archive memory + sprintf ((char *)bfr, "NEWSETUP"); + len = strlen ((char *)bfr); + bfr[len] = VP_LF; + + retVal = (*work->medium.write) (&work->medium, bfr, len+1); + if (retVal != len+1) + { + return ERROR; + } + + retVal = (*work->medium.read) (&work->medium, bfr, 1, 5000); + if (retVal != 1) + { + return ERROR; + } + if (bfr[0] != VP_ACK) + { + return ERROR; + } + return OK; +} + +int vpconfigSetRetransmitChannel +( + WVIEWD_WORK *work, + int channel +) +{ + int retVal, len; + int16_t tmp[16]; + uint8_t *bfr = (uint8_t *)tmp; + + strcpy ((char *)bfr, "EEBWR 18 1"); + len = strlen ((char *)bfr); + bfr[len] = VP_LF; + + if ((*work->medium.write) (&work->medium, bfr, len + 1) != len + 1) + { + return ERROR; + } + + if (vpifGetAck (work, 2000) == ERROR) + { + (*work->medium.flush) (&work->medium, WV_QUEUE_INPUT); + return ERROR; + } + + memset (bfr, 0, sizeof (tmp)); + bfr[0] = channel; + retVal = writeWithCRC (work, bfr, 1); + if (retVal != 1) + { + return ERROR; + } + + sleep (1); + + // Setting the retransmit channel requires clearing the archive memory + sprintf ((char *)bfr, "NEWSETUP"); + len = strlen ((char *)bfr); + bfr[len] = VP_LF; + + retVal = (*work->medium.write) (&work->medium, bfr, len+1); + if (retVal != len+1) + { + return ERROR; + } + + if (vpifGetAck (work, 5000) == ERROR) + { + (*work->medium.flush) (&work->medium, WV_QUEUE_INPUT); + return ERROR; + } + + return OK; +} + +int vpconfigSetRainCollectorSize +( + WVIEWD_WORK *work, + VPRO_RAIN_COLLECTOR_SIZE size +) +{ + int retVal, len; + uint16_t tmp[16]; + uint8_t *bfr = (uint8_t *)tmp, newval; + + strcpy ((char *)bfr, "EEBRD 2B 1"); + len = strlen ((char *)bfr); + bfr[len] = VP_LF; + + if ((*work->medium.write) (&work->medium, bfr, len + 1) != len + 1) + { + return ERROR; + } + + if (vpifGetAck (work, 2000) == ERROR) + { + (*work->medium.flush) (&work->medium, WV_QUEUE_INPUT); + return ERROR; + } + + memset (bfr, 0, sizeof (tmp)); + retVal = readWithCRC (work, bfr, 3, 2000); + if (retVal != 3) + { + return ERROR; + } + + newval = bfr[0] & 0xCF | ((int) size << 4); + + strcpy ((char *)bfr, "EEBWR 2B 1"); + len = strlen ((char *)bfr); + bfr[len] = VP_LF; + + if ((*work->medium.write) (&work->medium, bfr, len + 1) != len + 1) + { + return ERROR; + } + + if (vpifGetAck (work, 2000) == ERROR) + { + (*work->medium.flush) (&work->medium, WV_QUEUE_INPUT); + return ERROR; + } + + memset (bfr, 0, sizeof (tmp)); + bfr[0] = newval; + retVal = writeWithCRC (work, bfr, 1); + if (retVal != 1) + { + return ERROR; + } + + return OK; +} + +int vpconfigSetWindCupSize +( + WVIEWD_WORK *work, + int isLarge +) +{ + int retVal, len; + uint16_t tmp[16]; + uint8_t *bfr = (uint8_t *)tmp, newval; + + strcpy ((char *)bfr, "EEBRD 2B 1"); + len = strlen ((char *)bfr); + bfr[len] = VP_LF; + + if ((*work->medium.write) (&work->medium, bfr, len + 1) != len + 1) + { + return ERROR; + } + + if (vpifGetAck (work, 2000) == ERROR) + { + (*work->medium.flush) (&work->medium, WV_QUEUE_INPUT); + return ERROR; + } + + memset (bfr, 0, sizeof (tmp)); + retVal = readWithCRC (work, bfr, 3, 2000); + if (retVal != 3) + { + return ERROR; + } + + newval = bfr[0] & 0xF7 | (isLarge == 0 ? 0 : 0x8); + + strcpy ((char *)bfr, "EEBWR 2B 1"); + len = strlen ((char *)bfr); + bfr[len] = VP_LF; + + if ((*work->medium.write) (&work->medium, bfr, len + 1) != len + 1) + { + return ERROR; + } + + if (vpifGetAck (work, 2000) == ERROR) + { + (*work->medium.flush) (&work->medium, WV_QUEUE_INPUT); + return ERROR; + } + + memset (bfr, 0, sizeof (tmp)); + bfr[0] = newval; + retVal = writeWithCRC (work, bfr, 1); + if (retVal != 1) + { + return ERROR; + } + + return OK; +} +#endif + diff --git a/stations/VantagePro/vproInterface.h b/stations/VantagePro/vproInterface.h new file mode 100644 index 0000000..b7d0ebe --- /dev/null +++ b/stations/VantagePro/vproInterface.h @@ -0,0 +1,379 @@ +#ifndef INC_vproInterfaceh +#define INC_vproInterfaceh +/*--------------------------------------------------------------------------- + + FILENAME: + vproInterface.h + + PURPOSE: + Provide non-medium-specific utilities for the Davis Vantage Pro + interface protocol. + + REVISION HISTORY: + Date Engineer Revision Remarks + 06/07/2005 M.S. Teel 0 Original + 04/12/2008 W. Krenn 1 rainTicksPerInch + RainCollectorType + + NOTES: + + + LICENSE: + Copyright (c) 2004, Mark S. Teel (mark@teel.ws) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +/* ... System include files +*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* ... Library include files +*/ +#include +#include +#include +#include + +/* ... Local include files +*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* ... some definitions +*/ + +#define VP_BYTE_LENGTH_MAX SERIAL_BYTE_LENGTH_MAX + +#define VP_ACK 0x06 +#define VP_NAK 0x21 +#define VP_CANCEL 0x1B +#define VP_BADCRC 0x18 +#define VP_CR 0x0D +#define VP_LF 0x0A + + +#define VP_RESPONSE_TIMEOUT(isIP) ((isIP) ? 10000L : 5000L) + +// time to wait before attempting archive record again: +#define VPIF_RETRY_ARCHIVE_INTERVAL 10000L + +#define VP_PARM_DO_RXCHECK "DO_RXCHECK" + + +// ... define the message types we receive +typedef enum +{ + SER_MSG_ACK = 1, + SER_MSG_ARCHIVE = 2, + SER_MSG_DMPAFT_HDR = 3, + SER_MSG_LOOP = 4, + SER_MSG_NONE = -1 +} SER_MSG_TYPES; + + +// ... define the messages we receive +typedef struct +{ + uint8_t interval; + uint16_t crc; +}__attribute__ ((packed)) ARCHIVE_INTERVAL; + +typedef struct +{ + uint16_t pages; + uint16_t firstRecIndex; + uint16_t crc; +}__attribute__ ((packed)) DMPAFT_HDR; + +typedef struct +{ + uint8_t seqNo; + ARCHIVE_RECORD record[5]; + uint8_t unused[4]; + uint16_t crc; +}__attribute__ ((packed)) ARCHIVE_PAGE; + +// ... serial format +/* ... define the LOOP (current readings) serial data format +*/ +typedef struct +{ + uint8_t name[4]; + uint8_t type; + uint16_t nextRecord; + uint16_t barometer; + int16_t inTemp; + uint8_t inHumidity; + int16_t outTemp; + uint8_t windSpeed; + uint8_t tenMinuteAvgWindSpeed; + uint16_t windDir; + uint8_t extraTemp1; + uint8_t extraTemp2; + uint8_t extraTemp3; + uint8_t resvExtraTemps[4]; + uint8_t soilTemp1; + uint8_t soilTemp2; + uint8_t soilTemp3; + uint8_t soilTemp4; + uint8_t leafTemp1; + uint8_t leafTemp2; + uint8_t resvLeafTemp[2]; + uint8_t outHumidity; + uint8_t extraHumid1; + uint8_t extraHumid2; + uint8_t resvHumidity[5]; + uint16_t rainRate; + uint8_t UV; + uint16_t radiation; + uint16_t stormRain; + uint16_t stormStartDate; + uint16_t dayRain; + uint16_t monthRain; + uint16_t yearRain; + uint16_t dayET; + uint16_t monthET; + uint16_t yearET; + uint8_t soilMoist1; + uint8_t soilMoist2; + uint8_t resvSoilMoist[2]; + uint8_t leafWet1; + uint8_t leafWet2; + uint8_t resvLeafWet[2]; + uint8_t resvAlarms[16]; + uint8_t txBatteryStatus; + uint16_t consBatteryVoltage; + uint8_t forecastIcon; + uint8_t forecastRule; + uint16_t sunrise; + uint16_t sunset; + uint8_t lf; + uint8_t cr; + uint16_t crc; +}__attribute__ ((packed)) LOOP_DATA; + + +// define Vantage Pro specific interface data here +typedef struct +{ + STATES_ID stateMachine; + SER_MSG_TYPES reqMsgType; + int16_t elevation; + uint16_t archivePages; + uint16_t archiveCurrentPage; + uint16_t archiveRecOffset; + int rxCheckGood; + int rxCheckMissed; + int rxCheckCRC; + int doRXCheck; + char rxCheck[64]; + uint16_t rxCheckPercent; + int timeSyncFlag; + int archiveRetryFlag; + int doLoopFlag; + int doArchiveFlag; + int sampleRain; // to track dailyRain changes + int sampleET; // to track dayET changes + + // vpconfig only + char fwVersion[32]; + int rainSeasonStart; + int16_t windDirectionCal; + uint8_t listenChannels; + uint8_t retransmitChannel; + uint8_t transmitterType[16]; + int rainCollectorSize; + int windCupSize; + + // rain + uint16_t RainCollectorType; + float rainTicksPerInch; + +} VP_IF_DATA; + + +// define some extra stimulus types for our purposes +typedef enum +{ + VP_STIM_READINGS = STIM_IO + 1, + VP_STIM_ARCHIVE = STIM_IO + 2 +} VPStims; + + +// ... function prototypes + +// ... flush the input buffer from the VP +extern void vpifFlush (WVIEWD_WORK *work); + +// ... wake up the Vantage Pro console; +// ... returns OK or ERROR +extern int vpifWakeupConsole (WVIEWD_WORK *work); + +// ... get the date and time from the Vantage Pro console; +// ... returns OK or ERROR +extern int vpifGetTime +( + WVIEWD_WORK *work, + uint16_t *year, + uint16_t *month, + uint16_t *day, + uint16_t *hour, + uint16_t *minute, + uint16_t *second +); + +// ... get the console's stored latitude and longitude +// ... return OK or ERROR +extern int vpifGetLatandLong +( + WVIEWD_WORK *work +); + +// ... get the console's archive interval +extern int vpifGetArchiveInterval (WVIEWD_WORK *work); + +// ... get the console's rain collector size: +extern int vpifGetRainCollectorSize (WVIEWD_WORK *work); + +// ... set the date and time for the Vantage Pro console; +// ... returns OK or ERROR +extern int vpifSetTime +( + WVIEWD_WORK *work, + uint16_t year, // ex: 2001 1998 2004 + uint16_t month, + uint16_t day, + uint16_t hour, // 24 hours + uint16_t minute, + uint16_t second +); + +// ... set the GMT offset for the Vantage Pro console; +// ... returns OK or ERROR +extern int vpifSetGMTOffset +( + WVIEWD_WORK *work, + int *offset +); + +extern int vpifGetAck (WVIEWD_WORK *work, int msWait); +extern int vpifGetRXCheck (WVIEWD_WORK *work); +extern void vpifIndicateStationUp (void); +extern void vpifIndicateLoopDone (void); +extern int vpifSynchronizeConsoleClock (WVIEWD_WORK *work); + + +// ... this guy reads/parses msgs and updates the internal data stores; +// ... uses the "reqMsgType" of the work area to determine msg type to read; +// ... returns OK or ERROR +extern int vpifReadMessage (WVIEWD_WORK *work, int expectACKFirst); + + +// ... send msgs to the console; +// ... will set the "reqMsgType" of the work area appropriately; +// ... returns OK or ERROR +extern int vpifSendAck (WVIEWD_WORK *work); +extern int vpifSendNak (WVIEWD_WORK *work); +extern int vpifSendCancel (WVIEWD_WORK *work); +extern int vpifSendDumpAfterRqst (WVIEWD_WORK *work); +extern int vpifSendDumpDateTimeRqst (WVIEWD_WORK *work); +extern int vpifSendLoopRqst (WVIEWD_WORK *work, int number); + + +// ... define the VP state machine states +typedef enum +{ + VPRO_STATE_STARTPROC = 1, + VPRO_STATE_RUN, + VPRO_STATE_DMPAFT_RQST, + VPRO_STATE_DMPAFT_ACK, + VPRO_STATE_RECV_ARCH, + VPRO_STATE_LOOP_RQST, + VPRO_STATE_READ_RECOVER, + VPRO_STATE_ERROR +} VPRO_STATES; + +extern int vproStartProcState (int state, void *stimulus, void *data); +extern int vproRunState (int state, void *stimulus, void *data); +extern int vproDumpAfterState (int state, void *stimulus, void *data); +extern int vproDumpAfterAckState (int state, void *stimulus, void *data); +extern int vproReceiveArchiveState (int state, void *stimulus, void *data); +extern int vproLoopState (int state, void *stimulus, void *data); +extern int vproReadRecoverState (int state, void *stimulus, void *data); +extern int vproStopState (int state, void *stimulus, void *data); +extern int vproErrorState (int state, void *stimulus, void *data); + + +#ifdef _VP_CONFIG_ONLY +typedef enum +{ + VPRO_SENSOR_ISS = 0x00, + VPRO_SENSOR_TEMP = 0x01, + VPRO_SENSOR_HUM = 0x02, + VPRO_SENSOR_TEMP_HUM = 0x03, + VPRO_SENSOR_WIND = 0x04, + VPRO_SENSOR_RAIN = 0x05, + VPRO_SENSOR_LEAF = 0x06, + VPRO_SENSOR_SOIL = 0x07, + VPRO_SENSOR_LEAF_SOIL = 0x08, + VPRO_SENSOR_SENSORLINK = 0x09, + VPRO_SENSOR_NONE = 0x0A +} VPRO_SENSOR_TYPES; + +typedef enum +{ + VPRO_RAIN_COLLECTOR_0_01_IN = 0x0, + VPRO_RAIN_COLLECTOR_0_2_MM = 0x1, + VPRO_RAIN_COLLECTOR_0_1_MM = 0x2 +} VPRO_RAIN_COLLECTOR_SIZE; + +// ... methods for vpconfig only +extern int vpconfigGetArchiveInterval (WVIEWD_WORK *work); +extern int vpconfigGetFWVersion (WVIEWD_WORK *work); +extern int vpconfigGetRainSeasonStart (WVIEWD_WORK *work); +extern int vpconfigGetWindDirectionCal (WVIEWD_WORK *work); +extern int vpconfigGetTransmitters (WVIEWD_WORK *work); +extern int vpconfigGetRainCollectorSize (WVIEWD_WORK *work); +extern int vpconfigGetWindCupSize (WVIEWD_WORK *work); +extern int vpconfigSetInterval (WVIEWD_WORK *work, int interval); +extern int vpconfigClearArchiveMemory (WVIEWD_WORK *work); +extern int vpconfigSetElevation (WVIEWD_WORK *work, int elevation); +extern int vpconfigSetGain (WVIEWD_WORK *work, int on); +extern int vpconfigSetLatandLong (WVIEWD_WORK *work, int latitude, int longitude); +extern int vpconfigSetRainSeasonStart (WVIEWD_WORK *work, int startMonth); +extern int vpconfigSetRainYearToDate (WVIEWD_WORK *work, float rainAmount); +extern int vpconfigSetETYearToDate (WVIEWD_WORK *work, float etAmount); +extern int vpconfigSetWindDirectionCal (WVIEWD_WORK *work, int offset); +extern int vpconfigSetSensor (WVIEWD_WORK *work, int channel, VPRO_SENSOR_TYPES sensorType); +extern int vpconfigSetRetransmitChannel (WVIEWD_WORK *work, int channel); +extern int vpconfigSetRainCollectorSize (WVIEWD_WORK *work, VPRO_RAIN_COLLECTOR_SIZE rainCollectorSize); +extern int vpconfigSetWindCupSize (WVIEWD_WORK *work, int isLarge); +#endif + +#endif + diff --git a/stations/VantagePro/vproStates.c b/stations/VantagePro/vproStates.c new file mode 100755 index 0000000..f532c26 --- /dev/null +++ b/stations/VantagePro/vproStates.c @@ -0,0 +1,749 @@ +/*--------------------------------------------------------------------------- + + FILENAME: + vproStates.c + + PURPOSE: + Provide the wview vpro state machine handlers. + + REVISION HISTORY: + Date Engineer Revision Remarks + 08/14/03 M.S. Teel 0 Original + 04/12/2008 W. Krenn 1 rainTicksPerInch + RainCollectorType + + NOTES: + + + LICENSE: + Copyright (c) 2004, Mark S. Teel (mark@teel.ws) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +/* ... System include files +*/ +#include + +/* ... Library include files +*/ + +/* ... Local include files +*/ +#include +#include +#include +#include + + +/* ... global memory declarations +*/ + +/* ... global memory referenced +*/ + +/* ... static (local) memory declarations +*/ + +/* ... state machine methods +*/ + +int vproStartProcState (int state, void *stimulus, void *data) +{ + STIM *stim = (STIM *)stimulus; + WVIEWD_WORK *work = (WVIEWD_WORK *)data; + time_t ntime; + struct tm locTime; + int secsToWait, secsNow, numTries; + + switch (stim->type) + { + case STIM_DUMMY: + // ... this one starts this state machine + ntime = time (NULL); + localtime_r (&ntime, &locTime); + + if (locTime.tm_sec > 50 || locTime.tm_sec < 5) + { + if (locTime.tm_sec < 5) + secsNow = locTime.tm_sec + 60; + else + secsNow = locTime.tm_sec; + secsToWait = 65 - secsNow; // start at 5 secs after + + // we are too close to the archive record being available + // for comfort, wait until it has passed + radMsgLog (PRI_STATUS, + "starting too close to the top-of-minute, " + "waiting %d secs before continuing", + secsToWait); + radUtilsSleep (secsToWait * 1000); + } + + // ... make sure we can wakeup the console + numTries = 1; + while ((vpifWakeupConsole (work) == ERROR) && + (numTries < WVD_INITIAL_WAKEUP_TRIES)) + { + radMsgLog (PRI_HIGH, "vproStartProcState: WAKEUP failed - retry"); + radUtilsSleep (1000); + numTries ++; + } + if (numTries == WVD_INITIAL_WAKEUP_TRIES) + { + radMsgLog (PRI_HIGH, "vproStartProcState: WAKEUP failed - ERROR"); + emailAlertSend(ALERT_TYPE_STATION_VP_WAKEUP); + return VPRO_STATE_ERROR; + } + + // we need the archive interval now + if (vpifGetArchiveInterval (work) == ERROR) + { + radMsgLog (PRI_HIGH, "vproStartProcState: vpifGetArchiveInterval failed"); + emailAlertSend(ALERT_TYPE_STATION_READ); + return VPRO_STATE_ERROR; + } + + // sanity check the archive interval against the most recent record + if (stationVerifyArchiveInterval (work) == ERROR) + { + radMsgLog (PRI_HIGH, "vproStartProcState: stationVerifyArchiveInterval failed!"); + radMsgLog (PRI_HIGH, "You must either move old /var/wview/archive files out of the way -or-"); + radMsgLog (PRI_HIGH, "fix the station setting..."); + radProcessEventsSend (NULL, STATION_INIT_COMPLETE_EVENT, 1); + emailAlertSend(ALERT_TYPE_STATION_READ); + return VPRO_STATE_ERROR; + } + else + { + radMsgLog (PRI_STATUS, "station archive interval: %d minutes", + work->archiveInterval); + } + + // get the station RainCollectorSize + if (vpifGetRainCollectorSize(work) == ERROR) + { + radMsgLog (PRI_HIGH, "vproStartProcState: vpifGetRainCollectorSize failed"); + emailAlertSend(ALERT_TYPE_STATION_READ); + return VPRO_STATE_ERROR; + } + else + { + radMsgLog (PRI_STATUS, "station rain ticks/inch: %.0f", + ((VP_IF_DATA *)(work->stationData))->rainTicksPerInch); + } + + + // now do the initial archive record sync and loop data + ((VP_IF_DATA *)(work->stationData))->doLoopFlag = TRUE; + + // wakeup the console + if (vpifWakeupConsole (work) == ERROR) + { + radMsgLog (PRI_HIGH, "vproStartProcState: WAKEUP2 failed - ERROR"); + emailAlertSend(ALERT_TYPE_STATION_VP_WAKEUP); + return VPRO_STATE_ERROR; + } + + if (work->stationGeneratesArchives) + { + // Sync to console archive records: + if (vpifSendDumpAfterRqst (work) == ERROR) + { + radMsgLog (PRI_HIGH, "vproStartProcState: DMPAFT_RQST failed"); + emailAlertSend(ALERT_TYPE_STATION_ARCHIVE); + return VPRO_STATE_ERROR; + } + + radProcessTimerStart (work->ifTimer, VP_RESPONSE_TIMEOUT(work->stationIsWLIP)); + return VPRO_STATE_DMPAFT_RQST; + } + else + { + // Just retrieve loop data. + if (vpifSendLoopRqst (work, 1) == ERROR) + { + radMsgLog (PRI_HIGH, "vproStartProcState: LOOP_RQST failed"); + emailAlertSend(ALERT_TYPE_STATION_LOOP); + return VPRO_STATE_ERROR; + } + + radProcessTimerStart (work->ifTimer, VP_RESPONSE_TIMEOUT(work->stationIsWLIP)); + return VPRO_STATE_LOOP_RQST; + } + + case STIM_QMSG: + case STIM_EVENT: + case STIM_TIMER: + break; + } + + return state; +} + +int vproRunState (int state, void *stimulus, void *data) +{ + STIM *stim = (STIM *)stimulus; + WVIEWD_WORK *work = (WVIEWD_WORK *)data; + + switch (stim->type) + { + case VP_STIM_READINGS: + ((VP_IF_DATA *)(work->stationData))->doLoopFlag = TRUE; + + // check to see if we need to retry the archive record + if (((VP_IF_DATA *)(work->stationData))->archiveRetryFlag) + { + radMsgLog (PRI_MEDIUM, "vproRunState: retrying archive record from console:"); + radMsgLog (PRI_MEDIUM, "vproRunState: you may need to cycle power on the console" + "(including batteries) to resolve this condition."); + emailAlertSend(ALERT_TYPE_STATION_ARCHIVE); + if (vpifWakeupConsole (work) == ERROR) + { + radMsgLog (PRI_HIGH, "vproRunState: ARC WAKEUP failed"); + return VPRO_STATE_RUN; + } + + if (vpifSendDumpAfterRqst (work) == ERROR) + { + radMsgLog (PRI_HIGH, "vproRunState: DMPAFT_RQST failed"); + emailAlertSend(ALERT_TYPE_STATION_ARCHIVE); + return VPRO_STATE_ERROR; + } + + radProcessTimerStart (work->ifTimer, VP_RESPONSE_TIMEOUT(work->stationIsWLIP)); + return VPRO_STATE_DMPAFT_RQST; + } + else + { + // ... wakeup the console + if (vpifWakeupConsole (work) == ERROR) + { + radMsgLog (PRI_HIGH, "vproRunState: LOOP WAKEUP failed"); + return VPRO_STATE_RUN; + } + + if (vpifSendLoopRqst (work, 1) == ERROR) + { + radMsgLog (PRI_HIGH, "vproRunState: LOOP_RQST failed"); + emailAlertSend(ALERT_TYPE_STATION_LOOP); + return VPRO_STATE_ERROR; + } + + radProcessTimerStart (work->ifTimer, VP_RESPONSE_TIMEOUT(work->stationIsWLIP)); + return VPRO_STATE_LOOP_RQST; + } + + case VP_STIM_ARCHIVE: + ((VP_IF_DATA *)(work->stationData))->archiveRetryFlag = TRUE; + // ... wakeup the console + if (vpifWakeupConsole (work) == ERROR) + { + radMsgLog (PRI_HIGH, "vproRunState: ARC WAKEUP failed: retrying..."); + radUtilsSleep (1000); + if (vpifWakeupConsole (work) == ERROR) + { + radMsgLog (PRI_HIGH, "vproRunState: 2nd ARC WAKEUP failed!"); + return VPRO_STATE_RUN; + } + } + + if (vpifSendDumpAfterRqst (work) == ERROR) + { + radMsgLog (PRI_HIGH, "vproRunState: DMPAFT_RQST failed"); + emailAlertSend(ALERT_TYPE_STATION_ARCHIVE); + return VPRO_STATE_ERROR; + } + + radProcessTimerStart (work->ifTimer, VP_RESPONSE_TIMEOUT(work->stationIsWLIP)); + return VPRO_STATE_DMPAFT_RQST; + + case STIM_IO: + // ... read data from the station + if (vpifReadMessage (work, FALSE) == ERROR) + { + radProcessTimerStart (work->ifTimer, WVD_READ_RECOVER_INTERVAL); + emailAlertSend(ALERT_TYPE_STATION_READ); + return VPRO_STATE_READ_RECOVER; + } + + return state; + } + + return state; +} + +int vproDumpAfterState (int state, void *stimulus, void *data) +{ + STIM *stim = (STIM *)stimulus; + WVIEWD_WORK *work = (WVIEWD_WORK *)data; + + switch (stim->type) + { + case STIM_TIMER: + // serial IF timer expiry + // wakeup the console + if (vpifWakeupConsole (work) == ERROR) + { + radMsgLog (PRI_HIGH, "vproDumpAfterState: WAKEUP failed"); + return VPRO_STATE_RUN; + } + + if (vpifSendDumpAfterRqst (work) == ERROR) + { + radMsgLog (PRI_HIGH, "vproDumpAfterState: DMPAFT_RQST failed"); + emailAlertSend(ALERT_TYPE_STATION_ARCHIVE); + return VPRO_STATE_ERROR; + } + + radProcessTimerStart (work->ifTimer, VP_RESPONSE_TIMEOUT(work->stationIsWLIP)); + return state; + + case STIM_IO: + radProcessTimerStop (work->ifTimer); + + /* ... read data from the station + */ + if (vpifReadMessage (work, TRUE) == ERROR) + { + radProcessTimerStart (work->ifTimer, WVD_READ_RECOVER_INTERVAL); + emailAlertSend(ALERT_TYPE_STATION_READ); + return VPRO_STATE_READ_RECOVER; + } + + if (vpifSendDumpDateTimeRqst (work) == ERROR) + { + radMsgLog (PRI_HIGH, "vproDumpAfterState: DMP_DATETIME failed"); + emailAlertSend(ALERT_TYPE_STATION_ARCHIVE); + return VPRO_STATE_ERROR; + } + + radProcessTimerStart (work->ifTimer, 2*VP_RESPONSE_TIMEOUT(work->stationIsWLIP)); + return VPRO_STATE_DMPAFT_ACK; + } + + return state; +} + +int vproDumpAfterAckState (int state, void *stimulus, void *data) +{ + STIM *stim = (STIM *)stimulus; + WVIEWD_WORK *work = (WVIEWD_WORK *)data; + + switch (stim->type) + { + case STIM_TIMER: + // serial IF timer expiry + // cancel the download + if (vpifSendCancel (work) == ERROR) + { + radMsgLog (PRI_HIGH, "vproDumpAfterAckState: CANCEL failed"); + emailAlertSend(ALERT_TYPE_STATION_ARCHIVE); + return VPRO_STATE_ERROR; + } + + if (vpifWakeupConsole (work) == ERROR) + { + radMsgLog (PRI_HIGH, "vproDumpAfterAckState: WAKEUP failed"); + return VPRO_STATE_RUN; + } + + if (vpifSendDumpDateTimeRqst (work) == ERROR) + { + radMsgLog (PRI_HIGH, "vproDumpAfterAckState: DMP_DATETIME failed"); + emailAlertSend(ALERT_TYPE_STATION_ARCHIVE); + return VPRO_STATE_ERROR; + } + + radProcessTimerStart (work->ifTimer, 2*VP_RESPONSE_TIMEOUT(work->stationIsWLIP)); + return state; + + case STIM_IO: + radProcessTimerStop (work->ifTimer); + + /* ... read data from the station + */ + if (vpifReadMessage (work, TRUE) == ERROR) + { + radProcessTimerStart (work->ifTimer, WVD_READ_RECOVER_INTERVAL); + radMsgLog (PRI_HIGH, "vproDumpAfterAckState: station read failed"); + emailAlertSend(ALERT_TYPE_STATION_READ); + return VPRO_STATE_READ_RECOVER; + } + + if (((VP_IF_DATA *)(work->stationData))->archivePages > 0) + { + if (vpifSendAck (work) == ERROR) + { + radMsgLog (PRI_HIGH, "vproDumpAfterAckState: ACK failed"); + emailAlertSend(ALERT_TYPE_STATION_ARCHIVE); + return VPRO_STATE_ERROR; + } + + ((VP_IF_DATA *)(work->stationData))->reqMsgType = SER_MSG_ARCHIVE; + radProcessTimerStart (work->ifTimer, VP_RESPONSE_TIMEOUT(work->stationIsWLIP)); +#if 0 + radMsgLog (PRI_STATUS, "downloading %d pages from Vantage Pro...", + ((VP_IF_DATA *)(work->stationData))->archivePages); +#endif + ((VP_IF_DATA *)(work->stationData))->archiveCurrentPage = 0; + return VPRO_STATE_RECV_ARCH; + } + else + { + if (vpifSendCancel (work) == ERROR) + { + radMsgLog (PRI_HIGH, "vproDumpAfterAckState: CANCEL failed"); + emailAlertSend(ALERT_TYPE_STATION_ARCHIVE); + return VPRO_STATE_ERROR; + } + + if (((VP_IF_DATA *)(work->stationData))->doLoopFlag) + { + // continue with the data acquisition + if (vpifWakeupConsole (work) == ERROR) + { + radMsgLog (PRI_HIGH, "vproDumpAfterAckState (initial): WAKEUP failed"); + emailAlertSend(ALERT_TYPE_STATION_VP_WAKEUP); + return VPRO_STATE_ERROR; + } + + if (vpifSendLoopRqst (work, 1) == ERROR) + { + radMsgLog (PRI_HIGH, "vproDumpAfterAckState: LOOP_RQST failed"); + emailAlertSend(ALERT_TYPE_STATION_LOOP); + return VPRO_STATE_ERROR; + } + + radProcessTimerStart (work->ifTimer, VP_RESPONSE_TIMEOUT(work->stationIsWLIP)); + return VPRO_STATE_LOOP_RQST; + } + + // go back to IDLE + return VPRO_STATE_RUN; + } + } + + return state; +} + +int vproReceiveArchiveState (int state, void *stimulus, void *data) +{ + STIM *stim = (STIM *)stimulus; + WVIEWD_WORK *work = (WVIEWD_WORK *)data; + int recsRX = 0; + + switch (stim->type) + { + case STIM_TIMER: + // serial IF timer expiry + radMsgLog (PRI_HIGH, "vproReceiveArchiveState: timed out waiting for archive page from VP console!"); + + if (vpifSendCancel (work) == ERROR) + { + radMsgLog (PRI_HIGH, "vproReceiveArchiveState: CANCEL failed"); + emailAlertSend(ALERT_TYPE_STATION_ARCHIVE); + return VPRO_STATE_ERROR; + } + + if (!work->runningFlag) + { + // continue with the data acquisition + if (vpifWakeupConsole (work) == ERROR) + { + radMsgLog (PRI_HIGH, "vproReceiveArchiveState (initial): WAKEUP failed"); + emailAlertSend(ALERT_TYPE_STATION_VP_WAKEUP); + return VPRO_STATE_ERROR; + } + + if (vpifSendLoopRqst (work, 1) == ERROR) + { + radMsgLog (PRI_HIGH, "vproReceiveArchiveState: LOOP_RQST failed"); + emailAlertSend(ALERT_TYPE_STATION_LOOP); + return VPRO_STATE_ERROR; + } + + radProcessTimerStart (work->ifTimer, VP_RESPONSE_TIMEOUT(work->stationIsWLIP)); + return VPRO_STATE_LOOP_RQST; + } + + // go back to IDLE + return VPRO_STATE_RUN; + + case STIM_IO: + radProcessTimerStop (work->ifTimer); + + // read data from the station: + recsRX = vpifReadMessage (work, TRUE); + if (recsRX == ERROR) + { + // Don't let this lock up the IF: + radMsgLog (PRI_HIGH, "vproReceiveArchiveState: read archive page failed"); + + radUtilsSleep (50); + + if (vpifSendCancel (work) == ERROR) + { + radMsgLog (PRI_HIGH, "vproReceiveArchiveState: CANCEL failed"); + emailAlertSend(ALERT_TYPE_STATION_ARCHIVE); + return VPRO_STATE_ERROR; + } + + radUtilsSleep (50); + + if (((VP_IF_DATA *)(work->stationData))->doLoopFlag) + { + // continue with the data acquisition: + if (vpifWakeupConsole (work) == ERROR) + { + radMsgLog (PRI_HIGH, "vproReceiveArchiveState (initial): WAKEUP failed"); + emailAlertSend(ALERT_TYPE_STATION_VP_WAKEUP); + return VPRO_STATE_ERROR; + } + + if (vpifSendLoopRqst (work, 1) == ERROR) + { + radMsgLog (PRI_HIGH, "vproReceiveArchiveState: LOOP_RQST failed"); + emailAlertSend(ALERT_TYPE_STATION_LOOP); + return VPRO_STATE_ERROR; + } + + radProcessTimerStart (work->ifTimer, VP_RESPONSE_TIMEOUT(work->stationIsWLIP)); + return VPRO_STATE_LOOP_RQST; + } + + // go back to IDLE + return VPRO_STATE_RUN; + } + + if (((VP_IF_DATA *)(work->stationData))->archiveCurrentPage < + ((VP_IF_DATA *)(work->stationData))->archivePages) + { + radProcessTimerStart (work->ifTimer, VP_RESPONSE_TIMEOUT(work->stationIsWLIP)); + return state; + } + else + { +#if 0 + radMsgLog (PRI_STATUS, "... %d pages done", + ((VP_IF_DATA *)(work->stationData))->archivePages); +#endif + + radUtilsSleep (250); // let him send any pending data + + if (vpifSendCancel (work) == ERROR) + { + radMsgLog (PRI_HIGH, "vproReceiveArchiveState: CANCEL failed"); + emailAlertSend(ALERT_TYPE_STATION_ARCHIVE); + return VPRO_STATE_ERROR; + } + + vpifFlush (work); + radUtilsSleep (1); + + if (vpifWakeupConsole (work) == ERROR) + { + radMsgLog (PRI_HIGH, "vproReceiveArchiveState: WAKEUP1 failed"); + return VPRO_STATE_RUN; + } + + if (((VP_IF_DATA *)(work->stationData))->doRXCheck) + { + // get the RX stats from the VP + if (vpifGetRXCheck (work) == ERROR) + { + // uh oh - give the VP console a short break... + radMsgLog (PRI_MEDIUM, + "vproReceiveArchiveState: vpifGetRXCheck failed: %s", + ((VP_IF_DATA *)(work->stationData))->rxCheck); + radUtilsSleep (1000); + } + + // always wait a tad here... + radUtilsSleep (250); + } + + if (((VP_IF_DATA *)(work->stationData))->doLoopFlag) + { + // continue with the data acquisition + if (vpifWakeupConsole (work) == ERROR) + { + radMsgLog (PRI_HIGH, "vproReceiveArchiveState (initial): WAKEUP failed"); + emailAlertSend(ALERT_TYPE_STATION_VP_WAKEUP); + return VPRO_STATE_ERROR; + } + + if (vpifSendLoopRqst (work, 1) == ERROR) + { + radMsgLog (PRI_HIGH, "vproReceiveArchiveState: LOOP_RQST failed"); + emailAlertSend(ALERT_TYPE_STATION_LOOP); + return VPRO_STATE_ERROR; + } + + radProcessTimerStart (work->ifTimer, VP_RESPONSE_TIMEOUT(work->stationIsWLIP)); + return VPRO_STATE_LOOP_RQST; + } + + // go back to IDLE + return VPRO_STATE_RUN; + } + } + + return state; +} + +int vproLoopState (int state, void *stimulus, void *data) +{ + STIM *stim = (STIM *)stimulus; + WVIEWD_WORK *work = (WVIEWD_WORK *)data; + + switch (stim->type) + { + case VP_STIM_ARCHIVE: + ((VP_IF_DATA *)(work->stationData))->doArchiveFlag = TRUE; + break; + + case STIM_TIMER: + // serial IF timer expiry + // wakeup the console + if (vpifWakeupConsole (work) == ERROR) + { + radMsgLog (PRI_HIGH, "vproLoopState: WAKEUP failed"); + return VPRO_STATE_RUN; + } + + if (vpifSendLoopRqst (work, 1) == ERROR) + { + radMsgLog (PRI_HIGH, "vproLoopState: LOOP_RQST failed"); + emailAlertSend(ALERT_TYPE_STATION_LOOP); + return VPRO_STATE_ERROR; + } + + radProcessTimerStart (work->ifTimer, VP_RESPONSE_TIMEOUT(work->stationIsWLIP)); + return state; + + case STIM_IO: + radProcessTimerStop (work->ifTimer); + + // read data from the station + if (vpifReadMessage (work, TRUE) == ERROR) + { + radProcessTimerStart (work->ifTimer, WVD_READ_RECOVER_INTERVAL); + emailAlertSend(ALERT_TYPE_STATION_READ); + return VPRO_STATE_READ_RECOVER; + } + + ((VP_IF_DATA *)(work->stationData))->doLoopFlag = FALSE; + + // check to see if this was the first time through + if (!work->runningFlag) + { + // indicate we are done with startup activities... + vpifIndicateStationUp (); + } + else + { + // indicate the LOOP packet is done + vpifIndicateLoopDone (); + } + + // Check if we got an archive stimulus while doing the LOOP data: + if (((VP_IF_DATA *)(work->stationData))->doArchiveFlag) + { + ((VP_IF_DATA *)(work->stationData))->doArchiveFlag = FALSE; + if (vpifWakeupConsole (work) == ERROR) + { + radMsgLog (PRI_HIGH, "vproLoopState: WAKEUP failed"); + return VPRO_STATE_RUN; + } + + if (vpifSendDumpAfterRqst (work) == ERROR) + { + radMsgLog (PRI_HIGH, "vproLoopState: DMPAFT_RQST failed"); + emailAlertSend(ALERT_TYPE_STATION_ARCHIVE); + return VPRO_STATE_ERROR; + } + radProcessTimerStart (work->ifTimer, VP_RESPONSE_TIMEOUT(work->stationIsWLIP)); + return VPRO_STATE_DMPAFT_RQST; + } + + // check to see if we have a pending time sync + if (((VP_IF_DATA *)(work->stationData))->timeSyncFlag) + { + if (vpifSynchronizeConsoleClock (work) == OK) + { + ((VP_IF_DATA *)(work->stationData))->timeSyncFlag = 0; + } + } + + return VPRO_STATE_RUN; + } + + return state; +} + +int vproReadRecoverState (int state, void *stimulus, void *data) +{ + STIM *stim = (STIM *)stimulus; + WVIEWD_WORK *work = (WVIEWD_WORK *)data; + + switch (stim->type) + { + case STIM_TIMER: + // serial IF timer expiry + // wakeup the console + if (vpifWakeupConsole (work) == ERROR) + { + if (++work->numReadRetries > WVD_READ_RECOVER_MAX_RETRIES) + { + radMsgLog (PRI_HIGH, + "vproReadRecoverState: max retries attempted - giving up!"); + return VPRO_STATE_ERROR; + } + + radProcessTimerStart (work->ifTimer, WVD_READ_RECOVER_INTERVAL); + return state; + } + + // ... OK, let's return to normal + work->numReadRetries = 0; + return VPRO_STATE_RUN; + + case STIM_IO: + /* ... flush data from the station + */ + vpifFlush (work); + break; + } + + return state; +} + +static int errorStateReported = 0; +int vproErrorState (int state, void *stimulus, void *data) +{ + STIM *stim = (STIM *)stimulus; + WVIEWD_WORK *work = (WVIEWD_WORK *)data; + + switch (stim->type) + { + case STIM_IO: + vpifFlush (work); + case STIM_QMSG: + case STIM_EVENT: + case STIM_TIMER: + if (!errorStateReported) + { + radMsgLog (PRI_STATUS, + "%s:vproErrorState: received stimulus %d", + PROC_NAME_DAEMON, + stim->type); + errorStateReported = 1; + } + break; + } + + return state; +} diff --git a/stations/Virtual/Makefile.am b/stations/Virtual/Makefile.am new file mode 100755 index 0000000..4801734 --- /dev/null +++ b/stations/Virtual/Makefile.am @@ -0,0 +1,66 @@ +# Makefile - Texas Weather Instruments station daemon + +# define the executable to be built +bin_PROGRAMS = wviewd_virtual + +# define include directories +INCLUDES = \ + -I$(top_srcdir)/common \ + -I$(top_srcdir)/stations/common \ + -I$(prefix)/include \ + -D_GNU_SOURCE \ + -DWV_CONFIG_DIR=\"$(sysconfdir)/wview\" \ + -DWV_RUN_DIR=\"$(localstatedir)/wview\" \ + -DBUILD_WVIEWD + +# define the sources +wviewd_virtual_SOURCES = \ + $(top_srcdir)/common/sensor.c \ + $(top_srcdir)/common/wvutils.c \ + $(top_srcdir)/common/wvconfig.c \ + $(top_srcdir)/common/status.c \ + $(top_srcdir)/common/dbsqlite.c \ + $(top_srcdir)/common/datafeed.c \ + $(top_srcdir)/common/dbsqliteHiLow.c \ + $(top_srcdir)/common/windAverage.c \ + $(top_srcdir)/common/emailAlerts.c \ + $(top_srcdir)/stations/common/computedData.c \ + $(top_srcdir)/stations/common/daemon.c \ + $(top_srcdir)/stations/common/station.c \ + $(top_srcdir)/stations/common/serial.c \ + $(top_srcdir)/stations/common/ethernet.c \ + $(top_srcdir)/stations/common/stormRain.c \ + $(top_srcdir)/stations/common/parser.c \ + $(top_srcdir)/stations/Virtual/virtualInterface.c \ + $(top_srcdir)/stations/Virtual/virtualProtocol.c \ + $(top_srcdir)/common/sensor.h \ + $(top_srcdir)/common/datadefs.h \ + $(top_srcdir)/common/datafeed.h \ + $(top_srcdir)/common/dbsqlite.h \ + $(top_srcdir)/common/services.h \ + $(top_srcdir)/common/sysdefs.h \ + $(top_srcdir)/common/wvconfig.h \ + $(top_srcdir)/common/status.h \ + $(top_srcdir)/common/windAverage.h \ + $(top_srcdir)/common/emailAlerts.h \ + $(top_srcdir)/common/beaufort.h \ + $(top_srcdir)/stations/common/computedData.h \ + $(top_srcdir)/stations/common/daemon.h \ + $(top_srcdir)/stations/common/station.h \ + $(top_srcdir)/stations/common/serial.h \ + $(top_srcdir)/stations/common/ethernet.h \ + $(top_srcdir)/stations/common/stormRain.h \ + $(top_srcdir)/stations/common/parser.h \ + $(top_srcdir)/stations/Virtual/virtualInterface.h \ + $(top_srcdir)/stations/Virtual/virtualProtocol.h + +# define libraries +wviewd_virtual_LDADD = + +# define library directories +wviewd_virtual_LDFLAGS = -L$(prefix)/lib -L$(prefix)/usr/lib -L/usr/lib + +if CROSSCOMPILE +wviewd_virtual_LDFLAGS += $(prefix)/lib/crt1.o $(prefix)/lib/crti.o $(prefix)/lib/crtn.o +endif + diff --git a/stations/Virtual/virtualInterface.c b/stations/Virtual/virtualInterface.c new file mode 100755 index 0000000..82628fb --- /dev/null +++ b/stations/Virtual/virtualInterface.c @@ -0,0 +1,453 @@ +/*--------------------------------------------------------------------------- + + FILENAME: + virtualInterface.c + + PURPOSE: + Provide the virtual station interface API and utilities. + + REVISION HISTORY: + Date Engineer Revision Remarks + 12/14/2009 M.S. Teel 0 Original + + NOTES: + + LICENSE: + Copyright (c) 2009, Mark S. Teel (mark@teel.ws) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +/* ... System include files +*/ + +/* ... Library include files +*/ + +/* ... Local include files +*/ +#include + +/* ... global memory declarations +*/ + +/* ... local memory +*/ + +static VIRTUAL_IF_DATA virtualWorkData; + +void (*ArchiveIndicator) (ARCHIVE_PKT* newRecord); + +static void storeLoopPkt (LOOP_PKT *dest, VIRTUAL_IF_DATA *src); +static int RestoreConnection(WVIEWD_WORK *work); + + +////////////****////**** S T A T I O N A P I ****////****//////////// +///// Must be provided by each supported wview station interface ////// + +// station-supplied init function +// -- Can Be Asynchronous - event indication required -- +// +// MUST: +// - set the 'stationGeneratesArchives' flag in WVIEWD_WORK: +// if the station generates archive records (TRUE) or they should be +// generated automatically by the daemon from the sensor readings (FALSE) +// - Initialize the 'stationData' store for station work area +// - Initialize the interface medium +// - do initial LOOP acquisition +// - do any catch-up on archive records if there is a data logger +// - 'work->runningFlag' can be used for start up synchronization but should +// not be modified by the station interface code +// - indicate init is done by sending the STATION_INIT_COMPLETE_EVENT event to +// this process (radProcessEventsSend (NULL, STATION_INIT_COMPLETE_EVENT, 0)) +// +// OPTIONAL: +// - Initialize a state machine or any other construct required for the +// station interface - these should be stored in the 'stationData' store +// +// 'archiveIndication' - indication callback used to pass back an archive record +// generated as a result of 'stationGetArchive' being called; should receive a +// NULL pointer for 'newRecord' if no record available; only used if +// 'stationGeneratesArchives' flag is set to TRUE by the station interface +// +// Returns: OK or ERROR +// +int stationInit +( + WVIEWD_WORK *work, + void (*archiveIndication)(ARCHIVE_PKT* newRecord) +) +{ + int i; + time_t nowTime = time(NULL) - (WV_SECONDS_IN_HOUR * 12); + ARCHIVE_PKT recordStore; + ARCHIVE_PKT newestRecord; + char tempStr[WVIEW_MAX_PATH]; + + memset (&virtualWorkData, 0, sizeof(virtualWorkData)); + + // save the archive indication callback (we should never need it) + ArchiveIndicator = archiveIndication; + + // set our work data pointer + work->stationData = &virtualWorkData; + + // set the Archive Generation flag to indicate the VIRTUAL DOES NOT + // generate them + work->stationGeneratesArchives = TRUE; + + // initialize the medium abstraction based on user configuration + if (!strcmp (work->stationInterface, "serial")) + { + radMsgLog (PRI_HIGH, "stationInit: serial medium not supported for virtual station!"); + return ERROR; + } + else if (!strcmp (work->stationInterface, "ethernet")) + { + if (ethernetMediumInit (&work->medium, work->stationHost, work->stationPort) + == ERROR) + { + radMsgLog (PRI_HIGH, "stationInit: ethernet MediumInit failed"); + return ERROR; + } + } + else + { + radMsgLog (PRI_HIGH, "stationInit: medium %s not supported", + work->stationInterface); + return ERROR; + } + + // initialize the interface using the media specific routine + if ((*(work->medium.init))(&work->medium, work->stationDevice) == ERROR) + { + radMsgLog (PRI_HIGH, "stationInit: medium setup failed"); + return ERROR; + } + + // Make the socket blocking: + radSocketSetBlocking ((*(work->medium.getsocket))(&work->medium), TRUE); + + // Reset the stationType to include the host:port: + sprintf(tempStr, "%s:%s:%d", + work->stationType, work->stationHost, work->stationPort); + wvstrncpy(work->stationType, tempStr, sizeof(work->stationType)); + + if (!strcmp (work->stationInterface, "ethernet")) + { + radMsgLog (PRI_STATUS, "VIRTUAL on %s:%d opened ...", + work->stationHost, work->stationPort); + } + + // grab the station configuration now + if (stationGetConfigValueInt (work, + STATION_PARM_ELEVATION, + &virtualWorkData.elevation) + == ERROR) + { + radMsgLog (PRI_HIGH, "stationInit: stationGetConfigValueInt ELEV failed!"); + (*(work->medium.exit)) (&work->medium); + return ERROR; + } + if (stationGetConfigValueFloat (work, + STATION_PARM_LATITUDE, + &virtualWorkData.latitude) + == ERROR) + { + radMsgLog (PRI_HIGH, "stationInit: stationGetConfigValueInt LAT failed!"); + (*(work->medium.exit)) (&work->medium); + return ERROR; + } + if (stationGetConfigValueFloat (work, + STATION_PARM_LONGITUDE, + &virtualWorkData.longitude) + == ERROR) + { + radMsgLog (PRI_HIGH, "stationInit: stationGetConfigValueInt LONG failed!"); + (*(work->medium.exit)) (&work->medium); + return ERROR; + } + if (stationGetConfigValueInt (work, + STATION_PARM_ARC_INTERVAL, + &virtualWorkData.archiveInterval) + == ERROR) + { + radMsgLog (PRI_HIGH, "stationInit: stationGetConfigValueInt ARCINT failed!"); + (*(work->medium.exit)) (&work->medium); + return ERROR; + } + + // set the work archive interval now + work->archiveInterval = virtualWorkData.archiveInterval; + + // sanity check the archive interval against the most recent record + if (stationVerifyArchiveInterval (work) == ERROR) + { + // bad magic! + radMsgLog (PRI_HIGH, "stationInit: stationVerifyArchiveInterval failed!"); + radMsgLog (PRI_HIGH, "You must either move old archive data out of the way -or-"); + radMsgLog (PRI_HIGH, "fix the interval setting..."); + (*(work->medium.exit)) (&work->medium); + return ERROR; + } + else + { + radMsgLog (PRI_STATUS, "station archive interval: %d minutes", + work->archiveInterval); + } + + radMsgLog (PRI_STATUS, "Starting station interface: VIRTUAL"); + + // This must be done here: + work->archiveDateTime = dbsqliteArchiveGetNewestTime(&newestRecord); + if ((int)work->archiveDateTime == ERROR) + { + work->archiveDateTime = 0; + radMsgLog (PRI_STATUS, "stationInit: no archive records found in database!"); + } + + // initialize the station interface + if (virtualProtocolInit(work) == ERROR) + { + radMsgLog (PRI_HIGH, "stationInit: virtualProtocolInit failed!"); + (*(work->medium.exit)) (&work->medium); + return ERROR; + } + + // do the initial GetReadings now + if (virtualProtocolGetReadings(work, &virtualWorkData.virtualReadings) != OK) + { + radMsgLog (PRI_HIGH, "stationInit: initial virtualProtocolGetReadings failed!"); + virtualProtocolExit (work); + (*(work->medium.exit)) (&work->medium); + return ERROR; + } + + // populate the LOOP structure + storeLoopPkt (&work->loopPkt, &virtualWorkData); + + // we must indicate successful completion here - + // even though we are synchronous, the daemon wants to see this event + radProcessEventsSend(NULL, STATION_INIT_COMPLETE_EVENT, 0); + + return OK; +} + +// station-supplied exit function +// +// Returns: N/A +// +void stationExit (WVIEWD_WORK *work) +{ + virtualProtocolExit (work); + (*(work->medium.exit)) (&work->medium); + + return; +} + +// station-supplied function to retrieve positional info (lat, long, elev) - +// should populate 'work' fields: latitude, longitude, elevation +// -- Synchronous -- +// +// - If station does not store these parameters, they can be retrieved from the +// wview.conf file (see daemon.c for example conf file use) - user must choose +// station type "Generic" when running the wviewconfig script +// +// Returns: OK or ERROR +// +int stationGetPosition (WVIEWD_WORK *work) +{ + // just set the values from our internal store - we retrieved them in + // stationInit + work->elevation = (int16_t)virtualWorkData.elevation; + if (virtualWorkData.latitude >= 0) + work->latitude = (int16_t)((virtualWorkData.latitude*10)+0.5); + else + work->latitude = (int16_t)((virtualWorkData.latitude*10)-0.5); + if (virtualWorkData.longitude >= 0) + work->longitude = (int16_t)((virtualWorkData.longitude*10)+0.5); + else + work->longitude = (int16_t)((virtualWorkData.longitude*10)-0.5); + + radMsgLog (PRI_STATUS, "station location: elevation: %d feet", + work->elevation); + + radMsgLog (PRI_STATUS, "station location: latitude: %3.1f %c longitude: %3.1f %c", + (float)abs(work->latitude)/10.0, + ((work->latitude < 0) ? 'S' : 'N'), + (float)abs(work->longitude)/10.0, + ((work->longitude < 0) ? 'W' : 'E')); + + return OK; +} + +// station-supplied function to indicate a time sync should be performed if the +// station maintains time, otherwise may be safely ignored +// -- Can Be Asynchronous -- +// +// Returns: OK or ERROR +// +int stationSyncTime (WVIEWD_WORK *work) +{ + // VIRTUAL does not keep time... + return OK; +} + +// station-supplied function to indicate sensor readings should be performed - +// should populate 'work' struct: loopPkt (see datadefs.h for minimum field reqs) +// -- Can Be Asynchronous -- +// +// - indicate readings are complete by sending the STATION_LOOP_COMPLETE_EVENT +// event to this process (radProcessEventsSend (NULL, STATION_LOOP_COMPLETE_EVENT, 0)) +// +// Returns: OK or ERROR +// +int stationGetReadings (WVIEWD_WORK *work) +{ + // we will do this synchronously... + + // get readings from station + if (virtualProtocolGetReadings(work, &virtualWorkData.virtualReadings) == OK) + { + // populate the LOOP structure + storeLoopPkt(&work->loopPkt, &virtualWorkData); + + // indicate we are done + radProcessEventsSend(NULL, STATION_LOOP_COMPLETE_EVENT, 0); + } + + return OK; +} + +// station-supplied function to indicate an archive record should be generated - +// MUST populate an ARCHIVE_RECORD struct and indicate it to 'archiveIndication' +// function passed into 'stationInit' +// -- Asynchronous - callback indication required -- +// +// Returns: OK or ERROR +// +// Note: 'archiveIndication' should receive a NULL pointer for the newRecord if +// no record is available +// Note: This function will only be invoked by the wview daemon if the +// 'stationInit' function set the 'stationGeneratesArchives' to TRUE +// +int stationGetArchive (WVIEWD_WORK *work) +{ + // Let the protocol module handle archive requests: + virtualProtocolGetArchive(work); + return OK; +} + +// station-supplied function to indicate data is available on the station +// interface medium (serial or ethernet) - +// It is the responsibility of the station interface to read the data from the +// medium and process appropriately. The data does not have to be read within +// the context of this function, but may be used to stimulate a state machine. +// -- Synchronous -- +// +// Returns: N/A +// +void stationDataIndicate (WVIEWD_WORK *work) +{ + int retVal, done = FALSE; + + if (virtualProtocolDataIndicate(work) == ERROR_ABORT) + { + // We need to try to reconnect: + radMsgLog (PRI_HIGH, "VIRTUAL: remote station lost - retrying..."); + virtualProtocolExit(work); + (*(work->medium.exit))(&work->medium); + + while (! done && ! work->exiting) + { + retVal = RestoreConnection(work); + if (retVal == ERROR_ABORT) + { + radMsgLog (PRI_HIGH, "VIRTUAL: restore connection failed - exiting"); + radProcessSetExitFlag(); + done = TRUE; + } + else if (retVal == OK) + { + radMsgLog (PRI_HIGH, "VIRTUAL: restore connection success"); + virtualProtocolInit(work); + done = TRUE; + } + else + { + radMsgLog (PRI_HIGH, "VIRTUAL: try again in 15 seconds to restore connection."); + radUtilsSleep(15000); + } + } + } + + return; +} + +// station-supplied function to receive IPM messages - any message received by +// the generic station message handler which is not recognized will be passed +// to the station-specific code through this function. +// It is the responsibility of the station interface to process the message +// appropriately (or ignore it). +// -- Synchronous -- +// +// Returns: N/A +// +void stationMessageIndicate (WVIEWD_WORK *work, int msgType, void *msg) +{ + // N/A + return; +} + +// station-supplied function to indicate the interface timer has expired - +// It is the responsibility of the station interface to start/stop the interface +// timer as needed for the particular station requirements. +// The station interface timer is specified by the 'ifTimer' member of the +// WVIEWD_WORK structure. No other timers in that structure should be manipulated +// in any way by the station interface code. +// -- Synchronous -- +// +// Returns: N/A +// +void stationIFTimerExpiry (WVIEWD_WORK *work) +{ + return; +} + + +////////////****//// S T A T I O N A P I E N D ////****//////////// + + +// ... ----- static (local) methods ----- ... + +static int RestoreConnection(WVIEWD_WORK *work) +{ + if (ethernetMediumInit (&work->medium, work->stationHost, work->stationPort) + == ERROR) + { + radMsgLog (PRI_HIGH, "RestoreConnection: ethernet MediumInit failed"); + return ERROR_ABORT; + } + + // initialize the interface using the media specific routine + if ((*(work->medium.init))(&work->medium, work->stationDevice) == ERROR) + { + radMsgLog (PRI_HIGH, "RestoreConnection: medium setup failed"); + return ERROR; + } + + // Make the socket blocking: + radSocketSetBlocking ((*(work->medium.getsocket))(&work->medium), TRUE); + + return OK; +} + +static void storeLoopPkt (LOOP_PKT *dest, VIRTUAL_IF_DATA *src) +{ + *dest = src->virtualReadings; + + return; +} + diff --git a/stations/Virtual/virtualInterface.h b/stations/Virtual/virtualInterface.h new file mode 100755 index 0000000..cc0f550 --- /dev/null +++ b/stations/Virtual/virtualInterface.h @@ -0,0 +1,77 @@ +#ifndef INC_virtualInterfaceh +#define INC_virtualInterfaceh +/*--------------------------------------------------------------------------- + + FILENAME: + virtualInterface.h + + PURPOSE: + Provide the virtual station interface API and utilities. + + REVISION HISTORY: + Date Engineer Revision Remarks + 12/17/2009 M.S. Teel 0 Original + + NOTES: + + LICENSE: + Copyright (c) 2009, Mark S. Teel (mark@teel.ws) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +/* ... System include files +*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* ... Library include files +*/ +#include +#include +#include +#include + +/* ... Local include files +*/ +#include +#include +#include +#include +#include +#include +#include +#include + +#include + + +// define TWI-specific interface data here +typedef struct +{ + int elevation; + float latitude; + float longitude; + int archiveInterval; + LOOP_PKT virtualReadings; +} VIRTUAL_IF_DATA; + + +// Prototypes: + + +#endif + diff --git a/stations/Virtual/virtualProtocol.c b/stations/Virtual/virtualProtocol.c new file mode 100755 index 0000000..6e98f49 --- /dev/null +++ b/stations/Virtual/virtualProtocol.c @@ -0,0 +1,282 @@ +/*--------------------------------------------------------------------------- + + FILENAME: + virtualProtocol.c + + PURPOSE: + Provide protocol utilities for virtual station communication. + + REVISION HISTORY: + Date Engineer Revision Remarks + 12/17/2009 M.S. Teel 0 Original + + NOTES: + + LICENSE: + Copyright (c) 2009, Mark S. Teel (mark@teel.ws) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +/* ... System include files +*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/* ... Library include files +*/ +#include +#include +#include + +/* ... Local include files +*/ +#include +#include +#include +#include + +/* ... global memory declarations +*/ +extern void (*ArchiveIndicator)(ARCHIVE_PKT* newRecord); + +/* ... local memory +*/ + +static VIRTUAL_WORK virtualWork; + + +/* ... methods +*/ + +static int ReadDataFrame(WVIEWD_WORK *work) +{ + int retVal; + LOOP_PKT loopData; + LOOP_PKT hostLoopData; + ARCHIVE_PKT archiveRecord; + ARCHIVE_PKT hostRecord; + RADSOCK_ID sockId = (*work->medium.getsocket)(&work->medium); + + /* try to find the start frame (this blocks if the socket is empty) */ + retVal = datafeedSyncStartOfFrame(sockId); + switch (retVal) + { + case ERROR: + return ERROR; + + case ERROR_ABORT: + return ERROR_ABORT; + + case FALSE: + return FALSE; + + case DF_LOOP_PKT_TYPE: + /* OK, we have a loop update coming (this may block) */ + if ((*work->medium.read)(&work->medium, (void *)&loopData, sizeof (loopData), 1000) + != sizeof (loopData)) + { + return FALSE; + } + + // Convert from network byte order: + datafeedConvertLOOP_NTOH(&hostLoopData, &loopData); + + // Store it: + virtualWork.data.loopData = hostLoopData; + return DF_LOOP_PKT_TYPE; + + case DF_ARCHIVE_PKT_TYPE: + /* OK, we have an archive coming (this may block) */ + if ((*work->medium.read)(&work->medium, (void *)&archiveRecord, sizeof(archiveRecord), 1000) + != sizeof (archiveRecord)) + { + return FALSE; + } + + // Convert from network byte order: + datafeedConvertArchive_NTOH(&hostRecord, &archiveRecord); + + virtualWork.data.archiveData = hostRecord; + + if (hostRecord.dateTime != 0) + { + time_t archtime; + archtime = hostRecord.dateTime; + radMsgLog (PRI_STATUS, "VIRTUAL: RX archive from remote station: %s", ctime(&archtime)); + } + + if (virtualWork.IsArchiveNeeded) + { + // Indicate it right now: + virtualWork.IsArchiveReceived = FALSE; + virtualWork.IsArchiveNeeded = FALSE; + (*ArchiveIndicator)(&virtualWork.data.archiveData); + + // Add all but cumulative: + dbsqliteHiLowUpdateArchive(&virtualWork.data.archiveData); + } + else + { + // Indicate a new record is ready: + virtualWork.IsArchiveReceived = TRUE; + } + + return DF_ARCHIVE_PKT_TYPE; + } + + return FALSE; +} + +/////////////////////////////////////////////////////////////////////////// +/////////////////////////////// A P I /////////////////////////////////// +int virtualProtocolInit (WVIEWD_WORK *work) +{ + time_t nowtime = time(NULL); + uint32_t dateTime; + int retVal, retries = 0; + + memset (&virtualWork, 0, sizeof(virtualWork)); + + // Let the socket settle down: + radUtilsSleep (100); + + // We need to sync up the archive records: + while (work->archiveDateTime < nowtime) + { + // Send the request: + // write the frame start: + if ((*work->medium.write)(&work->medium, + (void *)DF_RQST_ARCHIVE_START_FRAME, + DF_START_FRAME_LENGTH) + != DF_START_FRAME_LENGTH) + { + radMsgLog (PRI_HIGH, "VIRTUAL: write sync error!"); + return ERROR; + } + + // Send the dateTime: + // convert to network byte order: + dateTime = htonl(work->archiveDateTime); + if ((*work->medium.write)(&work->medium, &dateTime, sizeof(dateTime)) + != sizeof(dateTime)) + { + radMsgLog (PRI_HIGH, "VIRTUAL: write data error!"); + return ERROR; + } + + // OK, block waiting on it: + retVal = 0; + retries = 0; + while (retVal != DF_ARCHIVE_PKT_TYPE && retries < VIRTUAL_MAX_RETRIES) + { + retVal = ReadDataFrame(work); + if (retVal == ERROR || retVal == ERROR_ABORT) + { + radMsgLog (PRI_HIGH, "VIRTUAL: socket error - aborting!"); + return ERROR; + } + retries ++; + } + if (retries == VIRTUAL_MAX_RETRIES) + { + // No Taco Bueno: + radMsgLog (PRI_HIGH, "VIRTUAL: archive read failed on %d tries", retries); + return ERROR; + } + + // Check the time stamp: + if (virtualWork.data.archiveData.dateTime < work->archiveDateTime) + { + // No new records: + radMsgLog (PRI_STATUS, "VIRTUAL: all archive records received"); + work->archiveDateTime = nowtime; + } + else + { + // Good stuff: + (*ArchiveIndicator)(&virtualWork.data.archiveData); + + // If not running yet, add to HILOW database: + dbsqliteHiLowStoreArchive(&virtualWork.data.archiveData); + + work->archiveDateTime = virtualWork.data.archiveData.dateTime; + } + } + + // Reset the archive handling flags: + virtualWork.IsArchiveReceived = FALSE; + virtualWork.IsArchiveNeeded = FALSE; + + // Now we need to wait for initial readings: + retVal = 0; + radMsgLog (PRI_STATUS, "VIRTUAL: waiting for initial readings..."); + while (retVal != DF_LOOP_PKT_TYPE) + { + retVal = ReadDataFrame(work); + if (retVal == ERROR || retVal == ERROR_ABORT) + { + radMsgLog (PRI_HIGH, "VIRTUAL: socket error - aborting!"); + return ERROR; + } + else if (retVal == DF_LOOP_PKT_TYPE) + { + radMsgLog (PRI_STATUS, "VIRTUAL: RX LOOP"); + } + } + + return OK; +} + +void virtualProtocolExit (WVIEWD_WORK *work) +{ + // nothing to clean up... + return; +} + +int virtualProtocolDataIndicate(WVIEWD_WORK *work) +{ + return ReadDataFrame(work); +} + +int virtualProtocolGetReadings(WVIEWD_WORK *work, LOOP_PKT *store) +{ + // Use the last LOOP data we received: + *store = virtualWork.data.loopData; + + return OK; +} + +int virtualProtocolGetArchive(WVIEWD_WORK *work) +{ + if (! virtualWork.IsArchiveReceived) + { + virtualWork.IsArchiveNeeded = TRUE; + return FALSE; + } + + // Indicate it and reset flags: + virtualWork.IsArchiveReceived = FALSE; + virtualWork.IsArchiveNeeded = FALSE; + (*ArchiveIndicator)(&virtualWork.data.archiveData); + + // Add all but cumulative: + dbsqliteHiLowUpdateArchive(&virtualWork.data.archiveData); + + return TRUE; +} + diff --git a/stations/Virtual/virtualProtocol.h b/stations/Virtual/virtualProtocol.h new file mode 100755 index 0000000..62bdb04 --- /dev/null +++ b/stations/Virtual/virtualProtocol.h @@ -0,0 +1,92 @@ +#ifndef INC_virtualProtocolh +#define INC_virtualProtocolh +/*--------------------------------------------------------------------------- + + FILENAME: + virtualProtocol.h + + PURPOSE: + Provide protocol utilities for virtual station communication. + + REVISION HISTORY: + Date Engineer Revision Remarks + 12/17/2009 M.S. Teel 0 Original + + + NOTES: + + + LICENSE: + Copyright (c) 2009, Mark S. Teel (mark@teel.ws) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +/* ... System include files +*/ +#include +#include +#include +#include +#include +#include +#include + +/* ... Library include files +*/ +#include +#include +#include + +/* ... Local include files +*/ +#include +#include +#include +#include +#include +#include + + +#define VIRTUAL_RESPONSE_TIMEOUT 1000 +#define VIRTUAL_MAX_RETRIES 5 + +typedef struct +{ + LOOP_PKT loopData; + ARCHIVE_PKT archiveData; +} +VIRTUAL_DATA; + +// define the work area +typedef struct +{ + VIRTUAL_DATA data; + int IsArchiveReceived; + int IsArchiveNeeded; +} +VIRTUAL_WORK; + + +// function prototypes + +// call once during initialization +extern int virtualProtocolInit(WVIEWD_WORK *work); + +// do cleanup +extern void virtualProtocolExit(WVIEWD_WORK *work); + +// initiate a synchronous sensor collection: +extern int virtualProtocolGetReadings(WVIEWD_WORK *work, LOOP_PKT *store); + +// indicate data is available: +extern int virtualProtocolDataIndicate(WVIEWD_WORK *work); + +// Retrieve archive record or set "need it" flag: +extern int virtualProtocolGetArchive(WVIEWD_WORK *work); + + +#endif + diff --git a/stations/WH1080/Makefile.am b/stations/WH1080/Makefile.am new file mode 100755 index 0000000..874d2f2 --- /dev/null +++ b/stations/WH1080/Makefile.am @@ -0,0 +1,66 @@ +# Makefile - Fine Offset WH1080 (and friends) station daemon (USB HID) + +# define the executable to be built +bin_PROGRAMS = wviewd_wh1080 + +# define include directories +INCLUDES = \ + -I$(top_srcdir)/common \ + -I$(top_srcdir)/stations/common \ + -I$(prefix)/include \ + -D_GNU_SOURCE \ + -DWV_CONFIG_DIR=\"$(sysconfdir)/wview\" \ + -DWV_RUN_DIR=\"$(localstatedir)/wview\" \ + -DBUILD_WVIEWD + +# define the sources +wviewd_wh1080_SOURCES = \ + $(top_srcdir)/common/sensor.c \ + $(top_srcdir)/common/wvutils.c \ + $(top_srcdir)/common/wvconfig.c \ + $(top_srcdir)/common/status.c \ + $(top_srcdir)/common/dbsqlite.c \ + $(top_srcdir)/common/dbsqliteHiLow.c \ + $(top_srcdir)/common/windAverage.c \ + $(top_srcdir)/common/emailAlerts.c \ + $(top_srcdir)/stations/common/computedData.c \ + $(top_srcdir)/stations/common/daemon.c \ + $(top_srcdir)/stations/common/station.c \ + $(top_srcdir)/stations/common/stormRain.c \ + $(top_srcdir)/stations/common/parser.c \ + $(top_srcdir)/stations/common/usbhid.c \ + $(top_srcdir)/stations/WH1080/wh1080Interface.c \ + $(top_srcdir)/stations/WH1080/wh1080Protocol.c \ + $(top_srcdir)/common/sensor.h \ + $(top_srcdir)/common/datadefs.h \ + $(top_srcdir)/common/dbsqlite.h \ + $(top_srcdir)/common/services.h \ + $(top_srcdir)/common/sysdefs.h \ + $(top_srcdir)/common/wvconfig.h \ + $(top_srcdir)/common/windAverage.h \ + $(top_srcdir)/common/emailAlerts.h \ + $(top_srcdir)/common/beaufort.h \ + $(top_srcdir)/stations/common/computedData.h \ + $(top_srcdir)/stations/common/daemon.h \ + $(top_srcdir)/stations/common/station.h \ + $(top_srcdir)/stations/common/stormRain.h \ + $(top_srcdir)/stations/common/parser.h \ + $(top_srcdir)/stations/common/hidapi.h \ + $(top_srcdir)/stations/common/usbhid.h \ + $(top_srcdir)/stations/WH1080/wh1080Interface.h \ + $(top_srcdir)/stations/WH1080/wh1080Protocol.h + + +if DARWIN +wviewd_wh1080_SOURCES += $(top_srcdir)/stations/common/hidapi-osx.c +wviewd_wh1080_LDFLAGS = -L$(prefix)/lib -L/usr/lib -L/usr/local/lib -framework IOKit -framework CoreFoundation +wviewd_wh1080_LDADD = +else +wviewd_wh1080_SOURCES += $(top_srcdir)/stations/common/hidapi-linux.c +wviewd_wh1080_LDFLAGS = -L$(prefix)/lib -L/usr/lib -L/usr/local/lib +if FREEBSD +wviewd_wh1080_LDADD = -lusb +else +wviewd_wh1080_LDADD = -lusb-1.0 +endif +endif diff --git a/stations/WH1080/wh1080Interface.c b/stations/WH1080/wh1080Interface.c new file mode 100755 index 0000000..ad31f3e --- /dev/null +++ b/stations/WH1080/wh1080Interface.c @@ -0,0 +1,313 @@ +/*--------------------------------------------------------------------------- + + FILENAME: + wh1080Interface.c + + PURPOSE: + Provide the Fine Offset WH1080 station interface API and utilities. + + REVISION HISTORY: + Date Engineer Revision Remarks + 02/17/2011 M.S. Teel 0 Original + + NOTES: + The WH1080 station provides a USB HID interface for I/O + + LICENSE: + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +/* ... System include files +*/ + +/* ... Library include files +*/ + +/* ... Local include files +*/ +#include + +/* ... global memory declarations +*/ + +/* ... local memory +*/ + +static WH1080_IF_DATA wh1080WorkData; +static void (*ArchiveIndicator) (ARCHIVE_PKT* newRecord); + +static void serialPortConfig (int fd); + + + +////////////****////**** S T A T I O N A P I ****////****//////////// +///// Must be provided by each supported wview station interface ////// + +// station-supplied init function +// -- Can Be Asynchronous - event indication required -- +// +// MUST: +// - set the 'stationGeneratesArchives' flag in WVIEWD_WORK: +// if the station generates archive records (TRUE) or they should be +// generated automatically by the daemon from the sensor readings (FALSE) +// - Initialize the 'stationData' store for station work area +// - Initialize the interface medium +// - do initial LOOP acquisition +// - do any catch-up on archive records if there is a data logger +// - 'work->runningFlag' can be used for start up synchronization but should +// not be modified by the station interface code +// - indicate init is done by sending the STATION_INIT_COMPLETE_EVENT event to +// this process (radProcessEventsSend (NULL, STATION_INIT_COMPLETE_EVENT, 0)) +// +// OPTIONAL: +// - Initialize a state machine or any other construct required for the +// station interface - these should be stored in the 'stationData' store +// +// 'archiveIndication' - indication callback used to pass back an archive record +// generated as a result of 'stationGetArchive' being called; should receive a +// NULL pointer for 'newRecord' if no record available; only used if +// 'stationGeneratesArchives' flag is set to TRUE by the station interface +// +// Returns: OK or ERROR +// +int stationInit +( + WVIEWD_WORK *work, + void (*archiveIndication)(ARCHIVE_PKT* newRecord) +) +{ + int i; + STIM stim; + + memset (&wh1080WorkData, 0, sizeof(wh1080WorkData)); + + // save the archive indication callback (we should never need it) + ArchiveIndicator = archiveIndication; + + // set our work data pointer + work->stationData = &wh1080WorkData; + + // set the Archive Generation flag to indicate the WMR918 DOES NOT + // generate them + work->stationGeneratesArchives = FALSE; + + // The WH1080 is a USB-only device: + if (usbhidMediumInit (&work->medium, WH1080_VENDOR_ID, WH1080_PRODUCT_ID, FALSE, FALSE) == ERROR) + { + radMsgLog (PRI_HIGH, "stationInit: USB MediumInit failed"); + return ERROR; + } + + radMsgLog (PRI_STATUS, "WH1080 on USB %4.4X:%4.4X configured ...", + WH1080_VENDOR_ID, WH1080_PRODUCT_ID); + + + // grab the station configuration now + if (stationGetConfigValueInt (work, + STATION_PARM_ELEVATION, + &wh1080WorkData.elevation) + == ERROR) + { + radMsgLog (PRI_HIGH, "stationInit: stationGetConfigValueInt ELEV failed!"); + return ERROR; + } + if (stationGetConfigValueFloat (work, + STATION_PARM_LATITUDE, + &wh1080WorkData.latitude) + == ERROR) + { + radMsgLog (PRI_HIGH, "stationInit: stationGetConfigValueInt LAT failed!"); + return ERROR; + } + if (stationGetConfigValueFloat (work, + STATION_PARM_LONGITUDE, + &wh1080WorkData.longitude) + == ERROR) + { + radMsgLog (PRI_HIGH, "stationInit: stationGetConfigValueInt LONG failed!"); + return ERROR; + } + if (stationGetConfigValueInt (work, + STATION_PARM_ARC_INTERVAL, + &wh1080WorkData.archiveInterval) + == ERROR) + { + radMsgLog (PRI_HIGH, "stationInit: stationGetConfigValueInt ARCINT failed!"); + return ERROR; + } + + // set the work archive interval now + work->archiveInterval = wh1080WorkData.archiveInterval; + + // sanity check the archive interval against the most recent record + if (stationVerifyArchiveInterval (work) == ERROR) + { + // bad magic! + radMsgLog (PRI_HIGH, "stationInit: stationVerifyArchiveInterval failed!"); + radMsgLog (PRI_HIGH, "You must either move old archive data out of the way -or-"); + radMsgLog (PRI_HIGH, "fix the interval setting..."); + return ERROR; + } + else + { + radMsgLog (PRI_STATUS, "station archive interval: %d minutes", + work->archiveInterval); + } + + radMsgLog (PRI_STATUS, "Starting station interface: WH1080"); + + if (wh1080Init (work) == ERROR) + { + radMsgLog (PRI_HIGH, "stationInit: wh1080Init failed!"); + return ERROR; + } + + return OK; +} + +// station-supplied exit function +// +// Returns: N/A +// +void stationExit (WVIEWD_WORK *work) +{ + wh1080Exit (work); + (*(work->medium.usbhidExit)) (&work->medium); + + return; +} + +// station-supplied function to retrieve positional info (lat, long, elev) - +// should populate 'work' fields: latitude, longitude, elevation +// -- Synchronous -- +// +// - If station does not store these parameters, they can be retrieved from the +// wview.conf file (see daemon.c for example conf file use) - user must choose +// station type "Generic" when running the wviewconfig script +// +// Returns: OK or ERROR +// +int stationGetPosition (WVIEWD_WORK *work) +{ + // just set the values from our internal store - we retrieved them in + // stationInit + work->elevation = (int16_t)wh1080WorkData.elevation; + if (wh1080WorkData.latitude >= 0) + work->latitude = (int16_t)((wh1080WorkData.latitude*10)+0.5); + else + work->latitude = (int16_t)((wh1080WorkData.latitude*10)-0.5); + if (wh1080WorkData.longitude >= 0) + work->longitude = (int16_t)((wh1080WorkData.longitude*10)+0.5); + else + work->longitude = (int16_t)((wh1080WorkData.longitude*10)-0.5); + + radMsgLog (PRI_STATUS, "station location: elevation: %d feet", + work->elevation); + + radMsgLog (PRI_STATUS, "station location: latitude: %3.1f %c longitude: %3.1f %c", + (float)abs(work->latitude)/10.0, + ((work->latitude < 0) ? 'S' : 'N'), + (float)abs(work->longitude)/10.0, + ((work->longitude < 0) ? 'W' : 'E')); + + return OK; +} + +// station-supplied function to indicate a time sync should be performed if the +// station maintains time, otherwise may be safely ignored +// -- Can Be Asynchronous -- +// +// Returns: OK or ERROR +// +int stationSyncTime (WVIEWD_WORK *work) +{ + // We don't use the WH1080 time... + return OK; +} + +// station-supplied function to indicate sensor readings should be performed - +// should populate 'work' struct: loopPkt (see datadefs.h for minimum field reqs) +// -- Can Be Asynchronous -- +// +// - indicate readings are complete by sending the STATION_LOOP_COMPLETE_EVENT +// event to this process (radProcessEventsSend (NULL, STATION_LOOP_COMPLETE_EVENT, 0)) +// +// Returns: OK or ERROR +// +int stationGetReadings (WVIEWD_WORK *work) +{ + wh1080GetReadings (work); + + return OK; +} + +// station-supplied function to indicate an archive record should be generated - +// MUST populate an ARCHIVE_RECORD struct and indicate it to 'archiveIndication' +// function passed into 'stationInit' +// -- Asynchronous - callback indication required -- +// +// Returns: OK or ERROR +// +// Note: 'archiveIndication' should receive a NULL pointer for the newRecord if +// no record is available +// Note: This function will only be invoked by the wview daemon if the +// 'stationInit' function set the 'stationGeneratesArchives' to TRUE +// +int stationGetArchive (WVIEWD_WORK *work) +{ + // just indicate a NULL record, WMR918 does not generate them (and this + // function should never be called!) + (*ArchiveIndicator) (NULL); + return OK; +} + +// station-supplied function to indicate data is available on the station +// interface medium (serial or ethernet) - +// It is the responsibility of the station interface to read the data from the +// medium and process appropriately. The data does not have to be read within +// the context of this function, but may be used to stimulate a state machine. +// -- Synchronous -- +// +// Returns: N/A +// +void stationDataIndicate (WVIEWD_WORK *work) +{ + // N/A + return; +} + +// station-supplied function to receive IPM messages - any message received by +// the generic station message handler which is not recognized will be passed +// to the station-specific code through this function. +// It is the responsibility of the station interface to process the message +// appropriately (or ignore it). +// -- Synchronous -- +// +// Returns: N/A +// +void stationMessageIndicate (WVIEWD_WORK *work, int msgType, void *msg) +{ + // N/A + return; +} + +// station-supplied function to indicate the interface timer has expired - +// It is the responsibility of the station interface to start/stop the interface +// timer as needed for the particular station requirements. +// The station interface timer is specified by the 'ifTimer' member of the +// WVIEWD_WORK structure. No other timers in that structure should be manipulated +// in any way by the station interface code. +// -- Synchronous -- +// +// Returns: N/A +// +void stationIFTimerExpiry (WVIEWD_WORK *work) +{ + return; +} + +////////////****//// S T A T I O N A P I E N D ////****//////////// + diff --git a/stations/WH1080/wh1080Interface.h b/stations/WH1080/wh1080Interface.h new file mode 100755 index 0000000..3c46d42 --- /dev/null +++ b/stations/WH1080/wh1080Interface.h @@ -0,0 +1,60 @@ +#ifndef INC_wh1080interfaceh +#define INC_wh1080interfaceh +/*--------------------------------------------------------------------------- + + FILENAME: + wh1080Interface.h + + PURPOSE: + Provide the Fine Offset WH1080 station interface + API and utilities. + + NOTES: + + + LICENSE: + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +/* ... System include files +*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* ... Library include files +*/ +#include +#include +#include +#include + +/* ... Local include files +*/ +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#endif + diff --git a/stations/WH1080/wh1080Protocol.c b/stations/WH1080/wh1080Protocol.c new file mode 100755 index 0000000..97ef1ec --- /dev/null +++ b/stations/WH1080/wh1080Protocol.c @@ -0,0 +1,722 @@ +/*--------------------------------------------------------------------------- + + FILENAME: + wh1080Protocol.c + + PURPOSE: + Provide protocol utilities for WH1080 station communication. + + REVISION HISTORY: + Date Engineer Revision Remarks + 02/17/2011 M.S. Teel 0 Original + + NOTES: + Parts of this implementation were inspired by the fowsr project + (C) Arne-Jorgen Auberg (arne.jorgen.auberg@gmail.com) with hidapi + mods by Bill Northcott. + + LICENSE: + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +/* ... System include files +*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* ... Library include files +*/ +#include +#include +#include + +/* ... Local include files +*/ +#include +#include +#include +#include +#include + +/* ... global memory declarations +*/ + +/* ... local memory +*/ + +static WH1080_WORK wh1080Work; + +// Define the position, decode type, conversion factor and the storage variable +// for the station data retrieved via USB: +static WH1080_DECODE_TYPE decodeVals[] = +{ + {0, ub, 1.0, &wh1080Work.sensorData.delay}, // Minutes since last stored reading (1:240) + {1, ub, 1.0, &wh1080Work.sensorData.inhumidity}, // Indoor relative humidity % (1:99), 0xFF means invalid + {2, ss, 0.1, &wh1080Work.sensorData.intemp}, // Indoor temp; Multiply by 0.1 to get C (-40:+60), 0xFFFF means invalid + {4, ub, 1.0, &wh1080Work.sensorData.outhumidity}, // Outdoor relative humidity % (1:99), 0xFF means invalid + {5, ss, 0.1, &wh1080Work.sensorData.outtemp}, // Outdoor temp; Multiply by 0.1 to get C (-40:+60) , 0xFFFF means invalid + {7, us, 0.1, &wh1080Work.sensorData.pressure}, // Pressure; Multiply by 0.1 to get hPa (920:1080), 0xFFFF means invalid + {9, wa, 0.1, &wh1080Work.sensorData.windAvgSpeed}, // Avg Wind; Multiply by 0.1 to get m/s (0:50), 0xFF means invalid + {10, wg, 0.1, &wh1080Work.sensorData.windGustSpeed}, // Gust Wind; Multiply by 0.1 to get m/s (0:50), 0xFF means invalid + {12, wd, 1.0, &wh1080Work.sensorData.windDir}, // Wind Dir; Multiply by 22.5 (0-15), 7th bit indicates invalid data + {13, us, 0.3, &wh1080Work.sensorData.rain}, // Rain; Multiply by 0.33 to get mm + {15, pb, 1.0, &wh1080Work.sensorData.status} // Status; 6th bit indicates loss of contact with sensors, 7th bit indicates rainfall overflow +}; + +// Local methods: + +static uint16_t getUSHORT(char* raw) +{ + unsigned char lo = (unsigned char)raw[0]; + unsigned char hi = (unsigned char)raw[1]; + return lo + (hi * 256); +} + +static int16_t getSHORT(char* raw) +{ + unsigned char lo = (unsigned char)raw[0]; + unsigned char hi = (unsigned char)raw[1]; + uint16_t us = lo + (hi * 256); + if (us >= 0x8000) // Test for sign bit + return -(us - 0x8000); // Negative value + else + return us; // Positive value +} + +static uint8_t bcdDecode(uint8_t byte) +{ + uint8_t lo = byte & 0x0F; + uint8_t hi = byte / 16; + + return (lo + (hi * 10)); +} + +static int decodeSensor(char* raw, enum ws_types ws_type, float scale, float* var) +{ + float fresult; + uint16_t usTemp; + + switch (ws_type) + { + case ub: + if ((unsigned char)raw[0] == 0xFF) + { + // Deal with humidity < 10% problem by hard-coding to 9: + fresult = 9; + } + else + { + fresult = (unsigned char)raw[0] * scale; + } + break; + case us: + usTemp = getUSHORT(raw); + if (usTemp == 0xFFFF) + { + return ERROR; + } + fresult = usTemp * scale; + break; + case ss: + if (((unsigned char)raw[0] == 0xFF) && ((unsigned char)raw[1] == 0xFF)) + { + return ERROR; + } + fresult = getSHORT(raw) * scale; + break; + case pb: + fresult = (unsigned char)raw[0]; + break; + case wa: + // wind average - 12 bits split across a byte and a nibble + if (((unsigned char)raw[0] == 0xFF) && (((unsigned char)raw[2] & 0x0F) == 0x0F)) + { + return ERROR; + } + fresult = (unsigned char)raw[0] + (((unsigned char)raw[2] & 0x0F) * 256); + fresult = fresult * scale; + break; + case wg: + // wind gust - 12 bits split across a byte and a nibble + if (((unsigned char)raw[0] == 0xFF) && (((unsigned char)raw[1] & 0xF0) == 0xF0)) + { + return ERROR; + } + fresult = (unsigned char)raw[0] + (((unsigned char)raw[1] & 0xF0) * 16); + fresult = fresult * scale; + break; + case wd: + if (((unsigned char)raw[0] & 0x80) == 0x80) + { + return ERROR; + } + fresult = (unsigned char)raw[0] * scale; + fresult *= 22.5; + break; + default: + fresult = ARCHIVE_VALUE_NULL; + break; + } + + *var = fresult; + return OK; +} + +// Expects the medium to already be open: +static int readBlock (WVIEWD_WORK *work, int offset, uint8_t* buffer) +{ + // Read 32 bytes data at offset 'offset': + // After sending the read command, the device will send back 32 bytes data within 100ms. + // If not, then it means the command has not been received correctly. + // Read repeatedly until the record stabilizes (two consecutive reads are equal). + + uint8_t rqstBuffer[8], oldBuffer[32], newBuffer[32]; + int retVal, readDone = FALSE, firstTime = TRUE; + + rqstBuffer[0] = 0xA1; // READ COMMAND + rqstBuffer[1] = (char)(offset / 256); // READ ADDRESS HIGH + rqstBuffer[2] = (char)(offset & 0xFF); // READ ADDRESS LOW + rqstBuffer[3] = 0x20; // END MARK + rqstBuffer[4] = 0xA1; // READ COMMAND + rqstBuffer[5] = (char)(offset / 256); // READ ADDRESS HIGH + rqstBuffer[6] = (char)(offset & 0xFF); // READ ADDRESS LOW + rqstBuffer[7] = 0x20; // END MARK + + // Read any pending data on USB bus and discard before starting: + (*(work->medium.usbhidRead))(&work->medium, newBuffer, 32, 500); + + // Read until consecutive reads produce an identical buffer: + while ((!work->exiting) && (!readDone)) + { + // Request read of 32-byte chunk from offset: + retVal = (*(work->medium.usbhidWrite))(&work->medium, rqstBuffer, 8); + if (retVal != 8) + { + radMsgLog (PRI_HIGH, "WH1080: write data request failed!"); + return ERROR; + } + + // Read 32-byte chunk and place in buffer: + retVal = (*(work->medium.usbhidRead))(&work->medium, newBuffer, 32, 1000); + if (retVal != 32) + { + radMsgLog (PRI_HIGH, "WH1080: read data block failed!"); + return ERROR; + } + + // Compare to previous result: + if (!firstTime) + { + if (memcmp(oldBuffer, newBuffer, 32) == 0) + { + // They match and we are done: + memcpy(buffer, newBuffer, 32); + readDone = TRUE; + } + else + { + radMsgLog (PRI_STATUS, "WH1080: readBlock buffer still changing"); + } + } + else + { + firstTime = FALSE; + } + + memcpy(oldBuffer, newBuffer, 32); + } + + return OK; +} + +// Expects the medium to already be open: +static int writeBlock (WVIEWD_WORK *work, int offset, uint8_t* buffer) +{ + // Write 32 bytes data at offset 'offset': + + char rqstBuffer[8]; + int retVal; + + rqstBuffer[0] = 0xA0; // WRITE COMMAND + rqstBuffer[1] = (char)(offset / 256); // WRITE ADDRESS HIGH + rqstBuffer[2] = (char)(offset & 0xFF); // WRITE ADDRESS LOW + rqstBuffer[3] = 0x20; // END MARK + rqstBuffer[4] = 0xA0; // WRITE COMMAND + rqstBuffer[5] = (char)(offset / 256); // WRITE ADDRESS HIGH + rqstBuffer[6] = (char)(offset & 0xFF); // WRITE ADDRESS LOW + rqstBuffer[7] = 0x20; // END MARK + + // Request write of 32-byte chunk from offset: + retVal = (*(work->medium.usbhidWrite))(&work->medium, rqstBuffer, 8); + if (retVal != 8) + { + radMsgLog (PRI_HIGH, "WH1080: write data request failed!"); + return ERROR; + } + + // Write 32-byte chunk: + retVal = (*(work->medium.usbhidWrite))(&work->medium, buffer, 32); + if (retVal != 32) + { + radMsgLog (PRI_HIGH, "WH1080: write data block failed!"); + return ERROR; + } + + // Read 8-byte ACK: + retVal = (*(work->medium.usbhidRead))(&work->medium, buffer, 8, 1000); + if (retVal != 8) + { + radMsgLog (PRI_HIGH, "WH1080: read data ACK failed!"); + return ERROR; + } + + return OK; +} + +// Expects the medium to already be open: +static int writeDataRefresh (WVIEWD_WORK *work) +{ + uint8_t rqstBuffer[8], readBuffer[8]; + int retVal; + + rqstBuffer[0] = 0xA2; // One byte write command + rqstBuffer[1] = 0; + rqstBuffer[2] = 0x1A; + rqstBuffer[3] = 0x20; + rqstBuffer[4] = 0xA2; + rqstBuffer[5] = 0xAA; + rqstBuffer[6] = 0; + rqstBuffer[7] = 0x20; + + retVal = (*(work->medium.usbhidWrite))(&work->medium, rqstBuffer, 8); + if (retVal != 8) + { + radMsgLog (PRI_HIGH, "WH1080: write data ACK failed!"); + return ERROR; + } + + // Read 8-byte ACK: + retVal = (*(work->medium.usbhidRead))(&work->medium, readBuffer, 8, 1000); + if (retVal != 8) + { + radMsgLog (PRI_HIGH, "WH1080: read data ACK failed!"); + return ERROR; + } + + return OK; +} + +// Expects the medium to already be open: +static int readFixedBlock(WVIEWD_WORK *work, uint8_t* block) +{ + // Read fixed block: + if (readBlock(work, 0, block) == ERROR) + { + radMsgLog (PRI_HIGH, "WH1080: readFixedBlock readBlock failed"); + return ERROR; + } + + // Check for valid magic numbers: + // This is hardly an exhaustive list and I can find no definitive + // documentation that lists all possible values; further, I suspect it is + // more of a header than a magic number... + if ((block[0] == 0x55) || + (block[0] == 0xFF) || + (block[0] == 0x01) || + ((block[0] == 0x00) && (block[1] == 0x1E)) || + ((block[0] == 0x00) && (block[1] == 0x01))) + { + return OK; + } + else + { + radMsgLog (PRI_HIGH, "WH1080: readFixedBlock bad magic number %2.2X %2.2X", + (int)block[0], (int)block[1]); + radMsgLog(PRI_HIGH, + "WH1080: You may want to clear the memory on the station " + "console to remove any invalid records or data..."); + return ERROR_ABORT; + } +} + +// Expects the medium to already be open: +static int writeFixedBlock(WVIEWD_WORK *work, uint8_t* block) +{ + // Set for valid data: + block[0] = 0x55; + block[1] = 0xAA; + + if (writeBlock(work, 0, block) == ERROR) + { + return ERROR; + } + + if (writeDataRefresh(work) == ERROR) + { + return ERROR; + } + + return OK; +} + +// Returns: +// OK - if new record retrieved +// ERROR - if there was an interface error +// ERROR_ABORT - if there is no new record (WH1080 generates new records at +// best once a minute) +static int readStationData (WVIEWD_WORK *work) +{ + WH1080_IF_DATA* ifWorkData = (WH1080_IF_DATA*)work->stationData; + int currentPosition, readPosition, index, retVal; + + if ((*(work->medium.usbhidInit))(&work->medium) != OK) + { + return ERROR; + } + + // Read the WH1080 fixed block: + retVal = readFixedBlock(work, &wh1080Work.controlBlock[0]); + if (retVal == ERROR_ABORT) + { + // Try again later (bad magic number): + (*(work->medium.usbhidExit))(&work->medium); + return ERROR_ABORT; + } + else if (retVal == ERROR) + { + // USB interface error: + (*(work->medium.usbhidExit))(&work->medium); + return ERROR; + } + + // Get the current record position; the WH1080 reports the record it is + // building, thus if it changes we need the prior just finished record: + currentPosition = (int)getUSHORT(&wh1080Work.controlBlock[WH1080_CURRENT_POS]); + + // Make sure the index is aligned on 16-byte boundary: + if ((currentPosition % 16) != 0) + { + // bogus, try again later: + (*(work->medium.usbhidExit))(&work->medium); + return ERROR_ABORT; + } + + // Is this the first time? + if (wh1080Work.lastRecord == -1) + { + // Yes. + wh1080Work.lastRecord = currentPosition; + (*(work->medium.usbhidExit))(&work->medium); + return ERROR_ABORT; + } + + // Is there a new record? + if (currentPosition == wh1080Work.lastRecord) + { + // No, wait till it is finished. + (*(work->medium.usbhidExit))(&work->medium); + return ERROR_ABORT; + } + + // Read last record that is now complete: + if (readBlock(work, wh1080Work.lastRecord, &wh1080Work.recordBlock[0]) == ERROR) + { + radMsgLog (PRI_HIGH, "WH1080: read data block at index %d failed!", + wh1080Work.lastRecord); + (*(work->medium.usbhidExit))(&work->medium); + return ERROR; + } + + (*(work->medium.usbhidExit))(&work->medium); + + readPosition = wh1080Work.lastRecord; + wh1080Work.lastRecord = currentPosition; + +//radMsgLogData(wh1080Work.recordBlock, 32); + + // Is the record valid? Check for unpopulated record or no sensor data + // received status bit: + if ((wh1080Work.recordBlock[WH1080_STATUS] & 0x40) != 0) + { + // No! + radMsgLog (PRI_HIGH, + "WH1080: data block at index %d has bad status, ignoring the record", + readPosition); + return ERROR_ABORT; + } + + // Parse the data received: + for (index = 0; index < WH1080_NUM_SENSORS; index ++) + { + if (decodeSensor(&wh1080Work.recordBlock[decodeVals[index].pos], + decodeVals[index].ws_type, + decodeVals[index].scale, + decodeVals[index].var) + != OK) + { + // Bad sensor data, abort this cycle: + radMsgLog (PRI_HIGH, + "WH1080: data block at index %d has bad sensor value, ignoring the record", + readPosition); + return ERROR_ABORT; + } + } + + // Convert to Imperial units: + wh1080Work.sensorData.intemp = wvutilsConvertCToF(wh1080Work.sensorData.intemp); + wh1080Work.sensorData.outtemp = wvutilsConvertCToF(wh1080Work.sensorData.outtemp); + wh1080Work.sensorData.pressure = wvutilsConvertHPAToINHG(wh1080Work.sensorData.pressure); + wh1080Work.sensorData.windAvgSpeed = wvutilsConvertMPSToMPH(wh1080Work.sensorData.windAvgSpeed); + wh1080Work.sensorData.windGustSpeed = wvutilsConvertMPSToMPH(wh1080Work.sensorData.windGustSpeed); + wh1080Work.sensorData.rain = wvutilsConvertMMToIN(wh1080Work.sensorData.rain); + + return OK; +} + + +static void storeLoopPkt (WVIEWD_WORK *work, LOOP_PKT *dest, WH1080_DATA *src) +{ + float tempfloat; + WH1080_IF_DATA* ifWorkData = (WH1080_IF_DATA*)work->stationData; + time_t nowTime = time(NULL); + + // Clear optional data: + stationClearLoopData(work); + + if ((10 < src->pressure && src->pressure < 50) && + (-150 < src->outtemp && src->outtemp < 150)) + { + // WH1080 produces station pressure + dest->stationPressure = src->pressure; + + // Apply calibration here so the computed values reflect it: + dest->stationPressure *= work->calMPressure; + dest->stationPressure += work->calCPressure; + + // compute sea-level pressure (BP) + tempfloat = wvutilsConvertSPToSLP(dest->stationPressure, + src->outtemp, + (float)ifWorkData->elevation); + dest->barometer = tempfloat; + + // calculate altimeter + tempfloat = wvutilsConvertSPToAltimeter(dest->stationPressure, + (float)ifWorkData->elevation); + dest->altimeter = tempfloat; + } + + if (-150 < src->outtemp && src->outtemp < 150) + { + dest->outTemp = src->outtemp; + } + + if (0 <= src->outhumidity && src->outhumidity <= 100) + { + tempfloat = src->outhumidity; + tempfloat += 0.5; + dest->outHumidity = (uint16_t)tempfloat; + } + + if (0 <= src->windAvgSpeed && src->windAvgSpeed <= 250) + { + tempfloat = src->windAvgSpeed; + dest->windSpeedF = tempfloat; + } + + if (0 <= src->windDir && src->windDir <= 360) + { + tempfloat = src->windDir; + tempfloat += 0.5; + dest->windDir = (uint16_t)tempfloat; + dest->windGustDir = (uint16_t)tempfloat; + } + + if (0 <= src->windGustSpeed && src->windGustSpeed <= 250) + { + tempfloat = src->windGustSpeed; + dest->windGustF = tempfloat; + } + + if (0 <= src->rain) + { + if (!work->runningFlag) + { + // just starting, so start with whatever the station reports: + ifWorkData->totalRain = src->rain; + dest->sampleRain = 0; + } + else + { + // process the rain accumulator + if (src->rain - ifWorkData->totalRain >= 0) + { + dest->sampleRain = src->rain - ifWorkData->totalRain; + ifWorkData->totalRain = src->rain; + } + else + { + // we had a counter reset... + dest->sampleRain = src->rain; + dest->sampleRain += (WH1080_RAIN_MAX - ifWorkData->totalRain); + ifWorkData->totalRain = src->rain; + } + } + + if (dest->sampleRain > 2) + { + // Not possible, filter it out: + dest->sampleRain = 0; + } + + // Update the rain accumulator: + sensorAccumAddSample (ifWorkData->rainRateAccumulator, nowTime, dest->sampleRain); + dest->rainRate = sensorAccumGetTotal (ifWorkData->rainRateAccumulator); + dest->rainRate *= (60/WH1080_RAIN_RATE_PERIOD); + } + else + { + dest->sampleRain = 0; + sensorAccumAddSample (ifWorkData->rainRateAccumulator, nowTime, dest->sampleRain); + dest->rainRate = sensorAccumGetTotal (ifWorkData->rainRateAccumulator); + dest->rainRate *= (60/WH1080_RAIN_RATE_PERIOD); + } + + dest->inTemp = src->intemp; + tempfloat = src->inhumidity; + tempfloat += 0.5; + dest->inHumidity = (uint16_t)tempfloat; + + return; +} + + +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////// A P I ///////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// + +int wh1080Init (WVIEWD_WORK *work) +{ + WH1080_IF_DATA* ifWorkData = (WH1080_IF_DATA*)work->stationData; + fd_set rfds; + struct timeval tv; + int ret; + time_t nowTime = time(NULL) - (WV_SECONDS_IN_HOUR/(60/WH1080_RAIN_RATE_PERIOD)); + ARCHIVE_PKT recordStore; + unsigned char controlBlock[WH1080_BUFFER_CHUNK]; + + memset (&wh1080Work, 0, sizeof(wh1080Work)); + + // Create the rain accumulator (WH1080_RAIN_RATE_PERIOD minute age) + // so we can compute rain rate: + ifWorkData->rainRateAccumulator = sensorAccumInit(WH1080_RAIN_RATE_PERIOD); + + // Populate the accumulator with the last WH1080_RAIN_RATE_PERIOD minutes: + while ((nowTime = dbsqliteArchiveGetNextRecord(nowTime, &recordStore)) != ERROR) + { + sensorAccumAddSample(ifWorkData->rainRateAccumulator, + recordStore.dateTime, + recordStore.value[DATA_INDEX_rain]); + } + + if ((*(work->medium.usbhidInit))(&work->medium) != OK) + { + return ERROR; + } + + // Set the station to log data once per minute: + while ((!work->exiting) && (readFixedBlock(work, controlBlock) != OK)) + { + radMsgLog (PRI_HIGH, "WH1080: Initial fixed block read failed"); + (*(work->medium.usbhidExit))(&work->medium); + radUtilsSleep(5000); + (*(work->medium.usbhidInit))(&work->medium); + radMsgLog (PRI_HIGH, "WH1080: Retrying initial fixed block read"); + } + + // For some reason the WH1080 wants the IF closed between a read and a write: + (*(work->medium.usbhidExit))(&work->medium); + if (work->exiting) + { + return ERROR; + } + + controlBlock[WH1080_SAMPLING_INTERVAL] = 1; + (*(work->medium.usbhidInit))(&work->medium); + + if (writeFixedBlock(work, controlBlock) == ERROR) + { + (*(work->medium.usbhidExit))(&work->medium); + return ERROR; + } + + radUtilsSleep(2000); + + (*(work->medium.usbhidExit))(&work->medium); + + radUtilsSleep(1000); + + wh1080Work.lastRecord = -1; + + // populate the LOOP structure: + radMsgLog (PRI_HIGH, "Waiting for the next weather record to be ready " + "in the console to populate initial wview sensor readings " + "(this could take some time);"); + radMsgLog (PRI_HIGH, "While waiting be sure you are receiving all sensors on the console;"); + radMsgLog (PRI_HIGH, "if not, you may need to relocate the sensors or the console."); + while ((!work->exiting) && (readStationData(work) != OK)) + { + radUtilsSleep(1000); + } + if (work->exiting) + { + return ERROR; + } + + ifWorkData->wh1080Readings = wh1080Work.sensorData; + storeLoopPkt (work, &work->loopPkt, &ifWorkData->wh1080Readings); + + // we must indicate successful completion here - + // even though we are synchronous, the daemon wants to see this event: + radProcessEventsSend (NULL, STATION_INIT_COMPLETE_EVENT, 0); + + return OK; +} + +void wh1080Exit (WVIEWD_WORK *work) +{ + return; +} + +void wh1080ReadData (WVIEWD_WORK *work) +{ + return; +} + +void wh1080GetReadings (WVIEWD_WORK *work) +{ + WH1080_IF_DATA* ifWorkData = (WH1080_IF_DATA*)work->stationData; + + if (readStationData(work) == OK) + { + // populate the LOOP structure: + ifWorkData->wh1080Readings = wh1080Work.sensorData; + storeLoopPkt (work, &work->loopPkt, &ifWorkData->wh1080Readings); + + // indicate the LOOP packet is done + radProcessEventsSend (NULL, STATION_LOOP_COMPLETE_EVENT, 0); + } +} + diff --git a/stations/WH1080/wh1080Protocol.h b/stations/WH1080/wh1080Protocol.h new file mode 100755 index 0000000..b7c5f13 --- /dev/null +++ b/stations/WH1080/wh1080Protocol.h @@ -0,0 +1,181 @@ +#ifndef INC_wh1080protocolh +#define INC_wh1080protocolh +/*--------------------------------------------------------------------------- + + FILENAME: + wh1080Protocol.h + + PURPOSE: + Provide protocol utilities for WH1080 station communication. + + REVISION HISTORY: + Date Engineer Revision Remarks + 02/17/2011 M.S. Teel 0 Original + + NOTES: + Parts of this implementation were inspired by the fowsr project + (C) Arne-Jorgen Auberg (arne.jorgen.auberg@gmail.com) with hidapi + mods by Bill Northcott. + + The WH1080 protocol is undocumented. The following was observed + by sniffing the USB interface: + + A1 is a read command: + It is sent as A1XX XX20 A1XX XX20 where XXXX is the offset in the + memory map. The WH1080 responds with 4 8 byte blocks to make up a + 32 byte read of address XXXX. + + A0 is a write command: + It is sent as A0XX XX20 A0XX XX20 where XXXX is the offset in the + memory map. It is followed by 4 8 byte chunks of data to be written + at the offset. The WH1080 acknowledges the write with an 8 byte + chunk: A5A5 A5A5. + + A2 is a one byte write command. + It is used as: A200 1A20 A2AA 0020 to indicate a data refresh. + The WH1080 acknowledges the write with an 8 byte chunk: A5A5 A5A5. + + LICENSE: + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +/* ... System include files +*/ +#include +#include +#include +#include +#include +#include +#include +#include + +/* ... Library include files +*/ +#include +#include +#include + +/* ... Local include files +*/ +#include +#include +#include +#include +#include +#include + +/* WH1080 */ +#define WH1080_VENDOR_ID 0x1941 +#define WH1080_PRODUCT_ID 0x8021 + + +// Define the rain rate acuumulator period (minutes): +#define WH1080_RAIN_RATE_PERIOD 5 + +// Weather Station buffer parameters: +#define WH1080_RAIN_MAX 0x10000 // Wrap value for rain counter +#define WH1080_BUFFER_START 0x100 // Size of fixed block + // start of buffer records +#define WH1080_BUFFER_CHUNK 0x20 // Size of chunk received over USB + +// Weather Station record memory positions: +#define WH1080_DELAY 0 // Position of delay parameter +#define WH1080_HUMIDITY_IN 1 // Position of inside humidity parameter +#define WH1080_TEMPERATURE_IN 2 // Position of inside temperature parameter +#define WH1080_HUMIDITY_OUT 4 // Position of outside humidity parameter +#define WH1080_TEMPERATURE_OUT 5 // Position of outside temperature parameter +#define WH1080_ABS_PRESSURE 7 // Position of absolute pressure parameter +#define WH1080_WIND_AVE 9 // Position of wind direction parameter +#define WH1080_WIND_GUST 10 // Position of wind direction parameter +#define WH1080_WIND_DIR 12 // Position of wind direction parameter +#define WH1080_RAIN 13 // Position of rain parameter +#define WH1080_STATUS 15 // Position of status parameter + +// Control block offsets: +#define WH1080_SAMPLING_INTERVAL 16 // Position of sampling interval +#define WH1080_DATA_COUNT 27 // Position of data_count parameter +#define WH1080_CURRENT_POS 30 // Position of current_pos parameter + +// Types for decoding raw weather station data. +// ub unsigned byte +// sb signed byte +// us unsigned short +// ss signed short +// dt date time bcd yymmddhhmm +// tt time bcd hhmm +// pb status - bit 6 lost contact - bit 7 rain counter overflow +// wa wind average low bits puls lower bits of address +2 +// wg wind gust low bits plus upper bits of address +1 +// wd wind direction + +enum ws_types {ub,sb,us,ss,dt,tt,pb,wa,wg,wd}; + +#define WH1080_NUM_SENSORS 11 + +// Define the decoder type structure: +typedef struct _decodeType +{ + int pos; + enum ws_types ws_type; + float scale; + float* var; +} WH1080_DECODE_TYPE; + +// define the readings collector +typedef struct +{ + float delay; + float inhumidity; + float intemp; + float outhumidity; + float outtemp; + float pressure; + float windAvgSpeed; + float windGustSpeed; + float windDir; + float rain; + float status; +} WH1080_DATA; + + +// define the work area +typedef struct +{ + WH1080_DATA sensorData; + uint8_t controlBlock[WH1080_BUFFER_CHUNK]; + uint8_t recordBlock[WH1080_BUFFER_CHUNK]; + int lastRecord; +} WH1080_WORK; + + +// define WH1080-specific interface data here +typedef struct +{ + int elevation; + float latitude; + float longitude; + int archiveInterval; + WH1080_DATA wh1080Readings; + float totalRain; // to track cumulative changes + WV_ACCUM_ID rainRateAccumulator; // to compute rain rate +} WH1080_IF_DATA; + + +// call once during initialization +extern int wh1080Init (WVIEWD_WORK *work); + +// do cleanup +extern void wh1080Exit (WVIEWD_WORK *work); + +// read data from station: +extern void wh1080ReadData (WVIEWD_WORK *work); + +// get loop packet data: +extern void wh1080GetReadings (WVIEWD_WORK *work); + +#endif + diff --git a/stations/WMR918/Makefile.am b/stations/WMR918/Makefile.am new file mode 100755 index 0000000..1cc8892 --- /dev/null +++ b/stations/WMR918/Makefile.am @@ -0,0 +1,64 @@ +# Makefile - Oregon Scientific WMR918 station daemon + +# define the executable to be built +bin_PROGRAMS = wviewd_wmr918 + +# define include directories +INCLUDES = \ + -I$(top_srcdir)/common \ + -I$(top_srcdir)/stations/common \ + -I$(prefix)/include \ + -D_GNU_SOURCE \ + -DWV_CONFIG_DIR=\"$(sysconfdir)/wview\" \ + -DWV_RUN_DIR=\"$(localstatedir)/wview\" \ + -DBUILD_WVIEWD + +# define the sources +wviewd_wmr918_SOURCES = \ + $(top_srcdir)/common/sensor.c \ + $(top_srcdir)/common/wvutils.c \ + $(top_srcdir)/common/wvconfig.c \ + $(top_srcdir)/common/status.c \ + $(top_srcdir)/common/dbsqlite.c \ + $(top_srcdir)/common/dbsqliteHiLow.c \ + $(top_srcdir)/common/windAverage.c \ + $(top_srcdir)/common/emailAlerts.c \ + $(top_srcdir)/stations/common/computedData.c \ + $(top_srcdir)/stations/common/daemon.c \ + $(top_srcdir)/stations/common/station.c \ + $(top_srcdir)/stations/common/serial.c \ + $(top_srcdir)/stations/common/ethernet.c \ + $(top_srcdir)/stations/common/stormRain.c \ + $(top_srcdir)/stations/common/parser.c \ + $(top_srcdir)/stations/WMR918/wmr918Interface.c \ + $(top_srcdir)/stations/WMR918/wmr918protocol.c \ + $(top_srcdir)/common/sensor.h \ + $(top_srcdir)/common/datadefs.h \ + $(top_srcdir)/common/dbsqlite.h \ + $(top_srcdir)/common/services.h \ + $(top_srcdir)/common/sysdefs.h \ + $(top_srcdir)/common/wvconfig.h \ + $(top_srcdir)/common/status.h \ + $(top_srcdir)/common/windAverage.h \ + $(top_srcdir)/common/emailAlerts.h \ + $(top_srcdir)/common/beaufort.h \ + $(top_srcdir)/stations/common/computedData.h \ + $(top_srcdir)/stations/common/daemon.h \ + $(top_srcdir)/stations/common/station.h \ + $(top_srcdir)/stations/common/serial.h \ + $(top_srcdir)/stations/common/ethernet.h \ + $(top_srcdir)/stations/common/stormRain.h \ + $(top_srcdir)/stations/common/parser.h \ + $(top_srcdir)/stations/WMR918/wmr918Interface.h \ + $(top_srcdir)/stations/WMR918/wmr918protocol.h + +# define libraries +wviewd_wmr918_LDADD = + +# define library directories +wviewd_wmr918_LDFLAGS = -L$(prefix)/lib -L$(prefix)/usr/lib -L/usr/lib + +if CROSSCOMPILE +wviewd_wmr918_LDFLAGS += $(prefix)/lib/crt1.o $(prefix)/lib/crti.o $(prefix)/lib/crtn.o +endif + diff --git a/stations/WMR918/doc/WMR9x8-Protocol.txt b/stations/WMR918/doc/WMR9x8-Protocol.txt new file mode 100644 index 0000000..4aa0297 --- /dev/null +++ b/stations/WMR918/doc/WMR9x8-Protocol.txt @@ -0,0 +1,208 @@ +John Stanley +stanley@peak.org + +WMR-918 serial data is transmitted at 9600 baud, 8N1. + +Each packet starts with two binary bytes of 0xFF. Each packet ends with a +checksum. The type of packet is given by the third byte, and ranges from +0x00 through 0x0f. Each type of packet has a predetermined length. + +For example, a typical type 5 packet looks like: + + ff ff 05 00 09 02 47 09 dc 0c 50 79 0f + +The checksum is the simple addition of each byte in the packet, excluding +the checksum byte, keeping only the lowest 8 bits. + +In all further examples, the leading "ff ff" will be omitted. The .tab +output from wmr918d omits the leading "ff ff". + +The known packet types are: + + 00 - anemometer and wind related data + 01 - rain guage + 02 - extra sensors + 03 - outside temp, humidity and dewpoint + 04 - unknown + 05 - inside temp, humidity, dewpoint, and baro. + 06 - inside temp, humidity, dewpoint, baro for wmr968 and some + wmr918's. + 07 - unknown + 08 - unknown + 09 - unknown + 0a - unknown + 0b - unknown + 0c - unknown + 0d - unknown + 0e - sequence number + 0f - hourly status report + +The following description will attempt to use the same format as the WX200 +listing by Mike Wingstrom. H is a hex digit from 0 to f. D is a decimal digit +from 0 through 9. B is bit encoded, bit 3 is the high bit (0x8). + +Type 00 + + Example: 00 00 90 01 00 00 00 07 96 + +Byte Nibble Bit Meaning +00. 0 00 Anemometer data packet +00. 1 Bx Battery status. Higher value == lower battery volt +00. 1 xB Unknown +00. 2 DD Gust direction, bc of 0. +05. 9 DD Sea level reference, ab of . Add this to raw + bp from byte 6 to get sea level pressure. + + Example: 21.1 Celsius, 46% humidity, dew point 9 Celsius + BP 1015 mb, sea level pressure 1015 mb. + +Type 06 + + Example: 06 00 29 02 41 09 8b 61 90 33 06 2e + +Byte Nibble Bit Meaning +06. 0 06 Inside sensor data +06. 1 Bx Battery status. Higher value == lower battery volt +06. 1 xB Unknown +06. 2 DD Inside temp, bc of -?. +06. 9 DD Sea level reference, cd of . +06.10 DD Sea level reference, ab of Add this to raw + bp from byte 6 and 7 to get sea level pressure. + + Example: 22.9 Celsius, 41% humidity, dew point 9 Celsius + BP 995 mb, sea level pressure 1028.9 mb. + +Type E + + Example: 0e 81 8d + +Byte Nibble Bit Meaning +0e. 0 0e Sequence number packet +0e. 1 Bx Status, high bit, battery for main unit +0e. 1 DD After removing high bit, minute chime + + Example: status = 1, time is hh:01 + +Type F + + Example: 0f 80 07 09 03 00 a0 + +Byte Nibble Bit Meaning +0f. 0 0f Hour Chime +0f. 1 BB Status. High bit == battery low +0f. 2 DD Hour +0f. 3 DD Day +0f. 4 DD Month +0f. 5 DD Year + + Example: It's 7AM, 9-March-2000, do you know where your weather + station is? + + +Thanks to the following people for providing additional data and pointing +out my stupid mistakes: + +John R. Covert +Brad and Stephanie Grant +Giovanni BOGLIONE +Alan K. Jackson +Roy (garlic) +Krenn Werner +Steven Danz +Barry Newton diff --git a/stations/WMR918/wmr918Interface.c b/stations/WMR918/wmr918Interface.c new file mode 100755 index 0000000..5122d7e --- /dev/null +++ b/stations/WMR918/wmr918Interface.c @@ -0,0 +1,414 @@ +/*--------------------------------------------------------------------------- + + FILENAME: + ws2300Interface.c + + PURPOSE: + Provide the Oregon Scientific WMR918 station interface API and utilities. + + REVISION HISTORY: + Date Engineer Revision Remarks + 04/09/2008 M.S. Teel 0 Original + + NOTES: + + + LICENSE: + Copyright (c) 2008, Mark S. Teel (mark@teel.ws) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +/* ... System include files +*/ + +/* ... Library include files +*/ + +/* ... Local include files +*/ +#include + +/* ... global memory declarations +*/ + +/* ... local memory +*/ + +static WMR918_IF_DATA wmr918WorkData; +static void (*ArchiveIndicator) (ARCHIVE_PKT* newRecord); + +static void serialPortConfig (int fd); + + + +////////////****////**** S T A T I O N A P I ****////****//////////// +///// Must be provided by each supported wview station interface ////// + +// station-supplied init function +// -- Can Be Asynchronous - event indication required -- +// +// MUST: +// - set the 'stationGeneratesArchives' flag in WVIEWD_WORK: +// if the station generates archive records (TRUE) or they should be +// generated automatically by the daemon from the sensor readings (FALSE) +// - Initialize the 'stationData' store for station work area +// - Initialize the interface medium +// - do initial LOOP acquisition +// - do any catch-up on archive records if there is a data logger +// - 'work->runningFlag' can be used for start up synchronization but should +// not be modified by the station interface code +// - indicate init is done by sending the STATION_INIT_COMPLETE_EVENT event to +// this process (radProcessEventsSend (NULL, STATION_INIT_COMPLETE_EVENT, 0)) +// +// OPTIONAL: +// - Initialize a state machine or any other construct required for the +// station interface - these should be stored in the 'stationData' store +// +// 'archiveIndication' - indication callback used to pass back an archive record +// generated as a result of 'stationGetArchive' being called; should receive a +// NULL pointer for 'newRecord' if no record available; only used if +// 'stationGeneratesArchives' flag is set to TRUE by the station interface +// +// Returns: OK or ERROR +// +int stationInit +( + WVIEWD_WORK *work, + void (*archiveIndication)(ARCHIVE_PKT* newRecord) +) +{ + int i; + STIM stim; + + memset (&wmr918WorkData, 0, sizeof(wmr918WorkData)); + + // save the archive indication callback (we should never need it) + ArchiveIndicator = archiveIndication; + + // set our work data pointer + work->stationData = &wmr918WorkData; + + // set the Archive Generation flag to indicate the WMR918 DOES NOT + // generate them + work->stationGeneratesArchives = FALSE; + + // initialize the medium abstraction based on user configuration + if (!strcmp (work->stationInterface, "serial")) + { + if (serialMediumInit (&work->medium, serialPortConfig, O_RDONLY | O_NOCTTY | O_NDELAY) == ERROR) + { + radMsgLog (PRI_HIGH, "stationInit: serial MediumInit failed"); + return ERROR; + } + } + else if (!strcmp (work->stationInterface, "ethernet")) + { + if (ethernetMediumInit (&work->medium, work->stationHost, work->stationPort) + == ERROR) + { + radMsgLog (PRI_HIGH, "stationInit: ethernet MediumInit failed"); + return ERROR; + } + } + else + { + radMsgLog (PRI_HIGH, "stationInit: medium %s not supported", + work->stationInterface); + return ERROR; + } + + // initialize the interface using the media specific routine + if ((*(work->medium.init)) (&work->medium, work->stationDevice) == ERROR) + { + radMsgLog (PRI_HIGH, "stationInit: medium setup failed"); + return ERROR; + } + + if (!strcmp (work->stationInterface, "serial")) + { + radMsgLog (PRI_STATUS, "WMR918 on %s opened ...", + work->stationDevice); + } + else if (!strcmp (work->stationInterface, "ethernet")) + { + radMsgLog (PRI_STATUS, "WMR918 on %s:%d opened ...", + work->stationHost, work->stationPort); + } + + // grab the station configuration now + if (stationGetConfigValueInt (work, + STATION_PARM_ELEVATION, + &wmr918WorkData.elevation) + == ERROR) + { + radMsgLog (PRI_HIGH, "stationInit: stationGetConfigValueInt ELEV failed!"); + (*(work->medium.exit)) (&work->medium); + return ERROR; + } + if (stationGetConfigValueFloat (work, + STATION_PARM_LATITUDE, + &wmr918WorkData.latitude) + == ERROR) + { + radMsgLog (PRI_HIGH, "stationInit: stationGetConfigValueInt LAT failed!"); + (*(work->medium.exit)) (&work->medium); + return ERROR; + } + if (stationGetConfigValueFloat (work, + STATION_PARM_LONGITUDE, + &wmr918WorkData.longitude) + == ERROR) + { + radMsgLog (PRI_HIGH, "stationInit: stationGetConfigValueInt LONG failed!"); + (*(work->medium.exit)) (&work->medium); + return ERROR; + } + if (stationGetConfigValueInt (work, + STATION_PARM_ARC_INTERVAL, + &wmr918WorkData.archiveInterval) + == ERROR) + { + radMsgLog (PRI_HIGH, "stationInit: stationGetConfigValueInt ARCINT failed!"); + (*(work->medium.exit)) (&work->medium); + return ERROR; + } + if (stationGetConfigValueInt (work, + STATION_PARM_OUTSIDE_CHANNEL, + &wmr918WorkData.outsideChannel) + == ERROR) + { + radMsgLog (PRI_HIGH, "stationInit: stationGetConfigValueInt outside channel failed!"); + (*(work->medium.exit)) (&work->medium); + return ERROR; + } + + // set the work archive interval now + work->archiveInterval = wmr918WorkData.archiveInterval; + + // sanity check the archive interval against the most recent record + if (stationVerifyArchiveInterval (work) == ERROR) + { + // bad magic! + radMsgLog (PRI_HIGH, "stationInit: stationVerifyArchiveInterval failed!"); + radMsgLog (PRI_HIGH, "You must either move old archive data out of the way -or-"); + radMsgLog (PRI_HIGH, "fix the interval setting..."); + (*(work->medium.exit)) (&work->medium); + return ERROR; + } + else + { + radMsgLog (PRI_STATUS, "station archive interval: %d minutes", + work->archiveInterval); + } + + radMsgLog (PRI_STATUS, "Starting station interface: WMR918"); + + if (wmr918Init (work) == ERROR) + { + radMsgLog (PRI_HIGH, "stationInit: wmr918Init failed!"); + (*(work->medium.exit)) (&work->medium); + return ERROR; + } + + return OK; +} + +// station-supplied exit function +// +// Returns: N/A +// +void stationExit (WVIEWD_WORK *work) +{ + wmr918Exit (work); + (*(work->medium.exit)) (&work->medium); + + return; +} + +// station-supplied function to retrieve positional info (lat, long, elev) - +// should populate 'work' fields: latitude, longitude, elevation +// -- Synchronous -- +// +// - If station does not store these parameters, they can be retrieved from the +// wview.conf file (see daemon.c for example conf file use) - user must choose +// station type "Generic" when running the wviewconfig script +// +// Returns: OK or ERROR +// +int stationGetPosition (WVIEWD_WORK *work) +{ + // just set the values from our internal store - we retrieved them in + // stationInit + work->elevation = (int16_t)wmr918WorkData.elevation; + if (wmr918WorkData.latitude >= 0) + work->latitude = (int16_t)((wmr918WorkData.latitude*10)+0.5); + else + work->latitude = (int16_t)((wmr918WorkData.latitude*10)-0.5); + if (wmr918WorkData.longitude >= 0) + work->longitude = (int16_t)((wmr918WorkData.longitude*10)+0.5); + else + work->longitude = (int16_t)((wmr918WorkData.longitude*10)-0.5); + + radMsgLog (PRI_STATUS, "station location: elevation: %d feet", + work->elevation); + + radMsgLog (PRI_STATUS, "station location: latitude: %3.1f %c longitude: %3.1f %c", + (float)abs(work->latitude)/10.0, + ((work->latitude < 0) ? 'S' : 'N'), + (float)abs(work->longitude)/10.0, + ((work->longitude < 0) ? 'W' : 'E')); + + return OK; +} + +// station-supplied function to indicate a time sync should be performed if the +// station maintains time, otherwise may be safely ignored +// -- Can Be Asynchronous -- +// +// Returns: OK or ERROR +// +int stationSyncTime (WVIEWD_WORK *work) +{ + // We don't use the WMR918 time... + return OK; +} + +// station-supplied function to indicate sensor readings should be performed - +// should populate 'work' struct: loopPkt (see datadefs.h for minimum field reqs) +// -- Can Be Asynchronous -- +// +// - indicate readings are complete by sending the STATION_LOOP_COMPLETE_EVENT +// event to this process (radProcessEventsSend (NULL, STATION_LOOP_COMPLETE_EVENT, 0)) +// +// Returns: OK or ERROR +// +int stationGetReadings (WVIEWD_WORK *work) +{ + wmr918GetReadings (work); + + return OK; +} + +// station-supplied function to indicate an archive record should be generated - +// MUST populate an ARCHIVE_RECORD struct and indicate it to 'archiveIndication' +// function passed into 'stationInit' +// -- Asynchronous - callback indication required -- +// +// Returns: OK or ERROR +// +// Note: 'archiveIndication' should receive a NULL pointer for the newRecord if +// no record is available +// Note: This function will only be invoked by the wview daemon if the +// 'stationInit' function set the 'stationGeneratesArchives' to TRUE +// +int stationGetArchive (WVIEWD_WORK *work) +{ + // just indicate a NULL record, WMR918 does not generate them (and this + // function should never be called!) + (*ArchiveIndicator) (NULL); + return OK; +} + +// station-supplied function to indicate data is available on the station +// interface medium (serial or ethernet) - +// It is the responsibility of the station interface to read the data from the +// medium and process appropriately. The data does not have to be read within +// the context of this function, but may be used to stimulate a state machine. +// -- Synchronous -- +// +// Returns: N/A +// +void stationDataIndicate (WVIEWD_WORK *work) +{ + wmr918ReadData (work); + return; +} + +// station-supplied function to receive IPM messages - any message received by +// the generic station message handler which is not recognized will be passed +// to the station-specific code through this function. +// It is the responsibility of the station interface to process the message +// appropriately (or ignore it). +// -- Synchronous -- +// +// Returns: N/A +// +void stationMessageIndicate (WVIEWD_WORK *work, int msgType, void *msg) +{ + // N/A + return; +} + +// station-supplied function to indicate the interface timer has expired - +// It is the responsibility of the station interface to start/stop the interface +// timer as needed for the particular station requirements. +// The station interface timer is specified by the 'ifTimer' member of the +// WVIEWD_WORK structure. No other timers in that structure should be manipulated +// in any way by the station interface code. +// -- Synchronous -- +// +// Returns: N/A +// +void stationIFTimerExpiry (WVIEWD_WORK *work) +{ + return; +} + +////////////****//// S T A T I O N A P I E N D ////****//////////// + + +// ... ----- static (local) methods ----- ... + +static void serialPortConfig (int fd) +{ + struct termios port; + int portstatus; + + // We want full control of what is set, simply reset the entire port struct: + memset (&port, 0, sizeof(port)); + + // Serial control options: + port.c_cflag &= ~PARENB; // No parity + port.c_cflag &= ~CSTOPB; // One stop bit + port.c_cflag &= ~CSIZE; // Character size mask + port.c_cflag |= CS8; // Character size 8 bits + port.c_cflag |= CREAD; // Enable Receiver + port.c_cflag &= ~HUPCL; // No "hangup" + port.c_cflag &= ~CRTSCTS; // No flowcontrol + port.c_cflag |= CLOCAL; // Ignore modem control lines + + // Baudrate: + cfsetispeed (&port, B9600); + cfsetospeed (&port, B9600); + + // Serial local options: + // Raw input = clear ICANON, ECHO, ECHOE, and ISIG + // Disable misc other local features = clear FLUSHO, NOFLSH, TOSTOP, PENDIN, + // and IEXTEN + // So we actually clear all flags in port.c_lflag: + port.c_lflag = 0; + + // Serial input options: + // Disable parity check = clear INPCK, PARMRK, and ISTRIP + // Disable software flow control = clear IXON, IXOFF, and IXANY + // Disable any translation of CR and LF = clear INLCR, IGNCR, and ICRNL + // Ignore break condition on input = set IGNBRK + // Ignore parity errors just in case = set IGNPAR; + // So we can clear all flags except IGNBRK and IGNPAR: + port.c_iflag = IGNBRK|IGNPAR; + + // Serial output options: + port.c_oflag = 0; + + port.c_cc[VTIME] = 10; // timer 1s + port.c_cc[VMIN] = 0; // blocking read until 1 char + + tcsetattr (fd, TCSANOW, &port); + tcflush (fd, TCIOFLUSH); + + return; +} + diff --git a/stations/WMR918/wmr918Interface.h b/stations/WMR918/wmr918Interface.h new file mode 100755 index 0000000..41dd1e8 --- /dev/null +++ b/stations/WMR918/wmr918Interface.h @@ -0,0 +1,64 @@ +#ifndef INC_wmr918Interfaceh +#define INC_wmr918Interfaceh +/*--------------------------------------------------------------------------- + + FILENAME: + wmr918Interface.h + + PURPOSE: + Provide the Oregon Scientific WMR station interface API and utilities. + + REVISION HISTORY: + Date Engineer Revision Remarks + 04/09/2008 M.S. Teel 0 Original + + NOTES: + + + LICENSE: + Copyright (c) 2008, Mark S. Teel (mark@teel.ws) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +/* ... System include files +*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* ... Library include files +*/ +#include +#include +#include +#include + +/* ... Local include files +*/ +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#endif + diff --git a/stations/WMR918/wmr918protocol.c b/stations/WMR918/wmr918protocol.c new file mode 100755 index 0000000..9f4fda9 --- /dev/null +++ b/stations/WMR918/wmr918protocol.c @@ -0,0 +1,663 @@ +/*--------------------------------------------------------------------------- + + FILENAME: + wmr918protocol.c + + PURPOSE: + Provide protocol utilities for WMR918 station communication. + + REVISION HISTORY: + Date Engineer Revision Remarks + 02/27/2008 M.S. Teel 0 Original + + NOTES: + + LICENSE: + Copyright (c) 2008, Mark S. Teel (mark@teel.ws) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +/* ... System include files +*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/* ... Library include files +*/ +#include +#include +#include + +/* ... Local include files +*/ +#include +#include +#include +#include + +/* ... global memory declarations +*/ + +/* ... local memory +*/ + +static WMR918_WORK wmr918Work; + +static int wmr918GroupLength[16] = +{ + 8, 13, 6, 6, 4, 10, 11, 0, 0, 0, 0, 0, 0, 0, 2, 6 +}; + +static char* WMRSensorNames[4] = +{ + "Wind", + "Rain", + "OutTemp", + "InTemp" +}; + +static int readStationData (WVIEWD_WORK *work) +{ + int i, retVal, groupType, checkSum, channel; + float tempFloat; + WMR918_IF_DATA* ifWorkData = (WMR918_IF_DATA*)work->stationData; + uint8_t *pPacket = &wmr918Work.readData[2]; + + // read the first three bytes -- 0xff, 0xff, + retVal = (*work->medium.read) (&work->medium, &wmr918Work.readData[0], 3, WMR918_READ_TIMEOUT); + if (retVal != 3) + { + radMsgLog (PRI_MEDIUM, "readStationData: read header failed: %s", + strerror(errno)); + emailAlertSend(ALERT_TYPE_STATION_READ); + return ERROR; + } + + while ((wmr918Work.readData[0] != 0xff) || + (wmr918Work.readData[1] != 0xff) || + (wmr918Work.readData[2] > 15)) + { + wmr918Work.readData[0] = wmr918Work.readData[1]; + wmr918Work.readData[1] = wmr918Work.readData[2]; + retVal = (*work->medium.read) (&work->medium, &wmr918Work.readData[2], 1, WMR918_READ_TIMEOUT); + if (retVal != 1) + { + radMsgLog (PRI_MEDIUM, "readStationData: read header X failed: %s", + strerror(errno)); + emailAlertSend(ALERT_TYPE_STATION_READ); + return ERROR; + } + } + + groupType = (int)wmr918Work.readData[2]; + + // read remaining bytes of this type + checksum: + retVal = (*work->medium.read) (&work->medium, + &wmr918Work.readData[3], + wmr918GroupLength[groupType], + WMR918_READ_TIMEOUT); + if (retVal != wmr918GroupLength[groupType]) + { + radMsgLog (PRI_MEDIUM, "readStationData: read payload failed: %s", + strerror(errno)); + emailAlertSend(ALERT_TYPE_STATION_READ); + return ERROR; + } + + // Verify checksum: + checkSum = 0; + for (i = 0; i < wmr918GroupLength[groupType] + 2; i ++) + { + checkSum += wmr918Work.readData[i]; + } + checkSum &= 0xFF; + if (checkSum != wmr918Work.readData[wmr918GroupLength[groupType] + 2]) + { + radMsgLog (PRI_MEDIUM, "readStationData: checksum mismatch: computed %2.2X, RX %2.2X", + checkSum, wmr918Work.readData[wmr918GroupLength[groupType] + 2]); + emailAlertSend(ALERT_TYPE_STATION_READ); + return ERROR; + } + + // Parse it for data: + switch (groupType) + { + case WMR918GROUP0: + { + wmr918Work.dataRXMask |= WMR918_SENSOR_WIND; + + // Battery status: + wmr918Work.sensorData.windBatteryStatus = HI(pPacket[1]); + + // Wind gust direction: + tempFloat = (float)(NUM(pPacket[2]) + (100 * LO(pPacket[3]))); + wmr918Work.sensorData.maxWindDir = tempFloat; + wmr918Work.sensorData.windDir = wmr918Work.sensorData.maxWindDir; + + // Gust speed: + tempFloat = (float)(HI(pPacket[3])); + tempFloat *= 0.1; + tempFloat += (float)(NUM(pPacket[4])); + tempFloat *= 2.237; // convert to mph + wmr918Work.sensorData.maxWindSpeed = tempFloat; + + // Average speed: + tempFloat = (float)(NUM(pPacket[5])); + tempFloat *= 0.1; + tempFloat += (float)(LO(pPacket[6]) * 10); + tempFloat *= 2.237; // convert to mph + wmr918Work.sensorData.windSpeed = tempFloat; + break; + } + case WMR918GROUP1: + { + wmr918Work.dataRXMask |= WMR918_SENSOR_RAIN; + + // Battery status: + wmr918Work.sensorData.rainBatteryStatus = HI(pPacket[1]); + + // Rain rate: + tempFloat = (float)(NUM(pPacket[2]) + (100 * LO(pPacket[3]))); + tempFloat *= 0.03937; + wmr918Work.sensorData.rainrate = tempFloat; + + // Rain total: + tempFloat = (float)NUM(pPacket[4]); + tempFloat += (float)(NUM(pPacket[5]) * 100); + tempFloat *= 0.03937; + wmr918Work.sensorData.rain = tempFloat; + break; + } + case WMR918GROUP2: + { + // Up to 3 channels for WMR968, capture value + channel = LO(pPacket[1]); + + // verify channel and set to use as index: + if (channel != 0x4 && channel != 0x2 && channel != 0x1) + { + radMsgLog(PRI_MEDIUM, "readStationData: group2 data has invalid channel %d", channel); + break; + } + + if (channel == 0x4) + { + channel = 3; + } + + if ((int)ifWorkData->outsideChannel == channel) + wmr918Work.dataRXMask |= WMR918_SENSOR_OUT_TEMP; + + // Battery status: + if ((int)ifWorkData->outsideChannel == channel) + wmr918Work.sensorData.outTempBatteryStatus = HI(pPacket[1]) & 0x7; + else if ((int)ifWorkData->outsideChannel < channel) + wmr918Work.sensorData.extraBatteryStatus[channel - 1] = HI(pPacket[1]); + else + wmr918Work.sensorData.extraBatteryStatus[channel] = HI(pPacket[1]); + + // Temp: + tempFloat = (float)NUM(pPacket[2]); + tempFloat *= 0.1; + tempFloat += (float)(LO(pPacket[3]) * 10); + if (BIT(HI(pPacket[3]),3)) + { + tempFloat *= -1.0; + } + tempFloat *= 9; + tempFloat /= 5; + tempFloat += 32; + if ((int)ifWorkData->outsideChannel == channel) + wmr918Work.sensorData.outTemp = tempFloat; + else if ((int)ifWorkData->outsideChannel < channel) + wmr918Work.sensorData.extraTemp[channel - 1] = tempFloat; + else + wmr918Work.sensorData.extraTemp[channel] = tempFloat; + + // Humidity: + tempFloat = (float)NUM(pPacket[4]); + if ((int)ifWorkData->outsideChannel == channel) + wmr918Work.sensorData.outHumidity = tempFloat; + else if ((int)ifWorkData->outsideChannel < channel) + wmr918Work.sensorData.extraHumidity[channel - 1] = tempFloat; + else + wmr918Work.sensorData.extraHumidity[channel] = tempFloat; + + break; + } + case WMR918GROUP3: + { + // check if this is the primary outside temperature selection + // if not then this sensor will become channel 0 extra sensor + if ((int)ifWorkData->outsideChannel == 0) + wmr918Work.dataRXMask |= WMR918_SENSOR_OUT_TEMP; + + // Battery status: + if ((int)ifWorkData->outsideChannel == 0) + wmr918Work.sensorData.outTempBatteryStatus = HI(pPacket[1]) & 0x7; + else + wmr918Work.sensorData.extraBatteryStatus[0] = HI(pPacket[1]); + + // Temp: + tempFloat = (float)NUM(pPacket[2]); + tempFloat *= 0.1; + tempFloat += (float)(LO(pPacket[3]) * 10); + if (BIT(HI(pPacket[3]),3)) + { + tempFloat *= -1.0; + } + tempFloat *= 9; + tempFloat /= 5; + tempFloat += 32; + if ((int)ifWorkData->outsideChannel == 0) + wmr918Work.sensorData.outTemp = tempFloat; + else + wmr918Work.sensorData.extraTemp[0] = tempFloat; + + // Humidity: + tempFloat = (float)NUM(pPacket[4]); + if ((int)ifWorkData->outsideChannel == 0) + wmr918Work.sensorData.outHumidity = tempFloat; + else + wmr918Work.sensorData.extraHumidity[0] = tempFloat; + + break; + } + case WMR918GROUP4: + { + int tmpChan; + // + // Channel is bit-position encoded in the low order of packet 1 range 1-3 + // + tmpChan = LO(pPacket[1]) & 0x07; + if (tmpChan == 4) + { + tmpChan = 3; + } + // Sanity -- keeping same. + if ((tmpChan <= 0) || (tmpChan > 4)) + { + tmpChan = 1; + } + // Temp: + tempFloat = (float)NUM(pPacket[2]); + tempFloat *= 0.1; + tempFloat += (float)(LO(pPacket[3]) * 10); + if (BIT(HI(pPacket[3]),3)) + { + tempFloat *= -1.0; + } + tempFloat *= 9; + tempFloat /= 5; + tempFloat += 32; + // + // To support legacy pool sensors take channel 1 (note - channel selection on the + // pool sensor is by switch on some models. + // + if (tmpChan == 1) + { + wmr918Work.sensorData.poolTempBatteryStatus = HI (pPacket[1]) & 0x7; + wmr918Work.sensorData.pool = tempFloat; + } + wmr918Work.sensorData.extraTemp[tmpChan - 1] = tempFloat; + break; + } + case WMR918GROUP5: + { + wmr918Work.dataRXMask |= WMR918_SENSOR_IN_TEMP; + + // Battery status: + wmr918Work.sensorData.inTempBatteryStatus = HI(pPacket[1]); + + // Temp: + tempFloat = (float)NUM(pPacket[2]); + tempFloat *= 0.1; + tempFloat += (float)(LO(pPacket[3]) * 10); + if (BIT(HI(pPacket[3]),3)) + { + tempFloat *= -1.0; + } + tempFloat *= 9; + tempFloat /= 5; + tempFloat += 32; + wmr918Work.sensorData.inTemp = tempFloat; + + // Humidity: + tempFloat = (float)NUM(pPacket[4]); + wmr918Work.sensorData.inHumidity = tempFloat; + + // BP: + tempFloat = (float)pPacket[6]; + tempFloat += 795; + tempFloat *= 0.02953; + wmr918Work.sensorData.pressure = tempFloat; + break; + } + case WMR918GROUP6: + { + wmr918Work.dataRXMask |= WMR918_SENSOR_IN_TEMP; + + // Battery status: + wmr918Work.sensorData.inTempBatteryStatus = HI(pPacket[1]); + + // Temp: + tempFloat = (float)NUM(pPacket[2]); + tempFloat *= 0.1; + tempFloat += (float)(LO(pPacket[3]) * 10); + if (BIT(HI(pPacket[3]),3)) + { + tempFloat *= -1.0; + } + tempFloat *= 9; + tempFloat /= 5; + tempFloat += 32; + wmr918Work.sensorData.inTemp = tempFloat; + + // Humidity: + tempFloat = (float)NUM(pPacket[4]); + wmr918Work.sensorData.inHumidity = tempFloat; + + // BP: + tempFloat = (float)((int)pPacket[6] + (int)(BIT(LO(pPacket[7]),0) << 8)); + tempFloat += 600; + tempFloat *= 0.02953; + wmr918Work.sensorData.pressure = tempFloat; + + // Forecast: + wmr918Work.sensorData.tendency = HI(pPacket[7]); + + break; + } + default: + { + break; + } + } + + return groupType; +} + +static void storeLoopPkt (WVIEWD_WORK *work, LOOP_PKT *dest, WMR918_DATA *src) +{ + float tempfloat; + WMR918_IF_DATA* ifWorkData = (WMR918_IF_DATA*)work->stationData; + time_t nowTime = time(NULL); + + // Clear optional data: + stationClearLoopData(work); + + + // WMR918 produces station pressure + if (10 < src->pressure && src->pressure < 50) + { + dest->stationPressure = src->pressure; + + // Apply calibration here so the computed values reflect it: + dest->stationPressure *= work->calMPressure; + dest->stationPressure += work->calCPressure; + + if (-150 < src->outTemp && src->outTemp < 150) + { + // compute sea-level pressure (BP) + dest->barometer = wvutilsConvertSPToSLP (dest->stationPressure, + src->outTemp, + (float)ifWorkData->elevation); + } + + // calculate altimeter + dest->altimeter = wvutilsConvertSPToAltimeter (dest->stationPressure, + (float)ifWorkData->elevation); + } + + if (-150 < src->outTemp && src->outTemp < 150) + dest->outTemp = src->outTemp; + + if (0 <= src->outHumidity && src->outHumidity <= 100) + { + tempfloat = src->outHumidity; + tempfloat += 0.5; + dest->outHumidity = (uint16_t)tempfloat; + } + + if (0 <= src->windSpeed && src->windSpeed <= 250) + { + tempfloat = src->windSpeed; + dest->windSpeedF = tempfloat; + } + + if (0 <= src->windDir && src->windDir <= 360) + { + tempfloat = src->windDir; + tempfloat += 0.5; + dest->windDir = (uint16_t)tempfloat; + } + + if (0 <= src->maxWindSpeed && src->maxWindSpeed <= 250) + { + tempfloat = src->maxWindSpeed; + dest->windGustF = tempfloat; + } + + if (0 <= src->maxWindDir && src->maxWindDir <= 360) + { + tempfloat = src->maxWindDir; + tempfloat += 0.5; + dest->windGustDir = (uint16_t)tempfloat; + } + + if (0 <= src->rain) + { + if (!work->runningFlag) + { + // just starting, so start with whatever the station reports: + ifWorkData->totalRain = src->rain; + dest->sampleRain = 0; + } + else + { + // process the rain accumulator + if (src->rain - ifWorkData->totalRain >= 0) + { + dest->sampleRain = src->rain - ifWorkData->totalRain; + ifWorkData->totalRain = src->rain; + } + else + { + // we had a counter reset... + dest->sampleRain = src->rain; + ifWorkData->totalRain = src->rain; + } + } + + if (dest->sampleRain > 2) + { + // Not possible, filter it out: + dest->sampleRain = 0; + } + + // Compute rain rate - the WMR918 rain rate is poor... + // Update the rain accumulator: + sensorAccumAddSample (ifWorkData->rainRateAccumulator, nowTime, dest->sampleRain); + dest->rainRate = sensorAccumGetTotal (ifWorkData->rainRateAccumulator); + dest->rainRate *= (60/WMR918_RAIN_RATE_PERIOD); + } + else + { + dest->sampleRain = 0; + sensorAccumAddSample (ifWorkData->rainRateAccumulator, nowTime, dest->sampleRain); + dest->rainRate = sensorAccumGetTotal (ifWorkData->rainRateAccumulator); + dest->rainRate *= (60/WMR918_RAIN_RATE_PERIOD); + } + + dest->inTemp = src->inTemp; + tempfloat = src->inHumidity; + tempfloat += 0.5; + dest->inHumidity = (uint16_t)tempfloat; + + dest->extraTemp[0] = (float)src->extraTemp[0]; + tempfloat = src->extraHumidity[0]; + tempfloat += 0.5; + dest->extraHumidity[0] = (uint16_t)tempfloat; + + dest->extraTemp[1] = (float)src->extraTemp[1]; + tempfloat = src->extraHumidity[1]; + tempfloat += 0.5; + dest->extraHumidity[1] = (uint16_t)tempfloat; + + dest->extraTemp[2] = (float)src->extraTemp[2]; + + // WMR918 specific: + tempfloat = src->extraHumidity[2]; + tempfloat += 0.5; + dest->wmr918Humid3 = (uint16_t)tempfloat; + + dest->wmr918Pool = src->pool; + + dest->wmr918WindBatteryStatus = src->windBatteryStatus; + dest->wmr918RainBatteryStatus = src->rainBatteryStatus; + dest->wmr918OutTempBatteryStatus = src->outTempBatteryStatus; + dest->wmr918InTempBatteryStatus = src->inTempBatteryStatus; + dest->wmr918poolTempBatteryStatus = src->poolTempBatteryStatus; + dest->wmr918extra1BatteryStatus = src->extraBatteryStatus[0]; + dest->wmr918extra2BatteryStatus = src->extraBatteryStatus[1]; + dest->wmr918extra3BatteryStatus = src->extraBatteryStatus[2]; + dest->wmr918Tendency = src->tendency; + + return; +} + + +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////// A P I ///////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// + +int wmr918Init (WVIEWD_WORK *work) +{ + WMR918_IF_DATA* ifWorkData = (WMR918_IF_DATA*)work->stationData; + fd_set rfds; + struct timeval tv; + int i, length, retVal; + time_t nowTime = time(NULL) - (WV_SECONDS_IN_HOUR/(60/WMR918_RAIN_RATE_PERIOD)); + ARCHIVE_PKT recordStore; + char outString[128]; + + memset (&wmr918Work, 0, sizeof(wmr918Work)); + + // Create the rain accumulator (WMR918_RAIN_RATE_PERIOD minute age) + // so we can compute rain rate: + ifWorkData->rainRateAccumulator = sensorAccumInit(WMR918_RAIN_RATE_PERIOD); + + // Populate the accumulator with the last WMR918_RAIN_RATE_PERIOD minutes: + while ((nowTime = dbsqliteArchiveGetNextRecord(nowTime, &recordStore)) != ERROR) + { + sensorAccumAddSample(ifWorkData->rainRateAccumulator, + recordStore.dateTime, + recordStore.value[DATA_INDEX_rain]); + } + + radMsgLog (PRI_MEDIUM, + "wmr918Init: waiting for first sensor packets (this may take some time):"); + while ((wmr918Work.dataRXMask != WMR918_SENSOR_ALL) && (! work->exiting)) + { + // Log what we are waiting for: + length = 0; + for (i = 0; i < 4; i ++) + { + if (! (wmr918Work.dataRXMask & (1 << i))) + { + length += sprintf(&outString[length], "%s ", WMRSensorNames[i]); + } + } + radMsgLog (PRI_MEDIUM, "wmr918Init: waiting for sensors: %s", outString); + + tv.tv_sec = 2; + tv.tv_usec = 0; + + FD_ZERO(&rfds); + FD_SET(work->medium.fd, &rfds); + if (select (work->medium.fd + 1, &rfds, NULL, NULL, &tv) > 0) + { + retVal = readStationData (work); + switch (retVal) + { + case WMR918GROUP0: + radMsgLog (PRI_MEDIUM, "received WIND packet..."); + break; + case WMR918GROUP1: + radMsgLog (PRI_MEDIUM, "received RAIN packet..."); + break; + case WMR918GROUP2: + radMsgLog (PRI_MEDIUM, "received EXTRA TEMP packet..."); + break; + case WMR918GROUP3: + radMsgLog (PRI_MEDIUM, "received OUT TEMP packet..."); + break; + case WMR918GROUP4: + radMsgLog (PRI_MEDIUM, "received POOL TEMP packet..."); + break; + case WMR918GROUP5: + case WMR918GROUP6: + radMsgLog (PRI_MEDIUM, "received IN TEMP packet..."); + break; + default: + break; + } + } + } + + if (! work->exiting) + { + radMsgLog (PRI_MEDIUM, "wmr918Init: first sensor packets received."); + } + + // populate the LOOP structure: + ifWorkData->wmr918Readings = wmr918Work.sensorData; + storeLoopPkt (work, &work->loopPkt, &ifWorkData->wmr918Readings); + + // we must indicate successful completion here - + // even though we are synchronous, the daemon wants to see this event + radProcessEventsSend (NULL, STATION_INIT_COMPLETE_EVENT, 0); + + return OK; +} + +void wmr918Exit (WVIEWD_WORK *work) +{ + WMR918_IF_DATA* ifWorkData = (WMR918_IF_DATA*)work->stationData; + + return; +} + +void wmr918ReadData (WVIEWD_WORK *work) +{ + // process received packets: + readStationData (work); +} + +void wmr918GetReadings (WVIEWD_WORK *work) +{ + WMR918_IF_DATA* ifWorkData = (WMR918_IF_DATA*)work->stationData; + + // populate the LOOP structure: + ifWorkData->wmr918Readings = wmr918Work.sensorData; + storeLoopPkt (work, &work->loopPkt, &ifWorkData->wmr918Readings); + + // indicate the LOOP packet is done + radProcessEventsSend (NULL, STATION_LOOP_COMPLETE_EVENT, 0); +} + diff --git a/stations/WMR918/wmr918protocol.h b/stations/WMR918/wmr918protocol.h new file mode 100755 index 0000000..43cca20 --- /dev/null +++ b/stations/WMR918/wmr918protocol.h @@ -0,0 +1,153 @@ +#ifndef INC_wmr918protocolh +#define INC_wmr918protocolh +/*--------------------------------------------------------------------------- + + FILENAME: + wmr918protocol.h + + PURPOSE: + Provide protocol utilities for WMR918 station communication. + + REVISION HISTORY: + Date Engineer Revision Remarks + 04/09/2008 M.S. Teel 0 Original + + NOTES: + + + LICENSE: + Copyright (c) 2008, Mark S. Teel (mark@teel.ws) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +/* ... System include files +*/ +#include +#include +#include +#include +#include +#include +#include +#include + +/* ... Library include files +*/ +#include +#include +#include + +/* ... Local include files +*/ +#include +#include +#include +#include +#include + + +#define WMR918_MAX_RETRIES 5 +#define WMR918_BUFFER_LENGTH 16 +#define WMR918_READ_TIMEOUT 500 + +// Define the rain rate acuumulator period (minutes): +#define WMR918_RAIN_RATE_PERIOD 5 + + +#define WMR918GROUP_LENGTH_MAX 13 + +#define WMR918GROUP0 0 /* 00 - anemometer and wind related data */ +#define WMR918GROUP1 1 /* 01 - rain guage */ +#define WMR918GROUP2 2 /* 02 - extra sensors */ +#define WMR918GROUP3 3 /* 03 - outside temp, humidity and dewpoint */ +#define WMR918GROUP4 4 /* 04 - pool */ +#define WMR918GROUP5 5 /* 05 - inside temp, humidity, dewpoint, and baro */ +#define WMR918GROUP6 6 /* 06 - inside temp, humidity, dewpoint, baro for + wmr968 and some wmr918's */ +#define WMR918GROUPE 14 /* 0e - sequence number */ +#define WMR918GROUPF 15 /* 0f - hourly status report */ + +// parsing helper macros: +#define LO(byte) (byte & 0x0f) +#define HI(byte) ((byte & 0xf0) >> 4) +#define MHI(byte) ((byte & 0x0f) << 4) +#define NUM(byte) (10 * HI(byte) + LO(byte)) +#define BIT(byte, bit) ((byte & (1 << bit)) >> bit) + +enum _SensorTypes +{ + WMR918_SENSOR_WIND = 0x01, + WMR918_SENSOR_RAIN = 0x02, + WMR918_SENSOR_OUT_TEMP = 0x04, + WMR918_SENSOR_IN_TEMP = 0x08, + WMR918_SENSOR_ALL = 0x0F +}; + +// define the readings collector +typedef struct +{ + float inTemp; + float outTemp; + float inHumidity; + float outHumidity; + float windDir; + float windSpeed; + float maxWindDir; + float maxWindSpeed; + float pressure; + float rain; + float rainrate; + float pool; + float extraTemp[3]; + float extraHumidity[3]; + uint8_t windBatteryStatus; + uint8_t rainBatteryStatus; + uint8_t outTempBatteryStatus; + uint8_t inTempBatteryStatus; + uint8_t poolTempBatteryStatus; + uint8_t extraBatteryStatus[3]; + uint8_t tendency; + +} WMR918_DATA; + + +// define the work area +typedef struct +{ + WMR918_DATA sensorData; + uint8_t readData[WMR918_BUFFER_LENGTH]; + uint8_t dataRXMask; +} WMR918_WORK; + + +// define WMR918-specific interface data here +typedef struct +{ + int elevation; + float latitude; + float longitude; + int archiveInterval; + WMR918_DATA wmr918Readings; + float totalRain; // to track cumulative changes + WV_ACCUM_ID rainRateAccumulator; // to compute rain rate + int outsideChannel; +} WMR918_IF_DATA; + + +// call once during initialization +extern int wmr918Init (WVIEWD_WORK *work); + +// do cleanup +extern void wmr918Exit (WVIEWD_WORK *work); + +// read data from station: +extern void wmr918ReadData (WVIEWD_WORK *work); + +// get loop packet data: +extern void wmr918GetReadings (WVIEWD_WORK *work); + +#endif + diff --git a/stations/WMRUSB/Makefile.am b/stations/WMRUSB/Makefile.am new file mode 100755 index 0000000..58f2fd1 --- /dev/null +++ b/stations/WMRUSB/Makefile.am @@ -0,0 +1,65 @@ +# Makefile - Oregon Scientific WMRUSB station daemon (USB HID) + +# define the executable to be built +bin_PROGRAMS = wviewd_wmrusb + +# define include directories +INCLUDES = \ + -I$(top_srcdir)/common \ + -I$(top_srcdir)/stations/common \ + -I$(prefix)/include \ + -D_GNU_SOURCE \ + -DWV_CONFIG_DIR=\"$(sysconfdir)/wview\" \ + -DWV_RUN_DIR=\"$(localstatedir)/wview\" \ + -DBUILD_WVIEWD + +# define the sources +wviewd_wmrusb_SOURCES = \ + $(top_srcdir)/common/sensor.c \ + $(top_srcdir)/common/wvutils.c \ + $(top_srcdir)/common/wvconfig.c \ + $(top_srcdir)/common/status.c \ + $(top_srcdir)/common/dbsqlite.c \ + $(top_srcdir)/common/dbsqliteHiLow.c \ + $(top_srcdir)/common/windAverage.c \ + $(top_srcdir)/common/emailAlerts.c \ + $(top_srcdir)/stations/common/computedData.c \ + $(top_srcdir)/stations/common/daemon.c \ + $(top_srcdir)/stations/common/station.c \ + $(top_srcdir)/stations/common/stormRain.c \ + $(top_srcdir)/stations/common/parser.c \ + $(top_srcdir)/stations/common/usbhid.c \ + $(top_srcdir)/stations/WMRUSB/wmrusbinterface.c \ + $(top_srcdir)/stations/WMRUSB/wmrusbprotocol.c \ + $(top_srcdir)/common/sensor.h \ + $(top_srcdir)/common/datadefs.h \ + $(top_srcdir)/common/dbsqlite.h \ + $(top_srcdir)/common/services.h \ + $(top_srcdir)/common/sysdefs.h \ + $(top_srcdir)/common/wvconfig.h \ + $(top_srcdir)/common/windAverage.h \ + $(top_srcdir)/common/emailAlerts.h \ + $(top_srcdir)/common/beaufort.h \ + $(top_srcdir)/stations/common/computedData.h \ + $(top_srcdir)/stations/common/daemon.h \ + $(top_srcdir)/stations/common/station.h \ + $(top_srcdir)/stations/common/stormRain.h \ + $(top_srcdir)/stations/common/parser.h \ + $(top_srcdir)/stations/common/hidapi.h \ + $(top_srcdir)/stations/common/usbhid.h \ + $(top_srcdir)/stations/WMRUSB/wmrusbinterface.h \ + $(top_srcdir)/stations/WMRUSB/wmrusbprotocol.h + +if DARWIN +wviewd_wmrusb_SOURCES += $(top_srcdir)/stations/common/hidapi-osx.c +wviewd_wmrusb_LDFLAGS = -L$(prefix)/lib -L/usr/lib -L/usr/local/lib -framework IOKit -framework CoreFoundation +wviewd_wmrusb_LDADD = +else +wviewd_wmrusb_SOURCES += $(top_srcdir)/stations/common/hidapi-linux.c +wviewd_wmrusb_LDFLAGS = -L$(prefix)/lib -L/usr/lib -L/usr/local/lib +if FREEBSD +wviewd_wmrusb_LDADD = -lusb +else +wviewd_wmrusb_LDADD = -lusb-1.0 +endif +endif diff --git a/stations/WMRUSB/wmrusbinterface.c b/stations/WMRUSB/wmrusbinterface.c new file mode 100755 index 0000000..5b161cf --- /dev/null +++ b/stations/WMRUSB/wmrusbinterface.c @@ -0,0 +1,332 @@ +/*--------------------------------------------------------------------------- + + FILENAME: + wmrusbinterface.c + + PURPOSE: + Provide the Oregon Scientific WMR station interface API and utilities. + + REVISION HISTORY: + Date Engineer Revision Remarks + 03/10/2011 M.S. Teel 1 Original + + NOTES: + The WMR station provides a USB HID interface for I/O + + LICENSE: + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +/* ... System include files +*/ + +/* ... Library include files +*/ + +/* ... Local include files +*/ +#include + +/* ... global memory declarations +*/ + +/* ... local memory +*/ + +static WMR_IF_DATA wmrWorkData; +static void (*ArchiveIndicator) (ARCHIVE_PKT* newRecord); + +static void serialPortConfig (int fd); + +////////////****////**** S T A T I O N A P I ****////****//////////// +///// Must be provided by each supported wview station interface ////// + +// station-supplied init function +// -- Can Be Asynchronous - event indication required -- +// +// MUST: +// - set the 'stationGeneratesArchives' flag in WVIEWD_WORK: +// if the station generates archive records (TRUE) or they should be +// generated automatically by the daemon from the sensor readings (FALSE) +// - Initialize the 'stationData' store for station work area +// - Initialize the interface medium +// - do initial LOOP acquisition +// - do any catch-up on archive records if there is a data logger +// - 'work->runningFlag' can be used for start up synchronization but should +// not be modified by the station interface code +// - indicate init is done by sending the STATION_INIT_COMPLETE_EVENT event to +// this process (radProcessEventsSend (NULL, STATION_INIT_COMPLETE_EVENT, 0)) +// +// OPTIONAL: +// - Initialize a state machine or any other construct required for the +// station interface - these should be stored in the 'stationData' store +// +// 'archiveIndication' - indication callback used to pass back an archive record +// generated as a result of 'stationGetArchive' being called; should receive a +// NULL pointer for 'newRecord' if no record available; only used if +// 'stationGeneratesArchives' flag is set to TRUE by the station interface +// +// Returns: OK or ERROR +// +int stationInit +( + WVIEWD_WORK *work, + void (*archiveIndication)(ARCHIVE_PKT* newRecord) +) +{ + int i; + STIM stim; + + memset (&wmrWorkData, 0, sizeof(wmrWorkData)); + + // save the archive indication callback (we should never need it) + ArchiveIndicator = archiveIndication; + + // set our work data pointer + work->stationData = &wmrWorkData; + + // set the Archive Generation flag to indicate the WMR918 DOES NOT + // generate them + work->stationGeneratesArchives = FALSE; + + // grab the station configuration now + if (stationGetConfigValueInt (work, + STATION_PARM_ELEVATION, + &wmrWorkData.elevation) + == ERROR) + { + radMsgLog (PRI_HIGH, "stationInit: stationGetConfigValueInt ELEV failed!"); + return ERROR; + } + if (stationGetConfigValueFloat (work, + STATION_PARM_LATITUDE, + &wmrWorkData.latitude) + == ERROR) + { + radMsgLog (PRI_HIGH, "stationInit: stationGetConfigValueInt LAT failed!"); + return ERROR; + } + if (stationGetConfigValueFloat (work, + STATION_PARM_LONGITUDE, + &wmrWorkData.longitude) + == ERROR) + { + radMsgLog (PRI_HIGH, "stationInit: stationGetConfigValueInt LONG failed!"); + return ERROR; + } + if (stationGetConfigValueInt (work, + STATION_PARM_ARC_INTERVAL, + &wmrWorkData.archiveInterval) + == ERROR) + { + radMsgLog (PRI_HIGH, "stationInit: stationGetConfigValueInt ARCINT failed!"); + return ERROR; + } + if (stationGetConfigValueInt (work, + STATION_PARM_OUTSIDE_CHANNEL, + &wmrWorkData.outsideChannel) + == ERROR) + { + radMsgLog (PRI_HIGH, "stationInit: stationGetConfigValueInt outside channel failed!"); + return ERROR; + } + + // set the work archive interval now + work->archiveInterval = wmrWorkData.archiveInterval; + + // sanity check the archive interval against the most recent record + if (stationVerifyArchiveInterval (work) == ERROR) + { + // bad magic! + radMsgLog (PRI_HIGH, "stationInit: stationVerifyArchiveInterval failed!"); + radMsgLog (PRI_HIGH, "You must either move old archive data out of the way -or-"); + radMsgLog (PRI_HIGH, "fix the interval setting..."); + return ERROR; + } + else + { + radMsgLog (PRI_STATUS, "station archive interval: %d minutes", + work->archiveInterval); + } + + radMsgLog (PRI_STATUS, "Starting station interface: WMR"); + + if (wmrInit (work) == ERROR) + { + radMsgLog (PRI_HIGH, "stationInit: wmrInit failed!"); + return ERROR; + } + + radMsgLog (PRI_STATUS, "WMR on USB %4.4X:%4.4X opened ...", + WMR_VENDOR_ID, WMR_PRODUCT_ID); + + return OK; +} + +// station-supplied exit function +// +// Returns: N/A +// +void stationExit (WVIEWD_WORK *work) +{ + wmrExit (work); + return; +} + +// station-supplied function to retrieve positional info (lat, long, elev) - +// should populate 'work' fields: latitude, longitude, elevation +// -- Synchronous -- +// +// - If station does not store these parameters, they can be retrieved from the +// wview.conf file (see daemon.c for example conf file use) - user must choose +// station type "Generic" when running the wviewconfig script +// +// Returns: OK or ERROR +// +int stationGetPosition (WVIEWD_WORK *work) +{ + // just set the values from our internal store - we retrieved them in + // stationInit + work->elevation = (int16_t)wmrWorkData.elevation; + if (wmrWorkData.latitude >= 0) + work->latitude = (int16_t)((wmrWorkData.latitude*10)+0.5); + else + work->latitude = (int16_t)((wmrWorkData.latitude*10)-0.5); + if (wmrWorkData.longitude >= 0) + work->longitude = (int16_t)((wmrWorkData.longitude*10)+0.5); + else + work->longitude = (int16_t)((wmrWorkData.longitude*10)-0.5); + + radMsgLog (PRI_STATUS, "station location: elevation: %d feet", + work->elevation); + + radMsgLog (PRI_STATUS, "station location: latitude: %3.1f %c longitude: %3.1f %c", + (float)abs(work->latitude)/10.0, + ((work->latitude < 0) ? 'S' : 'N'), + (float)abs(work->longitude)/10.0, + ((work->longitude < 0) ? 'W' : 'E')); + + return OK; +} + +// station-supplied function to indicate a time sync should be performed if the +// station maintains time, otherwise may be safely ignored +// -- Can Be Asynchronous -- +// +// Returns: OK or ERROR +// +int stationSyncTime (WVIEWD_WORK *work) +{ + // We don't use the WMR time... + return OK; +} + +// station-supplied function to indicate sensor readings should be performed - +// should populate 'work' struct: loopPkt (see datadefs.h for minimum field reqs) +// -- Can Be Asynchronous -- +// +// - indicate readings are complete by sending the STATION_LOOP_COMPLETE_EVENT +// event to this process (radProcessEventsSend (NULL, STATION_LOOP_COMPLETE_EVENT, 0)) +// +// Returns: OK or ERROR +// +int stationGetReadings (WVIEWD_WORK *work) +{ + wmrGetReadings (work); + + return OK; +} + +// station-supplied function to indicate an archive record should be generated - +// MUST populate an ARCHIVE_RECORD struct and indicate it to 'archiveIndication' +// function passed into 'stationInit' +// -- Asynchronous - callback indication required -- +// +// Returns: OK or ERROR +// +// Note: 'archiveIndication' should receive a NULL pointer for the newRecord if +// no record is available +// Note: This function will only be invoked by the wview daemon if the +// 'stationInit' function set the 'stationGeneratesArchives' to TRUE +// +int stationGetArchive (WVIEWD_WORK *work) +{ + // just indicate a NULL record, WMR918 does not generate them (and this + // function should never be called!) + (*ArchiveIndicator) (NULL); + return OK; +} + +// station-supplied function to indicate data is available on the station +// interface medium (serial or ethernet) - +// It is the responsibility of the station interface to read the data from the +// medium and process appropriately. The data does not have to be read within +// the context of this function, but may be used to stimulate a state machine. +// -- Synchronous -- +// +// Returns: N/A +// +void stationDataIndicate (WVIEWD_WORK *work) +{ + return; +} + +// station-supplied function to receive IPM messages - any message received by +// the generic station message handler which is not recognized will be passed +// to the station-specific code through this function. +// It is the responsibility of the station interface to process the message +// appropriately (or ignore it). +// -- Synchronous -- +// +// Returns: N/A +// +void stationMessageIndicate (WVIEWD_WORK *work, int msgType, void *msg) +{ + if (msgType == WVIEW_MSG_TYPE_STATION_DATA) + { + // Receive data from our reader thread: + wmrReadData(work, (WMRUSB_MSG_DATA*)msg); + } + + return; +} + +// station-supplied function to indicate the interface timer has expired - +// It is the responsibility of the station interface to start/stop the interface +// timer as needed for the particular station requirements. +// The station interface timer is specified by the 'ifTimer' member of the +// WVIEWD_WORK structure. No other timers in that structure should be manipulated +// in any way by the station interface code. +// -- Synchronous -- +// +// Returns: N/A +// +void stationIFTimerExpiry (WVIEWD_WORK *work) +{ + // restart our IF timer: + radProcessTimerStart (work->ifTimer, WMR_PROCESS_TIME_INTERVAL); + + // Process data: + while (wmrProcessData (work)); + +#ifdef WMR_COUNT_BYTES + if (++StatCount >= 60) + { + radMsgLog(PRI_MEDIUM, "STATS: raw:%d stream:%d pkt:%d cksum:%d unk:%d", + UsbRawBytes, + StreamBytes, + PacketBytes, + ChecksumBytes, + UnknownPacketType); + StatCount = 0; + } +#endif + + return; +} + +////////////****//// S T A T I O N A P I E N D ////****//////////// + diff --git a/stations/WMRUSB/wmrusbinterface.h b/stations/WMRUSB/wmrusbinterface.h new file mode 100755 index 0000000..922a9d7 --- /dev/null +++ b/stations/WMRUSB/wmrusbinterface.h @@ -0,0 +1,60 @@ +#ifndef INC_wmrusbinterfaceh +#define INC_wmrusbinterfaceh +/*--------------------------------------------------------------------------- + + FILENAME: + wmrusbinterface.h + + PURPOSE: + Provide the Oregon Scientific WMR USB station interface + API and utilities. + + NOTES: + + + LICENSE: + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +/* ... System include files +*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* ... Library include files +*/ +#include +#include +#include +#include + +/* ... Local include files +*/ +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#endif + diff --git a/stations/WMRUSB/wmrusbprotocol.c b/stations/WMRUSB/wmrusbprotocol.c new file mode 100755 index 0000000..748d819 --- /dev/null +++ b/stations/WMRUSB/wmrusbprotocol.c @@ -0,0 +1,1114 @@ +/*--------------------------------------------------------------------------- + + FILENAME: + wmrusbprotocol.c + + PURPOSE: + Provide protocol utilities for WMR station communication. + + REVISION HISTORY: + Date Engineer Revision Remarks + 03/10/2011 M.S. Teel 0 Original. + + NOTES: + + LICENSE: + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +/* ... System include files +*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* ... Library include files +*/ +#include +#include +#include + +/* ... Local include files +*/ +#include +#include +#include +#include +#include + +/* ... global memory declarations +*/ + +/* ... local memory +*/ +#ifdef WMR_COUNT_BYTES +int UsbRawBytes; +int StreamBytes; +int PacketBytes; +int ChecksumBytes; +int StatCount; +int UnknownPacketType; +#endif + +static WMR_WORK wmrWork; + +static char* WMRSensorNames[4] = +{ + "Wind", + "Rain", + "Temp", + "Pressure" +}; + +/* utility functions */ + +static float d2temp (int a, int b) +{ + a &= 0xff; + b &= 0xff; + int t = (b << 8) | a; + if (t & 0x8000) + { + t &= 0x7FFF; + return -(t / 10.0); + } + else + { + return (t / 10.0); + } +} + +static int checksum (unsigned char *ptr, int len) +{ + int sum = 0; + int a = ptr[len-2]; + int b = ptr[len-1]; + + int compare = (b << 8) | a; + len -= 2; + while (len) + { + sum += *ptr++; + --len; + } + + return (sum == compare); + +} + +/* frame decoding functions */ + +static void decodeRain (unsigned char *ptr) +{ + if (ptr[0] != 0xFF && ptr[1] != 0xFF && ptr[6] != 0xFF && ptr[7] != 0xFF) + { + wmrWork.sensorData.rainRate = 0.01 * ((int)(ptr[1]*255) + ptr[0]); + wmrWork.sensorData.rain1h = 0.01 * ((int)(ptr[3]*255) + ptr[2]); + wmrWork.sensorData.rain24h = 0.01 * ((int)(ptr[5]*255) + ptr[4]); + wmrWork.sensorData.rainAccum = 0.01 * ((int)(ptr[7]*255) + ptr[6]); + + wmrWork.dataRXMask |= WMR_SENSOR_RAIN; + + if (!wmrWork.started) + { + radMsgLog (PRI_MEDIUM, "received RAIN packet..."); + } +#ifdef WMR_DEBUG + radMsgLog(PRI_MEDIUM, "RAIN: Rate:%f, 1H:%f, 24H:%f, ACCUM:%f", + wmrWork.sensorData.rainRate, + wmrWork.sensorData.rain1h, + wmrWork.sensorData.rain24h, + wmrWork.sensorData.rainAccum); + radMsgLogData(ptr, 8); +#endif + } +} + +static void decodeWind (unsigned char *ptr) +{ + float avg, gust; + + if (ptr[0] != 0xFF && ptr[1] != 0xFF) + { + gust = (float)((int)ptr[2] + ((int)(ptr[3] & 0x0F)*256)); + gust /= 10; + gust = wvutilsConvertMPSToMPH(gust); + + avg = (float)((int)ptr[4]*16) + ((int)ptr[3] >> 4); + avg /= 10; + avg = wvutilsConvertMPSToMPH(avg); + + // Sanity check: + if ((gust < 0) || (gust > 250) || (avg < 0) || (avg > 250)) + { + // packet is bogus: + return; + } + + wmrWork.sensorData.windGustSpeed = gust; + wmrWork.sensorData.windAvgSpeed = avg; + + // wind direction unit is 22.5 degrees + wmrWork.sensorData.windDir = (ptr[0] & 0x0F) * 22.5; + + wmrWork.dataRXMask |= WMR_SENSOR_WIND; + + if (!wmrWork.started) + { + radMsgLog (PRI_MEDIUM, "received WIND packet..."); + } + } +} + +static void decodeTemp (unsigned char *ptr) +{ + unsigned int sensor = ptr[0] & 0x0F; + float humid, temp, dew; + + if (sensor < WMR_TEMP_SENSOR_COUNT) + { + humid = (float)ptr[3]; + temp = wvutilsConvertCToF(d2temp(ptr[1], ptr[2])); + dew = wvutilsConvertCToF(d2temp(ptr[4], ptr[5])); + + // Sanity check the values (allow extra sensors to have bogus humidity): + if (((sensor <= WMR_TEMP_SENSOR_OUT) && (humid > 100)) || (temp < -150) || (temp > 150)) + { + // this packet is bogus: + return; + } + + wmrWork.sensorData.humidity[sensor] = humid; + wmrWork.sensorData.temp[sensor] = temp; + wmrWork.sensorData.dewpoint[sensor] = dew; + + if (sensor == WMR_TEMP_SENSOR_OUT) + { + wmrWork.dataRXMask |= WMR_SENSOR_OUT_TEMP; + + if (!wmrWork.started) + { + radMsgLog (PRI_MEDIUM, "received TEMP packet..."); + } + } + } +} + +static void decodeTempOnly (unsigned char *ptr) +{ + unsigned int sensor = ptr[0] & 0x0F; + float temp; + + if (sensor < WMR_TEMP_SENSOR_COUNT) + { + temp = wvutilsConvertCToF(d2temp(ptr[1], ptr[2])); + + // Sanity check the values: + if ((temp < -150) || (temp > 150)) + { + // this packet is bogus: + return; + } + + wmrWork.sensorData.temp[sensor] = temp; + } +} + +static void decodePressure (unsigned char *ptr) +{ + float pressure; + + // station provides pressure in hPa + pressure = (((int)(ptr[1] & 0x0F)) << 8) + (int)ptr[0]; + pressure = wvutilsConvertHPAToINHG(pressure); + + // Sanity check it: + if ((pressure < 20) || (pressure > 40)) + { + // packet is bogus: + return; + } + + wmrWork.sensorData.pressure = pressure; + wmrWork.dataRXMask |= WMR_SENSOR_PRESSURE; + + if (!wmrWork.started) + { + radMsgLog (PRI_MEDIUM, "received PRESSURE packet..."); + } +} + +static void decodeUV (unsigned char *ptr) +{ + if (ptr[0] == 0xFF) + { + wmrWork.sensorData.UV = -1; + } + else + { + wmrWork.sensorData.UV = (int)(ptr[0] & 0x0F); + } +} + +static int IsFFFFPacketStart (uint8_t* value) +{ + if (value[0] == 0xFF && value[1] == 0xFF) + return TRUE; + else + return FALSE; +} + +static int IsD0PacketStart (uint8_t value) +{ + if (0xD2 <= value && value <= 0xD7) + return TRUE; + if (value == 0xD9) + return TRUE; + + return FALSE; +} + +static int IsPacketStart (uint8_t* pValue) +{ + if (wmrWork.protocol == WMR_PROTOCOL_FFFF) + { + return IsFFFFPacketStart(pValue); + } + else + { + return IsD0PacketStart(*pValue); + } +} + +static int getFFFFPktLength(uint8_t* bfr, int maxLen) +{ + uint8_t* tempPtr = bfr; + int RetVal; + int LoopMax = ((maxLen <= 64) ? maxLen : 64); + + // Skip first two FF's. + for (RetVal = 2; RetVal < LoopMax-1; RetVal ++) + { + if ((tempPtr[RetVal] == 0xFF) && (tempPtr[RetVal+1] == 0xFF)) + { + return RetVal; + } + } + + // If here no end of packet found. + return ((maxLen <= 64) ? 0 : -1); +} + +static int checkD0PktLength(uint8_t type, uint8_t length) +{ + int RetVal = TRUE; + + switch ((int)type) + { + case WMR_D0_HISTORY: + if (((int)length < 49) || ((int)length > 112)) + RetVal = FALSE; + break; + case WMR_D0_RAIN: + if ((int)length != 22) + RetVal = FALSE; + break; + case WMR_D0_TEMP: + if ((int)length != 16) + RetVal = FALSE; + break; + case WMR_D0_PRESSURE: + if ((int)length != 13) + RetVal = FALSE; + break; + case WMR_D0_WIND: + if ((int)length != 16) + RetVal = FALSE; + break; + case WMR_D0_STATUS: + if ((int)length != 8) + RetVal = FALSE; + break; + case WMR_D0_UV: + if ((int)length != 10) + RetVal = FALSE; + break; + default: + break; + } + + return RetVal; +} + +static void shiftUpReadBuffer(int numToShift) +{ + int i; + + if (numToShift > wmrWork.readIndex) + { + numToShift = wmrWork.readIndex; + } + + for (i = 0; (i + numToShift) < wmrWork.readIndex; i ++) + { + wmrWork.readData[i] = wmrWork.readData[i + numToShift]; + } + + wmrWork.readIndex -= numToShift; +} + +static int parseStationData(int length) +{ + uint8_t *ptr = &wmrWork.readData[0]; + +#ifdef WMR_DEBUG + radMsgLog(PRI_MEDIUM, "WMRDBG: parse packet"); + radMsgLogData(&wmrWork.readData[0], length); +#endif + if (wmrWork.protocol == WMR_PROTOCOL_FFFF) + { + // Check checksum: + if (!checksum(ptr+2, length-2)) + { + // Bad checksum: +#ifdef WMR_DEBUG + radMsgLog(PRI_MEDIUM, "RX packet checksum error: discarding %d bytes", length); + radMsgLogData(&wmrWork.readData[0], length); +#endif + return ERROR; + } + + switch ((int)ptr[3]) + { + case WMR_FFFF_RAIN: + decodeRain(ptr+4); + break; + case WMR_FFFF_TEMP: + decodeTemp(ptr+4); + break; + case WMR_FFFF_TEMPONLY: + decodeTempOnly(ptr+4); + break; + case WMR_FFFF_PRESSURE: + decodePressure(ptr+4); + break; + case WMR_FFFF_WIND: + decodeWind(ptr+4); + break; + case WMR_FFFF_UV: + decodeUV(ptr+5); + break; + default: +#ifdef WMR_COUNT_BYTES + UnknownPacketType ++; +#endif + break; + } + } + else + { + // Check checksum: + if (!checksum(ptr, length)) + { + // Bad checksum: + radMsgLog(PRI_MEDIUM, "packet checksum error"); + radMsgLogData(&wmrWork.readData[0], length); + return ERROR; + } + + switch ((int)ptr[0]) + { + case WMR_D0_RAIN: + decodeRain(ptr+7); + break; + case WMR_D0_TEMP: + decodeTemp(ptr+7); + break; + case WMR_D0_PRESSURE: + decodePressure(ptr+7); + break; + case WMR_D0_WIND: + decodeWind(ptr+7); + break; + case WMR_D0_STATUS: + //decodeStatus(ptr+7); + break; + case WMR_D0_UV: + decodeUV(ptr+7); + break; + default: +#ifdef WMR_COUNT_BYTES + UnknownPacketType ++; +#endif + break; + } + } + + return OK; +} + +static void storeLoopPkt (WVIEWD_WORK *work, LOOP_PKT *dest, WMR_DATA *src) +{ + float tempfloat; + WMR_IF_DATA* ifWorkData = (WMR_IF_DATA*)work->stationData; + time_t nowTime = time(NULL); + int i; + + // Clear optional data: + stationClearLoopData(work); + + if ((10 < src->pressure && src->pressure < 50) && + (-150 < src->temp[WMR_TEMP_SENSOR_OUT] && src->temp[WMR_TEMP_SENSOR_OUT] < 150)) + { + // wmr has Station Pressure: + dest->stationPressure = src->pressure; + + // Apply calibration here so the computed values reflect it: + dest->stationPressure *= work->calMPressure; + dest->stationPressure += work->calCPressure; + + // compute sea-level pressure (BP) + tempfloat = wvutilsConvertSPToSLP(dest->stationPressure, + src->temp[WMR_TEMP_SENSOR_OUT], + (float)ifWorkData->elevation); + dest->barometer = tempfloat; + + // calculate altimeter + tempfloat = wvutilsConvertSPToAltimeter(dest->stationPressure, + (float)ifWorkData->elevation); + dest->altimeter = tempfloat; + } + + if (-150 < src->temp[WMR_TEMP_SENSOR_OUT] && + src->temp[WMR_TEMP_SENSOR_OUT] < 150) + { + dest->outTemp = src->temp[WMR_TEMP_SENSOR_OUT]; + } + + if (0 <= src->humidity[WMR_TEMP_SENSOR_OUT] && + src->humidity[WMR_TEMP_SENSOR_OUT] <= 100) + { + tempfloat = src->humidity[WMR_TEMP_SENSOR_OUT]; + tempfloat += 0.5; + dest->outHumidity = (uint16_t)tempfloat; + } + + if (0 <= src->windAvgSpeed && src->windAvgSpeed <= 250) + { + tempfloat = src->windAvgSpeed; + dest->windSpeedF = tempfloat; + } + + if (0 <= src->windDir && src->windDir <= 360) + { + tempfloat = src->windDir; + tempfloat += 0.5; + dest->windDir = (uint16_t)tempfloat; + dest->windGustDir = (uint16_t)tempfloat; + } + + if (0 <= src->windGustSpeed && src->windGustSpeed <= 250) + { + tempfloat = src->windGustSpeed; + dest->windGustF = tempfloat; + if (dest->windGustF < dest->windSpeedF) + { + dest->windGustF = dest->windSpeedF; + } + } + + if (0 <= src->rainAccum) + { + if (ifWorkData->totalRain < 0) + { + // just starting, so start with whatever the station reports: + radMsgLog(PRI_STATUS, "storeLoopPacket: initializing rain accumulator..."); + ifWorkData->totalRain = src->rainAccum; + dest->sampleRain = 0; + } + else + { + // process the rain accumulator; check for crazy values: + if ((src->rainAccum - ifWorkData->totalRain) >= 0) + { + if ((src->rainAccum - ifWorkData->totalRain) < 2) + { + dest->sampleRain = src->rainAccum - ifWorkData->totalRain; + ifWorkData->totalRain = src->rainAccum; + } + else + { + // Value too large, ignore it. + dest->sampleRain = 0; + } + } + else + { + // we had a counter reset... + dest->sampleRain = src->rainAccum; + ifWorkData->totalRain = src->rainAccum; + } + } + + if (dest->sampleRain > 2) + { + // Not possible, filter it out: + dest->sampleRain = 0; + } + + // Update the rain accumulator: + sensorAccumAddSample (ifWorkData->rainRateAccumulator, nowTime, dest->sampleRain); + dest->rainRate = sensorAccumGetTotal (ifWorkData->rainRateAccumulator); + dest->rainRate *= (60/WMR_RAIN_RATE_PERIOD); + } + else + { + dest->sampleRain = 0; + } + + dest->inTemp = src->temp[WMR_TEMP_SENSOR_IN]; + tempfloat = src->humidity[WMR_TEMP_SENSOR_IN]; + tempfloat += 0.5; + dest->inHumidity = (uint16_t)tempfloat; + + dest->UV = src->UV; + + // Do the extras: + for (i = 0; i < WMR_TEMP_SENSOR_COUNT - 2; i ++) + { + dest->extraTemp[i] = src->temp[i+2]; + dest->extraHumidity[i] = src->humidity[i+2]; + } + + return; +} + +static int sendAck(WVIEWD_WORK *work) +{ + unsigned char buf[32]; + + memcpy(buf, "\x00\x00\x00\x00\x00\x00\x00\x00", 8); + (*(work->medium.usbhidWrite))(&work->medium, buf, 0x08); + (*(work->medium.usbhidWrite))(&work->medium, buf, 0x08); + + return OK; +} + +static int sendHeartbeat(WVIEWD_WORK *work) +{ + unsigned char buf[32]; + + // send the heartbeat message so the console will stream live data: + memcpy(buf, "\x07\xd0\x00\x00\x00\x00\x00\x00", 8); + (*(work->medium.usbhidWrite))(&work->medium, buf, 0x08); + (*(work->medium.usbhidWrite))(&work->medium, buf, 0x08); + memcpy(buf, "\x01\x00\x00\x00\x00\x00\x00\x00", 8); + (*(work->medium.usbhidWrite))(&work->medium, buf, 0x08); + (*(work->medium.usbhidWrite))(&work->medium, buf, 0x08); + + return OK; +} + +static int sendReset(WVIEWD_WORK *work) +{ + unsigned char buf[32]; + + radMsgLog (PRI_MEDIUM, "wmr: Sending reset to console..."); + + // Send a reset: + memcpy(buf, "\x20\x00\x08\x01\x08\x00\x00\x00", 8); + (*(work->medium.usbhidWrite))(&work->medium, buf, 0x08); + (*(work->medium.usbhidWrite))(&work->medium, buf, 0x08); + + radUtilsSleep(100); + + // send the heartbeat message so the console will stream live data: + sendHeartbeat(work); + + return OK; +} + +// Read raw USB data and buffer it for later processing: +// Only used before the reader thread has been started. +static void readDataDirect (WVIEWD_WORK *work) +{ + int retVal, length; + uint8_t buf[8]; + + if ((radTimeGetSECSinceEpoch() - wmrWork.lastDataRX) >= 60) + { + // It has been too long since the last valid data packet was received, + // send a RESET: + sendReset(work); + wmrWork.lastDataRX = radTimeGetSECSinceEpoch(); + } + else if ((radTimeGetSECSinceEpoch() - wmrWork.heartBeatCounter) >= WMR_HEARTBEAT_INTERVAL) + { + // send the heartbeat message so the console will keep streaming live data: + sendHeartbeat(work); + wmrWork.heartBeatCounter = radTimeGetSECSinceEpoch(); + radUtilsSleep(10); + } + + // Read on the USB interface: + while (wmrWork.readIndex < WMR_BUFFER_LENGTH) + { + retVal = (*(work->medium.usbhidRead))(&work->medium, buf, 8, WMR_READ_WAIT); + if (retVal == 8) + { + sendAck(work); + // first octet is a length field: + length = buf[0]; + if ((length < 8) && ((wmrWork.readIndex + length) < WMR_BUFFER_LENGTH)) + { + memcpy(&wmrWork.readData[wmrWork.readIndex], buf+1, length); + wmrWork.readIndex += length; +#ifdef WMR_DEBUG + radMsgLog(PRI_MEDIUM, "WMRDBG: USB RX %d", length); + radMsgLogData(&wmrWork.readData[0], wmrWork.readIndex); +#endif + } + + wmrWork.lastDataRX = radTimeGetSECSinceEpoch(); + } + else if (retVal == ERROR) + { + radMsgLog (PRI_HIGH, "readDataDirect: read error..."); + return; + } + else + { + // Try again later: + return; + } + } + + return; +} + +// Dynamically figure out if we are talking to a D0-D9 (WMR200) station or +// a FFFF 00XX (WMR88A/WMR100N) station so we know how to decode the packets: +static int detectStationProtocol (WVIEWD_WORK *work) +{ + int index; + + radMsgLog(PRI_MEDIUM, "wmrInit: Auto-detecting protocol..."); + + wmrWork.readIndex = 0; + + // Read on the USB interface for a while: + while (wmrWork.readIndex < 32) + { + readDataDirect(work); + radUtilsSleep(250); + } + + // OK, now we have some data to examine: + // First look for FFFF bytes, this is definitive (?): + wmrWork.protocol = WMR_PROTOCOL_UNKNOWN; + for (index = 0; index < 31; index ++) + { + if (IsFFFFPacketStart(&wmrWork.readData[index])) + { + wmrWork.protocol = WMR_PROTOCOL_FFFF; + radMsgLog (PRI_MEDIUM, "wmrInit: found old FFFF framed protocol"); + break; + } + } + + if (wmrWork.protocol == WMR_PROTOCOL_UNKNOWN) + { + // Assume D0: + wmrWork.protocol = WMR_PROTOCOL_D0; + radMsgLog (PRI_MEDIUM, "wmrInit: found D0-D9 framed protocol"); + } + + // Initialize the data RX time: + wmrWork.lastDataRX = radTimeGetSECSinceEpoch(); + + return OK; +} + +// The reader thread: +static void ReaderThread(RAD_THREAD_ID threadId, void* threadData) +{ + int retVal, length, RestartNeeded; + WMRUSB_MSG_DATA msg; + uint8_t buf[8]; + WVIEWD_WORK* work = (WVIEWD_WORK*)threadData; + + radMsgLog (PRI_STATUS, "wmr: read thread started..."); + +#ifdef WMR_COUNT_BYTES + UsbRawBytes = StreamBytes = PacketBytes = ChecksumBytes = StatCount = 0; +#endif + + // Recreate medium blocking: + if (usbhidMediumInit (&work->medium, WMR_VENDOR_ID, WMR_PRODUCT_ID, FALSE, TRUE) == ERROR) + { + radMsgLog (PRI_HIGH, "ReaderThread: USB MediumInit failed"); + return; + } + + // Main loop: + while (!radthreadShouldExit(threadId)) + { + // Open the interface. + if ((*(work->medium.usbhidInit))(&work->medium) != OK) + { + radMsgLog (PRI_HIGH, "wmr: read thread failed to open HID device!"); + radUtilsSleep(WMR_REESTABLISH_SLEEP); + continue; + } + + // Read on the USB interface: + RestartNeeded = FALSE; + msg.length = 0; + while (!RestartNeeded && !radthreadShouldExit(threadId)) + { + if ((radTimeGetSECSinceEpoch() - wmrWork.lastDataRX) >= 60) + { + // It has been too long since the last valid data packet was received, + // send a RESET: + sendReset(work); + + // Restart to bump the HID interface. + RestartNeeded = TRUE; + + wmrWork.lastDataRX = radTimeGetSECSinceEpoch(); + } + else if ((radTimeGetSECSinceEpoch() - wmrWork.heartBeatCounter) >= WMR_HEARTBEAT_INTERVAL) + { + // send the heartbeat message so the console will keep streaming live data: + sendHeartbeat(work); + wmrWork.heartBeatCounter = radTimeGetSECSinceEpoch(); + radUtilsSleep(10); + } + else + { + // Read on the interface: + // We are using blocking IO. + retVal = (*(work->medium.usbhidRead))(&work->medium, buf, 8, 0); + if (retVal == 8) + { + sendAck(work); + + // first octet is a length field: + length = buf[0]; + if (length < 8) + { +#ifdef WMR_COUNT_BYTES + UsbRawBytes += length; +#ifdef WMR_DUMP_RAW_USB + radMsgLog(PRI_HIGH, "USBRAW RX: %d: %2.2X%2.2X%2.2X%2.2X%2.2X%2.2X%2.2X", + (int)buf[0],buf[1],buf[2],buf[3],buf[4],buf[5],buf[6],buf[7]); +#endif +#endif + memcpy(&msg.data[msg.length], buf+1, length); + msg.length += length; + + if (msg.length > 8) + { + // Send to our consumer: + radMsgRouterMessageSend(WVIEW_MSG_TYPE_STATION_DATA, &msg, sizeof(msg)); + msg.length = 0; + } + } + + wmrWork.lastDataRX = radTimeGetSECSinceEpoch(); + } + else if (retVal == ERROR) + { + radMsgLog (PRI_HIGH, "wmr: read error..."); + RestartNeeded = TRUE; + } + } + } + + // Close the interface. + (*(work->medium.usbhidExit))(&work->medium); + } + + radMsgLog (PRI_STATUS, "wmr: read thread exiting..."); + return; +} + +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////// A P I ///////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// + +int wmrInit (WVIEWD_WORK *work) +{ + WMR_IF_DATA* ifWorkData = (WMR_IF_DATA*)work->stationData; + unsigned char buf[32]; + char outString[128]; + int i, length, printCounter; + time_t nowTime = time(NULL) - (WV_SECONDS_IN_HOUR/(60/WMR_RAIN_RATE_PERIOD)); + ARCHIVE_PKT recordStore; + + memset (&wmrWork, 0, sizeof(wmrWork)); + wmrWork.sensorData.rainAccum = -1; + ifWorkData->totalRain = -1; + + // Create the rain accumulator (WMR_RAIN_RATE_PERIOD minute age) + // so we can compute rain rate: + ifWorkData->rainRateAccumulator = sensorAccumInit(WMR_RAIN_RATE_PERIOD); + + // Populate the accumulator with the last WMR_RAIN_RATE_PERIOD minutes: + while ((nowTime = dbsqliteArchiveGetNextRecord(nowTime, &recordStore)) != ERROR) + { + sensorAccumAddSample(ifWorkData->rainRateAccumulator, + recordStore.dateTime, + recordStore.value[DATA_INDEX_rain]); + } + + // Open non-blocking: + if (usbhidMediumInit (&work->medium, WMR_VENDOR_ID, WMR_PRODUCT_ID, FALSE, FALSE) == ERROR) + { + radMsgLog (PRI_HIGH, "stationInit: USB MediumInit failed"); + return ERROR; + } + + // Initialize the USB interface: + if ((*(work->medium.usbhidInit))(&work->medium) != OK) + { + return ERROR; + } + + // Send a reset: + sendReset(work); + + // Toss any previously received data: + wmrWork.readIndex = 0; + + radUtilsSleep(10); + + // Initialize the data RX time: + wmrWork.lastDataRX = radTimeGetSECSinceEpoch(); + + // Autodetect FFFF or D0-DF framing: + detectStationProtocol(work); + + // Get initial readings: + radMsgLog (PRI_MEDIUM, + "wmrInit: waiting for first sensor packets (this may take some time):"); + radMsgLog (PRI_MEDIUM, + "wview requires one packet from each sensor suite (except rain) before it can complete initialization."); + radMsgLog (PRI_MEDIUM, + "If one of your sensors is out of range or malfunctioning, wview will not complete initialization."); + printCounter = 10; + while ((wmrWork.dataRXMask < WMR_SENSOR_ALL) && (!work->exiting)) + { + if (++printCounter >= 10) + { + // Log what we are waiting for: + length = 0; + for (i = 0; i < 4; i ++) + { + if (!(wmrWork.dataRXMask & (1 << i))) + { + length += sprintf(&outString[length], "%s ", WMRSensorNames[i]); + } + } + radMsgLog (PRI_MEDIUM, "wmrInit: waiting for sensors: %s", outString); + printCounter = 0; + } + + readDataDirect(work); + wmrProcessData(work); + radUtilsSleep(WMR_PROCESS_TIME_INTERVAL); + } + + wmrWork.dataRXMask = 0; + + // Close the USB interface. The reader thread will reopen it. + (*(work->medium.usbhidExit))(&work->medium); + + if (!work->exiting) + { + radMsgLog (PRI_MEDIUM, "wmrInit: first sensor packets received."); + } + + // Reset the USB buffer. + wmrWork.readIndex = 0; + + // Create the USB reader thread: + work->threadId = radthreadCreate(ReaderThread, work); + if (work->threadId == NULL) + { + radMsgLog (PRI_HIGH, "wmrInit: radthreadCreate failed!"); + return ERROR; + } + + wmrWork.started = TRUE; + + // Start our IF timer: + radProcessTimerStart (work->ifTimer, WMR_PROCESS_TIME_INTERVAL); + + // populate the LOOP structure: + ifWorkData->wmrReadings = wmrWork.sensorData; + storeLoopPkt (work, &work->loopPkt, &ifWorkData->wmrReadings); + + // we must indicate successful completion here - + // even though we are synchronous, the daemon wants to see this event + radProcessEventsSend (NULL, STATION_INIT_COMPLETE_EVENT, 0); + + return OK; +} + +void wmrExit (WVIEWD_WORK *work) +{ + radthreadWaitExit(work->threadId); + radUtilsSleep(100); + radMsgLog (PRI_MEDIUM, "wmrExit: read thread stopped."); + return; +} + +// Read raw USB data and buffer it for later processing: +void wmrReadData (WVIEWD_WORK *work, WMRUSB_MSG_DATA* msg) +{ + if ((wmrWork.readIndex + msg->length) < WMR_BUFFER_LENGTH) + { + memcpy(&wmrWork.readData[wmrWork.readIndex], msg->data, msg->length); + wmrWork.readIndex += msg->length; + +#ifdef WMR_DEBUG + radMsgLog(PRI_MEDIUM, "WMRDBG: USB RX"); + radMsgLogData(&wmrWork.readData[0], wmrWork.readIndex); +#endif +#ifdef WMR_COUNT_BYTES + StreamBytes += msg->length; +#endif + } + + return; +} + +// Enforce packet framing and pass to parse engine if a packet frame is complete: +int wmrProcessData (WVIEWD_WORK *work) +{ + int pktLength, rval, index = 0; + int retVal = FALSE; + + // First, hunt for a packet start sequence (0xFFFF or 0xD2-0xD9): + while ((index < wmrWork.readIndex - 1) && !IsPacketStart(&wmrWork.readData[index])) + { + index ++; + } + + // Do we need to toss junk at the front of the buffer? + if (index > 0) + { + +#ifdef WMR_DEBUG + radMsgLog(PRI_MEDIUM, "WMRDBG: Frame: Tossing %d bytes", index); + radMsgLogData(&wmrWork.readData[0], index); +#endif + + // Lose the rubbish: + shiftUpReadBuffer(index); + } + + if (wmrWork.protocol == WMR_PROTOCOL_FFFF) + { + // Do we have a packet start and type? + if (wmrWork.readIndex >= 4) + { + // Get the packet length and see if we have a complete packet: + pktLength = getFFFFPktLength(wmrWork.readData, wmrWork.readIndex); + + if (pktLength > 4) + { + // We have a completion, process it: + rval = parseStationData(pktLength); + retVal = TRUE; + rval = rval; + +#ifdef WMR_COUNT_BYTES + if (rval != ERROR) + { + PacketBytes += pktLength; + radMsgLog(PRI_MEDIUM, "PKT RX: %d", pktLength); + } + else + { + ChecksumBytes += pktLength; + } +#endif + + // Delete it: + shiftUpReadBuffer(pktLength); + } + else if (pktLength != 0) + { + // Shift up two, something's amiss. + shiftUpReadBuffer(2); + } + } + } + else // wmrWork.protocol == WMR_PROTOCOL_D0 + { + // Do we have a packet start and length? + if (wmrWork.readIndex >= 2) + { + // Get the packet length and see if we have a complete packet: + pktLength = wmrWork.readData[1]; + + if (pktLength < 2) + { + // Invalid: + shiftUpReadBuffer(1); + } + // Sanity check the length: + else if (!checkD0PktLength(wmrWork.readData[0], wmrWork.readData[1])) + { + // Invalid: + shiftUpReadBuffer(1); + } + else if (pktLength <= wmrWork.readIndex) + { + // We have a completion, process it: + parseStationData(pktLength); + retVal = TRUE; + +#ifdef WMR_COUNT_BYTES +PacketBytes += pktLength; +#endif + + // Delete it: + shiftUpReadBuffer(pktLength); + } + } + } + + return retVal; +} + +void wmrGetReadings (WVIEWD_WORK *work) +{ + WMR_IF_DATA* ifWorkData = (WMR_IF_DATA*)work->stationData; + + // populate the LOOP structure: + if (wmrWork.dataRXMask != 0) + { + ifWorkData->wmrReadings = wmrWork.sensorData; + storeLoopPkt (work, &work->loopPkt, &ifWorkData->wmrReadings); + wmrWork.dataRXMask = 0; + + // indicate the LOOP packet is done + radProcessEventsSend (NULL, STATION_LOOP_COMPLETE_EVENT, 0); + } +} + diff --git a/stations/WMRUSB/wmrusbprotocol.h b/stations/WMRUSB/wmrusbprotocol.h new file mode 100755 index 0000000..550732d --- /dev/null +++ b/stations/WMRUSB/wmrusbprotocol.h @@ -0,0 +1,213 @@ +#ifndef INC_wmrusbprotocolh +#define INC_wmrusbprotocolh +/*--------------------------------------------------------------------------- + + FILENAME: + wmrusbprotocol.h + + PURPOSE: + Provide protocol utilities for WMR station communication. + + REVISION HISTORY: + Date Engineer Revision Remarks + 03/10/2011 M.S. Teel 0 Original. + + NOTES: + + + LICENSE: + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +/* ... System include files +*/ +#include +#include +#include +#include +#include +#include +#include +#include + +/* ... Library include files +*/ +#include +#include +#include + +/* ... Local include files +*/ +#include +#include +#include +#include +#include +#include + +// Uncomment this to enable debug messages (lots of logs). +//#define WMR_DEBUG 1 + +// Uncomment this to enable byte counts (USB raw, stream, packet). +//#define WMR_COUNT_BYTES 1 + +// Uncomment this to dump raw USB RX bytes (lots of logs). +//#define WMR_DUMP_RAW_USB 1 + +#ifdef WMR_COUNT_BYTES +extern int UsbRawBytes; +extern int StreamBytes; +extern int PacketBytes; +extern int ChecksumBytes; +extern int StatCount; +extern int UnknownPacketType; +#endif + + +/* WMR-200 */ +#define WMR_VENDOR_ID 0x0fde +#define WMR_PRODUCT_ID 0xca01 + + +#define WMR_BUFFER_LENGTH 255 +#define WMR_READ_WAIT 1000 +#define WMR_TEMP_SENSOR_COUNT 10 +#define WMR_REESTABLISH_SLEEP 10000 // 10 secs +#define WMR_PROCESS_TIME_INTERVAL 1000 // 1 second +#define WMR_HEARTBEAT_INTERVAL 10 // seconds + +// Define the rain rate acuumulator period (minutes): +#define WMR_RAIN_RATE_PERIOD 5 + +// Define the WMRUSB station data message: +// WVIEW_MSG_TYPE_STATION_DATA (WMRUSB version): +typedef struct +{ + uint8_t data[WMR_BUFFER_LENGTH]; + int length; +} +__attribute__ ((packed)) WMRUSB_MSG_DATA; + + + +// Define WMR protocol types: +typedef enum +{ + WMR_PROTOCOL_UNKNOWN = 0, + WMR_PROTOCOL_FFFF = 1, + WMR_PROTOCOL_D0 = 2 +} WMR_PROTOCOL_TYPE; + +// Used for RX mask: +enum _SensorTypes +{ + WMR_SENSOR_WIND = 0x01, + WMR_SENSOR_RAIN = 0x02, + WMR_SENSOR_OUT_TEMP = 0x04, + WMR_SENSOR_PRESSURE = 0x08, + WMR_SENSOR_ALL = 0x0D // Don't require rain to start +}; + +#define WMR_TEMP_SENSOR_OUT 1 +#define WMR_TEMP_SENSOR_IN 0 + +// Define WMR_D0 pkt types: +typedef enum +{ + WMR_D0_HISTORY = 0xD2, + WMR_D0_RAIN = 0xD4, + WMR_D0_TEMP = 0xD7, + WMR_D0_PRESSURE = 0xD6, + WMR_D0_WIND = 0xD3, + WMR_D0_STATUS = 0xD9, + WMR_D0_UV = 0xD5 +} WMR_D0_TYPE; + +// Define WMR_FFFF pkt types: +typedef enum +{ + WMR_FFFF_RAIN = 0x41, + WMR_FFFF_TEMP = 0x42, + WMR_FFFF_TEMPONLY = 0x44, + WMR_FFFF_PRESSURE = 0x46, + WMR_FFFF_UV = 0x47, + WMR_FFFF_WIND = 0x48, + WMR_FFFF_DATETIME = 0x60 +} WMR_FFFF_TYPE; + + +// parsing helper macros: +#define LO(byte) (byte & 0x0f) +#define HI(byte) ((byte & 0xf0) >> 4) +#define MHI(byte) ((byte & 0x0f) << 4) +#define NUM(byte) (10 * HI(byte) + LO(byte)) +#define BIT(byte, bit) ((byte & (1 << bit)) >> bit) + + +// define the readings collector +typedef struct +{ + float temp[WMR_TEMP_SENSOR_COUNT]; + float humidity[WMR_TEMP_SENSOR_COUNT]; + float dewpoint[WMR_TEMP_SENSOR_COUNT]; + float windDir; + float windAvgSpeed; + float windGustSpeed; + float pressure; + int UV; + float rain1h; + float rain24h; + float rainAccum; + float rainRate; + uint8_t tendency; +} WMR_DATA; + + +// define the work area +typedef struct +{ + WMR_PROTOCOL_TYPE protocol; + int started; + WMR_DATA sensorData; + uint8_t readData[WMR_BUFFER_LENGTH]; + int readIndex; + uint32_t heartBeatCounter; + uint32_t lastDataRX; + uint8_t dataRXMask; +} WMR_WORK; + + +// define WMR-specific interface data here +typedef struct +{ + int elevation; + float latitude; + float longitude; + int archiveInterval; + WMR_DATA wmrReadings; + float totalRain; // to track cumulative changes + int outsideChannel; + WV_ACCUM_ID rainRateAccumulator; // to compute rain rate +} WMR_IF_DATA; + + +// call once during initialization +extern int wmrInit (WVIEWD_WORK *work); + +// do cleanup +extern void wmrExit (WVIEWD_WORK *work); + +// read data from station: +extern void wmrReadData (WVIEWD_WORK *work, WMRUSB_MSG_DATA* msg); + +// Enforce packet framing and pass to parse engine if a packet frame is complete: +extern int wmrProcessData (WVIEWD_WORK *work); + +// get loop packet data: +extern void wmrGetReadings (WVIEWD_WORK *work); + +#endif + diff --git a/stations/WS-2300/Makefile.am b/stations/WS-2300/Makefile.am new file mode 100755 index 0000000..c052ed0 --- /dev/null +++ b/stations/WS-2300/Makefile.am @@ -0,0 +1,64 @@ +# Makefile - La Crosse WS-2300 station daemon + +# define the executable to be built +bin_PROGRAMS = wviewd_ws2300 + +# define include directories +INCLUDES = \ + -I$(top_srcdir)/common \ + -I$(top_srcdir)/stations/common \ + -I$(prefix)/include \ + -D_GNU_SOURCE \ + -DWV_CONFIG_DIR=\"$(sysconfdir)/wview\" \ + -DWV_RUN_DIR=\"$(localstatedir)/wview\" \ + -DBUILD_WVIEWD + +# define the sources +wviewd_ws2300_SOURCES = \ + $(top_srcdir)/common/sensor.c \ + $(top_srcdir)/common/wvutils.c \ + $(top_srcdir)/common/wvconfig.c \ + $(top_srcdir)/common/status.c \ + $(top_srcdir)/common/dbsqlite.c \ + $(top_srcdir)/common/dbsqliteHiLow.c \ + $(top_srcdir)/common/windAverage.c \ + $(top_srcdir)/common/emailAlerts.c \ + $(top_srcdir)/stations/common/computedData.c \ + $(top_srcdir)/stations/common/daemon.c \ + $(top_srcdir)/stations/common/station.c \ + $(top_srcdir)/stations/common/serial.c \ + $(top_srcdir)/stations/common/ethernet.c \ + $(top_srcdir)/stations/common/stormRain.c \ + $(top_srcdir)/stations/common/parser.c \ + $(top_srcdir)/stations/WS-2300/ws2300Interface.c \ + $(top_srcdir)/stations/WS-2300/ws2300protocol.c \ + $(top_srcdir)/common/sensor.h \ + $(top_srcdir)/common/datadefs.h \ + $(top_srcdir)/common/dbsqlite.h \ + $(top_srcdir)/common/services.h \ + $(top_srcdir)/common/sysdefs.h \ + $(top_srcdir)/common/wvconfig.h \ + $(top_srcdir)/common/status.h \ + $(top_srcdir)/common/windAverage.h \ + $(top_srcdir)/common/emailAlerts.h \ + $(top_srcdir)/common/beaufort.h \ + $(top_srcdir)/stations/common/computedData.h \ + $(top_srcdir)/stations/common/daemon.h \ + $(top_srcdir)/stations/common/station.h \ + $(top_srcdir)/stations/common/serial.h \ + $(top_srcdir)/stations/common/ethernet.h \ + $(top_srcdir)/stations/common/stormRain.h \ + $(top_srcdir)/stations/common/parser.h \ + $(top_srcdir)/stations/WS-2300/ws2300Interface.h \ + $(top_srcdir)/stations/WS-2300/ws2300protocol.h + +# define libraries +wviewd_ws2300_LDADD = + +# define library directories +wviewd_ws2300_LDFLAGS = -L$(prefix)/lib -L$(prefix)/usr/lib -L/usr/lib + +if CROSSCOMPILE +wviewd_ws2300_LDFLAGS += $(prefix)/lib/crt1.o $(prefix)/lib/crti.o $(prefix)/lib/crtn.o +endif + diff --git a/stations/WS-2300/ws2300Interface.c b/stations/WS-2300/ws2300Interface.c new file mode 100755 index 0000000..2844d61 --- /dev/null +++ b/stations/WS-2300/ws2300Interface.c @@ -0,0 +1,494 @@ +/*--------------------------------------------------------------------------- + + FILENAME: + ws2300Interface.c + + PURPOSE: + Provide the La Crosse WS-2300 family station interface API and utilities. + + REVISION HISTORY: + Date Engineer Revision Remarks + 02/27/2008 M.S. Teel 0 Original + + NOTES: + + + LICENSE: + Copyright (c) 2008, Mark S. Teel (mark@teel.ws) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +/* ... System include files +*/ + +/* ... Library include files +*/ + +/* ... Local include files +*/ +#include + +/* ... global memory declarations +*/ + +/* ... local memory +*/ + +static WS2300_IF_DATA ws2300WorkData; +static void (*ArchiveIndicator) (ARCHIVE_PKT *newRecord); + +static void serialPortConfig (int fd); + + +////////////****////**** S T A T I O N A P I ****////****//////////// +///// Must be provided by each supported wview station interface ////// + +// station-supplied init function +// -- Can Be Asynchronous - event indication required -- +// +// MUST: +// - set the 'stationGeneratesArchives' flag in WVIEWD_WORK: +// if the station generates archive records (TRUE) or they should be +// generated automatically by the daemon from the sensor readings (FALSE) +// - Initialize the 'stationData' store for station work area +// - Initialize the interface medium +// - do initial LOOP acquisition +// - do any catch-up on archive records if there is a data logger +// - 'work->runningFlag' can be used for start up synchronization but should +// not be modified by the station interface code +// - indicate init is done by sending the STATION_INIT_COMPLETE_EVENT event to +// this process (radProcessEventsSend (NULL, STATION_INIT_COMPLETE_EVENT, 0)) +// +// OPTIONAL: +// - Initialize a state machine or any other construct required for the +// station interface - these should be stored in the 'stationData' store +// +// 'archiveIndication' - indication callback used to pass back an archive record +// generated as a result of 'stationGetArchive' being called; should receive a +// NULL pointer for 'newRecord' if no record available; only used if +// 'stationGeneratesArchives' flag is set to TRUE by the station interface +// +// Returns: OK or ERROR +// +int stationInit +( + WVIEWD_WORK *work, + void (*archiveIndication)(ARCHIVE_PKT* newRecord) +) +{ + int i; + STIM stim; + + memset (&ws2300WorkData, 0, sizeof(ws2300WorkData)); + + // save the archive indication callback (we should never need it) + ArchiveIndicator = archiveIndication; + + // set our work data pointer + work->stationData = &ws2300WorkData; + + // set the Archive Generation flag to indicate the WS-2300 DOES NOT + // generate them + work->stationGeneratesArchives = FALSE; + + // initialize the medium abstraction based on user configuration + if (!strcmp (work->stationInterface, "serial")) + { + if (serialMediumInit (&work->medium, serialPortConfig, O_RDWR | O_NOCTTY | O_NDELAY) == ERROR) + { + radMsgLog (PRI_HIGH, "stationInit: serial MediumInit failed"); + return ERROR; + } + } + else if (!strcmp (work->stationInterface, "ethernet")) + { + if (ethernetMediumInit (&work->medium, work->stationHost, work->stationPort) + == ERROR) + { + radMsgLog (PRI_HIGH, "stationInit: ethernet MediumInit failed"); + return ERROR; + } + } + else + { + radMsgLog (PRI_HIGH, "stationInit: medium %s not supported", + work->stationInterface); + return ERROR; + } + + // initialize the interface using the media specific routine + if ((*(work->medium.init)) (&work->medium, work->stationDevice) == ERROR) + { + radMsgLog (PRI_HIGH, "stationInit: medium setup failed"); + return ERROR; + } + + if (!strcmp (work->stationInterface, "serial")) + { + radMsgLog (PRI_STATUS, "WS-2300 on %s opened ...", + work->stationDevice); + } + else if (!strcmp (work->stationInterface, "ethernet")) + { + radMsgLog (PRI_STATUS, "WS-2300 on %s:%d opened ...", + work->stationHost, work->stationPort); + } + + // grab the station configuration now + if (stationGetConfigValueInt (work, + STATION_PARM_ELEVATION, + &ws2300WorkData.elevation) + == ERROR) + { + radMsgLog (PRI_HIGH, "stationInit: stationGetConfigValueInt ELEV failed!"); + (*(work->medium.exit)) (&work->medium); + return ERROR; + } + if (stationGetConfigValueFloat (work, + STATION_PARM_LATITUDE, + &ws2300WorkData.latitude) + == ERROR) + { + radMsgLog (PRI_HIGH, "stationInit: stationGetConfigValueInt LAT failed!"); + (*(work->medium.exit)) (&work->medium); + return ERROR; + } + if (stationGetConfigValueFloat (work, + STATION_PARM_LONGITUDE, + &ws2300WorkData.longitude) + == ERROR) + { + radMsgLog (PRI_HIGH, "stationInit: stationGetConfigValueInt LONG failed!"); + (*(work->medium.exit)) (&work->medium); + return ERROR; + } + if (stationGetConfigValueInt (work, + STATION_PARM_ARC_INTERVAL, + &ws2300WorkData.archiveInterval) + == ERROR) + { + radMsgLog (PRI_HIGH, "stationInit: stationGetConfigValueInt ARCINT failed!"); + (*(work->medium.exit)) (&work->medium); + return ERROR; + } + + // now initialize the state machine + ws2300WorkData.stateMachine = radStatesInit (work); + if (ws2300WorkData.stateMachine == NULL) + { + radMsgLog (PRI_HIGH, "stationInit: radStatesInit failed"); + (*(work->medium.exit)) (&work->medium); + return ERROR; + } + + if (radStatesAddHandler (ws2300WorkData.stateMachine, + WS_STATE_STARTPROC, + wsStartProcState) + == ERROR) + { + radMsgLog (PRI_HIGH, "stationInit: radStatesAddHandler failed"); + radStatesExit (ws2300WorkData.stateMachine); + (*(work->medium.exit)) (&work->medium); + return ERROR; + } + if (radStatesAddHandler (ws2300WorkData.stateMachine, + WS_STATE_RUN, + wsRunState) + == ERROR) + { + radMsgLog (PRI_HIGH, "stationInit: radStatesAddHandler failed"); + radStatesExit (ws2300WorkData.stateMachine); + (*(work->medium.exit)) (&work->medium); + return ERROR; + } + if (radStatesAddHandler (ws2300WorkData.stateMachine, + WS_STATE_ADRS0_ACK, + wsAdrs0State) + == ERROR) + { + radMsgLog (PRI_HIGH, "stationInit: radStatesAddHandler failed"); + radStatesExit (ws2300WorkData.stateMachine); + (*(work->medium.exit)) (&work->medium); + return ERROR; + } + if (radStatesAddHandler (ws2300WorkData.stateMachine, + WS_STATE_ADRS1_ACK, + wsAdrs1State) + == ERROR) + { + radMsgLog (PRI_HIGH, "stationInit: radStatesAddHandler failed"); + radStatesExit (ws2300WorkData.stateMachine); + (*(work->medium.exit)) (&work->medium); + return ERROR; + } + if (radStatesAddHandler (ws2300WorkData.stateMachine, + WS_STATE_ADRS2_ACK, + wsAdrs2State) + == ERROR) + { + radMsgLog (PRI_HIGH, "stationInit: radStatesAddHandler failed"); + radStatesExit (ws2300WorkData.stateMachine); + (*(work->medium.exit)) (&work->medium); + return ERROR; + } + if (radStatesAddHandler (ws2300WorkData.stateMachine, + WS_STATE_ADRS3_ACK, + wsAdrs3State) + == ERROR) + { + radMsgLog (PRI_HIGH, "stationInit: radStatesAddHandler failed"); + radStatesExit (ws2300WorkData.stateMachine); + (*(work->medium.exit)) (&work->medium); + return ERROR; + } + if (radStatesAddHandler (ws2300WorkData.stateMachine, + WS_STATE_NUMBYTES_ACK, + wsNumBytesState) + == ERROR) + { + radMsgLog (PRI_HIGH, "stationInit: radStatesAddHandler failed"); + radStatesExit (ws2300WorkData.stateMachine); + (*(work->medium.exit)) (&work->medium); + return ERROR; + } + if (radStatesAddHandler (ws2300WorkData.stateMachine, + WS_STATE_ERROR, + wsErrorState) + == ERROR) + { + radMsgLog (PRI_HIGH, "stationInit: radStatesAddHandler failed"); + radStatesExit (ws2300WorkData.stateMachine); + (*(work->medium.exit)) (&work->medium); + return ERROR; + } + + radStatesSetState (ws2300WorkData.stateMachine, WS_STATE_STARTPROC); + + radMsgLog (PRI_STATUS, "Starting station interface: WS2300"); + + // dummy up a stimulus to get the state machine running + stim.type = STIM_DUMMY; + radStatesProcess (ws2300WorkData.stateMachine, &stim); + + return OK; +} + +// station-supplied exit function +// +// Returns: N/A +// +void stationExit (WVIEWD_WORK *work) +{ + ws2300Exit (work); + (*(work->medium.exit)) (&work->medium); + + return; +} + +// station-supplied function to retrieve positional info (lat, long, elev) - +// should populate 'work' fields: latitude, longitude, elevation +// -- Synchronous -- +// +// - If station does not store these parameters, they can be retrieved from the +// wview.conf file (see daemon.c for example conf file use) - user must choose +// station type "Generic" when running the wviewconfig script +// +// Returns: OK or ERROR +// +int stationGetPosition (WVIEWD_WORK *work) +{ + // just set the values from our internal store - we retrieved them in + // stationInit + work->elevation = (int16_t)ws2300WorkData.elevation; + if (ws2300WorkData.latitude >= 0) + work->latitude = (int16_t)((ws2300WorkData.latitude*10)+0.5); + else + work->latitude = (int16_t)((ws2300WorkData.latitude*10)-0.5); + if (ws2300WorkData.longitude >= 0) + work->longitude = (int16_t)((ws2300WorkData.longitude*10)+0.5); + else + work->longitude = (int16_t)((ws2300WorkData.longitude*10)-0.5); + + radMsgLog (PRI_STATUS, "station location: elevation: %d feet", + work->elevation); + + radMsgLog (PRI_STATUS, "station location: latitude: %3.1f %c longitude: %3.1f %c", + (float)abs(work->latitude)/10.0, + ((work->latitude < 0) ? 'S' : 'N'), + (float)abs(work->longitude)/10.0, + ((work->longitude < 0) ? 'W' : 'E')); + + return OK; +} + +// station-supplied function to indicate a time sync should be performed if the +// station maintains time, otherwise may be safely ignored +// -- Can Be Asynchronous -- +// +// Returns: OK or ERROR +// +int stationSyncTime (WVIEWD_WORK *work) +{ + // We don't use the WS-2300 time... + return OK; +} + +// station-supplied function to indicate sensor readings should be performed - +// should populate 'work' struct: loopPkt (see datadefs.h for minimum field reqs) +// -- Can Be Asynchronous -- +// +// - indicate readings are complete by sending the STATION_LOOP_COMPLETE_EVENT +// event to this process (radProcessEventsSend (NULL, STATION_LOOP_COMPLETE_EVENT, 0)) +// +// Returns: OK or ERROR +// +int stationGetReadings (WVIEWD_WORK *work) +{ + STIM stim; + + stim.type = WS_STIM_READINGS; + + // use the VP_STIM_READINGS stim to kick off the state machine + radStatesProcess (ws2300WorkData.stateMachine, &stim); + + return OK; +} + +// station-supplied function to indicate an archive record should be generated - +// MUST populate an ARCHIVE_RECORD struct and indicate it to 'archiveIndication' +// function passed into 'stationInit' +// -- Asynchronous - callback indication required -- +// +// Returns: OK or ERROR +// +// Note: 'archiveIndication' should receive a NULL pointer for the newRecord if +// no record is available +// Note: This function will only be invoked by the wview daemon if the +// 'stationInit' function set the 'stationGeneratesArchives' to TRUE +// +int stationGetArchive (WVIEWD_WORK *work) +{ + // just indicate a NULL record, WS-2300 does not generate them (and this + // function should never be called!) + (*ArchiveIndicator) (NULL); + return OK; +} + +// station-supplied function to indicate data is available on the station +// interface medium (serial or ethernet) - +// It is the responsibility of the station interface to read the data from the +// medium and process appropriately. The data does not have to be read within +// the context of this function, but may be used to stimulate a state machine. +// -- Synchronous -- +// +// Returns: N/A +// +void stationDataIndicate (WVIEWD_WORK *work) +{ + STIM stim; + + stim.type = STIM_IO; + + radStatesProcess (ws2300WorkData.stateMachine, &stim); + return; +} + +// station-supplied function to receive IPM messages - any message received by +// the generic station message handler which is not recognized will be passed +// to the station-specific code through this function. +// It is the responsibility of the station interface to process the message +// appropriately (or ignore it). +// -- Synchronous -- +// +// Returns: N/A +// +void stationMessageIndicate (WVIEWD_WORK *work, int msgType, void *msg) +{ + // N/A + return; +} + +// station-supplied function to indicate the interface timer has expired - +// It is the responsibility of the station interface to start/stop the interface +// timer as needed for the particular station requirements. +// The station interface timer is specified by the 'ifTimer' member of the +// WVIEWD_WORK structure. No other timers in that structure should be manipulated +// in any way by the station interface code. +// -- Synchronous -- +// +// Returns: N/A +// +void stationIFTimerExpiry (WVIEWD_WORK *work) +{ + STIM stim; + + stim.type = STIM_TIMER; + + radStatesProcess (ws2300WorkData.stateMachine, &stim); + return; +} + +////////////****//// S T A T I O N A P I E N D ////****//////////// + + +// ... ----- static (local) methods ----- ... + +static void serialPortConfig (int fd) +{ + struct termios port; + int portstatus; + + // We want full control of what is set and simply reset the entire port struct + memset (&port, 0, sizeof(port)); + + // Serial control options: + port.c_cflag &= ~PARENB; // No parity + port.c_cflag &= ~CSTOPB; // One stop bit + port.c_cflag &= ~CSIZE; // Character size mask + port.c_cflag |= CS8; // Character size 8 bits + port.c_cflag |= CREAD; // Enable Receiver + port.c_cflag &= ~HUPCL; // No "hangup" + port.c_cflag &= ~CRTSCTS; // No flowcontrol + port.c_cflag |= CLOCAL; // Ignore modem control lines + + // Baudrate, for newer systems + cfsetispeed (&port, B2400); + cfsetospeed (&port, B2400); + + // Serial local options: + // Raw input = clear ICANON, ECHO, ECHOE, and ISIG + // Disable misc other local features = clear FLUSHO, NOFLSH, TOSTOP, PENDIN, and IEXTEN + // So we actually clear all flags in port.c_lflag + port.c_lflag = 0; + + // Serial input options: + // Disable parity check = clear INPCK, PARMRK, and ISTRIP + // Disable software flow control = clear IXON, IXOFF, and IXANY + // Disable any translation of CR and LF = clear INLCR, IGNCR, and ICRNL + // Ignore break condition on input = set IGNBRK + // Ignore parity errors just in case = set IGNPAR; + // So we can clear all flags except IGNBRK and IGNPAR + port.c_iflag = IGNBRK|IGNPAR; + + // Serial output options: + // Raw output should disable all other output options + port.c_oflag &= ~OPOST; + + port.c_cc[VTIME] = 10; // timer 1s + port.c_cc[VMIN] = 0; // blocking read until 1 char + + tcsetattr (fd, TCSANOW, &port); + tcflush (fd, TCIOFLUSH); + + // Set DTR low and RTS high and leave other ctrl lines untouched + ioctl (fd, TIOCMGET, &portstatus); // get current port status + portstatus &= ~TIOCM_DTR; + portstatus |= TIOCM_RTS; + ioctl (fd, TIOCMSET, &portstatus); // set current port status + + return; +} + diff --git a/stations/WS-2300/ws2300Interface.h b/stations/WS-2300/ws2300Interface.h new file mode 100755 index 0000000..04ed620 --- /dev/null +++ b/stations/WS-2300/ws2300Interface.h @@ -0,0 +1,64 @@ +#ifndef INC_ws2300Interfaceh +#define INC_ws2300Interfaceh +/*--------------------------------------------------------------------------- + + FILENAME: + ws2300Interface.h + + PURPOSE: + Provide the La Crosse WS-2300 family station interface API and utilities. + + REVISION HISTORY: + Date Engineer Revision Remarks + 02/27/2008 M.S. Teel 0 Original + + NOTES: + + + LICENSE: + Copyright (c) 2008, Mark S. Teel (mark@teel.ws) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +/* ... System include files +*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* ... Library include files +*/ +#include +#include +#include +#include + +/* ... Local include files +*/ +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#endif + diff --git a/stations/WS-2300/ws2300protocol.c b/stations/WS-2300/ws2300protocol.c new file mode 100755 index 0000000..a3d4985 --- /dev/null +++ b/stations/WS-2300/ws2300protocol.c @@ -0,0 +1,961 @@ +/*--------------------------------------------------------------------------- + + FILENAME: + ws2300protocol.c + + PURPOSE: + Provide protocol utilities for WS-2300 station communication. + + REVISION HISTORY: + Date Engineer Revision Remarks + 02/27/2008 M.S. Teel 0 Original + 02/14/2010 MS Teel 1 Add user contributed + sensor multiple read + data check + + NOTES: + Portions of the WS-2300 interface code was inspired by the rw2300 + library, open2300 Version 1.10, Copyright 2003-2005, Kenneth Lavrsen. + + LICENSE: + Copyright (c) 2008, Mark S. Teel (mark@teel.ws) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +/* ... System include files +*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/* ... Library include files +*/ +#include +#include +#include + +/* ... Local include files +*/ +#include +#include +#include +#include + +/* ... global memory declarations +*/ + +/* ... local memory +*/ + +static WS2300_WORK ws2300Work; + +static WS_SENSOR_INFO sensorInfo[WS_SENSOR_MAX] = +{ + { 0x346, 2 }, + { 0x373, 2 }, + { 0x3FB, 1 }, + { 0x419, 1 }, + { 0x527, 3 }, + { 0x5D8, 3 }, + { 0x4D2, 3 }, +}; + + +static void encodeAddress (int address_in, uint8_t* address_out) +{ + int i = 0; + int adrbytes = 4; + uint8_t nibble; + + for (i = 0; i < adrbytes; i ++) + { + nibble = (address_in >> (4 * (3 - i))) & 0x0F; + address_out[i] = (uint8_t) (0x82 + (nibble * 4)); + } + + return; +} + +static void encodeData +( + int number, + uint8_t encode_constant, + uint8_t* data_in, + uint8_t* data_out +) +{ + int i = 0; + + for (i = 0; i < number; i ++) + { + data_out[i] = (uint8_t) (encode_constant + (data_in[i] * 4)); + } + + return; +} + +static uint8_t encodeLength (int number) +{ + int coded_number; + + coded_number = (uint8_t) (0xC2 + number * 4); + if (coded_number > 0xfe) + coded_number = 0xfe; + + return coded_number; +} + +/******************************************************************************* + * checksumSequence calculates the checksum for the first 4 + * commands sent to WS2300. + ******************************************************************************/ +static uint8_t checksumSequence (uint8_t* command, int sequence) +{ + int response; + + response = sequence * 16 + ((*command) - 0x82) / 4; + return (uint8_t)response; +} + +/******************************************************************************* + * checksumLast calculates the checksum for the last command + * which is sent just before data is received from WS2300 + ******************************************************************************/ +static uint8_t checksumLast (int number) +{ + int response; + + response = 0x30 + number; + return response; +} + + +/******************************************************************************* + * checksumDataRX calculates the checksum for the data bytes received + * from the WS2300 + ******************************************************************************/ +static uint8_t checksumDataRX (uint8_t* data, int number) +{ + int checksum = 0; + int i; + + for (i = 0; i < number; i ++) + { + checksum += data[i]; + } + + checksum &= 0xFF; + return (uint8_t) checksum; +} + +/******************************************************************************* + * resetStation WS2300 by sending command 06 + ******************************************************************************/ +static void resetStation (WVIEWD_WORK* work) +{ + uint8_t command = 0x06; + uint8_t answer; + int i; + + for (i = 0; i < 3; i ++) + { + // Discard any garbage in the buffers + tcflush (work->medium.fd, TCIFLUSH); + tcflush (work->medium.fd, TCOFLUSH); + + (*work->medium.write) (&work->medium, &command, 1); + (*work->medium.read) (&work->medium, &answer, 1, WS2300_READ_TIMEOUT); + + if (answer == 0x02) + { + return; + } + + // we sleep longer and longer for each retry + radUtilsSleep (50 * (i+1)); + } +} + +static int startDataRequest (WVIEWD_WORK* work, int startFlag) +{ + int retVal; + + if (startFlag || ws2300Work.currentSensor == WS_SENSOR_MAX) + { + ws2300Work.currentSensor = WS_SENSOR_IN_TEMP; + ws2300Work.numTries = 0; + ws2300Work.numGood = 0; + } + + memset (ws2300Work.commandData, 0, WS2300_BUFFER_LENGTH); + memset (ws2300Work.readData, 0, WS2300_BUFFER_LENGTH); + + // First 4 bytes are populated with converted address + encodeAddress (sensorInfo[ws2300Work.currentSensor].address, ws2300Work.commandData); + + // Last populate the 5th byte with the converted number of bytes + ws2300Work.commandData[4] = encodeLength (sensorInfo[ws2300Work.currentSensor].bytes); + + retVal = (*work->medium.write) (&work->medium, &ws2300Work.commandData[0], 1); + if (retVal != 1) + { + radMsgLog (PRI_MEDIUM, "startDataRequest: write address 0 failed: %s", + strerror(errno)); + emailAlertSend(ALERT_TYPE_STATION_LOOP); + return ERROR; + } + + return OK; +} + +static void RetryCurrentSensor(WVIEWD_WORK* work) +{ + radUtilsSleep(50); + + // reset the station: + resetStation(work); + + // Start the sensor gathering: + if (startDataRequest(work, FALSE) == ERROR) + { + radMsgLog (PRI_HIGH, "RetryCurrentSensor: startDataRequest failed!"); + return; + } + + radProcessTimerStart (work->ifTimer, WS_RESPONSE_TIMEOUT); +} + +static float computeRainRate (WVIEWD_WORK *work, float sampleRain) +{ + int sampleSECS = work->cdataInterval / 1000; + int multiplier; + float rainRate; + + multiplier = WV_SECONDS_IN_HOUR / sampleSECS; + rainRate = sampleRain * (float)multiplier; + return rainRate; +} + +static void storeLoopPkt (WVIEWD_WORK *work, LOOP_PKT *dest, WS2300_DATA *src) +{ + float tempfloat; + WS2300_IF_DATA* ifWorkData = (WS2300_IF_DATA*)work->stationData; + time_t nowTime = time (NULL); + + // Clear optional data: + stationClearLoopData(work); + + + // WS-2300 produces station pressure + if ((10 < src->pressure && src->pressure < 50) && + (-150 < src->outTemp && src->outTemp < 150)) + { + dest->stationPressure = src->pressure; + + // Apply calibration here so the computed values reflect it: + dest->stationPressure *= work->calMPressure; + dest->stationPressure += work->calCPressure; + + // compute sea-level pressure (BP) + tempfloat = wvutilsConvertSPToSLP (dest->stationPressure, + src->outTemp, + (float)ifWorkData->elevation); + dest->barometer = tempfloat; + + // calculate altimeter + tempfloat = wvutilsConvertSPToAltimeter (dest->stationPressure, + (float)ifWorkData->elevation); + dest->altimeter = tempfloat; + } + + if (-150 < src->outTemp && src->outTemp < 150) + dest->outTemp = src->outTemp; + + if (0 <= src->outHumidity && src->outHumidity <= 100) + { + tempfloat = src->outHumidity; + tempfloat += 0.5; + dest->outHumidity = (uint16_t)tempfloat; + } + + if (0 <= src->windSpeed && src->windSpeed <= 250) + { + tempfloat = src->windSpeed; + dest->windSpeedF = tempfloat; + } + + if (0 <= src->windDir && src->windDir <= 360) + { + tempfloat = src->windDir; + tempfloat += 0.5; + dest->windDir = (uint16_t)tempfloat; + } + + if (0 <= src->maxWindSpeed && src->maxWindSpeed <= 250) + { + tempfloat = src->maxWindSpeed; + dest->windGustF = tempfloat; + } + + if (0 <= src->maxWindDir && src->maxWindDir <= 360) + { + tempfloat = src->maxWindDir; + tempfloat += 0.5; + dest->windGustDir = (uint16_t)tempfloat; + } + + if (0 <= src->rain) + { + if (!work->runningFlag) + { + // just starting, so start with whatever the station reports: + ifWorkData->totalRain = src->rain; + dest->sampleRain = 0; + } + else + { + // process the rain accumulator + if (src->rain - ifWorkData->totalRain >= 0) + { + dest->sampleRain = src->rain - ifWorkData->totalRain; + ifWorkData->totalRain = src->rain; + } + else + { + // we had a counter reset... + dest->sampleRain = src->rain; + ifWorkData->totalRain = src->rain; + } + } + + if (dest->sampleRain > 2) + { + // Not possible, filter it out: + dest->sampleRain = 0; + } + + // Compute rain rate - the WS-2300 does not provide it! + // Update the rain accumulator: + sensorAccumAddSample (ifWorkData->rainRateAccumulator, nowTime, dest->sampleRain); + dest->rainRate = sensorAccumGetTotal (ifWorkData->rainRateAccumulator); + dest->rainRate *= (60/WS2300_RAIN_RATE_PERIOD); + } + else + { + dest->sampleRain = 0; + sensorAccumAddSample (ifWorkData->rainRateAccumulator, nowTime, dest->sampleRain); + dest->rainRate = sensorAccumGetTotal (ifWorkData->rainRateAccumulator); + dest->rainRate *= (60/WS2300_RAIN_RATE_PERIOD); + } + + dest->inTemp = src->inTemp; + tempfloat = src->inHumidity; + tempfloat += 0.5; + dest->inHumidity = (uint16_t)tempfloat; + + return; +} + + +//////////////////////////////////////////////////////////////////////////////// +////////////////////////////////// A P I ///////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// + +int ws2300Init (WVIEWD_WORK *work) +{ + WS2300_IF_DATA* ifWorkData = (WS2300_IF_DATA*)work->stationData; + time_t nowTime = time (NULL) - (WV_SECONDS_IN_HOUR/(60/WS2300_RAIN_RATE_PERIOD)); + ARCHIVE_PKT recordStore; + float tempRain; + + memset (&ws2300Work, 0, sizeof(ws2300Work)); + + // Create the rain accumulator (WS2300_RAIN_RATE_PERIOD minute age) + // so we can compute rain rate: + ifWorkData->rainRateAccumulator = sensorAccumInit (WS2300_RAIN_RATE_PERIOD); + + // Populate the accumulator with the last WS2300_RAIN_RATE_PERIOD minutes: + while ((nowTime = dbsqliteArchiveGetNextRecord(nowTime, &recordStore)) != ERROR) + { + sensorAccumAddSample(ifWorkData->rainRateAccumulator, + recordStore.dateTime, + recordStore.value[DATA_INDEX_rain]); + } + + // Reset the station: + resetStation (work); + radUtilsSleep (50); + resetStation (work); + + return OK; +} + +void ws2300Exit (WVIEWD_WORK *work) +{ + WS2300_IF_DATA* ifWorkData = (WS2300_IF_DATA*)work->stationData; + + // Clean up the rain accumulator: + sensorAccumExit (ifWorkData->rainRateAccumulator); + + return; +} + + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////// STATE HANDLERS //////////////////////////// +//////////////////////////////////////////////////////////////////////////////// + +int wsStartProcState (int state, void *stimulus, void *data) +{ + STIM *stim = (STIM *)stimulus; + WVIEWD_WORK *work = (WVIEWD_WORK *)data; + WS2300_IF_DATA* ifWorkData = (WS2300_IF_DATA*)work->stationData; + + switch (stim->type) + { + case STIM_DUMMY: + // ... this one starts this state machine + + // set the work archive interval now + work->archiveInterval = ifWorkData->archiveInterval; + + // sanity check the archive interval against the most recent record + if (stationVerifyArchiveInterval (work) == ERROR) + { + // bad magic! + radMsgLog (PRI_HIGH, "stationInit: stationVerifyArchiveInterval failed!"); + radMsgLog (PRI_HIGH, "You must either move old /var/wview/archive files out of the way -or-"); + radMsgLog (PRI_HIGH, "fix the wview.conf setting..."); + (*(work->medium.exit)) (&work->medium); + return WS_STATE_ERROR; + } + else + { + radMsgLog (PRI_STATUS, "station archive interval: %d minutes", + work->archiveInterval); + } + + ifWorkData->totalRain = 0; + + // initialize the station interface + if (ws2300Init (work) == ERROR) + { + radMsgLog (PRI_HIGH, "stationInit: ws2300Init failed!"); + (*(work->medium.exit)) (&work->medium); + return WS_STATE_ERROR; + } + + // Start the first sensor gathering: + if (startDataRequest (work, TRUE) == ERROR) + { + radMsgLog (PRI_HIGH, "stationInit: startDataRequest failed!"); + (*(work->medium.exit)) (&work->medium); + return WS_STATE_ERROR; + } + + radProcessTimerStart (work->ifTimer, WS_RESPONSE_TIMEOUT); + return WS_STATE_ADRS0_ACK; + } + + return state; +} + +int wsRunState (int state, void *stimulus, void *data) +{ + STIM *stim = (STIM *)stimulus; + WVIEWD_WORK *work = (WVIEWD_WORK *)data; + + switch (stim->type) + { + case WS_STIM_READINGS: + // Start the sensor gathering: + if (startDataRequest (work, TRUE) == ERROR) + { + radMsgLog (PRI_HIGH, "wsRunState: startDataRequest failed!"); + return WS_STATE_ERROR; + } + + radProcessTimerStart (work->ifTimer, WS_RESPONSE_TIMEOUT); + return WS_STATE_ADRS0_ACK; + } + + return state; +} + +int wsAdrs0State (int state, void *stimulus, void *data) +{ + STIM *stim = (STIM *)stimulus; + WVIEWD_WORK *work = (WVIEWD_WORK *)data; + int retVal; + uint8_t readData; + + switch (stim->type) + { + case STIM_IO: + radProcessTimerStop (work->ifTimer); + + retVal = (*work->medium.read) (&work->medium, &readData, 1, WS2300_READ_TIMEOUT); + if (retVal != 1) + { + radMsgLog (PRI_MEDIUM, "wsAdrs0State: read address 0 failed: %s", + strerror(errno)); + RetryCurrentSensor(work); + return WS_STATE_ADRS0_ACK; + } + if (readData != checksumSequence (&ws2300Work.commandData[0], 0)) + { + // start this sensor again: + RetryCurrentSensor(work); + return WS_STATE_ADRS0_ACK; + } + + // All good, go to next byte + retVal = (*work->medium.write) (&work->medium, &ws2300Work.commandData[1], 1); + if (retVal != 1) + { + radMsgLog (PRI_MEDIUM, "wsAdrs0State: write address 1 failed: %s", + strerror(errno)); + return WS_STATE_ERROR; + } + + radProcessTimerStart (work->ifTimer, WS_RESPONSE_TIMEOUT); + return WS_STATE_ADRS1_ACK; + + case STIM_TIMER: + // IF timer expiry - try again: + RetryCurrentSensor(work); + return WS_STATE_ADRS0_ACK; + } + + return state; +} + +int wsAdrs1State (int state, void *stimulus, void *data) +{ + STIM *stim = (STIM *)stimulus; + WVIEWD_WORK *work = (WVIEWD_WORK *)data; + int retVal; + uint8_t readData; + + switch (stim->type) + { + case STIM_IO: + radProcessTimerStop (work->ifTimer); + + retVal = (*work->medium.read) (&work->medium, &readData, 1, WS2300_READ_TIMEOUT); + if (retVal != 1) + { + radMsgLog (PRI_MEDIUM, "wsAdrs1State: read address 1 failed: %s", + strerror(errno)); + RetryCurrentSensor(work); + return WS_STATE_ADRS0_ACK; + } + if (readData != checksumSequence (&ws2300Work.commandData[1], 1)) + { + // start this sensor again: + RetryCurrentSensor(work); + return WS_STATE_ADRS0_ACK; + } + + // All good, go to next byte + retVal = (*work->medium.write) (&work->medium, &ws2300Work.commandData[2], 1); + if (retVal != 1) + { + radMsgLog (PRI_MEDIUM, "wsAdrs1State: write address 2 failed: %s", + strerror(errno)); + return WS_STATE_ERROR; + } + + radProcessTimerStart (work->ifTimer, WS_RESPONSE_TIMEOUT); + return WS_STATE_ADRS2_ACK; + + case STIM_TIMER: + // IF timer expiry - try again: + RetryCurrentSensor(work); + return WS_STATE_ADRS0_ACK; + } + + return state; +} + +int wsAdrs2State (int state, void *stimulus, void *data) +{ + STIM *stim = (STIM *)stimulus; + WVIEWD_WORK *work = (WVIEWD_WORK *)data; + int retVal; + uint8_t readData; + + switch (stim->type) + { + case STIM_IO: + radProcessTimerStop (work->ifTimer); + + retVal = (*work->medium.read) (&work->medium, &readData, 1, WS2300_READ_TIMEOUT); + if (retVal != 1) + { + radMsgLog (PRI_MEDIUM, "wsAdrs2State: read address 2 failed: %s", + strerror(errno)); + RetryCurrentSensor(work); + return WS_STATE_ADRS0_ACK; + } + if (readData != checksumSequence (&ws2300Work.commandData[2], 2)) + { + // start this sensor again: + RetryCurrentSensor(work); + return WS_STATE_ADRS0_ACK; + } + + // All good, go to next byte + retVal = (*work->medium.write) (&work->medium, &ws2300Work.commandData[3], 1); + if (retVal != 1) + { + radMsgLog (PRI_MEDIUM, "wsAdrs2State: write address 3 failed: %s", + strerror(errno)); + return WS_STATE_ERROR; + } + + radProcessTimerStart (work->ifTimer, WS_RESPONSE_TIMEOUT); + return WS_STATE_ADRS3_ACK; + + case STIM_TIMER: + // IF timer expiry - try again: + RetryCurrentSensor(work); + return WS_STATE_ADRS0_ACK; + } + + return state; +} + +int wsAdrs3State (int state, void *stimulus, void *data) +{ + STIM *stim = (STIM *)stimulus; + WVIEWD_WORK *work = (WVIEWD_WORK *)data; + int retVal; + uint8_t readData; + + switch (stim->type) + { + case STIM_IO: + radProcessTimerStop (work->ifTimer); + + retVal = (*work->medium.read) (&work->medium, &readData, 1, WS2300_READ_TIMEOUT); + if (retVal != 1) + { + radMsgLog (PRI_MEDIUM, "wsAdrs3State: read address 0 failed: %s", + strerror(errno)); + RetryCurrentSensor(work); + return WS_STATE_ADRS0_ACK; + } + if (readData != checksumSequence (&ws2300Work.commandData[3], 3)) + { + // start this sensor again: + RetryCurrentSensor(work); + return WS_STATE_ADRS0_ACK; + } + + // All good, go to next byte (NumBytes) + retVal = (*work->medium.write) (&work->medium, &ws2300Work.commandData[4], 1); + if (retVal != 1) + { + radMsgLog (PRI_MEDIUM, "wsAdrs3State: write NumBytes failed: %s", + strerror(errno)); + return WS_STATE_ERROR; + } + + radProcessTimerStart (work->ifTimer, WS_RESPONSE_TIMEOUT); + return WS_STATE_NUMBYTES_ACK; + + case STIM_TIMER: + // IF timer expiry - try again: + RetryCurrentSensor(work); + return WS_STATE_ADRS0_ACK; + } + + return state; +} + +int wsNumBytesState (int state, void *stimulus, void *data) +{ + STIM *stim = (STIM *)stimulus; + WVIEWD_WORK *work = (WVIEWD_WORK *)data; + int retVal; + uint8_t readData; + WS2300_IF_DATA* ifWorkData = (WS2300_IF_DATA*)work->stationData; + float tempFloat = 0; + int IsGoodValue; + + + switch (stim->type) + { + case STIM_IO: + radProcessTimerStop (work->ifTimer); + + // read the NumBytes ACK + retVal = (*work->medium.read) (&work->medium, &readData, 1, WS2300_READ_TIMEOUT); + if (retVal != 1) + { + radMsgLog (PRI_MEDIUM, "wsNumBytesState: read NumBytes ACK failed: %s", + strerror(errno)); + RetryCurrentSensor(work); + return WS_STATE_ADRS0_ACK; + } + + if (readData != checksumLast (sensorInfo[ws2300Work.currentSensor].bytes)) + { + // start this sensor again: + RetryCurrentSensor(work); + return WS_STATE_ADRS0_ACK; + } + + // read the data bytes (and CRC): + retVal = (*work->medium.read) (&work->medium, + &ws2300Work.readData[0], + sensorInfo[ws2300Work.currentSensor].bytes + 1, + WS2300_READ_TIMEOUT); + if (retVal != sensorInfo[ws2300Work.currentSensor].bytes + 1) + { + // start this sensor again: + RetryCurrentSensor(work); + return WS_STATE_ADRS0_ACK; + } + + // verify checksum: + readData = ws2300Work.readData[sensorInfo[ws2300Work.currentSensor].bytes]; + if (readData != checksumDataRX (&ws2300Work.readData[0], sensorInfo[ws2300Work.currentSensor].bytes)) + { + // start this sensor again: + RetryCurrentSensor(work); + return WS_STATE_ADRS0_ACK; + } + + // OK, finally, we have data from the station - + // switch on the sensor type to store it: + switch (ws2300Work.currentSensor) + { + case WS_SENSOR_IN_TEMP: + ws2300Work.sensorData.inTemp = ((((ws2300Work.readData[1] >> 4) * 10 + + (ws2300Work.readData[1] & 0xF) + + (ws2300Work.readData[0] >> 4) / 10.0 + + (ws2300Work.readData[0] & 0xF) / 100.0) - + 30.0) * 9 / 5 + 32); + tempFloat = ws2300Work.sensorData.inTemp; + break; + + case WS_SENSOR_OUT_TEMP: + ws2300Work.sensorData.outTemp = ((((ws2300Work.readData[1] >> 4) * 10 + + (ws2300Work.readData[1] & 0xF) + + (ws2300Work.readData[0] >> 4) / 10.0 + + (ws2300Work.readData[0] & 0xF) / 100.0) - + 30.0) * 9 / 5 + 32); + tempFloat = ws2300Work.sensorData.outTemp; + break; + + case WS_SENSOR_IN_HUMIDITY: + ws2300Work.sensorData.inHumidity = ((ws2300Work.readData[0] >> 4) * 10 + + (ws2300Work.readData[0] & 0xF)); + tempFloat = ws2300Work.sensorData.inHumidity; + break; + + case WS_SENSOR_OUT_HUMIDITY: + ws2300Work.sensorData.outHumidity = ((ws2300Work.readData[0] >> 4) * 10 + + (ws2300Work.readData[0] & 0xF)); + tempFloat = ws2300Work.sensorData.outHumidity; + break; + + case WS_SENSOR_WIND: + if ((ws2300Work.readData[0] != 0x00) || + ((ws2300Work.readData[1] == 0xFF) && (((ws2300Work.readData[2] & 0xF) == 0) || + ((ws2300Work.readData[2] & 0xF) == 1)))) + { + // Skip wind this time + break; + } + else + { + ws2300Work.sensorData.windDir = (ws2300Work.readData[2] >> 4) * 22.5; + ws2300Work.sensorData.windSpeed = ((((ws2300Work.readData[2] & 0xF) << 8) + + (ws2300Work.readData[1])) / 10.0 * 2.23693629); + ws2300Work.sensorData.maxWindDir = ws2300Work.sensorData.windDir; + ws2300Work.sensorData.maxWindSpeed = ws2300Work.sensorData.windSpeed; + tempFloat = ws2300Work.sensorData.windSpeed; + } + break; + + case WS_SENSOR_PRESSURE: + ws2300Work.sensorData.pressure = (((ws2300Work.readData[2] & 0xF) * 1000 + + (ws2300Work.readData[1] >> 4) * 100 + + (ws2300Work.readData[1] & 0xF) * 10 + + (ws2300Work.readData[0] >> 4) + + (ws2300Work.readData[0] & 0xF) / 10.0) / 33.8638864); + tempFloat = ws2300Work.sensorData.pressure; + break; + + case WS_SENSOR_RAIN: + ws2300Work.sensorData.rain = (((ws2300Work.readData[2] >> 4) * 1000 + + (ws2300Work.readData[2] & 0xF) * 100 + + (ws2300Work.readData[1] >> 4) * 10 + + (ws2300Work.readData[1] & 0xF) + + (ws2300Work.readData[0] >> 4) / + 10.0 + (ws2300Work.readData[0] & 0xF) / 100.0) / 25.4); + tempFloat = ws2300Work.sensorData.rain; + break; + } + + // Read each sensor at least 3 times to omit wild WS-2300 readings; + // Contributed by a WS-2300 user: + IsGoodValue = FALSE; + if (ws2300Work.numTries > 0) + { + // We read each sensor 3 or more times, until we get 3 values + // that are close to each other: + switch (ws2300Work.currentSensor) + { + case WS_SENSOR_RAIN: + // Rain must be +/- 0.02 inches: + if ((tempFloat >= (ws2300Work.lastAttempt - 0.02)) && + (tempFloat <= (ws2300Work.lastAttempt + 0.02))) + { + // Looks good + IsGoodValue = 1; + } + break; + + case WS_SENSOR_PRESSURE: + // Pressure must be +/- 0.2 inHg: + if ((tempFloat >= (ws2300Work.lastAttempt - 0.2)) && + (tempFloat <= (ws2300Work.lastAttempt + 0.2))) + { + // Looks good + IsGoodValue = 1; + } + break; + + case WS_SENSOR_WIND: + // Wind is trickier as it can fluctuate more than other + // weather data and we don't want to "prune" valid wind + // events; Wind must be +/- 2 MPH: + if ((tempFloat >= (ws2300Work.lastAttempt - 2)) && + (tempFloat <= (ws2300Work.lastAttempt + 2))) + { + // Looks good + IsGoodValue = 1; + } + break; + + default: + // Everything else is +/- 1 unit (temp and humidity) + // to be a good value match: + if ((tempFloat >= (ws2300Work.lastAttempt - 1.0)) && + (tempFloat <= (ws2300Work.lastAttempt + 1.0))) + { + // Looks good + IsGoodValue = 1; + } + break; + } + + ws2300Work.lastAttempt = tempFloat; + + if (IsGoodValue) + { + ws2300Work.numGood ++; + } + else + { + ws2300Work.numGood = 0; + } + } + + if ((ws2300Work.numGood < WS2300_NUM_GOOD_REQUIRED) && + (++ws2300Work.numTries < WS2300_MAX_RETRIES)) + { + // Read the same sensor again and see what we get: + if (startDataRequest(work, FALSE) == ERROR) + { + radMsgLog (PRI_HIGH, "wsNumBytesState: startDataRequestX failed!"); + return WS_STATE_ERROR; + } + + radProcessTimerStart(work->ifTimer, WS_RESPONSE_TIMEOUT); + return WS_STATE_ADRS0_ACK; + } + + + // Bump the sensor type: + ws2300Work.numTries = 0; + ws2300Work.numGood = 0; + ws2300Work.currentSensor ++; + if (ws2300Work.currentSensor >= WS_SENSOR_MAX) + { + // We are done! + // populate the LOOP structure: + ifWorkData->ws2300Readings = ws2300Work.sensorData; + storeLoopPkt (work, &work->loopPkt, &ifWorkData->ws2300Readings); + + // check to see if this was the first time through: + if (!work->runningFlag) + { + // we must indicate successful completion here - + // the daemon wants to see this event: + radProcessEventsSend (NULL, STATION_INIT_COMPLETE_EVENT, 0); + } + else + { + // indicate the LOOP packet is done: + radProcessEventsSend (NULL, STATION_LOOP_COMPLETE_EVENT, 0); + } + + return WS_STATE_RUN; + } + else + { + // start the next sensor: + if (startDataRequest (work, FALSE) == ERROR) + { + radMsgLog (PRI_HIGH, "wsNumBytesState: startDataRequestX failed!"); + return WS_STATE_ERROR; + } + + radProcessTimerStart (work->ifTimer, WS_RESPONSE_TIMEOUT); + return WS_STATE_ADRS0_ACK; + } + return state; + + case STIM_TIMER: + // IF timer expiry - try again: + RetryCurrentSensor(work); + return WS_STATE_ADRS0_ACK; + } + + return state; +} + +int wsErrorState (int state, void *stimulus, void *data) +{ + STIM *stim = (STIM *)stimulus; + WVIEWD_WORK *work = (WVIEWD_WORK *)data; + + switch (stim->type) + { + default: + radMsgLog (PRI_HIGH, "wsErrorState: invalid stimulus!"); + break; + } + + return state; +} + diff --git a/stations/WS-2300/ws2300protocol.h b/stations/WS-2300/ws2300protocol.h new file mode 100755 index 0000000..dc848b6 --- /dev/null +++ b/stations/WS-2300/ws2300protocol.h @@ -0,0 +1,167 @@ +#ifndef INC_ws2300protocolh +#define INC_ws2300protocolh +/*--------------------------------------------------------------------------- + + FILENAME: + ws2300protocol.h + + PURPOSE: + Provide protocol utilities for WS-2300 station communication. + + REVISION HISTORY: + Date Engineer Revision Remarks + 02/27/2008 M.S. Teel 0 Original + + NOTES: + + + LICENSE: + Copyright (c) 2008, Mark S. Teel (mark@teel.ws) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +/* ... System include files +*/ +#include +#include +#include +#include +#include +#include +#include +#include + +/* ... Library include files +*/ +#include +#include +#include + +/* ... Local include files +*/ +#include +#include +#include +#include +#include + + +#define WS2300_MAX_RETRIES 10 +#define WS_RESPONSE_TIMEOUT 1000L +#define WS2300_BUFFER_LENGTH 32 +#define WS2300_READ_TIMEOUT 1000 +#define WS2300_NUM_GOOD_REQUIRED 3 + + +// Define the rain rate acuumulator period (minutes): +#define WS2300_RAIN_RATE_PERIOD 5 + + +// define the readings collector +typedef struct +{ + float inTemp; + float outTemp; + float inHumidity; + float outHumidity; + float windDir; + float windSpeed; + float maxWindDir; + float maxWindSpeed; + float pressure; + float rain; + float rainrate; + +} WS2300_DATA; + +typedef enum +{ + WS_SENSOR_IN_TEMP = 0, + WS_SENSOR_OUT_TEMP, + WS_SENSOR_IN_HUMIDITY, + WS_SENSOR_OUT_HUMIDITY, + WS_SENSOR_WIND, + WS_SENSOR_PRESSURE, + WS_SENSOR_RAIN, + WS_SENSOR_MAX +} WSSensorTypes; + +typedef struct +{ + int address; + int bytes; +} WS_SENSOR_INFO; + + +// define the work area +typedef struct +{ + WS2300_DATA sensorData; + uint16_t sensorStatus; + WSSensorTypes currentSensor; + uint8_t commandData[WS2300_BUFFER_LENGTH]; + uint8_t readData[WS2300_BUFFER_LENGTH]; + float lastAttempt; + int numTries; + int numGood; +} WS2300_WORK; + + +// ... define the state machine states +typedef enum +{ + WS_STATE_STARTPROC = 1, + WS_STATE_RUN, + WS_STATE_ADRS0_ACK, + WS_STATE_ADRS1_ACK, + WS_STATE_ADRS2_ACK, + WS_STATE_ADRS3_ACK, + WS_STATE_NUMBYTES_ACK, + WS_STATE_ERROR +} WS_STATES; + + +// define WXT510-specific interface data here +typedef struct +{ + STATES_ID stateMachine; + int elevation; + float latitude; + float longitude; + int archiveInterval; + WS2300_DATA ws2300Readings; + float totalRain; // to track cumulative changes + WV_ACCUM_ID rainRateAccumulator; // to compute rain rate +} WS2300_IF_DATA; + + +// define an extra stimulus type for our purposes +typedef enum +{ + WS_STIM_READINGS = STIM_IO + 1 +} WSStims; + + +// function prototypes + +extern int wsStartProcState (int state, void *stimulus, void *data); +extern int wsRunState (int state, void *stimulus, void *data); +extern int wsAdrs0State (int state, void *stimulus, void *data); +extern int wsAdrs1State (int state, void *stimulus, void *data); +extern int wsAdrs2State (int state, void *stimulus, void *data); +extern int wsAdrs3State (int state, void *stimulus, void *data); +extern int wsNumBytesState (int state, void *stimulus, void *data); +extern int wsErrorState (int state, void *stimulus, void *data); + + +// call once during initialization +extern int ws2300Init (WVIEWD_WORK *work); + +// do cleanup +extern void ws2300Exit (WVIEWD_WORK *work); + +#endif + diff --git a/stations/WXT510/Makefile.am b/stations/WXT510/Makefile.am new file mode 100755 index 0000000..d1753a5 --- /dev/null +++ b/stations/WXT510/Makefile.am @@ -0,0 +1,67 @@ +# Makefile - Vaisala WXT510 station daemon + +# define the wxt510config target +SUBDIRS = wxt510config + +# define the executable to be built +bin_PROGRAMS = wviewd_wxt510 + +# define include directories +INCLUDES = \ + -I$(top_srcdir)/common \ + -I$(top_srcdir)/stations/common \ + -I$(prefix)/include \ + -D_GNU_SOURCE \ + -DWV_CONFIG_DIR=\"$(sysconfdir)/wview\" \ + -DWV_RUN_DIR=\"$(localstatedir)/wview\" \ + -DBUILD_WVIEWD + +# define the sources +wviewd_wxt510_SOURCES = \ + $(top_srcdir)/common/sensor.c \ + $(top_srcdir)/common/wvutils.c \ + $(top_srcdir)/common/wvconfig.c \ + $(top_srcdir)/common/status.c \ + $(top_srcdir)/common/dbsqlite.c \ + $(top_srcdir)/common/dbsqliteHiLow.c \ + $(top_srcdir)/common/windAverage.c \ + $(top_srcdir)/common/emailAlerts.c \ + $(top_srcdir)/stations/common/computedData.c \ + $(top_srcdir)/stations/common/daemon.c \ + $(top_srcdir)/stations/common/station.c \ + $(top_srcdir)/stations/common/serial.c \ + $(top_srcdir)/stations/common/ethernet.c \ + $(top_srcdir)/stations/common/stormRain.c \ + $(top_srcdir)/stations/common/parser.c \ + $(top_srcdir)/stations/WXT510/wxt510Interface.c \ + $(top_srcdir)/stations/WXT510/nmea0183.c \ + $(top_srcdir)/common/sensor.h \ + $(top_srcdir)/common/datadefs.h \ + $(top_srcdir)/common/dbsqlite.h \ + $(top_srcdir)/common/services.h \ + $(top_srcdir)/common/sysdefs.h \ + $(top_srcdir)/common/wvconfig.h \ + $(top_srcdir)/common/status.h \ + $(top_srcdir)/common/windAverage.h \ + $(top_srcdir)/common/emailAlerts.h \ + $(top_srcdir)/common/beaufort.h \ + $(top_srcdir)/stations/common/computedData.h \ + $(top_srcdir)/stations/common/daemon.h \ + $(top_srcdir)/stations/common/station.h \ + $(top_srcdir)/stations/common/serial.h \ + $(top_srcdir)/stations/common/ethernet.h \ + $(top_srcdir)/stations/common/stormRain.h \ + $(top_srcdir)/stations/common/parser.h \ + $(top_srcdir)/stations/WXT510/wxt510Interface.h \ + $(top_srcdir)/stations/WXT510/nmea0183.h + +# define libraries +wviewd_wxt510_LDADD = + +# define library directories +wviewd_wxt510_LDFLAGS = -L$(prefix)/lib -L$(prefix)/usr/lib -L/usr/lib + +if CROSSCOMPILE +wviewd_wxt510_LDFLAGS += $(prefix)/lib/crt1.o $(prefix)/lib/crti.o $(prefix)/lib/crtn.o +endif + diff --git a/stations/WXT510/nmea0183.c b/stations/WXT510/nmea0183.c new file mode 100755 index 0000000..e842cfd --- /dev/null +++ b/stations/WXT510/nmea0183.c @@ -0,0 +1,813 @@ +/*--------------------------------------------------------------------------- + + FILENAME: + nmea0183.c + + PURPOSE: + Provide protocol utilities for NMEA 0183 station communication. + + REVISION HISTORY: + Date Engineer Revision Remarks + 01/14/2006 M.S. Teel 0 Original + 03/23/2008 W. Krenn 1 modified rain/hail/heating + + NOTES: + + LICENSE: + Copyright (c) 2006, Mark S. Teel (mark@teel.ws) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +/* ... System include files +*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/* ... Library include files +*/ +#include +#include +#include + +/* ... Local include files +*/ +#include +#include +#include +#include + +/* ... global memory declarations +*/ + +/* ... local memory +*/ + +#define NMEA_XDUCER_MAX_TYPES 4 + +static NMEA0183_WORK nmeaWork; + +static char *TransducerTypes[NMEA_XDUCER_MAX_TYPES] = +{ + "WIND", + "THP", + "RAIN", + "SUPV" +}; + + +// compute NMEA checksum and generate the checksum string +static char *generateChecksum (char *command) +{ + static char returnBuffer[4]; + unsigned char highnibble, lownibble, sum = 0; + int i, length = strlen(command); + + for (i = 1; i < length; i ++) // skip the '$' character + { + sum ^= command[i]; + } + + highnibble = (sum >> 4) & 0x0F; + lownibble = sum & 0x0F; + returnBuffer[0] = '*'; + returnBuffer[1] = ((highnibble < 0xA) ? ('0' + highnibble) : (('A' - 0xA) + highnibble)); + returnBuffer[2] = ((lownibble < 0xA) ? ('0' + lownibble) : (('A' - 0xA) + lownibble)); + returnBuffer[3] = 0; + + return returnBuffer; +} + +// verify the checksum of a received line - +// (will truncate the checksum string from the input string) +static int verifyChecksum (char *inLine) +{ + int length = strlen(inLine); + char inCS[8]; + + if (length < 4) + { + // short string! + return FALSE; + } + + // the incoming line MUST be terminated with the checksum string + strncpy (inCS, &inLine[length-3], sizeof(inCS)); + inLine[length-3] = 0; + if (!strcmp(inCS, generateChecksum(inLine))) + { + return TRUE; + } + else + { + return FALSE; + } +} + +// read a line from the station +// returns TRUE if a line was read, FALSE otherwise +static int readLineFromStation (WVIEWD_WORK *work, char *store, int msTime) +{ + char buffer[NMEA_BYTE_LENGTH_MAX]; + uint64_t readTime = radTimeGetMSSinceEpoch() + (uint64_t)msTime; + int retVal, timeToRead, done = FALSE, byteCount = 0; + + // we read until we see a as a line terminator + while ((timeToRead = (uint32_t)(readTime - radTimeGetMSSinceEpoch())) > 0) + { + retVal = (*work->medium.read) (&work->medium, &buffer[byteCount], 1, timeToRead); + if (retVal != 1) + { + radMsgLog (PRI_MEDIUM, "NMEA: readLineFromStation: read failed!"); + (*work->medium.flush) (&work->medium, WV_QUEUE_INPUT); + emailAlertSend(ALERT_TYPE_STATION_READ); + return FALSE; + } + byteCount ++; + + // are we done? + if ((byteCount >= 2) && + (buffer[byteCount-2] == NMEA_CR) && + (buffer[byteCount-1] == NMEA_LF)) + { + // we are! + done = TRUE; + break; + } + } + + if (!done || byteCount <= 2) + { + // ran out of time or empty line + radMsgLog (PRI_MEDIUM, "NMEA: readLineFromStation: timeout or empty line!"); + (*work->medium.flush) (&work->medium, WV_QUEUE_INPUT); + emailAlertSend(ALERT_TYPE_STATION_READ); + return FALSE; + } + + // now lose the and + byteCount -= 2; + buffer[byteCount] = 0; + wvstrncpy (store, buffer, NMEA_BYTE_LENGTH_MAX); + return TRUE; +} + +// processes a transducer field set +// returns TRUE if the XDUCER is found, otherwise FALSE +static int processTransducer (PARSER_ID parser, int startIndex) +{ + char type; + int id; + float value; + char units; + char *retStr; + + // load up the fields properly + retStr = parserGetNumber (parser, startIndex); + if (retStr == NULL) + { + return FALSE; + } + type = retStr[0]; + retStr = parserGetNumber (parser, startIndex+1); + if (retStr == NULL) + { + return FALSE; + } + value = (float)atof (retStr); + retStr = parserGetNumber (parser, startIndex+2); + if (retStr == NULL) + { + return FALSE; + } + units = retStr[0]; + retStr = parserGetNumber (parser, startIndex+3); + if (retStr == NULL) + { + return FALSE; + } + id = atoi (retStr); + + // now we switch on the type field + switch (type) + { + case 'C': + { + switch (id) + { + case 0: + // TEMP - air + if (units != 'F') + { + break; + } + nmeaWork.sensorData.temperature = value; + nmeaWork.sensorStatus |= NMEA_DONE_TEMP; + break; + case 1: + // TEMP - internal + break; + case 2: + // TEMP - heating + nmeaWork.sensorData.heatingTemp = value; + nmeaWork.sensorStatus |= NMEA_DONE_HEAT_TEMP; + break; + } + + break; + } + case 'A': + { + switch (id) + { + case 0: + // DIRECTION - min + break; + case 1: + // DIRECTION - avg + if (units != 'D') + { + break; + } + nmeaWork.sensorData.windDir = value; + nmeaWork.sensorStatus |= NMEA_DONE_WINDDIR; + break; + case 2: + // DIRECTION - max + if (units != 'D') + { + break; + } + nmeaWork.sensorData.maxWindDir = value; + nmeaWork.sensorStatus |= NMEA_DONE_MAXWINDDIR; + break; + } + + break; + } + case 'S': + { + switch (id) + { + case 0: + // SPEED - min + break; + case 1: + // SPEED - avg + if (units != 'S') + { + break; + } + nmeaWork.sensorData.windSpeed = value; + nmeaWork.sensorStatus |= NMEA_DONE_WINDSPD; + break; + case 2: + // SPEED - max + if (units != 'S') + { + break; + } + nmeaWork.sensorData.maxWindSpeed = value; + nmeaWork.sensorStatus |= NMEA_DONE_MAXWINDSPD; + break; + } + + break; + } + case 'P': + { + // pressure is always ID 0 + if (units != 'I') + { + break; + } + nmeaWork.sensorData.pressure = value; + nmeaWork.sensorStatus |= NMEA_DONE_PRESSURE; + break; + } + case 'H': + { + // humidity is always ID 0 + if (units != 'P') + { + break; + } + nmeaWork.sensorData.humidity = value; + nmeaWork.sensorStatus |= NMEA_DONE_HUMIDITY; + break; + } + case 'V': + { + switch (id) + { + case 0: + // RAIN + if (units != 'I') + { + break; + } + nmeaWork.sensorData.rain = value; + nmeaWork.sensorStatus |= NMEA_DONE_RAIN; + break; + case 1: + // HAIL + nmeaWork.sensorData.hail = value; + nmeaWork.sensorStatus |= NMEA_DONE_HAIL; + break; + } + + break; + } + case 'Z': + { + switch (id) + { + case 0: + // DURATION - rain + nmeaWork.sensorData.rainduration = value; + // nmeaWork.sensorStatus |= NMEA_DONE_RAIN; + break; + case 1: + nmeaWork.sensorData.hailduration = value; + // nmeaWork.sensorStatus |= NMEA_DONE_RAIN; + // DURATION - hail + break; + } + + break; + } + case 'R': + { + switch (id) + { + case 0: + // RATE - rain + if (units != 'I') + { + break; + } + nmeaWork.sensorData.rainrate = value; + nmeaWork.sensorStatus |= NMEA_DONE_RAINRATE; + break; + case 1: + // RATE - hail + nmeaWork.sensorData.hailrate = value; + nmeaWork.sensorStatus |= NMEA_DONE_HAILRATE; + break; + case 2: + // Peak RATE - rain + nmeaWork.sensorData.rainpeakrate = value; + // nmeaWork.sensorStatus |= NMEA_DONE_RAINRATE; + break; + case 3: + // Peak RATE - hail + nmeaWork.sensorData.hailpeakrate = value; + // nmeaWork.sensorStatus |= NMEA_DONE_HAILRATE; + break; + } + + break; + } + case 'U': + { + switch (id) + { + case 0: + // VOLTAGE - supply + nmeaWork.sensorData.supplyVoltage = value; + nmeaWork.sensorStatus |= NMEA_DONE_SUP_VOLT; + break; + case 1: + // VOLTAGE - heating + nmeaWork.sensorData.heatingVoltage = value; + nmeaWork.sensorStatus |= NMEA_DONE_HEAT_VOLT; + break; + case 2: + // VOLTAGE - reference + nmeaWork.sensorData.referenceVoltage = value; + nmeaWork.sensorStatus |= NMEA_DONE_REF_VOLT; + break; + } + + break; + } + default: + { + break; + } + } + + return TRUE; +} + +// returns the number of transducers processed or ERROR +static int readSensorLine (WVIEWD_WORK *work) +{ + char buffer[NMEA_BYTE_LENGTH_MAX]; + int retVal, argIndex, done; + PARSER_ID parser; + + // read a line from the station + if (!readLineFromStation (work, buffer, NMEA_RESPONSE_TIMEOUT)) + { + return ERROR; + } + + // check the checksum integrity, this will truncate it as well + if (!verifyChecksum(buffer)) + { + // corruption? + radMsgLog (PRI_MEDIUM, "NMEA: readSensorLine: verifyChecksum failed!"); + emailAlertSend(ALERT_TYPE_STATION_READ); + return ERROR; + } + +//radMsgLog(PRI_STATUS,"DBG: RX: %s", buffer); + + // now parse this mother! + parser = parserInit (buffer, NMEA_DELIMITERS); + if (parser == NULL) + { + radMsgLog (PRI_MEDIUM, "NMEA: readSensorLine: parserInit failed!"); + return ERROR; + } + + // make sure it is the right kind of monkey... + if (strcmp (parserGetFirst(parser), NMEA_WIXDR_ID)) + { + // nope! + radMsgLog (PRI_MEDIUM, "NMEA: readSensorLine: NOT a WIXDR response!"); + parserExit (parser); + return ERROR; + } + + // now break it up by transducer + done = FALSE; + retVal = 0; + argIndex = 2; + while (!done) + { + if (processTransducer(parser, argIndex) == FALSE) + { + done = TRUE; + continue; + } + + retVal += 1; + argIndex += 4; // there are 4 fields per transducer + } + + parserExit (parser); + return retVal; +} + + + +/////////////////////////////////////////////////////////////////////////// +/////////////////////////////// A P I /////////////////////////////////// +int nmea0183Init (WVIEWD_WORK *work) +{ + char buffer[64]; + + memset (&nmeaWork, 0, sizeof(nmeaWork)); + + /////// Wakeup/Clear the WXT-510 /////// + // send a + buffer[0] = 0; + if (nmea0183WriteLineToStation (work, buffer, NULL, FALSE, TRUE) == ERROR) + { + radMsgLog (PRI_MEDIUM, "NMEA: nmea0183Init: W/C failed!"); + return ERROR; + } + radUtilsSleep (50); + + // send a ? command + sprintf (buffer, "?"); + if (nmea0183WriteLineToStation (work, buffer, NULL, FALSE, TRUE) == ERROR) + { + radMsgLog (PRI_MEDIUM, "NMEA: nmea0183Init: %s failed!", buffer); + return ERROR; + } + radUtilsSleep (50); + + + /////// Setup the WXT-510 to send sensor data in the format we want /////// + // Note: These setup commands are not NMEA 0183, rather WXT-510 ASCII + // commands - there are no NMEA equivalents... + + // --- Comm Settings --- + // set the device address to 0, RS232, NMEA polled, inter-message delay 20ms + sprintf (buffer, "0XU,A=0,M=Q,C=2,L=20"); + if (nmea0183WriteLineToStation (work, buffer, buffer, FALSE, FALSE) == ERROR) + { + radMsgLog (PRI_MEDIUM, "NMEA: nmea0183Init: %s failed!", buffer); + return ERROR; + } + radUtilsSleep (500); + + // set error messages OFF + sprintf (buffer, "0SU,S=N"); + if (nmea0183WriteLineToStation (work, buffer, buffer, FALSE, FALSE) == ERROR) + { + radMsgLog (PRI_MEDIUM, "NMEA: nmea0183Init: %s failed!", buffer); + return ERROR; + } + radUtilsSleep (50); + + + // send a reset command + sprintf (buffer, "0XZ"); + if (nmea0183WriteLineToStation (work, buffer, NULL, FALSE, TRUE) == ERROR) + { + radMsgLog (PRI_MEDIUM, "NMEA: nmea0183Init: %s failed!", buffer); + return ERROR; + } + + // wait to let the WXT-510 reset + radUtilsSleep (500); + + // send a ? command + sprintf (buffer, "?"); + if (nmea0183WriteLineToStation (work, buffer, "0", FALSE, FALSE) == ERROR) + { + radMsgLog (PRI_MEDIUM, "NMEA: nmea0183Init: %s failed!", buffer); + return ERROR; + } + radUtilsSleep (50); + + + // --- Wind sensors --- + // turn on average and max direction and speed + sprintf (buffer, "0WU,R=0110110001101100"); + if (nmea0183WriteLineToStation (work, buffer, "0WU,R=01101100&01101100", FALSE, FALSE) + == ERROR) + { + radMsgLog (PRI_MEDIUM, "NMEA: nmea0183Init: %s failed!", buffer); + return ERROR; + } + radUtilsSleep (25); + + // set update and averaging interval to 2 seconds for initial readings + sprintf (buffer, "0WU,I=2,A=2"); + if (nmea0183WriteLineToStation (work, buffer, buffer, FALSE, FALSE) == ERROR) + { + radMsgLog (PRI_MEDIUM, "NMEA: nmea0183Init: %s failed!", buffer); + return ERROR; + } + radUtilsSleep (25); + + // set units to MPH, direction correction to 0, response format type T + sprintf (buffer, "0WU,U=S,D=0,N=T"); + if (nmea0183WriteLineToStation (work, buffer, buffer, FALSE, FALSE) == ERROR) + { + radMsgLog (PRI_MEDIUM, "NMEA: nmea0183Init: %s failed!", buffer); + return ERROR; + } + radUtilsSleep (25); + + // --- THP sensors --- + // turn on air pressure, temperature and humidity + sprintf (buffer, "0TU,R=1101000011010000"); + if (nmea0183WriteLineToStation (work, buffer, "0TU,R=11010000&11010000", FALSE, FALSE) + == ERROR) + { + radMsgLog (PRI_MEDIUM, "NMEA: nmea0183Init: %s failed!", buffer); + return ERROR; + } + radUtilsSleep (25); + + // set pressure units to in/Hg, temperature units to Fahrenheit + sprintf (buffer, "0TU,P=I,T=F"); + if (nmea0183WriteLineToStation (work, buffer, buffer, FALSE, FALSE) == ERROR) + { + radMsgLog (PRI_MEDIUM, "NMEA: nmea0183Init: %s failed!", buffer); + return ERROR; + } + radUtilsSleep (25); + + // --- Precip sensors --- + // turn on rain/hail amount and intensity + // sprintf (buffer, "0RU,R=1011010010110100"); + // 0RU,R=11111111&11111111,I=60,U=I,S=I,M=T,Z=M + // if (nmea0183WriteLineToStation (work, buffer, "0RU,R=10110100&10110100", FALSE, FALSE) + + sprintf (buffer, "0RU,R=1111111111111111"); + if (nmea0183WriteLineToStation (work, buffer, "0RU,R=11111111&11111111", FALSE, FALSE) + == ERROR) + { + radMsgLog (PRI_MEDIUM, "NMEA: nmea0183Init: %s failed!", buffer); + return ERROR; + } + radUtilsSleep (25); + + // set units to imperial, manual reset of the counters + // sprintf (buffer, "0RU,U=I,S=I,Z=M"); + // NMEA: nmea0183WriteLineToStation: Expect:0RU,I=60,U=I,M=T,S=I,Z=M, Recv:0RU,I=60,U=I,S=I,M=T,Z=M + sprintf (buffer, "0RU,I=60,U=I,S=I,M=T,Z=M"); + if (nmea0183WriteLineToStation (work, buffer, buffer, FALSE, FALSE) == ERROR) + { + radMsgLog (PRI_MEDIUM, "NMEA: nmea0183Init: %s failed!", buffer); + return ERROR; + } + radUtilsSleep (25); + + // --- Supervisor message --- + // turn on everything + sprintf (buffer, "0SU,R=1111000011110000"); + if (nmea0183WriteLineToStation (work, buffer, "0SU,R=11110000&11110000", FALSE, FALSE) + == ERROR) + { + radMsgLog (PRI_MEDIUM, "NMEA: nmea0183Init: %s failed!", buffer); + return ERROR; + } + radUtilsSleep (25); + + // set heating control ON + sprintf (buffer, "0SU,H=Y"); + if (nmea0183WriteLineToStation (work, buffer, buffer, FALSE, FALSE) == ERROR) + { + radMsgLog (PRI_MEDIUM, "NMEA: nmea0183Init: %s failed!", buffer); + return ERROR; + } + + // wait here for the first wind cycle to finish, etc. + radMsgLog (PRI_STATUS, "NMEA: nmea0183Init: waiting 10 seconds to allow the station to settle..."); + radUtilsSleep (10000); + + return OK; +} + +int nmea0183PostInit (WVIEWD_WORK *work) +{ + char buffer[64]; + + // set wind averaging interval to one minute + sprintf (buffer, "0WU,I=60,A=60"); + if (nmea0183WriteLineToStation (work, buffer, buffer, FALSE, FALSE) == ERROR) + { + radMsgLog (PRI_MEDIUM, "NMEA: nmea0183PostInit: %s failed!", buffer); + return ERROR; + } + radUtilsSleep (25); + + return OK; +} + +void nmea0183Exit (WVIEWD_WORK *work) +{ + // nothing to clean up... + return; +} + +int nmea0183GetReadings (WVIEWD_WORK *work, NMEA0183_DATA *store) +{ + char buffer[64]; + int i; + + // request a sensor update + sprintf (buffer, "$--WIQ,XDR"); + if (nmea0183WriteLineToStation (work, buffer, NULL, TRUE, FALSE) == ERROR) + { + radMsgLog (PRI_MEDIUM, "NMEA: nmea0183GetReadings: nmea0183WriteLineToStation failed!"); + return ERROR; + } + + // clear the status bits so we know when we have received everything + nmeaWork.sensorStatus = 0; + + // read the 4 sensor output lines: + // wind, THP, precip, supervisor + for (i = 0; i < NMEA_XDUCER_MAX_TYPES; i ++) + { + if (readSensorLine(work) == ERROR) + { + radMsgLog (PRI_MEDIUM, "NMEA: nmea0183GetReadings: readSensorLine %s failed!", + TransducerTypes[i]); + return ERROR; + } + } + + // did we get everything? + if (nmeaWork.sensorStatus < NMEA_DONE_ALL) + { + radMsgLog (PRI_MEDIUM, "NMEA: nmea0183GetReadings: 0x%4.4X: not all sensors updated...", + nmeaWork.sensorStatus); + return ERROR; + } + + // copy the results to the destination + *store = nmeaWork.sensorData; + + return OK; +} + +// send a properly formatted line to the station, optionally checking the response +int nmea0183WriteLineToStation +( + WVIEWD_WORK *work, + char *strToSend, + char *expectedResp, + int generateCS, + int flushRXBuffer +) +{ + char temp[NMEA_BYTE_LENGTH_MAX]; + int length; + + if (generateCS) + { + sprintf (temp, "%s%s%c%c", + strToSend, + generateChecksum(strToSend), + NMEA_CR, + NMEA_LF); + } + else + { + sprintf (temp, "%s%c%c", + strToSend, + NMEA_CR, + NMEA_LF); + } + + length = strlen(temp); + if ((*work->medium.write) (&work->medium, temp, length) != length) + { + radMsgLog (PRI_MEDIUM, "NMEA: nmea0183WriteLineToStation: write error!"); + emailAlertSend(ALERT_TYPE_STATION_DEVICE); + return ERROR; + } + + // need to check the response? + if (expectedResp != NULL) + { + // read a line from the station + if (!readLineFromStation (work, temp, NMEA_RESPONSE_TIMEOUT)) + { + return ERROR; + } + + if (generateCS) + { + // verify the checksum (and truncate it) + if (!verifyChecksum(temp)) + { + // corruption? + radMsgLog (PRI_MEDIUM, "NMEA: nmea0183WriteLineToStation: verifyChecksum failed!"); + emailAlertSend(ALERT_TYPE_STATION_READ); + return ERROR; + } + } + + // do they match? + if (!strcmp(expectedResp, temp)) + { + return OK; + } + else + { + radMsgLog (PRI_MEDIUM, "NMEA: nmea0183WriteLineToStation: expected response mismatch!"); + radMsgLog (PRI_MEDIUM, "NMEA: nmea0183WriteLineToStation: Expect:%s, Recv:%s", + expectedResp, temp); + emailAlertSend(ALERT_TYPE_STATION_READ); + return ERROR; + } + } + else if (flushRXBuffer) + { + radUtilsSleep (50); + (*work->medium.flush) (&work->medium, WV_QUEUE_INPUT); + } + + return OK; +} + +int nmea0183ResetAccumulators (WVIEWD_WORK *work) +{ + char buffer[64]; + + // reset rain accumulators + sprintf (buffer, "0XZRU"); + if (nmea0183WriteLineToStation (work, buffer, NULL, FALSE, TRUE) == ERROR) + { + radMsgLog (PRI_MEDIUM, "NMEA: nmea0183ResetAccumulators: %s failed!", buffer); + return ERROR; + } + radUtilsSleep (25); + + return OK; +} diff --git a/stations/WXT510/nmea0183.h b/stations/WXT510/nmea0183.h new file mode 100755 index 0000000..be480bc --- /dev/null +++ b/stations/WXT510/nmea0183.h @@ -0,0 +1,153 @@ +#ifndef INC_nmea0183h +#define INC_nmea0183h +/*--------------------------------------------------------------------------- + + FILENAME: + nmea0183.h + + PURPOSE: + Provide protocol utilities for NMEA 0183 station communication. + + REVISION HISTORY: + Date Engineer Revision Remarks + 01/14/2006 M.S. Teel 0 Original + 03/23/2008 W. Krenn 1 add hail/rain duration + + + NOTES: + + + LICENSE: + Copyright (c) 2006, Mark S. Teel (mark@teel.ws) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +/* ... System include files +*/ +#include +#include +#include +#include +#include +#include +#include + +/* ... Library include files +*/ +#include +#include +#include + +/* ... Local include files +*/ +#include +#include +#include +#include + + +#ifndef _WXT510_CONFIG_ONLY +#define NMEA_RESPONSE_TIMEOUT 2000 +#else +#define NMEA_RESPONSE_TIMEOUT 500 +#endif + +#define NMEA_BYTE_LENGTH_MAX 256 +#define NMEA_CR 0x0D +#define NMEA_LF 0x0A +#define NMEA_DELIMITERS "," +#define NMEA_WIXDR_ID "$WIXDR" + + +// define the "done" bit assignments +enum +{ + NMEA_DONE_TEMP = 0x0001, + NMEA_DONE_HUMIDITY = 0x0002, + NMEA_DONE_WINDDIR = 0x0004, + NMEA_DONE_WINDSPD = 0x0008, + NMEA_DONE_MAXWINDDIR = 0x0010, + NMEA_DONE_MAXWINDSPD = 0x0020, + NMEA_DONE_PRESSURE = 0x0040, + NMEA_DONE_RAIN = 0x0080, + NMEA_DONE_RAINRATE = 0x0100, + NMEA_DONE_HAIL = 0x0200, + NMEA_DONE_HAILRATE = 0x0400, + NMEA_DONE_HEAT_TEMP = 0x0800, + NMEA_DONE_HEAT_VOLT = 0x1000, + NMEA_DONE_SUP_VOLT = 0x2000, + NMEA_DONE_REF_VOLT = 0x4000, + NMEA_DONE_ALL = 0x7FFF +}; + + +// define the readings collector +typedef struct +{ + float temperature; + float humidity; + float windDir; + float windSpeed; + float maxWindDir; + float maxWindSpeed; + float pressure; + float rain; + float rainrate; + float hail; + float hailrate; + float heatingTemp; + float heatingVoltage; + float supplyVoltage; + float referenceVoltage; + + float rainduration; + float rainpeakrate; + float hailduration; + float hailpeakrate; + +} +NMEA0183_DATA; + + +// define the work area +typedef struct +{ + NMEA0183_DATA sensorData; + uint16_t sensorStatus; + +} +NMEA0183_WORK; + + +// function prototypes + +// call once during initialization +extern int nmea0183Init (WVIEWD_WORK *work); + +// call once during initialization after first sensor readings +extern int nmea0183PostInit (WVIEWD_WORK *work); + +// do cleanup +extern void nmea0183Exit (WVIEWD_WORK *work); + +// initiate a synchronous sensor collection +extern int nmea0183GetReadings (WVIEWD_WORK *work, NMEA0183_DATA *store); + +// send a properly formatted line to the station, optionally checking +// the response +extern int nmea0183WriteLineToStation + ( + WVIEWD_WORK *work, + char *strToSend, + char *expectedResp, + int generateCS, + int flushRXBuffer + ); + +// reset the rain/hail accumulators to zero +extern int nmea0183ResetAccumulators (WVIEWD_WORK *work); + +#endif diff --git a/stations/WXT510/wxt510Interface.c b/stations/WXT510/wxt510Interface.c new file mode 100755 index 0000000..c3e218c --- /dev/null +++ b/stations/WXT510/wxt510Interface.c @@ -0,0 +1,598 @@ +/*--------------------------------------------------------------------------- + + FILENAME: + wxt510Interface.c + + PURPOSE: + Provide the WXT-510 station interface API and utilities. + + REVISION HISTORY: + Date Engineer Revision Remarks + 01/14/2006 M.S. Teel 0 Original + 03/23/2008 W. Krenn 1 modified rain/hail/heating + + NOTES: + + + LICENSE: + Copyright (c) 2006, Mark S. Teel (mark@teel.ws) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +/* ... System include files +*/ + +/* ... Library include files +*/ + +/* ... Local include files +*/ +#include + +/* ... global memory declarations +*/ + +/* ... local memory +*/ + +static WXT510_IF_DATA wxt510WorkData; +static WVIEWD_WORK* pwviewWork; + +static void (*ArchiveIndicator) (ARCHIVE_PKT* newRecord); + +static void serialPortConfig (int fd); +static void storeLoopPkt (WVIEWD_WORK* work, LOOP_PKT *dest, NMEA0183_DATA *src); + + + +////////////****////**** S T A T I O N A P I ****////****//////////// +///// Must be provided by each supported wview station interface ////// + +// station-supplied init function +// -- Can Be Asynchronous - event indication required -- +// +// MUST: +// - set the 'stationGeneratesArchives' flag in WVIEWD_WORK: +// if the station generates archive records (TRUE) or they should be +// generated automatically by the daemon from the sensor readings (FALSE) +// - Initialize the 'stationData' store for station work area +// - Initialize the interface medium +// - do initial LOOP acquisition +// - do any catch-up on archive records if there is a data logger +// - 'work->runningFlag' can be used for start up synchronization but should +// not be modified by the station interface code +// - indicate init is done by sending the STATION_INIT_COMPLETE_EVENT event to +// this process (radProcessEventsSend (NULL, STATION_INIT_COMPLETE_EVENT, 0)) +// +// OPTIONAL: +// - Initialize a state machine or any other construct required for the +// station interface - these should be stored in the 'stationData' store +// +// 'archiveIndication' - indication callback used to pass back an archive record +// generated as a result of 'stationGetArchive' being called; should receive a +// NULL pointer for 'newRecord' if no record available; only used if +// 'stationGeneratesArchives' flag is set to TRUE by the station interface +// +// Returns: OK or ERROR +// +int stationInit +( + WVIEWD_WORK *work, + void (*archiveIndication)(ARCHIVE_PKT* newRecord) +) +{ + int i; + + memset (&wxt510WorkData, 0, sizeof(wxt510WorkData)); + pwviewWork = work; + + // save the archive indication callback (we should never need it) + ArchiveIndicator = archiveIndication; + + // set our work data pointer + work->stationData = &wxt510WorkData; + + // set the Archive Generation flag to indicate the WXT-510 DOES NOT + // generate them + work->stationGeneratesArchives = FALSE; + + // initialize the medium abstraction based on user configuration + if (!strcmp (work->stationInterface, "serial")) + { + if (serialMediumInit (&work->medium, serialPortConfig, O_RDWR | O_NOCTTY | O_NDELAY) == ERROR) + { + radMsgLog (PRI_HIGH, "stationInit: serial MediumInit failed"); + return ERROR; + } + } + else if (!strcmp (work->stationInterface, "ethernet")) + { + if (ethernetMediumInit (&work->medium, work->stationHost, work->stationPort) + == ERROR) + { + radMsgLog (PRI_HIGH, "stationInit: ethernet MediumInit failed"); + return ERROR; + } + } + else + { + radMsgLog (PRI_HIGH, "stationInit: medium %s not supported", + work->stationInterface); + return ERROR; + } + + // initialize the interface using the media specific routine + if ((*(work->medium.init)) (&work->medium, work->stationDevice) == ERROR) + { + radMsgLog (PRI_HIGH, "stationInit: medium setup failed"); + return ERROR; + } + + if (!strcmp (work->stationInterface, "serial")) + { + radMsgLog (PRI_STATUS, "WXT510 on %s opened ...", + work->stationDevice); + } + else if (!strcmp (work->stationInterface, "ethernet")) + { + radMsgLog (PRI_STATUS, "WXT510 on %s:%d opened ...", + work->stationHost, work->stationPort); + } + +#ifndef _WXT510_CONFIG_ONLY + // grab the station configuration now + if (stationGetConfigValueInt (work, + STATION_PARM_ELEVATION, + &wxt510WorkData.elevation) + == ERROR) + { + radMsgLog (PRI_HIGH, "stationInit: stationGetConfigValueInt ELEV failed!"); + (*(work->medium.exit)) (&work->medium); + return ERROR; + } + if (stationGetConfigValueFloat (work, + STATION_PARM_LATITUDE, + &wxt510WorkData.latitude) + == ERROR) + { + radMsgLog (PRI_HIGH, "stationInit: stationGetConfigValueInt LAT failed!"); + (*(work->medium.exit)) (&work->medium); + return ERROR; + } + if (stationGetConfigValueFloat (work, + STATION_PARM_LONGITUDE, + &wxt510WorkData.longitude) + == ERROR) + { + radMsgLog (PRI_HIGH, "stationInit: stationGetConfigValueInt LONG failed!"); + (*(work->medium.exit)) (&work->medium); + return ERROR; + } + if (stationGetConfigValueInt (work, + STATION_PARM_ARC_INTERVAL, + &wxt510WorkData.archiveInterval) + == ERROR) + { + radMsgLog (PRI_HIGH, "stationInit: stationGetConfigValueInt ARCINT failed!"); + (*(work->medium.exit)) (&work->medium); + return ERROR; + } + + // set the work archive interval now + work->archiveInterval = wxt510WorkData.archiveInterval; + + // sanity check the archive interval against the most recent record + if (stationVerifyArchiveInterval (work) == ERROR) + { + // bad magic! + radMsgLog (PRI_HIGH, "stationInit: stationVerifyArchiveInterval failed!"); + radMsgLog (PRI_HIGH, "You must either move old archive data out of the way -or-"); + radMsgLog (PRI_HIGH, "fix the interval setting..."); + (*(work->medium.exit)) (&work->medium); + return ERROR; + } + else + { + radMsgLog (PRI_STATUS, "station archive interval: %d minutes", + work->archiveInterval); + } + + wxt510WorkData.totalRain = 0; + wxt510WorkData.totalHail = 0; + + radMsgLog (PRI_STATUS, "Starting station interface: WXT510"); + + // initialize the station interface + if (nmea0183Init(work) == ERROR) + { + radMsgLog (PRI_HIGH, "stationInit: nmea0183Init failed!"); + (*(work->medium.exit)) (&work->medium); + return ERROR; + } + + // do the initial GetReadings now + if (nmea0183GetReadings(work, &wxt510WorkData.nmeaReadings) != OK) + { + radMsgLog (PRI_HIGH, "stationInit: initial nmea0183GetReadings failed!"); + nmea0183Exit (work); + (*(work->medium.exit)) (&work->medium); + return ERROR; + } + + // do final initialization tasks + if (nmea0183PostInit(work) == ERROR) + { + radMsgLog (PRI_HIGH, "stationInit: nmea0183PostInit failed!"); + (*(work->medium.exit)) (&work->medium); + return ERROR; + } + + + // populate the LOOP structure + storeLoopPkt (work, &work->loopPkt, &wxt510WorkData.nmeaReadings); + + // start the rain accumulator reset timer - + // we use the provided interface timer (ifTimer) for this + radProcessTimerStart (work->ifTimer, (uint32_t)WXT_RAIN_RESET_INTERVAL); + + // we must indicate successful completion here - + // even though we are synchronous, the daemon wants to see this event + radProcessEventsSend (NULL, STATION_INIT_COMPLETE_EVENT, 0); + +#endif + + return OK; +} + +#ifndef _WXT510_CONFIG_ONLY + +// station-supplied exit function +// +// Returns: N/A +// +void stationExit (WVIEWD_WORK *work) +{ + nmea0183Exit (work); + (*(work->medium.exit)) (&work->medium); + + return; +} + +// station-supplied function to retrieve positional info (lat, long, elev) - +// should populate 'work' fields: latitude, longitude, elevation +// -- Synchronous -- +// +// - If station does not store these parameters, they can be retrieved from the +// wview.conf file (see daemon.c for example conf file use) - user must choose +// station type "Generic" when running the wviewconfig script +// +// Returns: OK or ERROR +// +int stationGetPosition (WVIEWD_WORK *work) +{ + // just set the values from our internal store - we retrieved them in + // stationInit + work->elevation = (int16_t)wxt510WorkData.elevation; + if (wxt510WorkData.latitude >= 0) + work->latitude = (int16_t)((wxt510WorkData.latitude*10)+0.5); + else + work->latitude = (int16_t)((wxt510WorkData.latitude*10)-0.5); + if (wxt510WorkData.longitude >= 0) + work->longitude = (int16_t)((wxt510WorkData.longitude*10)+0.5); + else + work->longitude = (int16_t)((wxt510WorkData.longitude*10)-0.5); + + radMsgLog (PRI_STATUS, "station location: elevation: %d feet", + work->elevation); + + radMsgLog (PRI_STATUS, "station location: latitude: %3.1f %c longitude: %3.1f %c", + (float)abs(work->latitude)/10.0, + ((work->latitude < 0) ? 'S' : 'N'), + (float)abs(work->longitude)/10.0, + ((work->longitude < 0) ? 'W' : 'E')); + + return OK; +} + +// station-supplied function to indicate a time sync should be performed if the +// station maintains time, otherwise may be safely ignored +// -- Can Be Asynchronous -- +// +// Returns: OK or ERROR +// +int stationSyncTime (WVIEWD_WORK *work) +{ + // WXT-510 does not keep time... + return OK; +} + +// station-supplied function to indicate sensor readings should be performed - +// should populate 'work' struct: loopPkt (see datadefs.h for minimum field reqs) +// -- Can Be Asynchronous -- +// +// - indicate readings are complete by sending the STATION_LOOP_COMPLETE_EVENT +// event to this process (radProcessEventsSend (NULL, STATION_LOOP_COMPLETE_EVENT, 0)) +// +// Returns: OK or ERROR +// +int stationGetReadings (WVIEWD_WORK *work) +{ + // we will do this synchronously... + + // get readings from station + if (nmea0183GetReadings(work, &wxt510WorkData.nmeaReadings) == OK) + { + // populate the LOOP structure + storeLoopPkt (work, &work->loopPkt, &wxt510WorkData.nmeaReadings); + + // indicate we are done + radProcessEventsSend (NULL, STATION_LOOP_COMPLETE_EVENT, 0); + } + + return OK; +} + +// station-supplied function to indicate an archive record should be generated - +// MUST populate an ARCHIVE_RECORD struct and indicate it to 'archiveIndication' +// function passed into 'stationInit' +// -- Asynchronous - callback indication required -- +// +// Returns: OK or ERROR +// +// Note: 'archiveIndication' should receive a NULL pointer for the newRecord if +// no record is available +// Note: This function will only be invoked by the wview daemon if the +// 'stationInit' function set the 'stationGeneratesArchives' to TRUE +// +int stationGetArchive (WVIEWD_WORK *work) +{ + // just indicate a NULL record, WXT-510 does not generate them (and this + // function should never be called!) + (*ArchiveIndicator) (NULL); + return OK; +} + +// station-supplied function to indicate data is available on the station +// interface medium (serial or ethernet) - +// It is the responsibility of the station interface to read the data from the +// medium and process appropriately. The data does not have to be read within +// the context of this function, but may be used to stimulate a state machine. +// -- Synchronous -- +// +// Returns: N/A +// +void stationDataIndicate (WVIEWD_WORK *work) +{ + // WXT510 station is synchronous... + return; +} + +// station-supplied function to receive IPM messages - any message received by +// the generic station message handler which is not recognized will be passed +// to the station-specific code through this function. +// It is the responsibility of the station interface to process the message +// appropriately (or ignore it). +// -- Synchronous -- +// +// Returns: N/A +// +void stationMessageIndicate (WVIEWD_WORK *work, int msgType, void *msg) +{ + // N/A + return; +} + +// station-supplied function to indicate the interface timer has expired - +// It is the responsibility of the station interface to start/stop the interface +// timer as needed for the particular station requirements. +// The station interface timer is specified by the 'ifTimer' member of the +// WVIEWD_WORK structure. No other timers in that structure should be manipulated +// in any way by the station interface code. +// -- Synchronous -- +// +// Returns: N/A +// +void stationIFTimerExpiry (WVIEWD_WORK *work) +{ + // the rain accumulator should be reset + nmea0183ResetAccumulators (work); + + // reset our container + wxt510WorkData.totalRain = 0; + + // restart the timer + radProcessTimerStart (work->ifTimer, (uint32_t)WXT_RAIN_RESET_INTERVAL); + + return; +} + +#endif + +////////////****//// S T A T I O N A P I E N D ////****//////////// + + +// ... ----- static (local) methods ----- ... + +static void serialPortConfig (int fd) +{ + struct termios port, tty, old; + + tcgetattr (fd, &port); + + cfsetispeed (&port, B19200); + cfsetospeed (&port, B19200); + + // set port to 8N1 + port.c_cflag &= ~PARENB; + port.c_cflag &= ~CSTOPB; + port.c_cflag &= ~CSIZE; + port.c_cflag &= ~CRTSCTS; // turn OFF H/W flow control + port.c_cflag |= CS8; + port.c_cflag |= (CREAD | CLOCAL); + + port.c_iflag &= ~(IXON | IXOFF | IXANY); // turn off SW flow control + + port.c_iflag &= ~(INLCR | ICRNL); // turn off other input magic + + port.c_oflag = 0; // NO output magic wanted + + port.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); + + tcsetattr (fd, TCSAFLUSH, &port); + + // bump DTR + if (pwviewWork->stationToggleDTR) + { + tcgetattr (fd, &tty); + tcgetattr (fd, &old); + cfsetospeed (&tty, B0); + cfsetispeed (&tty, B0); + tcsetattr (fd, TCSANOW, &tty); + sleep (1); + tcsetattr (fd, TCSANOW, &old); + } + + return; +} + +#ifndef _WXT510_CONFIG_ONLY + +static void storeLoopPkt (WVIEWD_WORK* work, LOOP_PKT *dest, NMEA0183_DATA *src) +{ + float tempfloat; + + // Clear optional data: + stationClearLoopData(work); + + if ((10 < src->pressure && src->pressure < 50) && + (-200 < src->temperature && src->temperature < 200)) + { + // WXT-510 produces station pressure + dest->stationPressure = src->pressure; + + // Apply calibration here so the computed values reflect it: + dest->stationPressure *= work->calMPressure; + dest->stationPressure += work->calCPressure; + + // compute sea-level pressure (BP) + tempfloat = wvutilsConvertSPToSLP(dest->stationPressure, + src->temperature, + (float)wxt510WorkData.elevation); + dest->barometer = tempfloat; + + // calculate altimeter + tempfloat = wvutilsConvertSPToAltimeter(dest->stationPressure, + (float)wxt510WorkData.elevation); + dest->altimeter = tempfloat; + } + + if (-200 < src->temperature && src->temperature < 200) + dest->outTemp = src->temperature; + + if (0 <= src->humidity && src->humidity <= 100) + { + tempfloat = src->humidity; + tempfloat += 0.5; + dest->outHumidity = (uint16_t)tempfloat; + } + + if (0 <= src->windSpeed && src->windSpeed <= 250) + { + tempfloat = src->windSpeed; + dest->windSpeedF = tempfloat; + } + + if (0 <= src->windDir && src->windDir <= 360) + { + tempfloat = src->windDir; + tempfloat += 0.5; + dest->windDir = (uint16_t)tempfloat; + } + + if (0 <= src->maxWindSpeed && src->maxWindSpeed <= 250) + { + tempfloat = src->maxWindSpeed; + dest->windGustF = tempfloat; + } + + if (0 <= src->maxWindDir && src->maxWindDir <= 360) + { + tempfloat = src->maxWindDir; + tempfloat += 0.5; + dest->windGustDir = (uint16_t)tempfloat; + } + + dest->rainRate = src->rainrate; + + // process the rain accumulator + if (0 <= src->rain) + { + if (src->rain - wxt510WorkData.totalRain >= 0) + { + dest->sampleRain = src->rain - wxt510WorkData.totalRain; + wxt510WorkData.totalRain = src->rain; + } + else + { + // we had a counter reset... + dest->sampleRain = src->rain; + wxt510WorkData.totalRain = src->rain; + } + + if (dest->sampleRain > 2) + { + // Not possible, filter it out: + dest->sampleRain = 0; + } + } + else + { + dest->sampleRain = 0; + } + + // process the hail accumulator + if (0 <= src->rain) + { + if (src->hail - wxt510WorkData.totalHail >= 0) + { + dest->wxt510Hail = src->hail - wxt510WorkData.totalHail; + wxt510WorkData.totalHail = src->hail; + } + else + { + // we had a counter reset... + dest->wxt510Hail = src->hail; + wxt510WorkData.totalHail = src->hail; + } + + if (dest->wxt510Hail > 5) + { + // Not possible, filter it out: + dest->wxt510Hail = 0; + } + } + else + { + dest->wxt510Hail = 0; + } + + // finally the Vaisala-specific ones + dest->wxt510Hailrate = src->hailrate; + dest->wxt510HeatingTemp = src->heatingTemp; + dest->wxt510HeatingVoltage = src->heatingVoltage; + dest->wxt510SupplyVoltage = src->supplyVoltage; + dest->wxt510ReferenceVoltage = src->referenceVoltage; + + dest->wxt510RainDuration = src->rainduration; + dest->wxt510RainPeakRate = src->rainpeakrate; + dest->wxt510HailDuration = src->hailduration; + dest->wxt510HailPeakRate = src->hailpeakrate; + dest->wxt510Rain = src->rain; + + return; +} + +#endif diff --git a/stations/WXT510/wxt510Interface.h b/stations/WXT510/wxt510Interface.h new file mode 100755 index 0000000..f34b213 --- /dev/null +++ b/stations/WXT510/wxt510Interface.h @@ -0,0 +1,80 @@ +#ifndef INC_wxt510Interfaceh +#define INC_wxt510Interfaceh +/*--------------------------------------------------------------------------- + + FILENAME: + wxt510Interface.h + + PURPOSE: + Provide the WXT-510 station interface API and utilities. + + REVISION HISTORY: + Date Engineer Revision Remarks + 01/14/2006 M.S. Teel 0 Original + 03/24/2008 W. Krenn 1 Hail + + NOTES: + + + LICENSE: + Copyright (c) 2006, Mark S. Teel (mark@teel.ws) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +/* ... System include files +*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* ... Library include files +*/ +#include +#include +#include +#include + +/* ... Local include files +*/ +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define WXT_RAIN_RESET_INTERVAL (1000 * 60 * 60 * 24) // 1 day + + + +// define WXT510-specific interface data here +typedef struct +{ + int elevation; + float latitude; + float longitude; + int archiveInterval; + NMEA0183_DATA nmeaReadings; + float totalRain; // to track accumulator changes + float totalHail; +} WXT510_IF_DATA; + + +#endif diff --git a/stations/WXT510/wxt510config/Makefile.am b/stations/WXT510/wxt510config/Makefile.am new file mode 100755 index 0000000..2ac839d --- /dev/null +++ b/stations/WXT510/wxt510config/Makefile.am @@ -0,0 +1,46 @@ +# Makefile - wxt510config + +#define the executable to be built +bin_PROGRAMS = wxt510config + +# define include directories +INCLUDES = \ + -I$(top_srcdir)/common \ + -I$(top_srcdir)/stations/common \ + -I$(top_srcdir)/stations/WXT510 \ + -I$(prefix)/include \ + -D_WXT510_CONFIG_ONLY \ + -DWV_CONFIG_DIR=\"$(sysconfdir)/wview\" \ + -DWV_RUN_DIR=\"$(localstatedir)/wview\" \ + -D_GNU_SOURCE + +# define the sources +wxt510config_SOURCES = \ + $(top_srcdir)/common/sysdefs.h \ + $(top_srcdir)/common/datadefs.h \ + $(top_srcdir)/common/emailAlerts.h \ + $(top_srcdir)/stations/common/serial.h \ + $(top_srcdir)/stations/common/ethernet.h \ + $(top_srcdir)/stations/common/daemon.h \ + $(top_srcdir)/stations/common/parser.h \ + $(top_srcdir)/common/wvutils.c \ + $(top_srcdir)/common/emailAlerts.c \ + $(top_srcdir)/stations/common/serial.c \ + $(top_srcdir)/stations/common/ethernet.c \ + $(top_srcdir)/stations/common/parser.c \ + $(top_srcdir)/stations/WXT510/nmea0183.h \ + $(top_srcdir)/stations/WXT510/wxt510Interface.h \ + $(top_srcdir)/stations/WXT510/nmea0183.c \ + $(top_srcdir)/stations/WXT510/wxt510Interface.c \ + $(top_srcdir)/stations/WXT510/wxt510config/wxt510config.c + +# define libraries +wxt510config_LDADD = + +# define library directories +wxt510config_LDFLAGS = -L$(prefix)/lib -L$(prefix)/usr/lib -L/usr/lib + +if CROSSCOMPILE +wxt510config_LDFLAGS += $(prefix)/lib/crt1.o $(prefix)/lib/crti.o $(prefix)/lib/crtn.o +endif + diff --git a/stations/WXT510/wxt510config/wxt510config.c b/stations/WXT510/wxt510config/wxt510config.c new file mode 100755 index 0000000..8509d3d --- /dev/null +++ b/stations/WXT510/wxt510config/wxt510config.c @@ -0,0 +1,335 @@ +/*--------------------------------------------------------------------- + + FILE NAME: + wxt510config.c + + PURPOSE: + Main entry point for the WXT-510 configuration utility. + + REVISION HISTORY: + Date Programmer Revision Function + 01/25/2006 M.S. Teel 0 Original + + ASSUMPTIONS: + None. + +------------------------------------------------------------------------*/ +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +/* ... global references +*/ + +/* ... local memory +*/ +// initial console wakeup number of attempts +#define WXT_INITIAL_WAKEUP_TRIES 10 + +static WVIEWD_WORK wvWork; + +#define WXT_NUMBER_BAUDS 8 +static speed_t BaudRates[WXT_NUMBER_BAUDS] = +{ + B19200, + B9600, + B4800, + B2400, + B1200, + B38400, + B57600, + B115200 +}; + +static char *BaudNames[WXT_NUMBER_BAUDS] = +{ + "B19200", + "B9600", + "B4800", + "B2400", + "B1200", + "B38400", + "B57600", + "B115200" +}; + +enum +{ + PARITY_NONE = 0, + PARITY_ODD, + PARITY_EVEN, + NUM_PARITIES +}; + +/* ... methods +*/ + +static void serialPortConfig (int fd, speed_t baud, int parity, int stopBits, int size) +{ + struct termios port; + + tcgetattr (fd, &port); + + cfsetispeed (&port, baud); + cfsetospeed (&port, baud); + + // set parity + if (parity == PARITY_NONE) + { + port.c_cflag &= ~PARENB; + } + else if (parity == PARITY_ODD) + { + port.c_cflag |= PARENB; + port.c_cflag |= PARODD; + } + else + { + port.c_cflag |= PARENB; + port.c_cflag &= ~PARODD; + } + + if (stopBits == 1) + { + port.c_cflag &= ~CSTOPB; + } + else + { + port.c_cflag |= CSTOPB; + } + + port.c_cflag &= ~CSIZE; + if (size == 8) + { + port.c_cflag |= CS8; + } + else + { + port.c_cflag |= CS7; + } + + port.c_cflag &= ~CRTSCTS; // turn OFF H/W flow control + port.c_cflag |= (CREAD | CLOCAL); + + port.c_iflag &= ~(IXON | IXOFF | IXANY); // turn off SW flow control + + port.c_iflag &= ~(INLCR | ICRNL); // turn off other input magic + + port.c_oflag = 0; // NO output magic wanted + + port.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); + + tcsetattr (fd, TCSAFLUSH, &port); + + return; +} + +static void portDTRToggle (int fd, int sec) +{ + struct termios tty, old; + + tcgetattr (fd, &tty); + tcgetattr (fd, &old); + cfsetospeed (&tty, B0); + cfsetispeed (&tty, B0); + tcsetattr (fd, TCSANOW, &tty); + if (sec > 0) + { + sleep (sec); + } + + tcsetattr (fd, TCSANOW, &old); +} + +static void USAGE (void) +{ + printf ("Usage: wxt510config [enable_DTR]\n\n"); + printf (" station_device (mandatory) serial device the WXT-510 is connected to\n"); + printf (" FreeBSD: /dev/cuaa0 - /dev/cuaa4\n"); + printf (" Linux: /dev/ttyS0 - /dev/ttyS4\n"); + printf (" Linux USB: /dev/ttyUSB0\n"); + printf (" Ethernet: host:port\n"); + printf (" enable_DTR (optional) enable DTR toggle during autobaud (0 or 1)\n"); + printf (" default = 1 (enable DTR toggle)\n\n"); + return; +} + +/* ... THE entry point +*/ +int main (int argc, char *argv[]) +{ + int baudIndex, parityIndex, stopIndex, sizeIndex, dtrEnable = TRUE; + char *tptr, host[128], port[16], buffer[WVIEW_MAX_PATH]; + ULONG (*radsysFptr) (uint8_t systemID); + + if (argc < 2) + { + USAGE (); + exit (1); + } + + // make sure we pull in radSystem.o when we link + radsysFptr = radSystemGetUpTimeSEC; + + memset (&wvWork, 0, sizeof (wvWork)); + + // do we need to parse an ethernet string here? + if (argv[1][0] != '/') + { + // guess so, did they give us host and port? + tptr = strstr (argv[1], ":"); + if (tptr == NULL) + { + printf ("wxt510config: invalid interface %s given - ", argv[1]); + printf ("not device and not host:port format - aborting!\n"); + exit (1); + } + + strcpy (wvWork.stationInterface, "ethernet"); + strncpy (wvWork.stationHost, argv[1], tptr - argv[1]); + wvWork.stationPort = atoi (tptr + 1); + } + else + { + // normal device specification + strcpy (wvWork.stationInterface, "serial"); + wvstrncpy (wvWork.stationDevice, argv[1], sizeof(wvWork.stationDevice)); + } + + if (argc > 2) + { + // Grab the DTR toggle setting: + if (!strcmp(argv[2], "0")) + { + dtrEnable = FALSE; + } + } + + // initialize the station abstraction + if (stationInit (&wvWork, NULL) == ERROR) + { + printf ("stationInit failed\n"); + exit (1); + } + + printf ("Detecting current WXT-510 communication settings...\n"); + + // now autobaud the station + for (baudIndex = 0; baudIndex < WXT_NUMBER_BAUDS; baudIndex ++) + { + for (parityIndex = PARITY_NONE; parityIndex < NUM_PARITIES; parityIndex ++) + { + for (stopIndex = 1; stopIndex <= 2; stopIndex ++) + { + for (sizeIndex = 8; sizeIndex >= 7; sizeIndex --) + { + printf ("Trying %s-%d-%c-%d...", + BaudNames[baudIndex], + sizeIndex, + ((parityIndex == 0) ? 'N' : ((parityIndex == 1) ? 'O' : 'E')), + stopIndex); + + // setup the port + serialPortConfig (wvWork.medium.fd, + BaudRates[baudIndex], + parityIndex, + stopIndex, + sizeIndex); + + tcflush (wvWork.medium.fd, TCIFLUSH); + tcflush (wvWork.medium.fd, TCOFLUSH); + + if (dtrEnable) + { + // bump the DTR line so the station will not hang in certain scenarios + portDTRToggle (wvWork.medium.fd, 1); + } + + // try to communicate - send a CR/LF then ? command + buffer[0] = 0; + nmea0183WriteLineToStation (&wvWork, buffer, NULL, FALSE, TRUE); + radUtilsSleep (50); + sprintf (buffer, "0XU,A=0"); + nmea0183WriteLineToStation (&wvWork, buffer, NULL, FALSE, TRUE); + sprintf (buffer, "?"); + if (nmea0183WriteLineToStation (&wvWork, buffer, "0", FALSE, FALSE) == OK) + { + printf ("Yes!\n"); + printf ("Found WXT-510 at %s-%d-%c-%d\n", + BaudNames[baudIndex], + sizeIndex, + ((parityIndex == 0) ? 'N' : ((parityIndex == 1) ? 'O' : 'E')), + stopIndex); + printf ("Setting to B19200-8-N-1...\n"); + + // set 19200 8-N-1 + sprintf (buffer, "0XU,B=19200,D=8,P=N,S=1"); + if (nmea0183WriteLineToStation (&wvWork, buffer, buffer, FALSE, FALSE) == ERROR) + { + printf ("Writing %s failed!\n", buffer); + return ERROR; + } + + // send a reset command + sprintf (buffer, "0XZ"); + if (nmea0183WriteLineToStation (&wvWork, buffer, NULL, FALSE, TRUE) == ERROR) + { + printf ("Writing %s failed!\n", buffer); + return ERROR; + } + radUtilsSleep (100); + + // now verify we got it done + serialPortConfig (wvWork.medium.fd, B19200, PARITY_NONE, 1, 8); + + // send a + buffer[0] = 0; + if (nmea0183WriteLineToStation (&wvWork, buffer, NULL, FALSE, TRUE) == ERROR) + { + printf ("CR/LF failed!\n"); + return ERROR; + } + radUtilsSleep (50); + + sprintf (buffer, "?"); + if (nmea0183WriteLineToStation (&wvWork, buffer, "0", FALSE, FALSE) == OK) + { + printf ("WXT-510 set to B19200-8-N-1 - config complete!\n"); + (*(wvWork.medium.exit)) (&wvWork.medium); + exit (0); + } + else + { + printf ("WXT-510 set to B19200-8-N-1 failed!!\n"); + (*(wvWork.medium.exit)) (&wvWork.medium); + exit (1); + } + } + else + { + printf ("No\n"); + } + } + } + } + } + + printf ("WXT-510 autodetect failed!!\n"); + (*(wvWork.medium.exit)) (&wvWork.medium); + exit (2); +} + +// Retrieve exit status: +int wviewdIsExiting(void) +{ + return wvWork.exiting; +} + diff --git a/stations/common/computedData.c b/stations/common/computedData.c new file mode 100755 index 0000000..3ce2956 --- /dev/null +++ b/stations/common/computedData.c @@ -0,0 +1,1207 @@ +/*--------------------------------------------------------------------------- + + FILENAME: + computedData.c + + PURPOSE: + Provide utilities to compute and store HILOW values. + + REVISION HISTORY: + Date Engineer Revision Remarks + 08/07/2005 M.S. Teel 0 Original + + NOTES: + + + LICENSE: + Copyright (c) 2005, Mark S. Teel (mark@teel.ws) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +/* ... System include files +*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/* ... Library include files +*/ +#include +#include + +/* ... Local include files +*/ +#include +#include + +/* ... global memory declarations +*/ + +/* ... local memory +*/ +static COMPDATA_WORK cdWork; +static ARCHIVE_PKT ArcRecStore; + + +// compute HILOW value deltas +static int computeDataChanges (WVIEWD_WORK *work) +{ + time_t retVal; + time_t timenow, timeweekago, timedayago, timehourago; + LOOP_PKT *current = &work->loopPkt; + float temp; + SENSOR_STORE *store = &work->sensors; + ARCHIVE_PKT arcRecord; + int sqlInterval = work->archiveInterval * 60; + + // get our three times of interest: + timenow = time(NULL); + timeweekago = timenow - (WV_SECONDS_IN_DAY * 7); + timedayago = timenow - WV_SECONDS_IN_DAY; + timehourago = timenow - WV_SECONDS_IN_HOUR; + + + // initialize the store + store->hourchangetemp = 0; + store->hourchangewind = 0; + store->hourchangewinddir = 0; + store->hourchangehumid = 0; + store->hourchangedewpt = 0; + store->hourchangebarom = 0; + store->daychangetemp = 0; + store->daychangewind = 0; + store->daychangewinddir = 0; + store->daychangehumid = 0; + store->daychangedewpt = 0; + store->daychangebarom = 0; + store->weekchangetemp = 0; + store->weekchangewind = 0; + store->weekchangewinddir = 0; + store->weekchangehumid = 0; + store->weekchangedewpt = 0; + store->weekchangebarom = 0; + + + // now on to the record examination... + retVal = dbsqliteArchiveGetFirstRecord(timeweekago-sqlInterval, + timeweekago+sqlInterval, + &arcRecord); + if (retVal != ERROR) + { + // Week Ago: + if (current->outTemp > ARCHIVE_VALUE_NULL && + arcRecord.value[DATA_INDEX_outTemp] > ARCHIVE_VALUE_NULL) + store->weekchangetemp = current->outTemp - (float)arcRecord.value[DATA_INDEX_outTemp]; + else + store->weekchangetemp = 0; + + if (arcRecord.value[DATA_INDEX_windSpeed] > ARCHIVE_VALUE_NULL) + store->weekchangewind = current->windSpeedF - (float)arcRecord.value[DATA_INDEX_windSpeed]; + else + store->weekchangewind = 0; + + if (arcRecord.value[DATA_INDEX_windDir] >= 0) + store->weekchangewinddir = (int16_t)((float)current->windDir - (float)arcRecord.value[DATA_INDEX_windDir]); + else + store->weekchangewinddir = 0; + + if (arcRecord.value[DATA_INDEX_outHumidity] > ARCHIVE_VALUE_NULL) + store->weekchangehumid = current->outHumidity - (uint16_t)arcRecord.value[DATA_INDEX_outHumidity]; + else + store->weekchangehumid = 0; + + if (current->dewpoint > ARCHIVE_VALUE_NULL && + arcRecord.value[DATA_INDEX_outTemp] > ARCHIVE_VALUE_NULL && + arcRecord.value[DATA_INDEX_outHumidity] > ARCHIVE_VALUE_NULL) + { + temp = wvutilsCalculateDewpoint ((float)arcRecord.value[DATA_INDEX_outTemp], + (float)arcRecord.value[DATA_INDEX_outHumidity]); + store->weekchangedewpt = current->dewpoint - temp; + } + else + { + store->weekchangedewpt = 0; + } + + if (current->barometer > ARCHIVE_VALUE_NULL && + arcRecord.value[DATA_INDEX_barometer] > ARCHIVE_VALUE_NULL) + store->weekchangebarom = current->barometer - (float)arcRecord.value[DATA_INDEX_barometer]; + else + store->weekchangebarom = 0; + } + + retVal = dbsqliteArchiveGetFirstRecord(timedayago-sqlInterval, + timedayago+sqlInterval, + &arcRecord); + if (retVal != ERROR) + { + // Day Ago: + if (current->outTemp > ARCHIVE_VALUE_NULL && + arcRecord.value[DATA_INDEX_outTemp] > ARCHIVE_VALUE_NULL) + store->daychangetemp = current->outTemp - (float)arcRecord.value[DATA_INDEX_outTemp]; + else + store->daychangetemp = 0; + + if (arcRecord.value[DATA_INDEX_windSpeed] > ARCHIVE_VALUE_NULL) + store->daychangewind = current->windSpeedF - (float)arcRecord.value[DATA_INDEX_windSpeed]; + else + store->daychangewind = 0; + + if (arcRecord.value[DATA_INDEX_windDir] >= 0) + store->daychangewinddir = (int16_t)((float)current->windDir - (float)arcRecord.value[DATA_INDEX_windDir]); + else + store->daychangewinddir = 0; + + if (arcRecord.value[DATA_INDEX_outHumidity] > ARCHIVE_VALUE_NULL) + store->daychangehumid = current->outHumidity - (uint16_t)arcRecord.value[DATA_INDEX_outHumidity]; + else + store->daychangehumid = 0; + + if (current->dewpoint > ARCHIVE_VALUE_NULL && + arcRecord.value[DATA_INDEX_outTemp] > ARCHIVE_VALUE_NULL && + arcRecord.value[DATA_INDEX_outHumidity] > ARCHIVE_VALUE_NULL) + { + temp = wvutilsCalculateDewpoint ((float)arcRecord.value[DATA_INDEX_outTemp], + (float)arcRecord.value[DATA_INDEX_outHumidity]); + store->daychangedewpt = current->dewpoint - temp; + } + else + { + store->daychangedewpt = 0; + } + + if (current->barometer > ARCHIVE_VALUE_NULL && + arcRecord.value[DATA_INDEX_barometer] > ARCHIVE_VALUE_NULL) + store->daychangebarom = current->barometer - (float)arcRecord.value[DATA_INDEX_barometer]; + else + store->daychangebarom = 0; + } + + retVal = dbsqliteArchiveGetFirstRecord(timehourago-sqlInterval, + timehourago+sqlInterval, + &arcRecord); + if (retVal != ERROR) + { + // Hour Ago: + if (current->outTemp > ARCHIVE_VALUE_NULL && + arcRecord.value[DATA_INDEX_outTemp] > ARCHIVE_VALUE_NULL) + store->hourchangetemp = current->outTemp - (float)arcRecord.value[DATA_INDEX_outTemp]; + else + store->hourchangetemp = 0; + + if (arcRecord.value[DATA_INDEX_windSpeed] > ARCHIVE_VALUE_NULL) + store->hourchangewind = current->windSpeedF - (float)arcRecord.value[DATA_INDEX_windSpeed]; + else + store->hourchangewind = 0; + + if (arcRecord.value[DATA_INDEX_windDir] >= 0) + store->hourchangewinddir = (int16_t)((float)current->windDir - (float)arcRecord.value[DATA_INDEX_windDir]); + else + store->hourchangewinddir = 0; + + if (arcRecord.value[DATA_INDEX_outHumidity] > ARCHIVE_VALUE_NULL) + store->hourchangehumid = current->outHumidity - (uint16_t)arcRecord.value[DATA_INDEX_outHumidity]; + else + store->hourchangehumid = 0; + + if (current->dewpoint > ARCHIVE_VALUE_NULL && + arcRecord.value[DATA_INDEX_outTemp] > ARCHIVE_VALUE_NULL && + arcRecord.value[DATA_INDEX_outHumidity] > ARCHIVE_VALUE_NULL) + { + temp = wvutilsCalculateDewpoint ((float)arcRecord.value[DATA_INDEX_outTemp], + (float)arcRecord.value[DATA_INDEX_outHumidity]); + store->hourchangedewpt = current->dewpoint - temp; + } + else + { + store->hourchangedewpt = 0; + } + + if (current->barometer > ARCHIVE_VALUE_NULL && + arcRecord.value[DATA_INDEX_barometer] > ARCHIVE_VALUE_NULL) + store->hourchangebarom = current->barometer - (float)arcRecord.value[DATA_INDEX_barometer]; + else + store->hourchangebarom = 0; + } + + return OK; +} + +// compute HILOW values for the current hour +static int computeDataHour (WVIEWD_WORK *work, time_t lastTime) +{ + int numrecs = 0; + time_t timenow = time(NULL); + struct tm bkntimenow; + SENSOR_STORE *store = &work->sensors; + + // do this so we pick up the proper hour/day when mins < archiveInterval + timenow -= (work->archiveInterval * 60); + + // build time for this hour: + localtime_r(&timenow, &bkntimenow); + bkntimenow.tm_min = 0; + bkntimenow.tm_sec = 0; + timenow = mktime(&bkntimenow); + + if (lastTime > timenow) + { + // Only add new records: + timenow = lastTime + 1; + } + + numrecs = dbsqliteHiLowGetHour(timenow, store, STF_HOUR); + + // were there any recs for this hour? + if (numrecs <= 0) + { + return ERROR; + } + else + { + return OK; + } +} + +// compute HILOW values for the given day +// Returns: number of good records processed +static int computeDataForDay +( + WVIEWD_WORK *work, + time_t dayStart, + SENSOR_TIMEFRAMES frame +) +{ + int retVal; + + retVal = dbsqliteHiLowGetDay(dayStart, &work->sensors, frame); + if (retVal >= 0) + { + return retVal; + } + else + { + return 0; + } +} + +// compute HILOW values for the current day +static int computeDataDay (WVIEWD_WORK *work, time_t lastTime) +{ + int retVal; + time_t timenow = time(NULL); + struct tm bkntimenow; + SENSOR_STORE *store = &work->sensors; + + // do this so we pick up the proper hour/day when mins < archiveInterval + timenow -= (work->archiveInterval * 60); + + // build time for this day: + localtime_r(&timenow, &bkntimenow); + bkntimenow.tm_hour = 0; + bkntimenow.tm_min = 0; + bkntimenow.tm_sec = 0; + bkntimenow.tm_isdst = -1; + timenow = mktime(&bkntimenow); + localtime_r(&timenow, &bkntimenow); + + if (lastTime > timenow) + { + // Only add new records: + timenow = lastTime + 1; + } + + retVal = computeDataForDay(work, timenow, STF_DAY); + if (retVal <= 0) + { + return ERROR; + } + else + { + radMsgLog(PRI_STATUS, "computeDataDay: %4.4d%2.2d%2.2d", + bkntimenow.tm_year + 1900, + bkntimenow.tm_mon + 1, + bkntimenow.tm_mday); + return OK; + } +} + +// compute HILOW values for the last 7 days +static int computeDataWeek (WVIEWD_WORK *work, time_t lastTime) +{ + int i, retVal, numrecs = 0; + time_t currentTime, timenow = time(NULL); + struct tm bkntimenow; + SENSOR_STORE *store = &work->sensors; + + // do this so we pick up the proper hour/day when mins < archiveInterval + currentTime = timenow; + timenow -= (work->archiveInterval * 60); + timenow -= (WV_SECONDS_IN_DAY * 7); + + // build time: + localtime_r(&timenow, &bkntimenow); + bkntimenow.tm_hour = 0; + bkntimenow.tm_min = 0; + bkntimenow.tm_sec = 0; + bkntimenow.tm_isdst = -1; + timenow = mktime(&bkntimenow); + localtime_r(&timenow, &bkntimenow); + + if (lastTime > timenow) + { + // Only add new records: + timenow = lastTime + 1; + } + + // Note: timenow may not point to the beginning of a day, so we renormalize + // at the bottom of the for loop. + for (i = 1; (timenow < currentTime) && (i <= 7); i ++) + { + retVal = computeDataForDay(work, timenow, STF_WEEK); + if (retVal > 0) + { + numrecs += retVal; + radMsgLog(PRI_STATUS, "computeDataWeek: %4.4d%2.2d%2.2d", + bkntimenow.tm_year + 1900, + bkntimenow.tm_mon + 1, + bkntimenow.tm_mday); + } + + timenow += WV_SECONDS_IN_DAY; + localtime_r(&timenow, &bkntimenow); + bkntimenow.tm_hour = 0; + bkntimenow.tm_min = 0; + bkntimenow.tm_sec = 0; + bkntimenow.tm_isdst = -1; + timenow = mktime(&bkntimenow); + localtime_r(&timenow, &bkntimenow); + } + + // were there any recs for this week? + if (numrecs == 0) + { + return ERROR; + } + else + { + return OK; + } +} + +// compute HILOW values for the given month +// Returns: number of good records processed +static int computeDataForMonth +( + WVIEWD_WORK *work, + int month, + int year, + SENSOR_TIMEFRAMES frame, + int yearRainFlag, + time_t lastTime +) +{ + int retVal; + time_t timenow; + struct tm bkntimenow; + + // build time for this month: + bkntimenow.tm_year = year - 1900; + bkntimenow.tm_mon = month - 1; + bkntimenow.tm_mday = 1; + bkntimenow.tm_hour = 0; + bkntimenow.tm_min = 0; + bkntimenow.tm_sec = 0; + bkntimenow.tm_isdst = -1; // we don't know... + timenow = mktime(&bkntimenow); + + if (lastTime > timenow) + { + // Only add new records: + timenow = lastTime + 1; + } + + + retVal = dbsqliteHiLowGetMonth(timenow, &work->sensors, frame, yearRainFlag); + if (retVal >= 0) + { + return retVal; + } + else + { + return 0; + } +} + +// compute HILOW values for the current month: +// Returns: 0 if OK or ERROR if an error occurs +static int computeDataMonth (WVIEWD_WORK *work, time_t lastTime) +{ + SENSOR_STORE *store = &work->sensors; + int retVal; + time_t timenow = time(NULL); + struct tm bkntimenow; + + // do this so we pick up the proper hour/day when mins < archiveInterval + timenow -= (work->archiveInterval * 60); + localtime_r (&timenow, &bkntimenow); + + // process the month + retVal = computeDataForMonth (work, + bkntimenow.tm_mon + 1, + bkntimenow.tm_year + 1900, + STF_MONTH, + FALSE, + lastTime); + if (retVal == ERROR || retVal == 0) + { + return ERROR; + } + + radMsgLog(PRI_STATUS, "computeDataMonth: %4.4d%2.2d", + bkntimenow.tm_year + 1900, bkntimenow.tm_mon + 1); + return OK; +} + +// compute HILOW values for the current year +static int computeDataYear (WVIEWD_WORK *work, time_t lastTime) +{ + SENSOR_STORE *store = &work->sensors; + int i, retVal, numrecs = 0; + int nowmonth, rainmonth, nowyear, rainyear; + time_t startTime, timenow = time(NULL); + struct tm startMonth, bkntimenow; + + // do this so we pick up the proper hour/day when mins < archiveInterval + timenow -= (work->archiveInterval * 60); + + localtime_r (&timenow, &bkntimenow); + nowmonth = bkntimenow.tm_mon + 1; + nowyear = bkntimenow.tm_year + 1900; + + bkntimenow.tm_mon = 0; + bkntimenow.tm_mday = 1; + bkntimenow.tm_hour = 0; + bkntimenow.tm_min = 0; + bkntimenow.tm_sec = 0; + bkntimenow.tm_isdst = -1; + startTime = mktime(&bkntimenow); + + if (lastTime > startTime) + { + // Only add new records: + startTime = lastTime + 1; + } + + localtime_r(&startTime, &startMonth); + + // loop through each month this year (so far) + for (i = startMonth.tm_mon+1; i <= nowmonth; i ++) + { + // process the month + retVal = computeDataForMonth (work, i, nowyear, STF_YEAR, FALSE, startTime); + if (retVal == ERROR || retVal == 0) + { + // just continue here + continue; + } + + radMsgLog(PRI_STATUS, "computeDataYear: %4.4d%2.2d", nowyear, i); + + // Clear this so it is only used the first time through: + startTime = 0; + + numrecs += retVal; + } + + // If we didn't have save data: + if (lastTime == 0) + { + // Clear rain/ET sensors: + sensorInit(&store->sensor[STF_YEAR][SENSOR_RAIN]); + sensorInit(&store->sensor[STF_YEAR][SENSOR_RAINRATE]); + sensorInit(&store->sensor[STF_YEAR][SENSOR_ET]); + + // Do the Year rain totals to account for rain season start: + rainmonth = work->stationRainSeasonStart; + rainyear = nowyear; + if (rainmonth > nowmonth) + { + // we need to go back a year... + rainyear --; + } + + localtime_r (&timenow, &bkntimenow); + bkntimenow.tm_year = rainyear - 1900; + bkntimenow.tm_mon = rainmonth - 1; + bkntimenow.tm_mday = 1; + bkntimenow.tm_hour = 0; + bkntimenow.tm_min = 0; + bkntimenow.tm_sec = 0; + bkntimenow.tm_isdst = -1; + startTime = mktime(&bkntimenow); + + // now loop till we get to this month: + while ((rainyear < nowyear) || ((rainmonth <= nowmonth) && (rainyear == nowyear))) + { + // process the month for yearly rain + computeDataForMonth (work, rainmonth, rainyear, STF_YEAR, TRUE, startTime); + + if (++rainmonth > 12) + { + rainmonth = 1; + rainyear ++; + } + + // Only use this the first time through: + startTime = 0; + } + } + return OK; +} + +// compute HILOW values for all time: +static int computeDataAllTime (WVIEWD_WORK *work, time_t firstTime) +{ + SENSOR_STORE *store = &work->sensors; + int i, j, retVal, numrecs = 0; + int nowmonth, nowyear; + time_t startTime, timenow = time(NULL); + struct tm startMonth, bkntimenow; + + // do this so we pick up the proper hour/day when mins < archiveInterval + timenow -= (work->archiveInterval * 60); + + localtime_r (&timenow, &bkntimenow); + nowmonth = bkntimenow.tm_mon + 1; + nowyear = bkntimenow.tm_year + 1900; + + localtime_r(&firstTime, &bkntimenow); + bkntimenow.tm_mday = 1; + bkntimenow.tm_hour = 0; + bkntimenow.tm_min = 0; + bkntimenow.tm_sec = 0; + bkntimenow.tm_isdst = -1; + startTime = mktime(&bkntimenow); + + localtime_r(&startTime, &startMonth); + + // loop through each month until we reach now: + for (i = startMonth.tm_mon+1, j = startMonth.tm_year+1900; + (j < nowyear) || (j == nowyear && i <= nowmonth); + i ++) + { + if (i > 12) + { + i = 1; + j ++; + } + + // process the month + retVal = computeDataForMonth (work, i, j, STF_ALL, FALSE, 0); + if (retVal <= 0) + { + // just continue here + continue; + } + + radMsgLog(PRI_STATUS, "computeDataAllTime: %4.4d%2.2d", j, i); + + numrecs += retVal; + } + + return OK; +} + +static void addRecordHour (WVIEWD_WORK *work, int clearFlag) +{ + if (clearFlag) + { + // initialize the store + sensorClearSet (work->sensors.sensor[STF_HOUR]); + windAverageReset (&work->sensors.wind[STF_HOUR]); + } + + sensorPropogateSample (work->sensors.sensor[STF_HOUR], + work->sensors.sensor[STF_INTERVAL]); + windAverageAddValue (&work->sensors.wind[STF_HOUR], + (int)windAverageCompute(&work->sensors.wind[STF_INTERVAL])); + return; +} + +static void addRecordDay (WVIEWD_WORK *work, int clearFlag) +{ + if (clearFlag) + { + // initialize the store + sensorClearSet (work->sensors.sensor[STF_DAY]); + windAverageReset (&work->sensors.wind[STF_DAY]); + } + + sensorPropogateSample (work->sensors.sensor[STF_DAY], + work->sensors.sensor[STF_INTERVAL]); + windAverageAddValue (&work->sensors.wind[STF_DAY], + (int)windAverageCompute(&work->sensors.wind[STF_INTERVAL])); + return; +} + +static void addRecordWeek (WVIEWD_WORK *work) +{ + sensorPropogateSample (work->sensors.sensor[STF_WEEK], + work->sensors.sensor[STF_INTERVAL]); + windAverageAddValue (&work->sensors.wind[STF_WEEK], + (int)windAverageCompute(&work->sensors.wind[STF_INTERVAL])); + return; +} + +static void addRecordMonth (WVIEWD_WORK *work, int clearFlag) +{ + if (clearFlag) + { + // initialize the store + sensorClearSet (work->sensors.sensor[STF_MONTH]); + windAverageReset (&work->sensors.wind[STF_MONTH]); + } + + sensorPropogateSample (work->sensors.sensor[STF_MONTH], + work->sensors.sensor[STF_INTERVAL]); + windAverageAddValue (&work->sensors.wind[STF_MONTH], + (int)windAverageCompute(&work->sensors.wind[STF_INTERVAL])); + return; +} + +static void addRecordYear (WVIEWD_WORK *work, int clearFlag) +{ + WV_SENSOR rainSensor, rainrateSensor, etSensor; + + if (clearFlag) + { + // initialize the store + rainSensor = work->sensors.sensor[STF_YEAR][SENSOR_RAIN]; + rainrateSensor = work->sensors.sensor[STF_YEAR][SENSOR_RAINRATE]; + etSensor = work->sensors.sensor[STF_YEAR][SENSOR_ET]; + sensorClearSet (work->sensors.sensor[STF_YEAR]); + work->sensors.sensor[STF_YEAR][SENSOR_RAIN] = rainSensor; + work->sensors.sensor[STF_YEAR][SENSOR_RAINRATE] = rainrateSensor; + work->sensors.sensor[STF_YEAR][SENSOR_ET] = etSensor; + windAverageReset (&work->sensors.wind[STF_YEAR]); + } + + sensorPropogateSample (work->sensors.sensor[STF_YEAR], + work->sensors.sensor[STF_INTERVAL]); + windAverageAddValue (&work->sensors.wind[STF_YEAR], + (int)windAverageCompute(&work->sensors.wind[STF_INTERVAL])); + return; +} + +static void addRecordAllTime (WVIEWD_WORK *work, int clearFlag) +{ + WV_SENSOR rainSensor, rainrateSensor, etSensor; + + if (clearFlag) + { + // initialize the store + rainSensor = work->sensors.sensor[STF_ALL][SENSOR_RAIN]; + rainrateSensor = work->sensors.sensor[STF_ALL][SENSOR_RAINRATE]; + etSensor = work->sensors.sensor[STF_ALL][SENSOR_ET]; + sensorClearSet (work->sensors.sensor[STF_ALL]); + work->sensors.sensor[STF_ALL][SENSOR_RAIN] = rainSensor; + work->sensors.sensor[STF_ALL][SENSOR_RAINRATE] = rainrateSensor; + work->sensors.sensor[STF_ALL][SENSOR_ET] = etSensor; + windAverageReset (&work->sensors.wind[STF_ALL]); + } + + sensorPropogateSample (work->sensors.sensor[STF_ALL], + work->sensors.sensor[STF_INTERVAL]); + windAverageAddValue (&work->sensors.wind[STF_ALL], + (int)windAverageCompute(&work->sensors.wind[STF_INTERVAL])); + return; +} + +static void resetRainRecordYear (WVIEWD_WORK *work) +{ + // initialize the store + sensorInit (&work->sensors.sensor[STF_YEAR][SENSOR_RAIN]); + sensorInit (&work->sensors.sensor[STF_YEAR][SENSOR_RAINRATE]); + sensorInit (&work->sensors.sensor[STF_YEAR][SENSOR_ET]); + sensorAddSample (&work->sensors.sensor[STF_YEAR][SENSOR_RAIN], + &work->sensors.sensor[STF_INTERVAL][SENSOR_RAIN], + FALSE); + sensorAddSample (&work->sensors.sensor[STF_YEAR][SENSOR_RAINRATE], + &work->sensors.sensor[STF_INTERVAL][SENSOR_RAINRATE], + FALSE); + sensorAddSample (&work->sensors.sensor[STF_YEAR][SENSOR_ET], + &work->sensors.sensor[STF_INTERVAL][SENSOR_ET], + FALSE); + return; +} + +static void intervalHousekeepingInit (WVIEWD_WORK *work) +{ + float windAvg, tempAvg; + + // Note: executed once during init + + // Add the interval average values + windAvg = sensorGetAvg (&work->sensors.sensor[STF_HOUR][SENSOR_WSPEED]); + tempAvg = sensorGetAvg (&work->sensors.sensor[STF_HOUR][SENSOR_OUTTEMP]); + work->loopPkt.intervalAvgWCHILL = wvutilsCalculateWindChill(tempAvg, windAvg); + work->loopPkt.intervalAvgWSPEEDF = windAvg; + + return; +} + +static void intervalHousekeeping (WVIEWD_WORK *work) +{ + float windAvg, tempAvg; + + // Note: do any processing here that needs to be done every archive interval + + // Add the interval average values + windAvg = sensorGetAvg (&work->sensors.sensor[STF_INTERVAL][SENSOR_WSPEED]); + tempAvg = sensorGetAvg (&work->sensors.sensor[STF_INTERVAL][SENSOR_OUTTEMP]); + work->loopPkt.intervalAvgWCHILL = wvutilsCalculateWindChill(tempAvg, windAvg); + work->loopPkt.intervalAvgWSPEEDF = windAvg; + + return; +} + +//////////////////////////////////////////////////////////////////////////// +///////////////////////////////// A P I ///////////////////////////////// +//////////////////////////////////////////////////////////////////////////// + +// store a new data sample for all sensors to the current archive interval: +int computedDataStoreSample (WVIEWD_WORK *work) +{ + WV_SENSOR sample[SENSOR_MAX]; + + sensorClearSet (sample); + + // convert from LOOP_PKT to a sensor sample + sensorUpdate (&sample[SENSOR_INTEMP], work->loopPkt.inTemp); + sensorUpdate (&sample[SENSOR_OUTTEMP], work->loopPkt.outTemp); + sensorUpdate (&sample[SENSOR_INHUMID], (float)work->loopPkt.inHumidity); + sensorUpdate (&sample[SENSOR_OUTHUMID], (float)work->loopPkt.outHumidity); + sensorUpdate (&sample[SENSOR_BP], work->loopPkt.barometer); + sensorUpdate (&sample[SENSOR_WSPEED], work->loopPkt.windSpeedF); + sensorUpdateWhen (&sample[SENSOR_WGUST], + work->loopPkt.windGustF, + (float)work->loopPkt.windGustDir); + sensorUpdate (&sample[SENSOR_DEWPOINT], work->loopPkt.dewpoint); + sensorUpdate (&sample[SENSOR_RAIN], work->loopPkt.sampleRain); + sensorUpdate (&sample[SENSOR_RAINRATE], work->loopPkt.rainRate); + sensorUpdate (&sample[SENSOR_WCHILL], work->loopPkt.windchill); + sensorUpdate (&sample[SENSOR_HINDEX], work->loopPkt.heatindex); + + if (work->loopPkt.sampleET > ARCHIVE_VALUE_NULL) + sensorUpdate (&sample[SENSOR_ET], work->loopPkt.sampleET); + if (work->loopPkt.UV != 0xFFFF) + sensorUpdate (&sample[SENSOR_UV], (float)work->loopPkt.UV); + if (work->loopPkt.radiation != 0xFFFF) + sensorUpdate (&sample[SENSOR_SOLRAD], (float)work->loopPkt.radiation); + if (work->loopPkt.wxt510Hail > ARCHIVE_VALUE_NULL) + sensorUpdate (&sample[SENSOR_HAIL], work->loopPkt.wxt510Hail); + if (work->loopPkt.wxt510Hailrate > ARCHIVE_VALUE_NULL) + sensorUpdate (&sample[SENSOR_HAILRATE], work->loopPkt.wxt510Hailrate); + + // store it + sensorPropogateSample (work->sensors.sensor[STF_INTERVAL], sample); + + // store the wind direction + windAverageAddValue (&work->sensors.wind[STF_INTERVAL], work->loopPkt.windDir); + + // Store to the HILOW database: + dbsqliteHiLowStoreSample(time(NULL), &work->loopPkt); + + return OK; +} + +ARCHIVE_PKT *computedDataGenerateArchive (WVIEWD_WORK *work) +{ + WV_SENSOR *sample = work->sensors.sensor[STF_INTERVAL]; + time_t nowtime = time (NULL); + struct tm bknTime; + Data_Indices index; + + // create the time_t time for the record: + localtime_r (&nowtime, &bknTime); + bknTime.tm_sec = 0; + ArcRecStore.dateTime = (int32_t)mktime(&bknTime); + + ArcRecStore.usUnits = 1; + ArcRecStore.interval = work->archiveInterval; + + // Set all values to NULL by default: + for (index = DATA_INDEX_barometer; index < DATA_INDEX_MAX; index ++) + { + ArcRecStore.value[index] = ARCHIVE_VALUE_NULL; + } + + // have we received any LOOP updates this interval? + if (sensorGetSamples(&sample[SENSOR_OUTTEMP]) == 0) + { + // we have not - squawk about it and exit + radMsgLog (PRI_MEDIUM, "computedDataGenerateArchive: no samples for this interval!"); + emailAlertSend(ALERT_TYPE_STATION_LOOP); + return NULL; + } + + // Set the values we can: + ArcRecStore.value[DATA_INDEX_outTemp] = (float)sensorGetAvg (&sample[SENSOR_OUTTEMP]); + ArcRecStore.value[DATA_INDEX_rain] = (float)sensorGetCumulative (&sample[SENSOR_RAIN]); + ArcRecStore.value[DATA_INDEX_rainRate] = (float)sensorGetHigh (&sample[SENSOR_RAINRATE]); + ArcRecStore.value[DATA_INDEX_barometer] = (float)sensorGetAvg (&sample[SENSOR_BP]); + ArcRecStore.value[DATA_INDEX_pressure] = + wvutilsConvertSLPToSP((float)ArcRecStore.value[DATA_INDEX_barometer], + (float)ArcRecStore.value[DATA_INDEX_outTemp], + (float)work->elevation); + ArcRecStore.value[DATA_INDEX_altimeter] = + wvutilsConvertSPToAltimeter((float)ArcRecStore.value[DATA_INDEX_pressure], + (float)work->elevation); + ArcRecStore.value[DATA_INDEX_inTemp] = (float)sensorGetAvg (&sample[SENSOR_INTEMP]); + ArcRecStore.value[DATA_INDEX_inHumidity] = (float)sensorGetAvg (&sample[SENSOR_INHUMID]); + ArcRecStore.value[DATA_INDEX_outHumidity] = (float)sensorGetAvg (&sample[SENSOR_OUTHUMID]); + ArcRecStore.value[DATA_INDEX_windSpeed] = (float)sensorGetAvg (&sample[SENSOR_WSPEED]); + if (sensorGetHigh (&sample[SENSOR_WGUST]) >= 0) + { + ArcRecStore.value[DATA_INDEX_windGust] = (float)sensorGetHigh (&sample[SENSOR_WGUST]); + } + else + { + ArcRecStore.value[DATA_INDEX_windGust] = 0; + } + + // save the high wind speed in the loop packet + work->loopPkt.windGustF = ArcRecStore.value[DATA_INDEX_windGust]; + + ArcRecStore.value[DATA_INDEX_dewpoint] = + wvutilsCalculateDewpoint ((float)ArcRecStore.value[DATA_INDEX_outTemp], + (float)ArcRecStore.value[DATA_INDEX_outHumidity]); + ArcRecStore.value[DATA_INDEX_windchill] = + wvutilsCalculateWindChill ((float)ArcRecStore.value[DATA_INDEX_outTemp], + (float)ArcRecStore.value[DATA_INDEX_windSpeed]); + ArcRecStore.value[DATA_INDEX_heatindex] = + wvutilsCalculateHeatIndex ((float)ArcRecStore.value[DATA_INDEX_outTemp], + (float)ArcRecStore.value[DATA_INDEX_outHumidity]); + + if (ArcRecStore.value[DATA_INDEX_windSpeed] > 0 || + ArcRecStore.value[DATA_INDEX_windGust] > 0) + { + ArcRecStore.value[DATA_INDEX_windDir] = (float)windAverageCompute(&work->sensors.wind[STF_INTERVAL]); + ArcRecStore.value[DATA_INDEX_windGustDir] = (float)sensorGetWhenHigh(&sample[SENSOR_WGUST]); + } + + // These are conditional based on loop data being populated: + if (work->loopPkt.radiation != 0xFFFF) + ArcRecStore.value[DATA_INDEX_radiation] = (float)sensorGetAvg (&sample[SENSOR_SOLRAD]); + if (work->loopPkt.UV != 0xFFFF) + ArcRecStore.value[DATA_INDEX_UV] = (float)sensorGetAvg (&sample[SENSOR_UV]); + if (work->loopPkt.sampleET != ARCHIVE_VALUE_NULL) + ArcRecStore.value[DATA_INDEX_ET] = (float)sensorGetCumulative (&sample[SENSOR_ET]); + if (work->loopPkt.wxt510Hail != ARCHIVE_VALUE_NULL) + ArcRecStore.value[DATA_INDEX_hail] = (float)sensorGetCumulative (&sample[SENSOR_HAIL]); + if (work->loopPkt.wxt510Hailrate != ARCHIVE_VALUE_NULL) + ArcRecStore.value[DATA_INDEX_hailrate] = (float)sensorGetHigh (&sample[SENSOR_HAILRATE]); + + + // Get a few directly from the last LOOP_PKT: + if (work->loopPkt.rxCheckPercent != 0xFFFF) + ArcRecStore.value[DATA_INDEX_rxCheckPercent] = (float)work->loopPkt.rxCheckPercent; + if (work->loopPkt.wxt510HeatingTemp != ARCHIVE_VALUE_NULL) + ArcRecStore.value[DATA_INDEX_heatingTemp] = (float)work->loopPkt.wxt510HeatingTemp; + if (work->loopPkt.wxt510HeatingVoltage != ARCHIVE_VALUE_NULL) + ArcRecStore.value[DATA_INDEX_heatingVoltage] = (float)work->loopPkt.wxt510HeatingVoltage; + if (work->loopPkt.wxt510SupplyVoltage != ARCHIVE_VALUE_NULL) + ArcRecStore.value[DATA_INDEX_supplyVoltage] = (float)work->loopPkt.wxt510SupplyVoltage; + if (work->loopPkt.wxt510ReferenceVoltage != ARCHIVE_VALUE_NULL) + ArcRecStore.value[DATA_INDEX_referenceVoltage] = (float)work->loopPkt.wxt510ReferenceVoltage; + if (work->loopPkt.wmr918WindBatteryStatus != 0xFF) + ArcRecStore.value[DATA_INDEX_windBatteryStatus] = (float)work->loopPkt.wmr918WindBatteryStatus; + if (work->loopPkt.wmr918RainBatteryStatus != 0xFF) + ArcRecStore.value[DATA_INDEX_rainBatteryStatus] = (float)work->loopPkt.wmr918RainBatteryStatus; + if (work->loopPkt.wmr918OutTempBatteryStatus != 0xFF) + ArcRecStore.value[DATA_INDEX_outTempBatteryStatus] = (float)work->loopPkt.wmr918OutTempBatteryStatus; + if (work->loopPkt.wmr918InTempBatteryStatus != 0xFF) + ArcRecStore.value[DATA_INDEX_inTempBatteryStatus] = (float)work->loopPkt.wmr918InTempBatteryStatus; + + if (work->loopPkt.extraTemp[0] != ARCHIVE_VALUE_NULL) + ArcRecStore.value[DATA_INDEX_extraTemp1] = (float)work->loopPkt.extraTemp[0]; + if (work->loopPkt.extraTemp[1] != ARCHIVE_VALUE_NULL) + ArcRecStore.value[DATA_INDEX_extraTemp2] = (float)work->loopPkt.extraTemp[1]; + if (work->loopPkt.extraTemp[2] != ARCHIVE_VALUE_NULL) + ArcRecStore.value[DATA_INDEX_extraTemp3]= (float)work->loopPkt.extraTemp[2]; + if (work->loopPkt.extraHumidity[0] != ARCHIVE_VALUE_NULL) + ArcRecStore.value[DATA_INDEX_extraHumid1] = (float)work->loopPkt.extraHumidity[0]; + if (work->loopPkt.extraHumidity[1] != ARCHIVE_VALUE_NULL) + ArcRecStore.value[DATA_INDEX_extraHumid2] = (float)work->loopPkt.extraHumidity[1]; + + return &ArcRecStore; +} + +void computedDataClearInterval (WVIEWD_WORK *work) +{ + // clear our interval store to start the next cycle + sensorClearSet (work->sensors.sensor[STF_INTERVAL]); + + windAverageReset (&work->sensors.wind[STF_INTERVAL]); + return; +} + + +// initialize the computed values from the archive records +int computedDataInit (WVIEWD_WORK *work) +{ + time_t lastArchiveTime, firstArchiveTime, nowtime = time(NULL); + struct tm bknnowtime; + int rainyear; + ARCHIVE_PKT recordStore; + + localtime_r(&nowtime, &bknnowtime); + rainyear = bknnowtime.tm_year + 1900; + if (work->stationRainSeasonStart > (bknnowtime.tm_mon + 1)) + { + // we need to go back a year... + rainyear --; + } + + radMsgLog (PRI_STATUS, "initializing computed data values..."); + + memset (&cdWork, 0, sizeof (cdWork)); + + // initialize the stores: + windAverageReset (&work->sensors.wind[STF_ALL]); + windAverageReset (&work->sensors.wind[STF_YEAR]); + windAverageReset (&work->sensors.wind[STF_MONTH]); + windAverageReset (&work->sensors.wind[STF_WEEK]); + windAverageReset (&work->sensors.wind[STF_DAY]); + windAverageReset (&work->sensors.wind[STF_HOUR]); + + sensorClearSet (work->sensors.sensor[STF_ALL]); + sensorClearSet (work->sensors.sensor[STF_YEAR]); + sensorClearSet (work->sensors.sensor[STF_MONTH]); + sensorClearSet (work->sensors.sensor[STF_WEEK]); + sensorClearSet (work->sensors.sensor[STF_DAY]); + sensorClearSet (work->sensors.sensor[STF_HOUR]); + + lastArchiveTime = 0; + firstArchiveTime = dbsqliteArchiveGetNextRecord(0, &recordStore); + + // Only compute if there are archive records: + if ((int)firstArchiveTime != ERROR) + { + // update ALL data types to get started (ignore failures) + computeDataAllTime(work, firstArchiveTime); + computeDataYear(work, lastArchiveTime); + + // Add in any preset values if the year is right: + if (work->stationRainETPresetYear == rainyear) + { + sensorAddCumulative (&work->sensors.sensor[STF_YEAR][SENSOR_RAIN], + work->stationRainYTDPreset); + sensorAddCumulative (&work->sensors.sensor[STF_YEAR][SENSOR_ET], + work->stationETYTDPreset); + } + + computeDataMonth (work, lastArchiveTime); + computeDataWeek (work, lastArchiveTime); + computeDataDay (work, lastArchiveTime); + computeDataHour (work, lastArchiveTime); + } + + sensorClearSet (work->sensors.sensor[STF_INTERVAL]); + + // initialize per-interval housekeeping + intervalHousekeepingInit (work); + + // save the current time so we will know when things need to be updated + nowtime -= (work->archiveInterval * 60); + localtime_r (&nowtime, &bknnowtime); + cdWork.currentHour = bknnowtime.tm_hour; + cdWork.currentDay = bknnowtime.tm_mday; + cdWork.currentMonth = bknnowtime.tm_mon; + cdWork.currentYear = bknnowtime.tm_year; + + return OK; +} + +void computedDataExit (WVIEWD_WORK *work) +{ + int rainyear; + time_t nowtime = time(NULL); + struct tm bkntimenow; + + localtime_r(&nowtime, &bkntimenow); + rainyear = bkntimenow.tm_year + 1900; + if (work->stationRainSeasonStart > (bkntimenow.tm_mon + 1)) + { + // we need to go back a year... + rainyear --; + } + + return; +} + +// update the computed values based on a new archive interval: +int computedDataUpdate (WVIEWD_WORK *work) +{ + time_t timenow = time(NULL); + struct tm bkntimenow; + + // do some per-interval housekeeping + intervalHousekeeping (work); + + // subtract an archive interval because the period 0 - (archiveInterval - 1) + // minutes of each hour is still really the previous hour's data + timenow -= (work->archiveInterval * 60); + localtime_r (&timenow, &bkntimenow); + + // always update the "change" values + computeDataChanges (work); + + // start at the lowest timeframe, that way we can bail out early + + // has the hour changed? + if (bkntimenow.tm_hour != cdWork.currentHour) + { + // save the new hour + cdWork.currentHour = bkntimenow.tm_hour; + + // update the new hour + addRecordHour (work, TRUE); + + // has the day changed? + if (bkntimenow.tm_mday != cdWork.currentDay) + { + // save the new day + cdWork.currentDay = bkntimenow.tm_mday; + + // update the last week + computeDataWeek (work, 0); + + // update the last day + addRecordDay (work, TRUE); + + // has the month changed? + if (bkntimenow.tm_mon != cdWork.currentMonth) + { + // save the new month + cdWork.currentMonth = bkntimenow.tm_mon; + + // update the month + addRecordMonth (work, TRUE); + + // has the year changed? + if (bkntimenow.tm_year != cdWork.currentYear) + { + // save the new year + cdWork.currentYear = bkntimenow.tm_year; + + // update the year + addRecordYear (work, TRUE); + addRecordAllTime(work, FALSE); + } + else + { + // just add record to year + addRecordYear (work, FALSE); + addRecordAllTime(work, FALSE); + } + + // has the rain season rolled over? + if ((cdWork.currentMonth+1) == work->stationRainSeasonStart) + { + // start the new rain season year! + resetRainRecordYear (work); + } + } + else + { + // just add record to month and year + addRecordMonth (work, FALSE); + addRecordYear (work, FALSE); + addRecordAllTime(work, FALSE); + } + } + else + { + // just add record to day, week, month and year + addRecordDay (work, FALSE); + addRecordWeek (work); + addRecordMonth (work, FALSE); + addRecordYear (work, FALSE); + addRecordAllTime(work, FALSE); + } + } + else + { + // just add record to hour, day, week, month and year + addRecordHour (work, FALSE); + addRecordDay (work, FALSE); + addRecordWeek (work); + addRecordMonth (work, FALSE); + addRecordYear (work, FALSE); + addRecordAllTime(work, FALSE); + } + + return OK; +} + +// update the computed values based on a new archive record: +int computedDataNewArchive (WVIEWD_WORK *work, ARCHIVE_PKT *newRecord) +{ +#if 0 + float carryOverRain = 0, carryOverET = 0; + + if (newRecord == NULL) + { + return ERROR; + } + + // save trace accumulator amounts: + if (newRecord->value[DATA_INDEX_rain] > ARCHIVE_VALUE_NULL) + { + carryOverRain = sensorGetCumulative(&work->sensors.sensor[STF_INTERVAL][SENSOR_RAIN]); + carryOverRain -= (float)newRecord->value[DATA_INDEX_rain]; + if (carryOverRain < 0) + { + carryOverRain = 0; + } + } + + if (newRecord->value[DATA_INDEX_ET] > ARCHIVE_VALUE_NULL) + { + carryOverET = sensorGetCumulative(&work->sensors.sensor[STF_INTERVAL][SENSOR_ET]); + carryOverET -= (float)newRecord->value[DATA_INDEX_ET]; + if (carryOverET < 0) + { + carryOverET = 0; + } + } + + // normalize the accumulator values to the archive record + sensorUpdateCumulative (&work->sensors.sensor[STF_INTERVAL][SENSOR_RAIN], + (float)newRecord->value[DATA_INDEX_rain]); + sensorUpdateCumulative (&work->sensors.sensor[STF_INTERVAL][SENSOR_ET], + (float)newRecord->value[DATA_INDEX_ET]); + + // store the accumulator trace amounts for the next clearInterval: + work->carryOverRain = carryOverRain; + work->carryOverET = carryOverET; +#endif + + return OK; +} diff --git a/stations/common/computedData.h b/stations/common/computedData.h new file mode 100644 index 0000000..686c5fc --- /dev/null +++ b/stations/common/computedData.h @@ -0,0 +1,84 @@ +#ifndef INC_computedDatah +#define INC_computedDatah +/*--------------------------------------------------------------------------- + + FILENAME: + computedData.h + + PURPOSE: + Provide utilities to compute and store HILOW values. + + REVISION HISTORY: + Date Engineer Revision Remarks + 08/07/2005 M.S. Teel 0 Original + + NOTES: + + + LICENSE: + Copyright (c) 2004, Mark S. Teel (mark@teel.ws) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +/* ... System include files +*/ +#include +#include +#include +#include +#include +#include + +/* ... Library include files +*/ +#include +#include + +/* ... Local include files +*/ +#include +#include +#include +#include "daemon.h" + +/* ... some definitions +*/ +typedef struct +{ + int currentHour; + int currentDay; + int currentMonth; + int currentYear; +} COMPDATA_WORK; + + +/* ... function prototypes +*/ + +// initialize the computed values from the archive records +extern int computedDataInit (WVIEWD_WORK *work); + +// save sensors and exit +extern void computedDataExit (WVIEWD_WORK *work); + +// update the computed values based on a new archive interval +extern int computedDataUpdate (WVIEWD_WORK *work); + +// update the computed values based on a new archive record +extern int computedDataNewArchive (WVIEWD_WORK *work, ARCHIVE_PKT *newRecord); + +// store a data sample in the current archive interval store +extern int computedDataStoreSample (WVIEWD_WORK *work); + +// create an archive record from our sensor store +extern ARCHIVE_PKT *computedDataGenerateArchive (WVIEWD_WORK *work); + +// clear the archive store +extern void computedDataClearInterval (WVIEWD_WORK *work); + + +#endif + diff --git a/stations/common/daemon.c b/stations/common/daemon.c new file mode 100755 index 0000000..12634a9 --- /dev/null +++ b/stations/common/daemon.c @@ -0,0 +1,1489 @@ +/*--------------------------------------------------------------------------- + + FILENAME: + daemon.c + + PURPOSE: + Provide the wview daemon entry point. + + REVISION HISTORY: + Date Engineer Revision Remarks + 08/14/03 M.S. Teel 0 Original + 08/04/2008 M.S. Teel 1 Change config to + wvconfig.h + + NOTES: + + + LICENSE: + Copyright (c) 2004, Mark S. Teel (mark@teel.ws) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +/* ... System include files +*/ +#include +#include +#include +#include + +/* ... Library include files +*/ +#include + +/* ... Local include files +*/ +#include +#include +#include +#include + +/* ... global memory declarations +*/ + +/* ... global memory referenced +*/ + +/* ... static (local) memory declarations +*/ +static WVIEWD_WORK wviewdWork; + +static char* wviewStatusLabels[STATUS_STATS_MAX] = +{ + "LOOP packets received", + "Archive packets generated", + "", + "" +}; + + +/* ... methods +*/ + +static int daemonStationLoopComplete (void) +{ + float tempf, sampleRain, sampleET; + + if (!wviewdWork.runningFlag) + { + return OK; + } + + // Adjust for calibrations: + // DO NOT calibrate pressure here: + + wviewdWork.loopPkt.inTemp *= wviewdWork.calMInTemp; + wviewdWork.loopPkt.inTemp += wviewdWork.calCInTemp; + + wviewdWork.loopPkt.outTemp *= wviewdWork.calMOutTemp; + wviewdWork.loopPkt.outTemp += wviewdWork.calCOutTemp; + + wviewdWork.loopPkt.inHumidity *= wviewdWork.calMInHumidity; + wviewdWork.loopPkt.inHumidity += wviewdWork.calCInHumidity; + if (wviewdWork.loopPkt.inHumidity > 100) + { + wviewdWork.loopPkt.inHumidity = 100; + } + + wviewdWork.loopPkt.outHumidity *= wviewdWork.calMOutHumidity; + wviewdWork.loopPkt.outHumidity += wviewdWork.calCOutHumidity; + if (wviewdWork.loopPkt.outHumidity > 100) + { + wviewdWork.loopPkt.outHumidity = 100; + } + + wviewdWork.loopPkt.windSpeedF *= wviewdWork.calMWindSpeed; + wviewdWork.loopPkt.windSpeedF += wviewdWork.calCWindSpeed; + + wviewdWork.loopPkt.windDir *= wviewdWork.calMWindDir; + wviewdWork.loopPkt.windDir += wviewdWork.calCWindDir; + wviewdWork.loopPkt.windDir %= 360; + + wviewdWork.loopPkt.windGustF *= wviewdWork.calMWindSpeed; + wviewdWork.loopPkt.windGustF += wviewdWork.calCWindSpeed; + + wviewdWork.loopPkt.sampleRain *= wviewdWork.calMRain; + wviewdWork.loopPkt.sampleRain += wviewdWork.calCRain; + + wviewdWork.loopPkt.rainRate *= wviewdWork.calMRainRate; + wviewdWork.loopPkt.rainRate += wviewdWork.calCRainRate; + + // now calculate a few after all calibrations: + wviewdWork.loopPkt.dewpoint = wvutilsCalculateDewpoint(wviewdWork.loopPkt.outTemp, + (float)wviewdWork.loopPkt.outHumidity); + wviewdWork.loopPkt.windchill = wvutilsCalculateWindChill(wviewdWork.loopPkt.outTemp, + wviewdWork.loopPkt.windSpeedF); + wviewdWork.loopPkt.heatindex = wvutilsCalculateHeatIndex(wviewdWork.loopPkt.outTemp, + (float)wviewdWork.loopPkt.outHumidity); + + // store the results: + computedDataStoreSample (&wviewdWork); + + sampleRain = sensorGetCumulative(&wviewdWork.sensors.sensor[STF_INTERVAL][SENSOR_RAIN]); + sampleET = sensorGetCumulative(&wviewdWork.sensors.sensor[STF_INTERVAL][SENSOR_ET]); + + // do some post-processing on the LOOP data: + tempf = stormRainGet(); + if (tempf > 0) + tempf += sampleRain; + wviewdWork.loopPkt.stormRain = tempf; + wviewdWork.loopPkt.stormStart = stormRainGetStartTimeT(); + + tempf = sensorGetCumulative(&wviewdWork.sensors.sensor[STF_DAY][SENSOR_RAIN]); + tempf += sampleRain; + wviewdWork.loopPkt.dayRain = tempf; + + tempf = sensorGetCumulative(&wviewdWork.sensors.sensor[STF_MONTH][SENSOR_RAIN]); + tempf += sampleRain; + wviewdWork.loopPkt.monthRain = tempf; + + tempf = sensorGetCumulative(&wviewdWork.sensors.sensor[STF_YEAR][SENSOR_RAIN]); + tempf += sampleRain; + wviewdWork.loopPkt.yearRain = tempf; + + if (sampleET > ARCHIVE_VALUE_NULL) + { + tempf = sensorGetCumulative(&wviewdWork.sensors.sensor[STF_DAY][SENSOR_ET]); + tempf += sampleET; + wviewdWork.loopPkt.dayET = tempf; + + tempf = sensorGetCumulative(&wviewdWork.sensors.sensor[STF_MONTH][SENSOR_ET]); + tempf += sampleET; + wviewdWork.loopPkt.monthET = tempf; + + tempf = sensorGetCumulative(&wviewdWork.sensors.sensor[STF_YEAR][SENSOR_ET]); + tempf += sampleET; + wviewdWork.loopPkt.yearET = tempf; + } + + wviewdWork.loopPkt.yearRainMonth = wviewdWork.stationRainSeasonStart; + + statusIncrementStat(WVIEW_STATS_LOOP_PKTS_RX); + return OK; +} + +static int daemonStationInitComplete (void *eventData) +{ + ARCHIVE_PKT newestRecord; + + if (eventData != 0) + { + // failed startup! + radMsgLog (PRI_HIGH, "daemonStationInitComplete: station startup failed!"); + return ERROR; + } + + if (!wviewdWork.runningFlag) + { + wviewdWork.runningFlag = TRUE; + + // set the positional data: + if (stationGetPosition (&wviewdWork) == ERROR) + { + radMsgLog (PRI_HIGH, "daemonStationInitComplete: stationGetPosition failed!"); + emailAlertSend(ALERT_TYPE_STATION_READ); + return ERROR; + } + + // !!! the order of these calls is very important !!! + + // Initialize the HILOW database interface: + computedDataInit (&wviewdWork); + + stormRainInit (wviewdWork.stationRainStormTrigger, + wviewdWork.stationRainStormIdleHours); + + computedDataClearInterval (&wviewdWork); + + // Clear last loop packet store: + memset(&wviewdWork.lastLoopPkt, 0, sizeof(wviewdWork.lastLoopPkt)); + + // we know it just finished an initial sensor readings, it is a + // REQUIREMENT of the stationInit API... + daemonStationLoopComplete (); + + // do an initial update to propogate the initial readings + // (so we have some data to start with) + computedDataUpdate (&wviewdWork); + + // start the timers... + stationStartArchiveTimerUniform (&wviewdWork); + stationStartCDataTimerUniform (&wviewdWork); + radProcessTimerStart (wviewdWork.pushTimer, 30000L); // first run + stationStartSyncTimerUniform (&wviewdWork, TRUE); // first run + + radMsgLog (PRI_STATUS, "-- Station Init Complete --"); + + // get the newest archive file record date/time + wviewdWork.archiveDateTime = dbsqliteArchiveGetNewestTime(&newestRecord); + if ((int)wviewdWork.archiveDateTime == ERROR) + { + wviewdWork.archiveDateTime = time(NULL); + radMsgLog (PRI_STATUS, "no archive records found in database!"); + } + else + { + radMsgLog (PRI_STATUS, "newest archive record: %4.4d-%2.2d-%2.2d %2.2d:%2.2d", + wvutilsGetYear(wviewdWork.archiveDateTime), + wvutilsGetMonth(wviewdWork.archiveDateTime), + wvutilsGetDay(wviewdWork.archiveDateTime), + wvutilsGetHour(wviewdWork.archiveDateTime), + wvutilsGetMin(wviewdWork.archiveDateTime)); + } + + // finally, answer all the WVIEW_RQST_TYPE_STATION_INFO requestors + // so they can continue initialization - our data is ready: + stationProcessInfoResponses(&wviewdWork); + } + + return OK; +} + +static void daemonCheckArchiveRecord (ARCHIVE_PKT *newRecord) +{ + static ARCHIVE_PKT LastPacket; + static int NoChangeCounter = 0; + + // See if any base values have changed: + if (LastPacket.value[DATA_INDEX_outTemp] != newRecord->value[DATA_INDEX_outTemp]) + { + NoChangeCounter = 0; + } + else if (LastPacket.value[DATA_INDEX_windSpeed] != newRecord->value[DATA_INDEX_windSpeed]) + { + NoChangeCounter = 0; + } + else if (LastPacket.value[DATA_INDEX_windDir] != newRecord->value[DATA_INDEX_windDir]) + { + NoChangeCounter = 0; + } + + // If counter exceeds threshold, send alert and reset: + if (NoChangeCounter >= WVD_FLATLINE_THRESHOLD(wviewdWork.archiveInterval)) + { + radMsgLog (PRI_MEDIUM, "daemonCheckArchiveRecord: basic data values not changing!"); + emailAlertSend(ALERT_TYPE_STATION_FLATLINE); + NoChangeCounter = 0; + } + + // Always reset last packet: + LastPacket = *newRecord; +} + +static void daemonStoreArchiveRecord (ARCHIVE_PKT *newRecord) +{ + float sampleRain; + int deltaTime; + + if (newRecord == NULL) + { + radMsgLog (PRI_MEDIUM, "daemonStoreArchiveRecord: record is NULL!"); + return; + } + + deltaTime = newRecord->dateTime - wviewdWork.archiveDateTime; + if (deltaTime == 0) + { + // discard it, same as previous record + radMsgLog (PRI_MEDIUM, + "daemonStoreArchiveRecord: record has same timestamp as previous!"); + return; + } + else if (deltaTime < 0) + { + // chunk it, it is just wrong + radMsgLog (PRI_MEDIUM, + "StoreArchiveRecord: record has earlier timestamp than previous (DST change?)"); + return; + } + + wviewdWork.archiveDateTime = newRecord->dateTime; + + wvutilsLogEvent (PRI_STATUS, "storing record for %4.4d-%2.2d-%2.2d %2.2d:%2.2d", + wvutilsGetYear(newRecord->dateTime), + wvutilsGetMonth(newRecord->dateTime), + wvutilsGetDay(newRecord->dateTime), + wvutilsGetHour(newRecord->dateTime), + wvutilsGetMin(newRecord->dateTime)); + + if (dbsqliteArchiveStoreRecord(newRecord) == ERROR) + { + radMsgLog (PRI_MEDIUM, "daemonStoreArchiveRecord: dbsqliteArchiveStoreRecord failed!!!"); + emailAlertSend(ALERT_TYPE_FILE_IO); + return; + } + + // Check for flatline values: + daemonCheckArchiveRecord(newRecord); + + // if we are running normally (out of init), do normal activities: + if (wviewdWork.runningFlag) + { + // Check to see if a DST change has occured: + // Note: wvutilsDetectDSTChange can only be called once per process per + // DST event. + if (wvutilsDetectDSTChange() != WVUTILS_DST_NO_CHANGE) + { + radMsgLog (PRI_STATUS, + "DST change: scheduling station time update (if supported)"); + + // Update the time zone info: + tzset(); + + // Adjust station time: + stationSyncTime(&wviewdWork); + } + + // compute storm rain: + if (newRecord->value[DATA_INDEX_rain] > ARCHIVE_VALUE_NULL && + newRecord->value[DATA_INDEX_rainRate] > ARCHIVE_VALUE_NULL) + { + stormRainUpdate ((float)newRecord->value[DATA_INDEX_rainRate], + (float)newRecord->value[DATA_INDEX_rain]); + } + + // sync to sensors: + wviewdWork.loopPkt.stormRain = stormRainGet(); + wviewdWork.loopPkt.stormStart = stormRainGetStartTimeT(); + wviewdWork.loopPkt.dayRain = sensorGetCumulative(&wviewdWork.sensors.sensor[STF_DAY][SENSOR_RAIN]); + wviewdWork.loopPkt.monthRain = sensorGetCumulative(&wviewdWork.sensors.sensor[STF_MONTH][SENSOR_RAIN]); + wviewdWork.loopPkt.yearRain = sensorGetCumulative(&wviewdWork.sensors.sensor[STF_YEAR][SENSOR_RAIN]); + wviewdWork.loopPkt.dayET = sensorGetCumulative(&wviewdWork.sensors.sensor[STF_DAY][SENSOR_ET]); + wviewdWork.loopPkt.monthET = sensorGetCumulative(&wviewdWork.sensors.sensor[STF_MONTH][SENSOR_ET]); + wviewdWork.loopPkt.yearET = sensorGetCumulative(&wviewdWork.sensors.sensor[STF_YEAR][SENSOR_ET]); + + // send archive notification: + stationSendArchiveNotifications (&wviewdWork, (float)newRecord->value[DATA_INDEX_rain]); + } + + statusIncrementStat(WVIEW_STATS_ARCHIVE_PKTS_RX); + return; +} + +static void daemonArchiveIndication (ARCHIVE_PKT *newRecord) +{ + if (newRecord != NULL) + { + daemonStoreArchiveRecord (newRecord); + + // Push to internal clients: + stationPushArchiveToClients(&wviewdWork, newRecord); + } + else if (wviewdWork.stationGeneratesArchives) + { + emailAlertSend(ALERT_TYPE_STATION_ARCHIVE); + } + + return; +} + + +/* ... system initialization +*/ +static int daemonSysInit (WVIEWD_WORK *work) +{ + char temp[256]; + char *installPath; + struct stat fileData; + FILE *pidfile; + + /* ... create our run directory if it is not there + */ + sprintf (temp, "%s", WVIEW_RUN_DIR); + if (stat (temp, &fileData) != 0) + { + if (mkdir (temp, 0755) != 0) + { + radMsgLogInit (PROC_NAME_DAEMON, TRUE, TRUE); + radMsgLog (PRI_CATASTROPHIC, + "Cannot create run directory: %s - aborting!", + temp); + radMsgLogExit (); + return -1; + } + } + + /* ... create our device directory if it is not there + */ + sprintf (temp, "%s/dev", WVIEW_RUN_DIR); + if (stat (temp, &fileData) != 0) + { + if (mkdir (temp, 0755) != 0) + { + radMsgLogInit (PROC_NAME_DAEMON, TRUE, TRUE); + radMsgLog (PRI_CATASTROPHIC, + "Cannot create device directory: %s - aborting!", + temp); + radMsgLogExit (); + return -1; + } + } + + sprintf (work->pidFile, "%s/%s", WVIEW_RUN_DIR, WVD_LOCK_FILE_NAME); + sprintf (work->fifoFile, "%s/dev/%s", WVIEW_RUN_DIR, PROC_NAME_DAEMON); + sprintf (work->statusFile, "%s/%s", WVIEW_STATUS_DIRECTORY, WVIEW_STATUS_FILE_NAME); + + /* ... check for our pid file, don't run if it is there + */ + if (stat (work->pidFile, &fileData) == 0) + { + radMsgLogInit (PROC_NAME_DAEMON, TRUE, TRUE); + radMsgLog (PRI_CATASTROPHIC, + "lock file %s exists, older copy may be running - aborting!", + work->pidFile); + radMsgLogExit (); + return -1; + } + + return 0; +} + +/* ... system exit +*/ +static int daemonSysExit (WVIEWD_WORK *work) +{ + struct stat fileData; + + /* ... delete our pid file + */ + if (stat (work->pidFile, &fileData) == 0) + { + unlink (work->pidFile); + } + + return 0; +} + + +static void defaultSigHandler (int signum) +{ + int retVal; + + switch (signum) + { + case SIGHUP: + // user wants us to change the verbosity setting + retVal = wvutilsToggleVerbosity (); + radMsgLog (PRI_STATUS, "wviewd: SIGHUP - toggling log verbosity %s", + ((retVal == 0) ? "OFF" : "ON")); + + radProcessSignalCatch(signum, defaultSigHandler); + return; + + case SIGPIPE: + // we have a far end socket disconnection, we'll handle it in the + // "read/write" code + radProcessSignalCatch(signum, defaultSigHandler); + break; + + case SIGBUS: + case SIGFPE: + case SIGSEGV: + case SIGXFSZ: + case SIGSYS: + // unrecoverable radProcessSignalCatch- we must exit right now! + radMsgLog (PRI_CATASTROPHIC, "wviewd: recv sig %d: shutting down!", signum); + abort(); + + case SIGCHLD: + wvutilsWaitForChildren(); + radProcessSignalCatch(signum, defaultSigHandler); + return; + + default: + // we can allow the process to exit normally... + if (wviewdWork.exiting) + { + radProcessSignalCatch(signum, defaultSigHandler); + return; + } + + radMsgLog (PRI_HIGH, "wviewd: recv sig %d: exiting!", signum); + + wviewdWork.exiting = TRUE; + radProcessSetExitFlag (); + + radProcessSignalCatch(signum, defaultSigHandler); + break; + } + + return; +} + +static void msgHandler +( + char *srcQueueName, + UINT msgType, + void *msg, + UINT length, + void *userData +) +{ + if (msgType == WVIEW_MSG_TYPE_POLL) + { + WVIEW_MSG_POLL* pPoll = (WVIEW_MSG_POLL*)msg; + wvutilsSendPMONPollResponse (pPoll->mask, PMON_PROCESS_WVIEWD); + return; + } + + stationProcessIPM (&wviewdWork, srcQueueName, msgType, msg); + return; +} + +static void evtHandler +( + UINT eventsRx, + UINT rxData, + void *userData +) +{ + // bleed off our special events + if (eventsRx & STATION_INIT_COMPLETE_EVENT) + { + // call the init complete handler + if (daemonStationInitComplete(userData) == ERROR) + { + stationSendShutdown(&wviewdWork); + wviewdWork.exiting = TRUE; + return; + } + + eventsRx &= ~STATION_INIT_COMPLETE_EVENT; + } + + if (eventsRx & STATION_LOOP_COMPLETE_EVENT) + { + // call the loop readings complete handler + daemonStationLoopComplete (); + + eventsRx &= ~STATION_LOOP_COMPLETE_EVENT; + } + + return; +} + + +static void archiveTimerHandler (void *parm) +{ + ARCHIVE_PKT* newRec; + time_t ntime; + + // get the current time + ntime = time (NULL); + + // check to see if system time has changed + if (ntime < (wviewdWork.nextArchiveTime - 4)) + { + // time was set back since our last timer start - restart the timer + radMsgLog (PRI_MEDIUM, "archiveTimerHandler: system time has skewed, adjusting..."); + stationStartArchiveTimerUniform (&wviewdWork); + return; + } + + if (wviewdWork.stationGeneratesArchives) + { + // tell the station to generate an archive record + // (he will indicate it back to us) + stationGetArchive (&wviewdWork); + } + else + { + // generate it on our own + newRec = computedDataGenerateArchive(&wviewdWork); + if (newRec != NULL) + { + daemonStoreArchiveRecord(newRec); + + // Push to internal clients: + stationPushArchiveToClients(&wviewdWork, newRec); + } + else + { + radMsgLog (PRI_MEDIUM, "STATION: no new archive record generated " + "probably caused by not receiving any LOOP data"); + emailAlertSend(ALERT_TYPE_STATION_ARCHIVE); + } + } + + // Update computed values: + computedDataUpdate (&wviewdWork); + + // clear for the next archive period: + computedDataClearInterval (&wviewdWork); + + // restart the timer + stationStartArchiveTimerUniform (&wviewdWork); + return; +} + +static void cdtimerHandler (void *parm) +{ + // tell the station to acquire data + stationGetReadings (&wviewdWork); + + // restart the timer + stationStartCDataTimerUniform (&wviewdWork); + return; +} + +static void pushTimerHandler (void *parm) +{ + // ... send to clients + stationPushDataToClients (&wviewdWork); + return; +} + +static void syncTimerHandler (void *parm) +{ + if (stationStartSyncTimerUniform(&wviewdWork, FALSE) == TRUE) + { + // tell the station to synchronize the station time (if required) + stationSyncTime (&wviewdWork); + } + + return; +} + +static void ifTimerHandler (void *parm) +{ + // we just pass through the IF timer to the station-specific indication + stationIFTimerExpiry (&wviewdWork); + + return; +} + +static void stationDataCallback (int fd, void *userData) +{ + // we just indicate the IF data to the station-specific function + stationDataIndicate (&wviewdWork); + + return; +} + + +/* ... the main entry point for the daemon process +*/ +int main (int argc, char *argv[]) +{ + void (*alarmHandler)(int); + FILE *pidfile; + int iValue; + double dValue; + const char* sValue; + int runAsDaemon = TRUE; + + if (argc > 1) + { + if (!strcmp(argv[1], "-f")) + { + runAsDaemon = FALSE; + } + } + + /* ... start with a clean slate + */ + memset (&wviewdWork, 0, sizeof (wviewdWork)); + + /* ... initialize some system stuff first + */ + if (daemonSysInit (&wviewdWork) == -1) + { + radMsgLogInit (PROC_NAME_DAEMON, TRUE, TRUE); + radMsgLog (PRI_CATASTROPHIC, "system init failed!\n"); + radMsgLogExit (); + exit (1); + } + + + /* ... call the global radlib system init function + */ + if (radSystemInit (WVIEW_SYSTEM_ID) == ERROR) + { + radMsgLogInit (PROC_NAME_DAEMON, TRUE, TRUE); + radMsgLog (PRI_CATASTROPHIC, "radSystemInit failed!"); + radMsgLogExit (); + exit (1); + } + + + /* ... call the radlib process init function + */ + if (radProcessInit (PROC_NAME_DAEMON, + wviewdWork.fifoFile, + PROC_NUM_TIMERS_DAEMON, + runAsDaemon, // TRUE for daemon + msgHandler, + evtHandler, + NULL) + == ERROR) + { + printf ("\nradProcessInit failed: %s\n\n", PROC_NAME_DAEMON); + radSystemExit (WVIEW_SYSTEM_ID); + exit (1); + } + + wviewdWork.myPid = getpid (); + pidfile = fopen (wviewdWork.pidFile, "w"); + if (pidfile == NULL) + { + radMsgLog (PRI_CATASTROPHIC, "lock file create failed!\n"); + radProcessExit (); + radSystemExit (WVIEW_SYSTEM_ID); + exit (1); + } + fprintf (pidfile, "%d", getpid ()); + fclose (pidfile); + + + alarmHandler = radProcessSignalGetHandler (SIGALRM); + radProcessSignalCatchAll (defaultSigHandler); + radProcessSignalCatch (SIGALRM, alarmHandler); + radProcessSignalRelease(SIGABRT); + + + radMsgLog (PRI_STATUS, "%s starting ...", globalWviewVersionStr); + radTimeGetMSSinceEpoch(); + wvutilsDetectDSTInit(); + + // get our configuration values: + if (wvconfigInit(TRUE) == ERROR) + { + radMsgLog (PRI_CATASTROPHIC, "config database is missing!!!\n"); + daemonSysExit (&wviewdWork); + radProcessExit (); + radSystemExit (WVIEW_SYSTEM_ID); + exit (1); + } + + // get the wview verbosity setting + if (wvutilsSetVerbosity (WV_VERBOSE_WVIEWD) == ERROR) + { + wvconfigExit(); + radMsgLog (PRI_CATASTROPHIC, "wvutilsSetVerbosity failed!"); + daemonSysExit (&wviewdWork); + radProcessExit (); + radSystemExit (WVIEW_SYSTEM_ID); + exit (1); + } + + ///// STATION_INTERFACE PROCESSING BEGIN ///// + sValue = wvconfigGetStringValue(configItem_STATION_STATION_TYPE); + if (sValue == NULL) + { + radMsgLog (PRI_MEDIUM, + "no station type given, defaulting to 'VantagePro'..."); + strcpy (wviewdWork.stationType, "VantagePro"); + } + else + { + wvstrncpy(wviewdWork.stationType, sValue, sizeof(wviewdWork.stationType)); + } + + if ((!strcmp(wviewdWork.stationType, "WMRUSB")) || + (!strcmp(wviewdWork.stationType, "WH1080"))) + { + // USB stations: + radMsgLog (PRI_MEDIUM, + "station interface: native USB ..."); + } + else + { + sValue = wvconfigGetStringValue(configItem_STATION_STATION_INTERFACE); + if (sValue == NULL) + { + radMsgLog (PRI_MEDIUM, + "no station interface given, defaulting to 'serial'..."); + strcpy (wviewdWork.stationInterface, "serial"); + } + else + { + wvstrncpy(wviewdWork.stationInterface, sValue, sizeof(wviewdWork.stationInterface)); + } + + // grab the Weatherlink retrieve archives flag: + iValue = wvconfigGetBooleanValue(configItem_STATION_STATION_RETRIEVE_ARCHIVE); + if (iValue >= 0) + { + wviewdWork.stationGeneratesArchives = iValue; + radMsgLog (PRI_MEDIUM, + "station %s archive records", + ((iValue) ? "generates" : "does not generate")); + } + else + { + // Default to the typical scenario. Stations not supporting archives + // will overwrite it. + wviewdWork.stationGeneratesArchives = TRUE; + } + + // process the interface type + if (!strcmp (wviewdWork.stationInterface, "serial")) + { + radMsgLog (PRI_MEDIUM, + "station interface: serial ..."); + + // we need a device name for serial IFs + sValue = wvconfigGetStringValue(configItem_STATION_STATION_DEV); + if (sValue == NULL) + { + wvconfigExit(); + radMsgLog (PRI_CATASTROPHIC, + "no serial device given, aborting..."); + daemonSysExit (&wviewdWork); + radProcessExit (); + radSystemExit (WVIEW_SYSTEM_ID); + exit (1); + } + else + { + wvstrncpy(wviewdWork.stationDevice, sValue, sizeof(wviewdWork.stationDevice)); + } + + // grab the DTR toggle flag: + iValue = wvconfigGetBooleanValue(configItem_STATION_STATION_DTR); + if (iValue >= 0) + { + wviewdWork.stationToggleDTR = iValue; + } + else + { + wviewdWork.stationToggleDTR = TRUE; + } + } + else if (!strcmp (wviewdWork.stationInterface, "ethernet")) + { + radMsgLog (PRI_MEDIUM, + "station interface: ethernet ..."); + + // we need host and port for ethernet + sValue = wvconfigGetStringValue(configItem_STATION_STATION_HOST); + if (sValue == NULL) + { + wvconfigExit(); + radMsgLog (PRI_CATASTROPHIC, + "no hostname given, aborting..."); + daemonSysExit (&wviewdWork); + radProcessExit (); + radSystemExit (WVIEW_SYSTEM_ID); + exit (1); + } + else + { + wvstrncpy(wviewdWork.stationHost, sValue, sizeof(wviewdWork.stationHost)); + + iValue = wvconfigGetINTValue(configItem_STATION_STATION_PORT); + if (iValue <= 0) + { + wvconfigExit(); + radMsgLog (PRI_CATASTROPHIC, + "no port given, aborting..."); + daemonSysExit (&wviewdWork); + radProcessExit (); + radSystemExit (WVIEW_SYSTEM_ID); + exit (1); + } + else + { + wviewdWork.stationPort = iValue; + + // grab the Weatherlink IP flag: + iValue = wvconfigGetBooleanValue(configItem_STATION_STATION_WLIP); + if (iValue >= 0) + { + wviewdWork.stationIsWLIP = iValue; + } + else + { + wviewdWork.stationIsWLIP = FALSE; + } + } + } + } + else + { + // invalid type specified - abort + wvconfigExit(); + radMsgLog (PRI_CATASTROPHIC, + "invalid STATION_INTERFACE %s given, aborting...", + wviewdWork.stationInterface); + daemonSysExit (&wviewdWork); + radProcessExit (); + radSystemExit (WVIEW_SYSTEM_ID); + exit (1); + } + } + ///// STATION_INTERFACE PROCESSING END ///// + + iValue = wvconfigGetINTValue(configItem_STATION_STATION_RAIN_SEASON_START); + if (iValue <= 0) + { + radMsgLog (PRI_MEDIUM, "Rain Season Start Month not found - defaulting to 1 (JAN)...\n"); + wviewdWork.stationRainSeasonStart = 1; + } + else + { + wviewdWork.stationRainSeasonStart = iValue; + if (wviewdWork.stationRainSeasonStart < 1 || + wviewdWork.stationRainSeasonStart > 12) + { + radMsgLog (PRI_MEDIUM, "Invalid Rain Season Start Month %d found - defaulting to 1 (JAN)...\n", + wviewdWork.stationRainSeasonStart); + wviewdWork.stationRainSeasonStart = 1; + } + else + { + radMsgLog (PRI_STATUS, "Rain Season Start Month set to %d\n", + wviewdWork.stationRainSeasonStart); + } + } + + dValue = wvconfigGetDOUBLEValue(configItem_STATION_STATION_RAIN_STORM_TRIGGER_START); + if (dValue <= 0.0) + { + radMsgLog (PRI_MEDIUM, + "no rain storm start trigger given, defaulting to 0.05 in/hr..."); + wviewdWork.stationRainStormTrigger = 0.05; + } + else + { + wviewdWork.stationRainStormTrigger = (float)dValue; + radMsgLog (PRI_STATUS, "Rain Storm Start Trigger set to %5.2f in/hr\n", + wviewdWork.stationRainStormTrigger); + } + + iValue = wvconfigGetINTValue(configItem_STATION_STATION_RAIN_STORM_IDLE_STOP); + if (iValue <= 0) + { + radMsgLog (PRI_MEDIUM, + "no rain storm idle stop time given, defaulting to 12 hours..."); + wviewdWork.stationRainStormIdleHours = 12; + } + else + { + wviewdWork.stationRainStormIdleHours = iValue; + radMsgLog (PRI_STATUS, "Rain Storm Stop Time set to %d hours\n", + wviewdWork.stationRainStormIdleHours); + } + + dValue = wvconfigGetDOUBLEValue(configItem_STATION_STATION_RAIN_YTD); + if (dValue < 0.0) + { + radMsgLog (PRI_MEDIUM, + "no rain YTD preset given, defaulting to 0.00 inches..."); + wviewdWork.stationRainStormIdleHours = 12; + } + else + { + wviewdWork.stationRainYTDPreset = (float)dValue; + radMsgLog (PRI_STATUS, "Rain YTD preset set to %.2f inches\n", + wviewdWork.stationRainYTDPreset); + } + + dValue = wvconfigGetDOUBLEValue(configItem_STATION_STATION_ET_YTD); + if (dValue < 0.0) + { + radMsgLog (PRI_MEDIUM, + "no ET YTD preset given, defaulting to 0.000 inches..."); + wviewdWork.stationETYTDPreset = 0; + } + else + { + wviewdWork.stationETYTDPreset = (float)dValue; + radMsgLog (PRI_STATUS, "ET YTD preset set to %.3f inches\n", + wviewdWork.stationETYTDPreset); + } + + iValue = wvconfigGetINTValue(configItem_STATION_STATION_RAIN_ET_YTD_YEAR); + if (iValue < 0) + { + radMsgLog (PRI_MEDIUM, + "no rain/ET YTD Year given, disabling..."); + wviewdWork.stationRainETPresetYear = 0; + } + else + { + wviewdWork.stationRainETPresetYear = iValue; + if (wviewdWork.stationRainETPresetYear < 2000 || + wviewdWork.stationRainETPresetYear > 3000) + { + radMsgLog (PRI_MEDIUM, + "bad rain/ET YTD Year given, disabling..."); + wviewdWork.stationRainETPresetYear = 0; + } + else + { + radMsgLog (PRI_STATUS, "rain/ET YTD preset Year set to %d\n", + wviewdWork.stationRainETPresetYear); + } + } + + iValue = wvconfigGetINTValue(configItem_STATION_POLL_INTERVAL); + if (iValue < 0) + { + radMsgLog (PRI_MEDIUM, + "no POLL_INTERVAL retrieved, setting to 30 seconds..."); + wviewdWork.cdataInterval = 30000; + } + else + { + wviewdWork.cdataInterval = iValue * 1000; + } + + if (((wviewdWork.cdataInterval % 1000) != 0) || + ((wviewdWork.cdataInterval/1000) > 60) || + ((60 % (wviewdWork.cdataInterval/1000)) != 0)) + { + radMsgLog (PRI_MEDIUM, + "station polling interval %d found in wview.conf is invalid:", + wviewdWork.cdataInterval); + radMsgLog (PRI_MEDIUM, + "defaulting to 30 seconds ..."); + radMsgLog (PRI_MEDIUM, + "Note: station polling interval must be less than 60 seconds"); + radMsgLog (PRI_MEDIUM, + " and an even divisor of 60 seconds (10000, 15000, 30000)"); + wviewdWork.cdataInterval = 30 * 1000; + } + else + { + radMsgLog (PRI_STATUS, "station polling interval set to %d seconds", + (wviewdWork.cdataInterval/1000)); + } + + iValue = wvconfigGetINTValue(configItem_STATION_PUSH_INTERVAL); + if (iValue < 0) + { + radMsgLog (PRI_MEDIUM, + "no PUSH_INTERVAL retrieved, setting to 60 seconds..."); + wviewdWork.pushInterval = 60000; + } + else + { + wviewdWork.pushInterval = iValue * 1000; + } + + + // Calibration configuration: + dValue = wvconfigGetDOUBLEValue(configItemCAL_MULT_BAROMETER); + if (dValue <= 0.0) + { + wviewdWork.calMBarometer = 1.00; + } + else + { + wviewdWork.calMBarometer = dValue; + } + dValue = wvconfigGetDOUBLEValue(configItemCAL_CONST_BAROMETER); + wviewdWork.calCBarometer = dValue; + + dValue = wvconfigGetDOUBLEValue(configItemCAL_MULT_PRESSURE); + if (dValue <= 0.0) + { + wviewdWork.calMPressure = 1.00; + } + else + { + wviewdWork.calMPressure = dValue; + } + dValue = wvconfigGetDOUBLEValue(configItemCAL_CONST_PRESSURE); + wviewdWork.calCPressure = dValue; + + dValue = wvconfigGetDOUBLEValue(configItemCAL_MULT_ALTIMETER); + if (dValue <= 0.0) + { + wviewdWork.calMAltimeter = 1.00; + } + else + { + wviewdWork.calMAltimeter = dValue; + } + dValue = wvconfigGetDOUBLEValue(configItemCAL_CONST_ALTIMETER); + wviewdWork.calCAltimeter = dValue; + + dValue = wvconfigGetDOUBLEValue(configItemCAL_MULT_INTEMP); + if (dValue <= 0.0) + { + wviewdWork.calMInTemp = 1.00; + } + else + { + wviewdWork.calMInTemp = dValue; + } + dValue = wvconfigGetDOUBLEValue(configItemCAL_CONST_INTEMP); + wviewdWork.calCInTemp = dValue; + + dValue = wvconfigGetDOUBLEValue(configItemCAL_MULT_OUTTEMP); + if (dValue <= 0.0) + { + wviewdWork.calMOutTemp = 1.00; + } + else + { + wviewdWork.calMOutTemp = dValue; + } + dValue = wvconfigGetDOUBLEValue(configItemCAL_CONST_OUTTEMP); + wviewdWork.calCOutTemp = dValue; + + dValue = wvconfigGetDOUBLEValue(configItemCAL_MULT_INHUMIDITY); + if (dValue <= 0.0) + { + wviewdWork.calMInHumidity = 1.00; + } + else + { + wviewdWork.calMInHumidity = dValue; + } + dValue = wvconfigGetDOUBLEValue(configItemCAL_CONST_INHUMIDITY); + wviewdWork.calCInHumidity = dValue; + + dValue = wvconfigGetDOUBLEValue(configItemCAL_MULT_OUTHUMIDITY); + if (dValue <= 0.0) + { + wviewdWork.calMOutHumidity = 1.00; + } + else + { + wviewdWork.calMOutHumidity = dValue; + } + dValue = wvconfigGetDOUBLEValue(configItemCAL_CONST_OUTHUMIDITY); + wviewdWork.calCOutHumidity = dValue; + + dValue = wvconfigGetDOUBLEValue(configItemCAL_MULT_WINDSPEED); + if (dValue <= 0.0) + { + wviewdWork.calMWindSpeed = 1.00; + } + else + { + wviewdWork.calMWindSpeed = dValue; + } + dValue = wvconfigGetDOUBLEValue(configItemCAL_CONST_WINDSPEED); + wviewdWork.calCWindSpeed = dValue; + + dValue = wvconfigGetDOUBLEValue(configItemCAL_MULT_WINDDIR); + if (dValue <= 0.0) + { + wviewdWork.calMWindDir = 1.00; + } + else + { + wviewdWork.calMWindDir = dValue; + } + dValue = wvconfigGetDOUBLEValue(configItemCAL_CONST_WINDDIR); + wviewdWork.calCWindDir = dValue; + + dValue = wvconfigGetDOUBLEValue(configItemCAL_MULT_RAIN); + if (dValue <= 0.0) + { + wviewdWork.calMRain = 1.00; + } + else + { + wviewdWork.calMRain = dValue; + } + dValue = wvconfigGetDOUBLEValue(configItemCAL_CONST_RAIN); + wviewdWork.calCRain = dValue; + + dValue = wvconfigGetDOUBLEValue(configItemCAL_MULT_RAINRATE); + if (dValue <= 0.0) + { + wviewdWork.calMRainRate = 1.00; + } + else + { + wviewdWork.calMRainRate = dValue; + } + dValue = wvconfigGetDOUBLEValue(configItemCAL_CONST_RAINRATE); + wviewdWork.calCRainRate = dValue; + + iValue = wvconfigGetBooleanValue(configItem_ENABLE_EMAIL); + if (iValue >= 0) + { + wviewdWork.IsAlertEmailsEnabled = iValue; + } + if (wviewdWork.IsAlertEmailsEnabled) + { + sValue = wvconfigGetStringValue(configItem_TO_EMAIL_ADDRESS); + if (sValue == NULL) + { + radMsgLog (PRI_HIGH, "NO alert email TO address given - disabling email alerts..."); + wviewdWork.IsAlertEmailsEnabled = 0; + } + else + { + wvstrncpy (wviewdWork.alertEmailToAdrs, sValue, sizeof(wviewdWork.alertEmailToAdrs)); + } + sValue = wvconfigGetStringValue(configItem_FROM_EMAIL_ADDRESS); + if (sValue == NULL) + { + radMsgLog (PRI_HIGH, "NO alert email FROM address given - disabling email alerts..."); + wviewdWork.IsAlertEmailsEnabled = 0; + } + else + { + wvstrncpy (wviewdWork.alertEmailFromAdrs, sValue, sizeof(wviewdWork.alertEmailFromAdrs)); + } + iValue = wvconfigGetBooleanValue(configItem_SEND_TEST_EMAIL); + if (iValue >= 0) + { + wviewdWork.IsTestEmailEnabled = iValue; + } + } + iValue = wvconfigGetBooleanValue(configItem_HTMLGEN_STATION_SHOW_IF); + if (iValue >= 0) + { + wviewdWork.showStationIF = iValue; + } + else + { + wviewdWork.showStationIF = TRUE; + } + + wvconfigExit (); + + if (statusInit(wviewdWork.statusFile, wviewStatusLabels) == ERROR) + { + radMsgLog (PRI_HIGH, "statusInit failed - exiting..."); + daemonSysExit (&wviewdWork); + radProcessExit (); + radSystemExit (WVIEW_SYSTEM_ID); + exit (1); + } + statusUpdate(STATUS_BOOTING); + + // ... Initialize the archive database interface: + if (dbsqliteArchiveInit() == ERROR) + { + radMsgLog (PRI_HIGH, "dbsqliteArchiveInit failed"); + statusUpdateMessage("dbsqliteArchiveInit failed"); + statusUpdate(STATUS_ERROR); + daemonSysExit (&wviewdWork); + radProcessExit (); + radSystemExit (WVIEW_SYSTEM_ID); + exit (1); + } + + + // Initialize timers: + wviewdWork.archiveTimer = radTimerCreate (NULL, archiveTimerHandler, NULL); + if (wviewdWork.archiveTimer == NULL) + { + radMsgLog (PRI_HIGH, "radTimerCreate failed"); + statusUpdateMessage("radTimerCreate failed"); + statusUpdate(STATUS_ERROR); + daemonSysExit (&wviewdWork); + radProcessExit (); + radSystemExit (WVIEW_SYSTEM_ID); + exit (1); + } + + wviewdWork.cdataTimer = radTimerCreate (NULL, cdtimerHandler, NULL); + if (wviewdWork.cdataTimer == NULL) + { + radMsgLog (PRI_HIGH, "radTimerCreate failed"); + statusUpdateMessage("radTimerCreate failed"); + statusUpdate(STATUS_ERROR); + radTimerDelete (wviewdWork.archiveTimer); + daemonSysExit (&wviewdWork); + radProcessExit (); + radSystemExit (WVIEW_SYSTEM_ID); + exit (1); + } + + wviewdWork.pushTimer = radTimerCreate (NULL, pushTimerHandler, NULL); + if (wviewdWork.pushTimer == NULL) + { + radMsgLog (PRI_HIGH, "radTimerCreate failed"); + statusUpdateMessage("radTimerCreate failed"); + statusUpdate(STATUS_ERROR); + radTimerDelete (wviewdWork.cdataTimer); + radTimerDelete (wviewdWork.archiveTimer); + daemonSysExit (&wviewdWork); + radProcessExit (); + radSystemExit (WVIEW_SYSTEM_ID); + exit (1); + } + + wviewdWork.syncTimer = radTimerCreate (NULL, syncTimerHandler, NULL); + if (wviewdWork.syncTimer == NULL) + { + radMsgLog (PRI_HIGH, "sync radTimerCreate failed"); + statusUpdateMessage("radTimerCreate failed"); + statusUpdate(STATUS_ERROR); + radTimerDelete (wviewdWork.cdataTimer); + radTimerDelete (wviewdWork.pushTimer); + radTimerDelete (wviewdWork.archiveTimer); + daemonSysExit (&wviewdWork); + radProcessExit (); + radSystemExit (WVIEW_SYSTEM_ID); + exit (1); + } + + wviewdWork.ifTimer = radTimerCreate (NULL, ifTimerHandler, NULL); + if (wviewdWork.ifTimer == NULL) + { + radMsgLog (PRI_HIGH, "sync radTimerCreate failed"); + statusUpdateMessage("radTimerCreate failed"); + statusUpdate(STATUS_ERROR); + radTimerDelete (wviewdWork.syncTimer); + radTimerDelete (wviewdWork.cdataTimer); + radTimerDelete (wviewdWork.pushTimer); + radTimerDelete (wviewdWork.archiveTimer); + daemonSysExit (&wviewdWork); + radProcessExit (); + radSystemExit (WVIEW_SYSTEM_ID); + exit (1); + } + + radProcessEventsAdd (STATION_INIT_COMPLETE_EVENT); + radProcessEventsAdd (STATION_LOOP_COMPLETE_EVENT); + + // register with the radlib message router + if (radMsgRouterInit (WVIEW_RUN_DIR) == ERROR) + { + radMsgLog (PRI_HIGH, "radMsgRouterInit failed!"); + statusUpdateMessage("radMsgRouterInit failed"); + statusUpdate(STATUS_ERROR); + radTimerDelete (wviewdWork.ifTimer); + radTimerDelete (wviewdWork.syncTimer); + radTimerDelete (wviewdWork.cdataTimer); + radTimerDelete (wviewdWork.pushTimer); + radTimerDelete (wviewdWork.archiveTimer); + daemonSysExit (&wviewdWork); + radProcessExit (); + radSystemExit (WVIEW_SYSTEM_ID); + exit (1); + } + + // enable message reception from the radlib router for worker requests + radMsgRouterMessageRegister (WVIEW_MSG_TYPE_REQUEST); + + // enable message reception from the radlib router for POLL msgs + radMsgRouterMessageRegister (WVIEW_MSG_TYPE_POLL); + + // enable message reception from the radlib router for ALERT msgs + radMsgRouterMessageRegister (WVIEW_MSG_TYPE_ALERT); + + // enable message reception from the radlib router for STATION_DATA msgs + radMsgRouterMessageRegister (WVIEW_MSG_TYPE_STATION_DATA); + + + // Initialize the HILOW database interface: + // (this cannot occur before the MsgRouter is initialized) + if (dbsqliteHiLowInit(TRUE) == ERROR) + { + radMsgLog (PRI_HIGH, "dbsqliteHiLowInit failed"); + statusUpdateMessage("dbsqliteHiLowInit failed"); + statusUpdate(STATUS_ERROR); + stationSendShutdown(&wviewdWork); + radMsgRouterExit (); + radTimerDelete (wviewdWork.ifTimer); + radTimerDelete (wviewdWork.syncTimer); + radTimerDelete (wviewdWork.cdataTimer); + radTimerDelete (wviewdWork.pushTimer); + radTimerDelete (wviewdWork.archiveTimer); + daemonSysExit (&wviewdWork); + radProcessExit (); + radSystemExit (WVIEW_SYSTEM_ID); + exit (1); + } + + + // initialize the station abstraction + radMsgLog (PRI_STATUS, "-- Station Init Start --"); + if (stationInit (&wviewdWork, daemonArchiveIndication) == ERROR) + { + radMsgLog (PRI_HIGH, "stationInit failed!"); + statusUpdateMessage("stationInit failed"); + statusUpdate(STATUS_ERROR); + stationSendShutdown(&wviewdWork); + radMsgRouterExit (); + radTimerDelete (wviewdWork.ifTimer); + radTimerDelete (wviewdWork.syncTimer); + radTimerDelete (wviewdWork.cdataTimer); + radTimerDelete (wviewdWork.pushTimer); + radTimerDelete (wviewdWork.archiveTimer); + daemonSysExit (&wviewdWork); + radProcessExit (); + radSystemExit (WVIEW_SYSTEM_ID); + exit (1); + } + + // register the station interface if it is device-based: + if (wviewdWork.medium.type == MEDIUM_TYPE_DEVICE) + { + if (radProcessIORegisterDescriptor (wviewdWork.medium.fd, + stationDataCallback, + NULL) + == ERROR) + { + radMsgLog (PRI_HIGH, "IORegDescriptor failed"); + statusUpdateMessage("IORegDescriptor failed"); + statusUpdate(STATUS_ERROR); + stationSendShutdown(&wviewdWork); + radMsgRouterExit (); + radTimerDelete (wviewdWork.ifTimer); + radTimerDelete (wviewdWork.syncTimer); + radTimerDelete (wviewdWork.cdataTimer); + radTimerDelete (wviewdWork.pushTimer); + radTimerDelete (wviewdWork.archiveTimer); + stationExit (&wviewdWork); + daemonSysExit (&wviewdWork); + radProcessExit (); + radSystemExit (WVIEW_SYSTEM_ID); + exit (1); + } + } + + // Send test email if it is enabled: + if (wviewdWork.IsTestEmailEnabled) + { + radMsgLog(PRI_STATUS, "Sending test email..."); + emailAlertSend(ALERT_TYPE_TEST); + } + + + statusUpdate(STATUS_RUNNING); + statusUpdateMessage("Normal operation"); + radMsgLog (PRI_STATUS, "running..."); + + + while (!wviewdWork.exiting) + { + // wait on timers, events, file descriptors, msgs + if (radProcessWait (0) == ERROR) + { + wviewdWork.exiting = TRUE; + } + } + + + statusUpdateMessage("exiting normally"); + radMsgLog (PRI_STATUS, "exiting normally..."); + statusUpdate(STATUS_SHUTDOWN); + + computedDataExit (&wviewdWork); + radMsgRouterExit (); + radTimerDelete (wviewdWork.ifTimer); + radTimerDelete (wviewdWork.syncTimer); + radTimerDelete (wviewdWork.pushTimer); + radTimerDelete (wviewdWork.cdataTimer); + radTimerDelete (wviewdWork.archiveTimer); + stationExit (&wviewdWork); + dbsqliteHiLowExit(); + dbsqliteArchiveExit(); + daemonSysExit (&wviewdWork); + radProcessExit (); + radSystemExit (WVIEW_SYSTEM_ID); + exit (0); +} + +// Retrieve exit status: +int wviewdIsExiting(void) +{ + return wviewdWork.exiting; +} + diff --git a/stations/common/daemon.h b/stations/common/daemon.h new file mode 100755 index 0000000..10db334 --- /dev/null +++ b/stations/common/daemon.h @@ -0,0 +1,218 @@ +#ifndef INC_daemonh +#define INC_daemonh +/*--------------------------------------------------------------------------- + + FILENAME: + daemon.h + + PURPOSE: + Provide the wview daemon definitions. + + REVISION HISTORY: + Date Engineer Revision Remarks + 08/14/03 M.S. Teel 0 Original + + NOTES: + + + LICENSE: + Copyright (c) 2004, Mark S. Teel (mark@teel.ws) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +/* ... System include files +*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* ... Library include files +*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* ... Local include files +*/ +#include +#include +#include +#include +#include +#include + + +/* !!!!!!!!!!!!!!!!!! HIDDEN, NOT FOR API USE !!!!!!!!!!!!!!!!!! +*/ + +#define WVIEW_MAX_PATH 256 + +typedef enum +{ + WVIEW_STATS_LOOP_PKTS_RX = 0, + WVIEW_STATS_ARCHIVE_PKTS_RX +} WVIEW_STATS; + + +// define the weather station interface abstraction +#define WV_QUEUE_INPUT 1 +#define WV_QUEUE_OUTPUT 2 + +typedef enum +{ + MEDIUM_TYPE_NONE = 1, // For simulator + MEDIUM_TYPE_DEVICE, // serial and ethernet devices + MEDIUM_TYPE_USBHID // USB HID interfaces +} WVIEW_MEDIUM_TYPE; + +typedef struct _wview_medium +{ + WVIEW_MEDIUM_TYPE type; + void *workData; + + // Only some of these are valid for a given medium type: + // MEDIUM_TYPE_DEVICE + int fd; + int (*init) (struct _wview_medium *medium, char *deviceName); + void (*exit) (struct _wview_medium *medium); + int (*restart) (struct _wview_medium *medium); + int (*read) (struct _wview_medium *medium, void *bfr, int len, int timeout); + int (*write) (struct _wview_medium *medium, void *buffer, int length); + void (*flush) (struct _wview_medium *medium, int queue); + void (*txdrain) (struct _wview_medium *medium); + RADSOCK_ID (*getsocket) (struct _wview_medium *medium); + + // MEDIUM_TYPE_USBHID + hid_device* hidDevice; + int (*usbhidInit)(struct _wview_medium *medium); + void (*usbhidExit)(struct _wview_medium *medium); + int (*usbhidRead) (struct _wview_medium *medium, void *bfr, int len, int timeout); + int (*usbhidReadSpecial) (struct _wview_medium *medium, void *bfr, int len, int timeout); + int (*usbhidWrite) (struct _wview_medium *medium, void *buffer, int length); + +} WVIEW_MEDIUM; + + +// 4 hours in ms +#define WVD_TIME_SYNC_INTERVAL (4L * 60L * 60L * 1000L) + +// initial console wakeup number of attempts +#define WVD_INITIAL_WAKEUP_TRIES 20 + +// time to wait before attempting read recovery +#define WVD_READ_RECOVER_INTERVAL 2500L + +#define WVD_READ_RECOVER_MAX_RETRIES 1000 + +// Number of archive intervals in 4 hours (flat line base values will trigger an alert): +#define WVD_FLATLINE_THRESHOLD(archiveInterval) (240/archiveInterval) + +// the wview daemon work area +typedef struct +{ + pid_t myPid; + WVIEW_MEDIUM medium; + void *stationData; // station-specific data store + int runningFlag; + RAD_THREAD_ID threadId; // Non-NULL if station uses threads + + int stationGeneratesArchives; + char pidFile[WVIEW_MAX_PATH]; + char fifoFile[WVIEW_MAX_PATH]; + char statusFile[WVIEW_MAX_PATH]; + char stationType[64]; + char stationInterface[16]; + char stationDevice[WVIEW_MAX_PATH]; + char stationHost[256]; + int stationPort; + int stationIsWLIP; + int stationToggleDTR; + int stationRainSeasonStart; + float stationRainStormTrigger; + int stationRainStormIdleHours; + float stationRainYTDPreset; + float stationETYTDPreset; + int stationRainETPresetYear; + uint16_t archiveInterval; + int16_t latitude; + int16_t longitude; + int16_t elevation; + time_t archiveDateTime; + time_t nextArchiveTime; // detect system clock changes + TIMER_ID archiveTimer; + TIMER_ID cdataTimer; + TIMER_ID pushTimer; + TIMER_ID syncTimer; + TIMER_ID ifTimer; + uint32_t cdataInterval; + uint32_t pushInterval; + SENSOR_STORE sensors; + LOOP_PKT loopPkt; + LOOP_PKT lastLoopPkt; + int numReadRetries; + int archiveRqstPending; + + // Calibration: + float calMBarometer; + float calCBarometer; + float calMPressure; + float calCPressure; + float calMAltimeter; + float calCAltimeter; + float calMInTemp; + float calCInTemp; + float calMOutTemp; + float calCOutTemp; + float calMInHumidity; + float calCInHumidity; + float calMOutHumidity; + float calCOutHumidity; + float calMWindSpeed; + float calCWindSpeed; + float calMWindDir; + float calCWindDir; + float calMRain; + float calCRain; + float calMRainRate; + float calCRainRate; + + // Alert Emails: + int IsAlertEmailsEnabled; + char alertEmailFromAdrs[WVIEW_STRING1_SIZE]; + char alertEmailToAdrs[WVIEW_STRING1_SIZE]; + int IsTestEmailEnabled; + + int showStationIF; + + int exiting; + +} WVIEWD_WORK; + + +/* !!!!!!!!!!!!!!!!!!!! END HIDDEN SECTION !!!!!!!!!!!!!!!!!!!!! +*/ + +// Retrieve exit status: +extern int wviewdIsExiting(void); + + +#endif + diff --git a/stations/common/ethernet.c b/stations/common/ethernet.c new file mode 100755 index 0000000..96ff30c --- /dev/null +++ b/stations/common/ethernet.c @@ -0,0 +1,390 @@ +/*--------------------------------------------------------------------------- + + FILENAME: + ethernet.c + + PURPOSE: + Provide utilities for station communication over ethernet via a + transparent port capable terminal server. + + REVISION HISTORY: + Date Engineer Revision Remarks + 06/09/2005 M.S. Teel 0 Original + + NOTES: + wview medium-specific routines to be supplied: + + xxxMediumInit - sets up function pointers and work area + xxxInit - opens the interface and configures it + xxxRead - blocking read until specified bytes are read + xxxWrite - write on medium + xxxExit - cleanup and close interface + xxxFlush - flush the input buffer from the VP + xxxDrain - wait/block until the transmit buffer is empty + + See daemon.h for details of the WVIEW_MEDIUM structure. + + LICENSE: + Copyright (c) 2005, Mark S. Teel (mark@teel.ws) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +/* ... System include files +*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/* ... Library include files +*/ +#include +#include + +/* ... Local include files +*/ +#include +#include +#include +#include + +/* ... global memory declarations +*/ + +/* ... local memory +*/ + +static int IsBlocking(RADSOCK_ID id) +{ + int flags; + + if ((flags = fcntl(id->sockfd, F_GETFL, 0)) < 0) + { + return FALSE; + } + + if ((flags & O_NONBLOCK) != 0) + { + return FALSE; + } + else + { + return TRUE; + } +} + + + +////////////////////////////////////////////////////////////////////////////// +// ... medium callback functions +////////////////////////////////////////////////////////////////////////////// + +static int ethernetInit (WVIEW_MEDIUM *med, char *deviceName) +{ + MEDIUM_ETHERNET *eth = (MEDIUM_ETHERNET *)med->workData; + + radMsgLog (PRI_STATUS, "ethernetInit: connecting to station @ %s:%d ...", + eth->host, eth->port); + eth->sockId = radSocketClientCreate (eth->host, eth->port); + if (eth->sockId == NULL) + { + radMsgLog (PRI_HIGH, "ethernetInit: radSocketClientCreate failed for %s:%d", + eth->host, eth->port); + return ERROR; + } + + // make the socket non-blocking + if (radSocketSetBlocking (eth->sockId, FALSE) == ERROR) + { + radMsgLog (PRI_HIGH, "ethernetInit: radSocketSetBlocking failed for %s:%d", + eth->host, eth->port); + radSocketDestroy (eth->sockId); + eth->sockId = NULL; + return ERROR; + } + + // set this so the upper layer can do selects on the socket descriptor + med->fd = radSocketGetDescriptor (eth->sockId); + + return OK; +} + +static void ethernetExit (WVIEW_MEDIUM *med) +{ + MEDIUM_ETHERNET *eth = (MEDIUM_ETHERNET *)med->workData; + + if (eth->sockId) + radSocketDestroy (eth->sockId); + + return; +} + +static int ethernetRestart (WVIEW_MEDIUM *med) +{ + ethernetExit(med); + radMsgLog (PRI_HIGH, "ethernetRestart: attempting socket restart"); + while ((!wviewdIsExiting()) && (ethernetInit(med, NULL) == ERROR)) + { + radMsgLog (PRI_HIGH, "ethernetRestart: socket restart failed"); + radUtilsSleep(5000); + radMsgLog (PRI_HIGH, "ethernetRestart: retrying socket restart"); + } + if (!wviewdIsExiting()) + { + radMsgLog (PRI_HIGH, "ethernetRestart: socket recovered"); + } + return OK; +} + +static int ethernetWrite (WVIEW_MEDIUM *med, void *buffer, int length) +{ + MEDIUM_ETHERNET *eth = (MEDIUM_ETHERNET *)med->workData; + int retVal; + + retVal = radSocketWriteExact (eth->sockId, buffer, length); + if (retVal <= 0) + { + // Socket error; try to re-establish: + radMsgLog (PRI_HIGH, "ethernetWrite: write error!"); + ethernetRestart(med); + return ERROR; + } + else if (retVal != length) + { + radMsgLog (PRI_HIGH, "ethernetWrite: failed: errno %d: %s", + errno, strerror(errno)); + return ERROR; + } + + return length; +} + +static int ethernetReadExact (WVIEW_MEDIUM *med, void *bfr, int len, int msTimeout) +{ + MEDIUM_ETHERNET *eth = (MEDIUM_ETHERNET *)med->workData; + int rval, cumTime = 0, index = 0, doRecover = FALSE; + uint64_t readTime; + uint8_t *ptr = (uint8_t *)bfr; + fd_set rfds, wfds; + struct timeval waitTime; + + while (index < len && cumTime < msTimeout) + { + readTime = radTimeGetMSSinceEpoch (); + doRecover = FALSE; + if (IsBlocking(eth->sockId)) + { + rval = radSocketReadExact (eth->sockId, &ptr[index], len - index); + if ((rval == ERROR) || (rval == 0)) + { + // Far end close/error; try to re-establish the socket: + doRecover = TRUE; + } + } + else + { + waitTime.tv_sec = (msTimeout-cumTime)/1000; + waitTime.tv_usec = ((msTimeout-cumTime)%1000)*1000; + FD_ZERO(&rfds); + FD_ZERO(&wfds); + FD_SET(radSocketGetDescriptor(eth->sockId), &rfds); + rval = select(radSocketGetDescriptor(eth->sockId) + 1, + &rfds, + &wfds, + (fd_set*)0, + &waitTime); + if (rval > 0) + { + if (FD_ISSET(radSocketGetDescriptor(eth->sockId), &rfds)) + { + // Socket ready for reading: + rval = radSocketReadExact (eth->sockId, &ptr[index], len - index); + if (rval < 0) + { + if (errno == EINTR || errno == EAGAIN) + { + // Try again: + rval = 0; + } + else + { + radMsgLog (PRI_HIGH, "ethernetReadExact: read error: %d: %s", + errno, strerror(errno)); + doRecover = TRUE; + } + } + else if (rval == 0) + { + // Far end close/error; try to re-establish the socket: + radMsgLog (PRI_HIGH, "ethernetReadExact: far-end appears to have closed."); + doRecover = TRUE; + } + } + else + { + rval = 0; + } + } + else + { + // select returned <= 0: + if (rval < 0) + { + if (errno == EINTR || errno == EAGAIN) + { + rval = 0; // Try again: + } + else + { + // Recover for all other errors. + radMsgLog (PRI_HIGH, "ethernetReadExact: select error: %d: %s", + errno, strerror(errno)); + doRecover = TRUE; + } + } + else + { + // Select timed out, return ERROR. + return ERROR; + } + } + } + + // Do we need to attempt recovery? + if (doRecover) + { + radMsgLog (PRI_HIGH, "ethernetReadExact: attempting socket recovery"); + ethernetRestart(med); + + // Go ahead and return ERROR, the caller can try again. + return ERROR; + } + else + { + index += rval; + } + + readTime = radTimeGetMSSinceEpoch () - readTime; + cumTime += (int)readTime; + if (index < len && cumTime < msTimeout) + { + readTime = radTimeGetMSSinceEpoch (); + radUtilsSleep (9); + readTime = radTimeGetMSSinceEpoch () - readTime; + cumTime += (int)readTime; + } + } + +#if 0 /* keep this around for debug purposes */ +if (index < len && len > 2) +{ + radMsgLog (PRI_MEDIUM, "ethernetReadExact: read %d of %d:", index, len); + radMsgLogData (ptr, index); +} +#endif + + return ((index == len) ? len : ERROR); +} + + +#define ETH_FLUSH_TIME 20 +static void ethernetFlush (WVIEW_MEDIUM *med, int queue) +{ + MEDIUM_ETHERNET *eth = (MEDIUM_ETHERNET *)med->workData; + uint8_t temp[SERIAL_BYTE_LENGTH_MAX]; + int rval, cumTime = 0; + uint64_t readTime; + + while (cumTime < ETH_FLUSH_TIME) + { + readTime = radTimeGetMSSinceEpoch (); + rval = radSocketReadExact (eth->sockId, temp, SERIAL_BYTE_LENGTH_MAX); + if (rval < 0) + { + if (errno != EINTR && errno != EAGAIN) + { + return; + } + } + + readTime = radTimeGetMSSinceEpoch () - readTime; + cumTime += (int)readTime; + + // now wait 5 ms (probably 10 ms on x86) + readTime = radTimeGetMSSinceEpoch (); + radUtilsSleep (5); + readTime = radTimeGetMSSinceEpoch () - readTime; + cumTime += (int)readTime; + + if (cumTime >= ETH_FLUSH_TIME && rval > 0) + { + // out of time, but data is still coming, fudge the time counter + cumTime = ETH_FLUSH_TIME - 1; + } + } + + return; +} + +static void ethernetDrain (WVIEW_MEDIUM *med) +{ + return; +} + +static RADSOCK_ID ethernetGetSocket(WVIEW_MEDIUM *med) +{ + MEDIUM_ETHERNET *eth = (MEDIUM_ETHERNET *)med->workData; + + return eth->sockId; +} + + +/* ... ----- API methods ----- +*/ + +int ethernetMediumInit (WVIEW_MEDIUM *medium, char *hostname, int port) +{ + MEDIUM_ETHERNET *work; + + memset (medium, 0, sizeof (*medium)); + + work = (MEDIUM_ETHERNET *) malloc (sizeof (*work)); + if (work == NULL) + { + return ERROR; + } + memset (work, 0, sizeof (*work)); + + medium->type = MEDIUM_TYPE_DEVICE; + + wvstrncpy (work->host, hostname, sizeof(work->host)); + work->port = port; + + // set our workData pointer for later use + medium->workData = (void *)work; + + medium->init = ethernetInit; + medium->exit = ethernetExit; + medium->restart = ethernetRestart; + medium->read = ethernetReadExact; + medium->write = ethernetWrite; + medium->flush = ethernetFlush; + medium->txdrain = ethernetDrain; + medium->getsocket = ethernetGetSocket; + + return OK; +} + diff --git a/stations/common/ethernet.h b/stations/common/ethernet.h new file mode 100755 index 0000000..7d8c15a --- /dev/null +++ b/stations/common/ethernet.h @@ -0,0 +1,68 @@ +#ifndef INC_etherneth +#define INC_etherneth +/*--------------------------------------------------------------------------- + + FILENAME: + ethernet.h + + PURPOSE: + Provide utilities for station communication over ethernet via a + transparent port capable terminal server. + + REVISION HISTORY: + Date Engineer Revision Remarks + 06/09/2005 M.S. Teel 0 Original + + NOTES: + + + LICENSE: + Copyright (c) 2005, Mark S. Teel (mark@teel.ws) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +/* ... System include files +*/ +#include +#include +#include +#include +#include +#include +#include + +/* ... Library include files +*/ +#include +#include +#include + +/* ... Local include files +*/ +#include +#include +#include + + +// define our work area +typedef struct +{ + RADSOCK_ID sockId; + char host[WVIEW_STRING1_SIZE]; + WVIEWD_WORK* wviewWork; + int port; +} MEDIUM_ETHERNET; + + + +/* ... function prototypes +*/ + +// this is the only globally visible method +extern int ethernetMediumInit (WVIEW_MEDIUM *medium, char *hostname, int port); + +#endif + diff --git a/stations/common/hidapi-linux.c b/stations/common/hidapi-linux.c new file mode 100755 index 0000000..3d4ac9c --- /dev/null +++ b/stations/common/hidapi-linux.c @@ -0,0 +1,788 @@ +/*--------------------------------------------------------------------------- + + FILENAME: + hidapi-linux.c + + PURPOSE: + Provide an adaptation of the hidapi utilities. + + REVISION HISTORY: + Date Engineer Revision Remarks + 02/15/2011 M.S. Teel 0 Original + + NOTES: + Based on source code for the hidapi. See the + copyright for that library below this header. + + LICENSE: + Copyright (c) 2011, Mark S. Teel (mteel2005@gmail.com) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ +/* ****************************************************** + HIDAPI - Multi-Platform library for + communication with HID devices. + + Alan Ott + Signal 11 Software + + 8/22/2009 + Linux Version - 6/2/2010 + Libusb Version - 8/13/2010 + + Copyright 2009, All Rights Reserved. + + This software may be used by anyone for any reason so + long as this copyright notice remains intact. + ********************************************************/ + +#if defined(__linux__) || defined(__FreeBSD__) + #ifdef __FreeBSD__ + #if __FreeBSD__ >= 8 + #define INCLUDE_SOURCE + #else + #undef INCLUDE_SOURCE + #endif + #else + #define INCLUDE_SOURCE + #endif +#else + #undef INCLUDE_SOURCE +#endif + +#ifdef INCLUDE_SOURCE +// libusb supported, proceed: + + +/* C */ +#include +#include +#include +#include +#include +#include + +/* Unix */ +#include +#include +#include +#include +#include +#include +#include + +/* GNU / LibUSB */ +#ifdef __FreeBSD__ +#include "libusb.h" +#else +#include "libusb-1.0/libusb.h" +#endif +#include "iconv.h" + +#include "hidapi.h" + +/* Linked List of input reports received from the device. */ +struct input_report { + uint8_t *data; + size_t len; + struct input_report *next; +}; + + +struct hid_device_ { + /* Handle to the actual device. */ + libusb_device_handle *device_handle; + + /* Endpoint information */ + int input_endpoint; + int output_endpoint; + int input_ep_max_packet_size; + + /* The interface number of the HID */ + int interface; + + /* Indexes of Strings */ + int manufacturer_index; + int product_index; + int serial_index; + + /* Whether blocking reads are used */ + int blocking; /* boolean */ + + /* Read thread objects */ + pthread_t thread; + pthread_mutex_t mutex; /* Protects input_reports */ + pthread_cond_t condition; + int shutdown_thread; + struct libusb_transfer *transfer; + + /* List of received input reports. */ + struct input_report *input_reports; +}; + +static int initialized = 0; + +static int return_data(hid_device *dev, unsigned char *data, size_t length); + +hid_device *new_hid_device() +{ + hid_device *dev = calloc(1, sizeof(hid_device)); + dev->device_handle = NULL; + dev->input_endpoint = 0; + dev->output_endpoint = 0; + dev->input_ep_max_packet_size = 0; + dev->interface = 0; + dev->manufacturer_index = 0; + dev->product_index = 0; + dev->serial_index = 0; + dev->blocking = 0; + dev->shutdown_thread = 0; + dev->transfer = NULL; + dev->input_reports = NULL; + + pthread_mutex_init(&dev->mutex, NULL); + pthread_cond_init(&dev->condition, NULL); + + return dev; +} + +static void register_error(hid_device *device, const char *op) +{ + +} + +/* Get the first language the device says it reports. This comes from + USB string #0. */ +static uint16_t get_first_language(libusb_device_handle *dev) +{ + uint16_t buf[32]; + int len; + +#ifdef __FreeBSD__ + + return 0; +#else + /* Get the string from libusb. */ + len = libusb_get_string_descriptor(dev, + 0x0, /* String ID */ + 0x0, /* Language */ + (unsigned char*)buf, + sizeof(buf)); + if (len < 4) + return 0x0; + + return buf[1]; // First two bytes are len and descriptor type. +#endif +} + + +static char *make_path(libusb_device *dev, int interface_number) +{ + char str[64]; + snprintf(str, sizeof(str), "%04x:%04x:%02x", + libusb_get_bus_number(dev), + libusb_get_device_address(dev), + interface_number); + str[sizeof(str)-1] = '\0'; + + return strdup(str); +} + +struct hid_device_info HID_API_EXPORT *hid_enumerate(uint16_t vendor_id, uint16_t product_id) +{ + libusb_device **devs; + libusb_device *dev; + libusb_device_handle *handle; + ssize_t num_devs; + int i = 0; + + struct hid_device_info *root = NULL; // return object + struct hid_device_info *cur_dev = NULL; + + setlocale(LC_ALL,""); + + if (!initialized) + { + libusb_init(NULL); + initialized = 1; + } + + num_devs = libusb_get_device_list(NULL, &devs); + while ((dev = devs[i++]) != NULL) + { + struct libusb_device_descriptor desc; + struct libusb_config_descriptor *conf_desc = NULL; + int skip = 1; + int j, k; + int interface_num = 0; + + int res = libusb_get_device_descriptor(dev, &desc); + uint16_t dev_vid = desc.idVendor; + uint16_t dev_pid = desc.idProduct; + + /* HID's are defined at the interface level. */ + if (desc.bDeviceClass != LIBUSB_CLASS_PER_INTERFACE) + continue; + + res = libusb_get_active_config_descriptor(dev, &conf_desc); + if (res < 0) + libusb_get_config_descriptor(dev, 0, &conf_desc); + for (j = 0; j < conf_desc->bNumInterfaces; j++) + { + const struct libusb_interface *intf = &conf_desc->interface[j]; + for (k = 0; k < intf->num_altsetting; k++) + { + const struct libusb_interface_descriptor *intf_desc; + intf_desc = &intf->altsetting[k]; + if (intf_desc->bInterfaceClass == LIBUSB_CLASS_HID) + { + interface_num = intf_desc->bInterfaceNumber; + skip = 0; + } + } + intf++; + } + libusb_free_config_descriptor(conf_desc); + + if (skip) + continue; + + /* Check the VID/PID against the arguments */ + if ((vendor_id == 0x0 && product_id == 0x0) || + (vendor_id == dev_vid && product_id == dev_pid)) + { + struct hid_device_info *tmp; + + /* VID/PID match. Create the record. */ + tmp = calloc(1, sizeof(struct hid_device_info)); + if (cur_dev) + { + cur_dev->next = tmp; + } + else + { + root = tmp; + } + cur_dev = tmp; + + /* Fill out the record */ + cur_dev->next = NULL; + cur_dev->path = make_path(dev, interface_num); + + /* VID/PID */ + cur_dev->vendor_id = dev_vid; + cur_dev->product_id = dev_pid; + } + } + + libusb_free_device_list(devs, 1); + + return root; +} + +void HID_API_EXPORT hid_free_enumeration(struct hid_device_info *devs) +{ + struct hid_device_info *d = devs; + while (d) + { + struct hid_device_info *next = d->next; + if (d->path) + free(d->path); + free(d); + d = next; + } +} + +hid_device * hid_open(uint16_t vendor_id, uint16_t product_id) +{ + struct hid_device_info *devs, *cur_dev; + const char *path_to_open = NULL; + hid_device *handle = NULL; + + devs = hid_enumerate(vendor_id, product_id); + cur_dev = devs; + while (cur_dev) + { + if (cur_dev->vendor_id == vendor_id && cur_dev->product_id == product_id) + { + path_to_open = cur_dev->path; + break; + } + cur_dev = cur_dev->next; + } + + if (path_to_open) + { + /* Open the device */ + handle = hid_open_path(path_to_open); + } + + hid_free_enumeration(devs); + + return handle; +} + +static void read_callback(struct libusb_transfer *transfer) +{ + hid_device *dev = transfer->user_data; + + if (transfer->status == LIBUSB_TRANSFER_COMPLETED) + { + + struct input_report *rpt = malloc(sizeof(*rpt)); + rpt->data = malloc(transfer->actual_length); + memcpy(rpt->data, transfer->buffer, transfer->actual_length); + rpt->len = transfer->actual_length; + rpt->next = NULL; + + pthread_mutex_lock(&dev->mutex); + + /* Attach the new report object to the end of the list. */ + if (dev->input_reports == NULL) + { + /* The list is empty. Put it at the root. */ + dev->input_reports = rpt; + pthread_cond_signal(&dev->condition); + } + else + { + /* Find the end of the list and attach. */ + struct input_report *cur = dev->input_reports; + int num_queued = 0; + while (cur->next != NULL) + { + cur = cur->next; + num_queued++; + } + cur->next = rpt; + + /* Pop one off if we've reached 30 in the queue. This + way we don't grow forever if the user never reads + anything from the device. */ + if (num_queued > 30) + { + return_data(dev, NULL, 0); + } + } + pthread_mutex_unlock(&dev->mutex); + } + else if (transfer->status == LIBUSB_TRANSFER_CANCELLED) + { + dev->shutdown_thread = 1; + return; + } + else if (transfer->status == LIBUSB_TRANSFER_NO_DEVICE) + { + dev->shutdown_thread = 1; + return; + } + else if (transfer->status == LIBUSB_TRANSFER_TIMED_OUT) + { + //printf("Timeout (normal)\n"); + } + else + { + printf("Unknown transfer code: %d\n", transfer->status); + } + + /* Re-submit the transfer object. */ + libusb_submit_transfer(transfer); +} + + +static void *read_thread(void *param) +{ + hid_device *dev = param; + unsigned char *buf; + const size_t length = dev->input_ep_max_packet_size; + + /* Set up the transfer object. */ + buf = malloc(length); + dev->transfer = libusb_alloc_transfer(0); + libusb_fill_interrupt_transfer(dev->transfer, + dev->device_handle, + dev->input_endpoint, + buf, + length, + read_callback, + dev, + 5000/*timeout*/); + + /* Make the first submission. Further submissions are made + from inside read_callback() */ + libusb_submit_transfer(dev->transfer); + + /* Handle all the events. */ + while (!dev->shutdown_thread) + { + int res; + struct timeval tv; + + tv.tv_sec = 0; + tv.tv_usec = 100; //TODO: Fix this value. + res = libusb_handle_events_timeout(NULL, &tv); + if (res < 0) + { + /* There was an error. Break out of this loop. */ + break; + } + } + +#if 0 // This is done in hid_close() + /* Cleanup before returning */ + free(dev->transfer->buffer); + libusb_free_transfer(dev->transfer); +#endif + + return NULL; +} + + +hid_device * HID_API_EXPORT hid_open_path(const char *path) +{ + hid_device *dev = NULL; + + dev = new_hid_device(); + + libusb_device **devs; + libusb_device *usb_dev; + ssize_t num_devs; + int res; + int i = 0; + int good_open = 0; + + setlocale(LC_ALL,""); + + if (!initialized) + { + libusb_init(NULL); + initialized = 1; + } + + num_devs = libusb_get_device_list(NULL, &devs); + while ((usb_dev = devs[i++]) != NULL) + { + struct libusb_device_descriptor desc; + struct libusb_config_descriptor *conf_desc = NULL; + int i,j,k; + libusb_get_device_descriptor(usb_dev, &desc); + + libusb_get_active_config_descriptor(usb_dev, &conf_desc); + for (j = 0; j < conf_desc->bNumInterfaces; j++) + { + const struct libusb_interface *intf = &conf_desc->interface[j]; + for (k = 0; k < intf->num_altsetting; k++) + { + const struct libusb_interface_descriptor *intf_desc; + intf_desc = &intf->altsetting[k]; + if (intf_desc->bInterfaceClass == LIBUSB_CLASS_HID) + { + char *dev_path = make_path(usb_dev, intf_desc->bInterfaceNumber); + if (!strcmp(dev_path, path)) + { + /* Matched Paths. Open this device */ + + // OPEN HERE // + res = libusb_open(usb_dev, &dev->device_handle); + if (res < 0) + { + printf("can't open device\n"); + break; + } + good_open = 1; + res = libusb_detach_kernel_driver(dev->device_handle, intf_desc->bInterfaceNumber); + if (res < 0) + { + //printf("Unable to detach. Maybe this is OK\n"); + } + + res = libusb_claim_interface(dev->device_handle, intf_desc->bInterfaceNumber); + if (res < 0) + { + printf("can't claim interface %d: %d\n", intf_desc->bInterfaceNumber, res); + libusb_close(dev->device_handle); + good_open = 0; + break; + } + + /* Store off the string descriptor indexes */ + dev->manufacturer_index = desc.iManufacturer; + dev->product_index = desc.iProduct; + dev->serial_index = desc.iSerialNumber; + + /* Store off the interface number */ + dev->interface = intf_desc->bInterfaceNumber; + + /* Find the INPUT and OUTPUT endpoints. An + OUTPUT endpoint is not required. */ + for (i = 0; i < intf_desc->bNumEndpoints; i++) + { + const struct libusb_endpoint_descriptor *ep + = &intf_desc->endpoint[i]; + + /* Determine the type and direction of this + endpoint. */ + int is_interrupt = + (ep->bmAttributes & LIBUSB_TRANSFER_TYPE_MASK) + == LIBUSB_TRANSFER_TYPE_INTERRUPT; + int is_output = + (ep->bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK) + == LIBUSB_ENDPOINT_OUT; + int is_input = + (ep->bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK) + == LIBUSB_ENDPOINT_IN; + + /* Decide whether to use it for intput or output. */ + if (dev->input_endpoint == 0 && + is_interrupt && is_input) + { + /* Use this endpoint for INPUT */ + dev->input_endpoint = ep->bEndpointAddress; + dev->input_ep_max_packet_size = ep->wMaxPacketSize; + } + if (dev->output_endpoint == 0 && + is_interrupt && is_output) + { + /* Use this endpoint for OUTPUT */ + dev->output_endpoint = ep->bEndpointAddress; + } + } + + pthread_create(&dev->thread, NULL, read_thread, dev); + + } + free(dev_path); + } + } + intf++; + } + libusb_free_config_descriptor(conf_desc); + + } + + libusb_free_device_list(devs, 1); + + // If we have a good handle, return it. + if (good_open) + { + return dev; + } + else + { + // Unable to open any devices. + free(dev); + return NULL; + } +} + + +int HID_API_EXPORT hid_write(hid_device *dev, const unsigned char *data, size_t length) +{ + int res; + int report_number = data[0]; + int skipped_report_id = 0; + + if (report_number == 0x0) + { + data++; + length--; + skipped_report_id = 1; + } + + + if (dev->output_endpoint <= 0) + { + /* No interrput out endpoint. Use the Control Endpoint */ + res = libusb_control_transfer(dev->device_handle, + LIBUSB_REQUEST_TYPE_CLASS|LIBUSB_RECIPIENT_INTERFACE|LIBUSB_ENDPOINT_OUT, + 0x09/*HID Set_Report*/, + (2/*HID output*/ << 8) | report_number, + dev->interface, + (unsigned char *)data, length, + 1000/*timeout millis*/); + + if (res < 0) + return -1; + + if (skipped_report_id) + length++; + + return length; + } + else + { + /* Use the interrupt out endpoint */ + int actual_length; + res = libusb_interrupt_transfer(dev->device_handle, + dev->output_endpoint, + (unsigned char*)data, + length, + &actual_length, 1000); + + if (res < 0) + return -1; + + if (skipped_report_id) + actual_length++; + + return actual_length; + } +} + +/* Helper function, to simplify hid_read(). + This should be called with dev->mutex locked. */ +static int return_data(hid_device *dev, unsigned char *data, size_t length) +{ + /* Copy the data out of the linked list item (rpt) into the + return buffer (data), and delete the liked list item. */ + struct input_report *rpt = dev->input_reports; + size_t len = (length < rpt->len)? length: rpt->len; + memcpy(data, rpt->data, len); + dev->input_reports = rpt->next; + free(rpt->data); + free(rpt); + return len; +} + + +int HID_API_EXPORT hid_read(hid_device *dev, unsigned char *data, size_t length) +{ + int bytes_read = -1; + + pthread_mutex_lock(&dev->mutex); + + /* There's an input report queued up. Return it. */ + if (dev->input_reports) + { + /* Return the first one */ + bytes_read = return_data(dev, data, length); + goto ret; + } + + if (dev->blocking) + { + pthread_cond_wait(&dev->condition, &dev->mutex); + bytes_read = return_data(dev, data, length); + } + else + { + bytes_read = 0; + } + +ret: + pthread_mutex_unlock(&dev->mutex); + + return bytes_read; +} + +int HID_API_EXPORT hid_set_nonblocking(hid_device *dev, int nonblock) +{ + dev->blocking = !nonblock; + + return 0; +} + + +int HID_API_EXPORT hid_send_feature_report(hid_device *dev, const unsigned char *data, size_t length) +{ + int res = -1; + int skipped_report_id = 0; + int report_number = data[0]; + + if (report_number == 0x0) + { + data++; + length--; + skipped_report_id = 1; + } + + res = libusb_control_transfer(dev->device_handle, + LIBUSB_REQUEST_TYPE_CLASS|LIBUSB_RECIPIENT_INTERFACE|LIBUSB_ENDPOINT_OUT, + 0x09/*HID set_report*/, + (3/*HID feature*/ << 8) | report_number, + dev->interface, + (unsigned char *)data, length, + 1000/*timeout millis*/); + + if (res < 0) + return -1; + + /* Account for the report ID */ + if (skipped_report_id) + length++; + + return length; +} + +int HID_API_EXPORT hid_get_feature_report(hid_device *dev, unsigned char *data, size_t length) +{ + int res = -1; + int skipped_report_id = 0; + int report_number = data[0]; + + if (report_number == 0x0) + { + /* Offset the return buffer by 1, so that the report ID + will remain in byte 0. */ + data++; + length--; + skipped_report_id = 1; + } + res = libusb_control_transfer(dev->device_handle, + LIBUSB_REQUEST_TYPE_CLASS|LIBUSB_RECIPIENT_INTERFACE|LIBUSB_ENDPOINT_IN, + 0x01/*HID get_report*/, + (3/*HID feature*/ << 8) | report_number, + dev->interface, + (unsigned char *)data, length, + 1000/*timeout millis*/); + + if (res < 0) + return -1; + + if (skipped_report_id) + res++; + + return res; +} + + +void HID_API_EXPORT hid_close(hid_device *dev) +{ + if (!dev) + return; + + /* Cause read_thread() to stop. */ + libusb_cancel_transfer(dev->transfer); + + /* Wait for read_thread() to end. */ + pthread_join(dev->thread, NULL); + + /* Close the handle */ + libusb_close(dev->device_handle); + + /* Clear out the queue of received reports. */ + pthread_mutex_lock(&dev->mutex); + while (dev->input_reports) + { + return_data(dev, NULL, 0); + } + pthread_mutex_unlock(&dev->mutex); + + /* Clean up the thread objects */ + pthread_mutex_destroy(&dev->mutex); + pthread_cond_destroy(&dev->condition); + + free(dev); +} + + +HID_API_EXPORT const wchar_t * HID_API_CALL hid_error(hid_device *dev) +{ + return NULL; +} + + +#endif // INCLUDE_SOURCE + diff --git a/stations/common/hidapi-osx.c b/stations/common/hidapi-osx.c new file mode 100755 index 0000000..e77e423 --- /dev/null +++ b/stations/common/hidapi-osx.c @@ -0,0 +1,664 @@ +/*--------------------------------------------------------------------------- + + FILENAME: + hidapi-osx.c + + PURPOSE: + Provide an adaptation of the hidapi utilities. + + REVISION HISTORY: + Date Engineer Revision Remarks + 02/15/2011 M.S. Teel 0 Original + + NOTES: + Based on source code for the hidapi. See the + copyright for that library below this header. + + LICENSE: + Copyright (c) 2011, Mark S. Teel (mteel2005@gmail.com) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ +/******************************************************* + HIDAPI - Multi-Platform library for + communication with HID devices. + + Alan Ott + Signal 11 Software + + 2010-07-03 + + Copyright 2010, All Rights Reserved. + + This software may be used by anyone for any reason so + long as this copyright notice remains intact. +********************************************************/ + +#ifdef __APPLE__ +// Mac OSX gcc. + +#include +#include +#include +#include +#include + +#include "hidapi.h" + +/* Linked List of input reports received from the device. */ +struct input_report { + uint8_t *data; + size_t len; + struct input_report *next; +}; + +struct hid_device_ { + IOHIDDeviceRef device_handle; + int blocking; + int uses_numbered_reports; + CFStringRef run_loop_mode; + uint8_t *input_report_buf; + struct input_report *input_reports; + pthread_mutex_t mutex; +}; + + +static hid_device *new_hid_device() +{ + hid_device *dev = calloc(1, sizeof(hid_device)); + dev->device_handle = NULL; + dev->blocking = 1; + dev->uses_numbered_reports = 0; + dev->input_reports = NULL; + + return dev; +} + +static IOHIDManagerRef hid_mgr = 0x0; + + +static long get_long_property(IOHIDDeviceRef device, CFStringRef key) +{ + CFTypeRef ref; + long value; + + ref = IOHIDDeviceGetProperty(device, key); + if (ref) + { + if (CFGetTypeID(ref) == CFNumberGetTypeID()) + { + CFNumberGetValue((CFNumberRef) ref, kCFNumberSInt32Type, &value); + return value; + } + } + return 0; +} + +static uint16_t get_vendor_id(IOHIDDeviceRef device) +{ + return get_long_property(device, CFSTR(kIOHIDVendorIDKey)); +} + +static uint16_t get_product_id(IOHIDDeviceRef device) +{ + return get_long_property(device, CFSTR(kIOHIDProductIDKey)); +} + + +static long get_max_report_length(IOHIDDeviceRef device) +{ + return get_long_property(device, CFSTR(kIOHIDMaxInputReportSizeKey)); +} + +static int get_string_property(IOHIDDeviceRef device, CFStringRef prop, wchar_t *buf, size_t len) +{ + CFStringRef str = IOHIDDeviceGetProperty(device, prop); + + buf[0] = 0x0000; + + if (str) + { + CFRange range; + range.location = 0; + range.length = len; + CFIndex used_buf_len; + CFStringGetBytes(str, + range, + kCFStringEncodingUTF32LE, + (char)'?', + FALSE, + (UInt8*)buf, + len, + &used_buf_len); + return used_buf_len; + } + else + return 0; + +} + +static int get_string_property_utf8(IOHIDDeviceRef device, CFStringRef prop, char *buf, size_t len) +{ + CFStringRef str = IOHIDDeviceGetProperty(device, prop); + + buf[0] = 0x0000; + + if (str) + { + CFRange range; + range.location = 0; + range.length = len; + CFIndex used_buf_len; + CFStringGetBytes(str, + range, + kCFStringEncodingUTF8, + (char)'?', + FALSE, + (UInt8*)buf, + len, + &used_buf_len); + return used_buf_len; + } + else + return 0; + +} + + +static int get_serial_number(IOHIDDeviceRef device, wchar_t *buf, size_t len) +{ + return get_string_property(device, CFSTR(kIOHIDSerialNumberKey), buf, len); +} + +static int get_manufacturer_string(IOHIDDeviceRef device, wchar_t *buf, size_t len) +{ + return get_string_property(device, CFSTR(kIOHIDManufacturerKey), buf, len); +} + +static int get_product_string(IOHIDDeviceRef device, wchar_t *buf, size_t len) +{ + return get_string_property(device, CFSTR(kIOHIDProductKey), buf, len); +} + + +/* Implementation of wcsdup() for Mac. */ +static wchar_t *dup_wcs(const wchar_t *s) +{ + size_t len = wcslen(s); + wchar_t *ret = malloc((len+1)*sizeof(wchar_t)); + wcscpy(ret, s); + + return ret; +} + + +static int make_path(IOHIDDeviceRef device, char *buf, size_t len) +{ + int res; + uint16_t vid, pid; + char transport[32]; + + buf[0] = '\0'; + + res = get_string_property_utf8(device, CFSTR(kIOHIDTransportKey), + transport, sizeof(transport)); + + if (!res) + return -1; + + vid = get_vendor_id(device); + pid = get_product_id(device); + + res = snprintf(buf, len, "%s_%04hx_%04hx_%p", + transport, vid, pid, device); + + + buf[len-1] = '\0'; + return res+1; +} + +static void init_hid_manager() +{ + /* Initialize all the HID Manager Objects */ + hid_mgr = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone); + IOHIDManagerSetDeviceMatching(hid_mgr, NULL); + IOHIDManagerScheduleWithRunLoop(hid_mgr, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); + IOHIDManagerOpen(hid_mgr, kIOHIDOptionsTypeNone); +} + + +struct hid_device_info HID_API_EXPORT *hid_enumerate(uint16_t vendor_id, uint16_t product_id) +{ + struct hid_device_info *root = NULL; // return object + struct hid_device_info *cur_dev = NULL; + CFIndex num_devices; + int i; + + setlocale(LC_ALL,""); + + /* Set up the HID Manager if it hasn't been done */ + if (!hid_mgr) + init_hid_manager(); + + /* Get a list of the Devices */ + CFSetRef device_set = IOHIDManagerCopyDevices(hid_mgr); + + /* Convert the list into a C array so we can iterate easily. */ + num_devices = CFSetGetCount(device_set); + IOHIDDeviceRef *device_array = calloc(num_devices, sizeof(IOHIDDeviceRef)); + CFSetGetValues(device_set, (const void **) device_array); + + /* Iterate over each device, making an entry for it. */ + for (i = 0; i < num_devices; i++) + { + uint16_t dev_vid; + uint16_t dev_pid; +#define BUF_LEN 256 + + wchar_t buf[BUF_LEN]; + char cbuf[BUF_LEN]; + + IOHIDDeviceRef dev = device_array[i]; + + dev_vid = get_vendor_id(dev); + dev_pid = get_product_id(dev); + + /* Check the VID/PID against the arguments */ + if ((vendor_id == 0x0 && product_id == 0x0) || + (vendor_id == dev_vid && product_id == dev_pid)) + { + struct hid_device_info *tmp; + size_t len; + + /* VID/PID match. Create the record. */ + tmp = malloc(sizeof(struct hid_device_info)); + if (cur_dev) + { + cur_dev->next = tmp; + } + else + { + root = tmp; + } + cur_dev = tmp; + + /* Fill out the record */ + cur_dev->next = NULL; + len = make_path(dev, cbuf, sizeof(cbuf)); + cur_dev->path = strdup(cbuf); + + /* Serial Number */ + get_serial_number(dev, buf, BUF_LEN); + cur_dev->serial_number = dup_wcs(buf); + + /* Manufacturer and Product strings */ + get_manufacturer_string(dev, buf, BUF_LEN); + cur_dev->manufacturer_string = dup_wcs(buf); + get_product_string(dev, buf, BUF_LEN); + cur_dev->product_string = dup_wcs(buf); + + /* VID/PID */ + cur_dev->vendor_id = dev_vid; + cur_dev->product_id = dev_pid; + } + } + + free(device_array); + CFRelease(device_set); + + return root; +} + +void HID_API_EXPORT hid_free_enumeration(struct hid_device_info *devs) +{ + /* This function is identical to the Linux version. Platform independent. */ + struct hid_device_info *d = devs; + while (d) + { + struct hid_device_info *next = d->next; + free(d->path); + free(d->serial_number); + free(d->manufacturer_string); + free(d->product_string); + free(d); + d = next; + } +} + +hid_device * HID_API_EXPORT hid_open(uint16_t vendor_id, uint16_t product_id) +{ + /* This function is identical to the Linux version. Platform independent. */ + struct hid_device_info *devs, *cur_dev; + const char *path_to_open = NULL; + hid_device * handle = NULL; + + devs = hid_enumerate(vendor_id, product_id); + cur_dev = devs; + while (cur_dev) + { + if (cur_dev->vendor_id == vendor_id && + cur_dev->product_id == product_id) + { + path_to_open = cur_dev->path; + break; + } + cur_dev = cur_dev->next; + } + + if (path_to_open) + { + /* Open the device */ + handle = hid_open_path(path_to_open); + } + + hid_free_enumeration(devs); + + return handle; +} + +/* The Run Loop calls this function for each input report received. + This function puts the data into a linked list to be picked up by + hid_read(). */ +void hid_report_callback(void *context, IOReturn result, void *sender, + IOHIDReportType report_type, uint32_t report_id, + uint8_t *report, CFIndex report_length) +{ + struct input_report *rpt; + hid_device *dev = context; + + /* Make a new Input Report object */ + rpt = calloc(1, sizeof(struct input_report)); + rpt->data = calloc(1, report_length); + memcpy(rpt->data, report, report_length); + rpt->len = report_length; + rpt->next = NULL; + + /* Attach the new report object to the end of the list. */ + if (dev->input_reports == NULL) + { + /* The list is empty. Put it at the root. */ + dev->input_reports = rpt; + } + else + { + /* Find the end of the list and attach. */ + struct input_report *cur = dev->input_reports; + while (cur->next != NULL) + cur = cur->next; + cur->next = rpt; + } + + /* Stop the Run Loop. This is mostly used for when blocking is + enabled, but it doesn't hurt for non-blocking as well. */ + CFRunLoopStop(CFRunLoopGetCurrent()); +} + +hid_device * HID_API_EXPORT hid_open_path(const char *path) +{ + int i; + hid_device *dev = NULL; + CFIndex num_devices; + + dev = new_hid_device(); + + /* Set up the HID Manager if it hasn't been done */ + if (!hid_mgr) + init_hid_manager(); + + CFSetRef device_set = IOHIDManagerCopyDevices(hid_mgr); + + num_devices = CFSetGetCount(device_set); + IOHIDDeviceRef *device_array = calloc(num_devices, sizeof(IOHIDDeviceRef)); + CFSetGetValues(device_set, (const void **) device_array); + for (i = 0; i < num_devices; i++) + { + char cbuf[BUF_LEN]; + size_t len; + IOHIDDeviceRef os_dev = device_array[i]; + + len = make_path(os_dev, cbuf, sizeof(cbuf)); + if (!strcmp(cbuf, path)) + { + // Matched Paths. Open this Device. + IOReturn ret = IOHIDDeviceOpen(os_dev, kIOHIDOptionsTypeNone); + if (ret == kIOReturnSuccess) + { + char str[32]; + CFIndex max_input_report_len; + + free(device_array); + CFRelease(device_set); + dev->device_handle = os_dev; + + /* Create the buffers for receiving data */ + max_input_report_len = (CFIndex) get_max_report_length(os_dev); + dev->input_report_buf = calloc(max_input_report_len, sizeof(uint8_t)); + + /* Create the Run Loop Mode for this device. + printing the reference seems to work. */ + sprintf(str, "%p", os_dev); + dev->run_loop_mode = + CFStringCreateWithCString(NULL, str, kCFStringEncodingASCII); + + /* Attach the device to a Run Loop */ + IOHIDDeviceScheduleWithRunLoop(os_dev, CFRunLoopGetCurrent(), dev->run_loop_mode); + IOHIDDeviceRegisterInputReportCallback( + os_dev, dev->input_report_buf, max_input_report_len, + &hid_report_callback, dev); + + pthread_mutex_init(&dev->mutex, NULL); + + return dev; + } + else + { + goto return_error; + } + } + } + +return_error: + free(device_array); + CFRelease(device_set); + free(dev); + return NULL; +} + +static int set_report(hid_device *dev, IOHIDReportType type, const unsigned char *data, size_t length) +{ + const unsigned char *data_to_send; + size_t length_to_send; + IOReturn res; + + if (data[0] == 0x0) + { + /* Not using numbered Reports. + Don't send the report number. */ + data_to_send = data+1; + length_to_send = length-1; + } + else + { + /* Using numbered Reports. + Send the Report Number */ + data_to_send = data; + length_to_send = length; + } + + res = IOHIDDeviceSetReport(dev->device_handle, + type, + data[0], /* Report ID*/ + data_to_send, length_to_send); + + if (res == kIOReturnSuccess) + { + return length; + } + else + return -1; + +} + +int HID_API_EXPORT hid_write(hid_device *dev, const unsigned char *data, size_t length) +{ + return set_report(dev, kIOHIDReportTypeOutput, data, length); +} + +/* Helper function, so that this isn't duplicated in hid_read(). */ +static int return_data(hid_device *dev, unsigned char *data, size_t length) +{ + /* Copy the data out of the linked list item (rpt) into the + return buffer (data), and delete the liked list item. */ + struct input_report *rpt = dev->input_reports; + size_t len = (length < rpt->len)? length: rpt->len; + memcpy(data, rpt->data, len); + dev->input_reports = rpt->next; + free(rpt->data); + free(rpt); + return len; +} + +int HID_API_EXPORT hid_read(hid_device *dev, unsigned char *data, size_t length) +{ + int ret_val = -1; + + /* Lock this function */ + pthread_mutex_lock(&dev->mutex); + + /* There's an input report queued up. Return it. */ + if (dev->input_reports) + { + /* Return the first one */ + ret_val = return_data(dev, data, length); + goto ret; + } + + /* There are no input reports queued up. + Need to get some from the OS. */ + + /* Move the device's run loop to this thread. */ + IOHIDDeviceScheduleWithRunLoop(dev->device_handle, CFRunLoopGetCurrent(), dev->run_loop_mode); + + if (dev->blocking) + { + /* Run the Run Loop until it stops timing out. In other + words, until something happens. This is necessary because + there is no INFINITE timeout value. */ + SInt32 code; + while (1) + { + code = CFRunLoopRunInMode(dev->run_loop_mode, 1000, TRUE); + + /* Return if some data showed up. */ + if (dev->input_reports) + break; + + /* Break if The Run Loop returns Finished or Stopped. */ + if (code != kCFRunLoopRunTimedOut && + code != kCFRunLoopRunHandledSource) + break; + } + + /* See if the run loop and callback gave us any reports. */ + if (dev->input_reports) + { + ret_val = return_data(dev, data, length); + goto ret; + } + else + { + ret_val = -1; /* An error occured (maybe CTRL-C?). */ + goto ret; + } + } + else + { + /* Non-blocking. See if the OS has any reports to give. */ + SInt32 code; + code = CFRunLoopRunInMode(dev->run_loop_mode, 0, TRUE); + if (dev->input_reports) + { + /* Return the first one */ + ret_val = return_data(dev, data, length); + goto ret; + } + else + { + ret_val = 0; /* No data*/ + goto ret; + } + } + +ret: + /* Unlock */ + pthread_mutex_unlock(&dev->mutex); + return ret_val; +} + +int HID_API_EXPORT hid_set_nonblocking(hid_device *dev, int nonblock) +{ + /* All Nonblocking operation is handled by the library. */ + dev->blocking = !nonblock; + + return 0; +} + +int HID_API_EXPORT hid_send_feature_report(hid_device *dev, const unsigned char *data, size_t length) +{ + return set_report(dev, kIOHIDReportTypeFeature, data, length); +} + +int HID_API_EXPORT hid_get_feature_report(hid_device *dev, unsigned char *data, size_t length) +{ + CFIndex len = length; + IOReturn res; + + res = IOHIDDeviceGetReport(dev->device_handle, + kIOHIDReportTypeFeature, + data[0], /* Report ID */ + data, &len); + if (res == kIOReturnSuccess) + return len; + else + return -1; +} + + +void HID_API_EXPORT hid_close(hid_device *dev) +{ + if (!dev) + return; + + /* Close the OS handle to the device. */ + IOHIDDeviceClose(dev->device_handle, kIOHIDOptionsTypeNone); + + /* Delete any input reports still left over. */ + struct input_report *rpt = dev->input_reports; + while (rpt) + { + struct input_report *next = rpt->next; + free(rpt->data); + free(rpt); + rpt = next; + } + + /* Free the string and the report buffer. */ + CFRelease(dev->run_loop_mode); + free(dev->input_report_buf); + pthread_mutex_destroy(&dev->mutex); + + free(dev); +} + + +HID_API_EXPORT const wchar_t * HID_API_CALL hid_error(hid_device *dev) +{ + // TODO: + + return NULL; +} + +#endif // Mac OSX. + diff --git a/stations/common/hidapi.h b/stations/common/hidapi.h new file mode 100755 index 0000000..58a7060 --- /dev/null +++ b/stations/common/hidapi.h @@ -0,0 +1,259 @@ +/*--------------------------------------------------------------------------- + + FILENAME: + hidapi.h + + PURPOSE: + Provide an adaptation of the hidapi utilities. + + REVISION HISTORY: + Date Engineer Revision Remarks + 02/15/2011 M.S. Teel 0 Original + + NOTES: + Based on source code for the hidapi. See the + copyright for that library below this header. + + LICENSE: + Copyright (c) 2011, Mark S. Teel (mteel2005@gmail.com) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ +/******************************************************* + HIDAPI - Multi-Platform library for + communication with HID devices. + + Alan Ott + Signal 11 Software + + 8/22/2009 + + Copyright 2009, All Rights Reserved. + + This software may be used by anyone for any reason so + long as this copyright notice remains intact. +********************************************************/ + +#include + +#ifdef _WIN32 + #define HID_API_EXPORT __declspec(dllexport) + #define HID_API_CALL _stdcall +#else + #define HID_API_EXPORT + #define HID_API_CALL +#endif + +#define HID_API_EXPORT_CALL HID_API_EXPORT HID_API_CALL + +#ifdef __cplusplus +extern "C" { +#endif + struct hid_device_; + typedef struct hid_device_ hid_device; + + struct hid_device_info { + /** Platform-specific device path */ + char *path; + /** Device Vendor ID */ + uint16_t vendor_id; + /** Device Product ID */ + uint16_t product_id; + /** Serial Number */ + wchar_t *serial_number; + /** Manufacturer String */ + wchar_t *manufacturer_string; + /** Product string */ + wchar_t *product_string; + + /** Pointer to the next device */ + struct hid_device_info *next; + }; + + + /** Enumerate the HID Devices. + This function returns a linked list of all the HID devices + attached to the system which match vendor_id and product_id. + If vendor_id and product_id are both set to 0, then all HID + devices will be returned. + + Params: + vendor_id: The Vendor ID (VID) of the types of device to open. + product_id: The Product ID (PID) of the types of device to open. + + Return: + This function returns a pointer to a linked list of type + struct hid_device, containing information about the HID devices + attached to the system, or NULL in the case of failure. Free + this linked list by calling hid_free_enumeration(). + */ + struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_enumerate(uint16_t vendor_id, uint16_t product_id); + + /** Free an enumeration Linked List + This function frees a linked list created by hid_enumerate(). + + Params: + devs: Pointer to a list of struct_device returned from + hid_enumerate(). + + Return: + This function does not return a value. + + */ + void HID_API_EXPORT HID_API_CALL hid_free_enumeration(struct hid_device_info *devs); + + /** Open a HID device using a Vendor ID (VID), Product ID (PID) and + optionally a serial number. If serial_number is NULL, the first + device with the specified VID and PID is opened. + + Params: + vendor_id: The Vendor ID (VID) of the device to open. + product_id: The Product ID (PID) of the device to open. + + Return: + This function returns a pointer to a hid_device object on + success or NULL on failure. + */ + HID_API_EXPORT hid_device * HID_API_CALL hid_open(uint16_t vendor_id, uint16_t product_id); + + /** Open a HID device by its path name. The path name be determined + by calling hid_enumerate(), or a platform-specific path name can + be used (eg: /dev/hidraw0 on Linux). + + Params: + path: The path name of the device to open + + Return: + This function returns a pointer to a hid_device object on + success or NULL on failure. + */ + HID_API_EXPORT hid_device * HID_API_CALL hid_open_path(const char *path); + + /** Write an Output report to a HID device. The first byte of data[] + must contain the Report ID. For devices which only support a single + report, this must be set to 0x0. The remaining bytes contain the + report data. Since the Report ID is mandatory, calls to hid_write() + will always contain one more byte than the report contains. For + example, if a hid report is 16 bytes long, 17 bytes must be passed + to hid_write(), the Report ID (or 0x0, for devices with a single + report), followed by the report data (16 bytes). In this example, + the length passed in would be 17. + + hid_write() will send the data on the first OUT endpoint, if one + exists. If it does not, it will send the data through the Control + Endpoint (Endpoint 0). + + Params: + device: A device handle returned from hid_open(). + data: The data to send, including the report number as + the first byte. + length: The length in bytes of the data to send. + + Return Value: + This function returns the actual number of bytes written and + -1 on error. + */ + int HID_API_EXPORT HID_API_CALL hid_write(hid_device *device, const unsigned char *data, size_t length); + + /** Read an Input report from a HID device. Input reports are returned + to the host through the INTERRUPT IN endpoint. The first byte will + contain the Report number if the device uses numbered reports. + + Params: + device: A device handle returned from hid_open(). + data: A buffer to put the read data into. + length: The number of bytes to read. For devices with multiple + reports, make sure to read an extra byte for the + report number. + + Return Value: + This function returns the actual number of bytes read and + -1 on error. + */ + int HID_API_EXPORT HID_API_CALL hid_read(hid_device *device, unsigned char *data, size_t length); + + /** Set the device handle to be non-blocking. In non-blocking mode + calls to hid_read() will return immediately with a value of 0 + if there is no data to be read. In blocking mode, hid_read() + will wait (block) until there is data to read before returning. + + Nonblocking can be turned on and off at any time. + + Params: + device: A device handle returned from hid_open(). + nonblock: 1 to enable nonblocking or 0 to disable + nonblocking. + + Return Value: + This function returns 0 on success and -1 on error. + */ + int HID_API_EXPORT HID_API_CALL hid_set_nonblocking(hid_device *device, int nonblock); + + /** Send a Feature report to the device. Feature reports are sent + over the Control endpoint as a Set_Report transfer. The first + byte of data[] must contain the Report ID. For devices which only + support a single report, this must be set to 0x0. The remaining + bytes contain the report data. Since the Report ID is mandatory, + calls to hid_send_feature_report() will always contain one more + byte than the report contains. For example, if a hid report is 16 + bytes long, 17 bytes must be passed to hid_send_feature_report(): + the Report ID (or 0x0, for devices which do not use numbered + reports), followed by the report data (16 bytes). In this example, + the length passed in would be 17. + + Params: + device: A device handle returned from hid_open(). + data: The data to send, including the report number as + the first byte. + length: The length in bytes of the data to send, including + the report number. + + Return Value: + This function returns the actual number of bytes written and + -1 on error. + */ + int HID_API_EXPORT HID_API_CALL hid_send_feature_report(hid_device *device, const unsigned char *data, size_t length); + + /** Get a feature report from a HID device. Make sure to set the + first byte of data[] to the Report ID of the report to be read. + Make sure to allow space for this extra byte in data[]. + + Params: + device: A device handle returned from hid_open(). + data: A buffer to put the read data into, including the + Report ID. Set the first byte of data[] to the Report + ID of the report to be read. + length: The number of bytes to read, including an extra byte + for the report ID. The buffer can be longer than the + actual report. + + Return Value: + This function returns the number of bytes read and + -1 on error. + */ + int HID_API_EXPORT HID_API_CALL hid_get_feature_report(hid_device *device, unsigned char *data, size_t length); + + /** Close a HID device. + + Params: + device: A device handle returned from hid_open(). + */ + void HID_API_EXPORT HID_API_CALL hid_close(hid_device *device); + + /** Get a string describing the last error which occurred. + + Params: + device: A device handle returned from hid_open(). + + Return Value: + This function returns a string containing the last error + which occurred or NULL if none has occurred. + */ + HID_API_EXPORT const wchar_t* HID_API_CALL hid_error(hid_device *device); + +#ifdef __cplusplus +} +#endif + diff --git a/stations/common/parser.c b/stations/common/parser.c new file mode 100755 index 0000000..f6a960c --- /dev/null +++ b/stations/common/parser.c @@ -0,0 +1,152 @@ +/*--------------------------------------------------------------------------- + + FILENAME: + parser.c + + PURPOSE: + Provide ASCII parsing utilities. + + REVISION HISTORY: + Date Engineer Revision Remarks + 01/15/2006 M.S. Teel 0 Original + + NOTES: + + + LICENSE: + Copyright (c) 2006, Mark S. Teel (mark@teel.ws) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +/* ... System include files +*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* ... Library include files +*/ + +/* ... Local include files +*/ +#include + + +/* ... local memory +*/ + +static void emptyTokenList (RADLIST_ID list) +{ + NODE_PTR node; + + for (node = radListRemoveFirst(list); + node != NULL; + node = radListRemoveFirst(list)) + { + radBufferRls (node); + } +} + + +// create a parsing object +PARSER_ID parserInit (char *inputString, char *delimiters) +{ + PARSER_ID newId; + PARSE_TOKEN *node; + char *token; + char temp[_MAX_PATH]; + + newId = (PARSER_ID)radBufferGet (sizeof(PARSE_DATA)); + if (newId == NULL) + { + return NULL; + } + + memset (newId, 0, sizeof(PARSE_DATA)); + radListReset (&newId->tokens); + + // parse the input + wvstrncpy (temp, inputString, _MAX_PATH); + token = strtok (temp, delimiters); + if (token == NULL) + { + radBufferRls (newId); + return NULL; + } + + while (token != NULL) + { + node = (PARSE_TOKEN *)radBufferGet (sizeof(PARSE_TOKEN)); + if (node == NULL) + { + emptyTokenList (&newId->tokens); + radBufferRls (newId); + return NULL; + } + + wvstrncpy (node->value, token, sizeof(node->value)); + radListAddToEnd (&newId->tokens, (NODE_PTR)node); + + // get the next one + token = strtok (NULL, delimiters); + } + + return newId; +} + +// destroy a parsing object +void parserExit (PARSER_ID id) +{ + emptyTokenList (&id->tokens); + radBufferRls (id); +} + +// get the first token +char *parserGetFirst (PARSER_ID id) +{ + id->current = (PARSE_TOKEN *)radListGetFirst (&id->tokens); + return id->current->value; +} + +// get the next token +char *parserGetNext (PARSER_ID id) +{ + id->current = (PARSE_TOKEN *)radListGetNext (&id->tokens, (NODE_PTR)id->current); + return id->current->value; +} + +// get the n'th token +char *parserGetNumber (PARSER_ID id, int n) +{ + PARSE_TOKEN *node; + int count; + + for (count = 1, node = (PARSE_TOKEN *)radListGetFirst(&id->tokens); + node != NULL; + count ++, node = (PARSE_TOKEN *)radListGetNext(&id->tokens, (NODE_PTR)node)) + { + if (count == n) + { + // return this one + id->current = node; + return id->current->value; + } + } + + // if here, we had no luck + return NULL; +} + diff --git a/stations/common/parser.h b/stations/common/parser.h new file mode 100644 index 0000000..e24580c --- /dev/null +++ b/stations/common/parser.h @@ -0,0 +1,80 @@ +#ifndef INC_parserh +#define INC_parserh +/*--------------------------------------------------------------------------- + + FILENAME: + parser.h + + PURPOSE: + Provide ASCII parsing utilities. + + REVISION HISTORY: + Date Engineer Revision Remarks + 01/15/2006 M.S. Teel 0 Original + + NOTES: + + LICENSE: + Copyright (c) 2006, Mark S. Teel (mark@teel.ws) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +/* ... System include files +*/ +#include +#include +#include +#include +#include +#include + +/* ... Library include files +*/ +#include +#include +#include +#include + +/* ... Local include files +*/ +#include + + +// define a token node +typedef struct +{ + NODE node; + char value[WVIEW_STRING1_SIZE]; +} PARSE_TOKEN; + +// parse ID +typedef struct +{ + RADLIST tokens; + PARSE_TOKEN *current; + +} PARSE_DATA, *PARSER_ID; + + +// function prototypes + +// create a parsing object +extern PARSER_ID parserInit (char *inputString, char *delimiters); + +// get the first token +extern char *parserGetFirst (PARSER_ID id); + +// get the next token +extern char *parserGetNext (PARSER_ID id); + +// get the n'th token (starts with 1); resets the current pointer +extern char *parserGetNumber (PARSER_ID id, int n); + +// destroy a parsing object +extern void parserExit (PARSER_ID id); + +#endif + diff --git a/stations/common/serial.c b/stations/common/serial.c new file mode 100755 index 0000000..13ee435 --- /dev/null +++ b/stations/common/serial.c @@ -0,0 +1,255 @@ +/*--------------------------------------------------------------------------- + + FILENAME: + serial.c + + PURPOSE: + Provide the weather station serial medium utilities. + + REVISION HISTORY: + Date Engineer Revision Remarks + 06/07/2005 M.S. Teel 0 Original + + NOTES: + wview medium-specific routines to be supplied: + + xxxMediumInit - sets up function pointers and work area + xxxInit - opens the interface and configures it + xxxRead - blocking read until specified bytes are read + xxxWrite - write on medium + xxxExit - cleanup and close interface + + See daemon.h for details of the WVIEW_MEDIUM structure. + + LICENSE: + Copyright (c) 2004, Mark S. Teel (mark@teel.ws) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +/* ... System include files +*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/* ... Library include files +*/ +#include +#include + +/* ... Local include files +*/ +#include +#include +#include +#include + +/* ... global memory declarations +*/ + +/* ... local memory +*/ +static MEDIUM_SERIAL mediumSerial; + + +////////////////////////////////////////////////////////////////////////////// +// ... medium callback functions +////////////////////////////////////////////////////////////////////////////// + +static int serialInit (WVIEW_MEDIUM *med, char *deviceName) +{ + MEDIUM_SERIAL *serialWork = (MEDIUM_SERIAL *)med->workData; + + // ... open our serial channel + med->fd = open (deviceName, serialWork->openFlags); + if (med->fd == -1) + { + radMsgLog (PRI_CATASTROPHIC, "Serial device %s failed to open: %s", + deviceName, strerror(errno)); + return ERROR; + } + + if (flock (med->fd, LOCK_EX) < 0) + { + if (errno == EOPNOTSUPP) + { + radMsgLog(PRI_MEDIUM, "serial device locking not supported by OS for %s", + deviceName); + } + else + { + radMsgLog (PRI_CATASTROPHIC, "Serial device %s locked by other program!", + deviceName); + return ERROR; + } + } + + // configure it + serialWork->portInit (med->fd); + + tcflush (med->fd, TCIFLUSH); + tcflush (med->fd, TCOFLUSH); + + // Save the device name: + strncpy(serialWork->device, deviceName, WVIEW_STRING2_SIZE); + + radUtilsSleep (1); + return OK; +} + +static void serialExit (WVIEW_MEDIUM *med) +{ + tcflush (med->fd, TCIFLUSH); + tcflush (med->fd, TCOFLUSH); + close (med->fd); + return; +} + +static int serialRestart (WVIEW_MEDIUM *med) +{ + MEDIUM_SERIAL *work = (MEDIUM_SERIAL*)med->workData; + + serialExit(med); + radMsgLog (PRI_HIGH, "serialRestart: attempting restart"); + while ((!wviewdIsExiting()) && (serialInit(med, work->device) == ERROR)) + { + radMsgLog (PRI_HIGH, "serialRestart: restart failed"); + radUtilsSleep(5000); + radMsgLog (PRI_HIGH, "serialRestart: retrying restart"); + } + if (!wviewdIsExiting()) + { + radMsgLog (PRI_HIGH, "serialRestart: recovered"); + } + return OK; + +} + +static int serialWrite (WVIEW_MEDIUM *med, void *buffer, int length) +{ + int retVal; + int wrerrno = 0; + + retVal = write (med->fd, buffer, length); + if (retVal != length) + { + if (retVal == -1) + { + wrerrno = errno; + } + + return retVal; + } + + // ... now wait for the TX buffer to empty out (this blocks) + tcdrain (med->fd); + + return retVal; +} + +static int serialReadExact (WVIEW_MEDIUM *med, void *bfr, int len, int msTimeout) +{ + int rval, cumTime = 0, index = 0; + uint64_t readTime; + uint8_t *ptr = (uint8_t *)bfr; + + while (index < len && cumTime < msTimeout) + { + readTime = radTimeGetMSSinceEpoch (); + rval = read (med->fd, &ptr[index], len - index); + if (rval < 0) + { + if (errno != EINTR && errno != EAGAIN) + { + return ERROR; + } + } + else + { + index += rval; + } + + readTime = radTimeGetMSSinceEpoch () - readTime; + cumTime += (int)readTime; + if (index < len && cumTime < msTimeout) + { + readTime = radTimeGetMSSinceEpoch (); + radUtilsSleep (9); + readTime = radTimeGetMSSinceEpoch () - readTime; + cumTime += (int)readTime; + } + } + + return ((index == len) ? len : ERROR); +} + +static void serialFlush (WVIEW_MEDIUM *med, int queue) +{ + if (queue == WV_QUEUE_INPUT) + { + tcflush (med->fd, TCIFLUSH); + } + else if (queue == WV_QUEUE_OUTPUT) + { + tcflush (med->fd, TCOFLUSH); + } + + return; +} + +static void serialDrain (WVIEW_MEDIUM *med) +{ + tcdrain (med->fd); + return; +} + +static RADSOCK_ID serialGetSocket(WVIEW_MEDIUM *med) +{ + return NULL; +} + + +// ... ----- API methods ----- + +int serialMediumInit (WVIEW_MEDIUM *medium, void (*portInit)(int fd), int openFlags) +{ + MEDIUM_SERIAL *work = &mediumSerial; + + memset (medium, 0, sizeof (*medium)); + memset (work, 0, sizeof (*work)); + + work->portInit = portInit; + work->openFlags = openFlags; + + medium->type = MEDIUM_TYPE_DEVICE; + + // set our workData pointer for later use + medium->workData = (void *)work; + + medium->init = serialInit; + medium->exit = serialExit; + medium->restart = serialRestart; + medium->read = serialReadExact; + medium->write = serialWrite; + medium->flush = serialFlush; + medium->txdrain = serialDrain; + medium->getsocket = serialGetSocket; + + return OK; +} + diff --git a/stations/common/serial.h b/stations/common/serial.h new file mode 100644 index 0000000..b1effbc --- /dev/null +++ b/stations/common/serial.h @@ -0,0 +1,63 @@ +#ifndef INC_serialh +#define INC_serialh +/*--------------------------------------------------------------------------- + + FILENAME: + serial.h + + PURPOSE: + Provide utilities for serial communications. + + REVISION HISTORY: + Date Engineer Revision Remarks + 08/15/03 M.S. Teel 0 Original + + NOTES: + + + LICENSE: + Copyright (c) 2004, Mark S. Teel (mark@teel.ws) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +/* ... System include files +*/ +#include +#include +#include +#include +#include +#include + +/* ... Library include files +*/ +#include +#include + +/* ... Local include files +*/ +#include +#include +#include + + +// define our work area +typedef struct +{ + void (*portInit) (int fd); + int openFlags; + char device[WVIEW_STRING2_SIZE]; +} MEDIUM_SERIAL; + + +/* ... function prototypes +*/ + +// this is the only globally visible method +extern int serialMediumInit (WVIEW_MEDIUM *medium, void (*portInit)(int fd), int openFlags); + +#endif + diff --git a/stations/common/serialReopen.c b/stations/common/serialReopen.c new file mode 100755 index 0000000..c3318cd --- /dev/null +++ b/stations/common/serialReopen.c @@ -0,0 +1,259 @@ +/*--------------------------------------------------------------------------- + + FILENAME: + serialReopen.c + + PURPOSE: + Provide utilities for serial communications. Port is not left open + but instead is opened for each read/write access. + + REVISION HISTORY: + Date Engineer Revision Remarks + 06/13/10 M.S. Teel 0 Original + + NOTES: + wview medium-specific routines to be supplied: + + xxxMediumInit - sets up function pointers and work area + xxxInit - opens the interface and configures it + xxxRead - blocking read until specified bytes are read + xxxWrite - write on medium + xxxExit - cleanup and close interface + + See daemon.h for details of the WVIEW_MEDIUM structure. + + LICENSE: + Copyright (c) 2004, Mark S. Teel (mark@teel.ws) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +/* ... System include files +*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/* ... Library include files +*/ +#include +#include + +/* ... Local include files +*/ +#include +#include +#include +#include + +/* ... global memory declarations +*/ + +/* ... local memory +*/ +static MEDIUM_SERIAL_REOPEN mediumSerialReopen; + + +static int openSerialPort(WVIEW_MEDIUM *med) +{ + MEDIUM_SERIAL_REOPEN *serialWork = (MEDIUM_SERIAL_REOPEN *)med->workData; + + // ... open our serial channel + med->fd = open (med->device, serialWork->openFlags); + if (med->fd == -1) + { + radMsgLog (PRI_CATASTROPHIC, "Serial device %s failed to open: %s", + med->device, strerror(errno)); + return ERROR; + } + + if (flock (med->fd, LOCK_EX) < 0) + { + if (errno == EOPNOTSUPP) + { + radMsgLog(PRI_MEDIUM, "serial device locking not supported by OS for %s", + med->device); + } + else + { + radMsgLog (PRI_CATASTROPHIC, "Serial device %s locked by other program!", + med->device); + return ERROR; + } + } + + // configure it + serialWork->portInit (med->fd); + + tcflush (med->fd, TCIFLUSH); + tcflush (med->fd, TCOFLUSH); + + radUtilsSleep (1); + return OK; + +} + +static int closeSerialPort(WVIEW_MEDIUM *med) +{ + if (med->fd != -1) + close(med->fd); + med->fd = -1; + return OK; +} + + +////////////////////////////////////////////////////////////////////////////// +// ... medium callback functions +////////////////////////////////////////////////////////////////////////////// + +static int serialReopenInit (WVIEW_MEDIUM *med, char *deviceName) +{ + strncpy(med->device, deviceName, WVIEW_MAX_PATH); + med->fd = -1; + return OK; +} + +static void serialReopenExit (WVIEW_MEDIUM *med) +{ + tcflush (med->fd, TCIFLUSH); + tcflush (med->fd, TCOFLUSH); + closeSerialPort(med); + return; +} + +static int serialReopenWrite (WVIEW_MEDIUM *med, void *buffer, int length) +{ + int retVal; + int wrerrno = 0; + + retVal = write (med->fd, buffer, length); + if (retVal != length) + { + if (retVal == -1) + { + wrerrno = errno; + } + + return retVal; + } + + // ... now wait for the TX buffer to empty out (this blocks) + tcdrain (med->fd); + + return retVal; +} + +static int serialReopenReadExact (WVIEW_MEDIUM *med, void *bfr, int len, int msTimeout) +{ + int rval, cumTime = 0, index = 0; + uint64_t readTime; + uint8_t *ptr = (uint8_t *)bfr; + + while (index < len && cumTime < msTimeout) + { + readTime = radTimeGetMSSinceEpoch (); + rval = read (med->fd, &ptr[index], len - index); + if (rval < 0) + { + if (errno != EINTR && errno != EAGAIN) + { + return ERROR; + } + } + else + { + index += rval; + } + + readTime = radTimeGetMSSinceEpoch () - readTime; + cumTime += (int)readTime; + if (index < len && cumTime < msTimeout) + { + readTime = radTimeGetMSSinceEpoch (); + radUtilsSleep (9); + readTime = radTimeGetMSSinceEpoch () - readTime; + cumTime += (int)readTime; + } + } + + return ((index == len) ? len : ERROR); +} + +static void serialReopenFlush (WVIEW_MEDIUM *med, int queue) +{ + if (queue == WV_QUEUE_INPUT) + { + tcflush (med->fd, TCIFLUSH); + } + else if (queue == WV_QUEUE_OUTPUT) + { + tcflush (med->fd, TCOFLUSH); + } + + return; +} + +static void serialReopenDrain (WVIEW_MEDIUM *med) +{ + tcdrain (med->fd); + return; +} + +static RADSOCK_ID serialReopenGetSocket(WVIEW_MEDIUM *med) +{ + return NULL; +} + +static void serialReopenSessionStart(struct _wview_medium *medium) +{ + return; +} + +static void serialReopenSessionEnd(struct _wview_medium *medium) +{ + return; +} + + +// ... ----- API methods ----- + +int serialReopenMediumInit (WVIEW_MEDIUM *medium, void (*portInit)(int fd), int openFlags) +{ + MEDIUM_SERIAL_REOPEN *work = &mediumSerialReopen; + + memset (medium, 0, sizeof (*medium)); + memset (work, 0, sizeof (*work)); + + work->portInit = portInit; + work->openFlags = openFlags; + + // set our workData pointer for later use + medium->workData = (void *)work; + + medium->init = serialReopenInit; + medium->exit = serialReopenExit; + medium->sessionStart = serialReopenSessionStart; + medium->sessionEnd = serialReopenSessionEnd; + medium->read = serialReopenReadExact; + medium->write = serialReopenWrite; + medium->flush = serialReopenFlush; + medium->txdrain = serialReopenDrain; + medium->getsocket = serialReopenGetSocket; + + return OK; +} + diff --git a/stations/common/serialReopen.h b/stations/common/serialReopen.h new file mode 100644 index 0000000..e809397 --- /dev/null +++ b/stations/common/serialReopen.h @@ -0,0 +1,63 @@ +#ifndef INC_serialReopenh +#define INC_serialReopenh +/*--------------------------------------------------------------------------- + + FILENAME: + serialReopen.h + + PURPOSE: + Provide utilities for serial communications. Port is not left open + but instead is opened for each read/write access. + + REVISION HISTORY: + Date Engineer Revision Remarks + 06/13/10 M.S. Teel 0 Original + + NOTES: + + + LICENSE: + Copyright (c) 2010, Mark S. Teel (mark@teel.ws) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +/* ... System include files +*/ +#include +#include +#include +#include +#include +#include + +/* ... Library include files +*/ +#include +#include + +/* ... Local include files +*/ +#include +#include +#include + + +// define our work area +typedef struct +{ + void (*portInit) (int fd); + int openFlags; +} MEDIUM_SERIAL_REOPEN; + + +/* ... function prototypes +*/ + +// this is the only globally visible method +extern int serialReopenMediumInit (WVIEW_MEDIUM *medium, void (*portInit)(int fd), int openFlags); + +#endif + diff --git a/stations/common/station.c b/stations/common/station.c new file mode 100755 index 0000000..857e464 --- /dev/null +++ b/stations/common/station.c @@ -0,0 +1,552 @@ +/*--------------------------------------------------------------------------- + + FILENAME: + station.c + + PURPOSE: + Provide the station abstraction utility. + + REVISION HISTORY: + Date Engineer Revision Remarks + 12/31/2005 M.S. Teel 0 Original + + NOTES: + + LICENSE: + Copyright (c) 2005, Mark S. Teel (mark@teel.ws) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +/* ... System include files +*/ + +/* ... Library include files +*/ +#include + +/* ... Local include files +*/ +#include + +/* ... global memory declarations +*/ + +static int newProcessEntryPoint (void * pargs) +{ + char* binFile = (char*)pargs; + + return (system(binFile)); +} + +static int processAlertMessage (WVIEWD_WORK *work, EmailAlertTypes type) +{ + char syscmnd[256]; + int retVal; + time_t timenow = time(NULL); + struct tm bknTime; + + if (! work->IsAlertEmailsEnabled || + ! strlen(work->alertEmailToAdrs) || + ! strlen(work->alertEmailFromAdrs)) + { + return OK; + } + + localtime_r(&timenow, &bknTime); + sprintf(syscmnd, "sendEmail -f %s -t %s -u \"%4.4d%2.2d%2.2d %2.2d:%2.2d: %s\" -m \"%s\"", + work->alertEmailFromAdrs, + work->alertEmailToAdrs, + bknTime.tm_year + 1900, + bknTime.tm_mon + 1, + bknTime.tm_mday, + bknTime.tm_hour, + bknTime.tm_min, + emailAlertGetSubject(type), + emailAlertGetBody(type)); + + radStartProcess (newProcessEntryPoint, syscmnd); + return OK; +} + + +// send shutdown notification +int stationSendShutdown (WVIEWD_WORK *work) +{ + WVIEW_MSG_SHUTDOWN msg; + + if (radMsgRouterMessageSend (WVIEW_MSG_TYPE_SHUTDOWN, &msg, sizeof(msg)) + == ERROR) + { + // can't send! + radMsgLog (PRI_HIGH, "radMsgRouterMessageSend failed: shutdown"); + return ERROR; + } + + return OK; +} + +int stationSendArchiveNotifications (WVIEWD_WORK *work, float sampleRain) +{ + WVIEW_MSG_ARCHIVE_NOTIFY notify; + int retVal; + HISTORY_DATA store; + + notify.dateTime = work->archiveDateTime; + notify.intemp = (int)floorf(work->loopPkt.inTemp * 10); + notify.inhumidity = work->loopPkt.inHumidity; + notify.temp = (int)floorf(work->loopPkt.outTemp * 10); + notify.humidity = work->loopPkt.outHumidity; + notify.barom = (int)floorf(work->loopPkt.barometer * 1000); + notify.stationPressure = (int)floorf(work->loopPkt.stationPressure * 1000); + notify.altimeter = (int)floorf(work->loopPkt.altimeter * 1000); + notify.winddir = work->loopPkt.windDir; + notify.wspeedF = work->loopPkt.windSpeedF; + notify.dewpoint = (int)floorf(work->loopPkt.dewpoint * 10); + notify.hiwspeedF = work->loopPkt.windGustF; + notify.rxPercent = work->loopPkt.rxCheckPercent; + notify.sampleRain = sampleRain; + notify.UV = work->loopPkt.UV; + notify.radiation = work->loopPkt.radiation; + + // Grab last 60 minutes and last 24 hours from database: + retVal = dbsqliteArchiveGetAverages (FALSE, + work->archiveInterval, + &store, + time(NULL) - WV_SECONDS_IN_HOUR, + WV_SECONDS_IN_HOUR/SECONDS_IN_INTERVAL(work->archiveInterval)); + if (retVal <= 0) + { + notify.rainHour = ARCHIVE_VALUE_NULL; + } + else + { + notify.rainHour = store.values[DATA_INDEX_rain]; + } + + retVal = dbsqliteArchiveGetAverages (FALSE, + work->archiveInterval, + &store, + time(NULL) - WV_SECONDS_IN_DAY, + WV_SECONDS_IN_DAY/SECONDS_IN_INTERVAL(work->archiveInterval)); + if (retVal <= 0) + { + notify.rainDay = ARCHIVE_VALUE_NULL; + } + else + { + notify.rainDay = store.values[DATA_INDEX_rain]; + } + + notify.rainToday = sensorGetCumulative (&work->sensors.sensor[STF_DAY][SENSOR_RAIN]); + + + if (radMsgRouterMessageSend (WVIEW_MSG_TYPE_ARCHIVE_NOTIFY, ¬ify, sizeof(notify)) + == ERROR) + { + // can't send! + radMsgLog (PRI_HIGH, "radMsgRouterMessageSend failed: notify"); + return ERROR; + } + + return OK; +} + +int stationProcessInfoResponses (WVIEWD_WORK *work) +{ + WVIEW_MSG_STATION_INFO apath; + + if (!work->archiveRqstPending) + { + return OK; + } + + apath.lastArcTime = work->archiveDateTime; + apath.archiveInterval = work->archiveInterval; + apath.latitude = work->latitude; + apath.longitude = work->longitude; + apath.elevation = work->elevation; + if (! work->showStationIF) + { + sprintf(apath.stationType, "%s", work->stationType); + } + else + { + if (work->medium.type == MEDIUM_TYPE_USBHID) + { + sprintf(apath.stationType, "%s (USB)", work->stationType); + } + else if (work->medium.type == MEDIUM_TYPE_NONE) + { + sprintf(apath.stationType, "%s", work->stationType); + } + else if (!strcmp(work->stationInterface, "ethernet")) + { + sprintf(apath.stationType, "%s (%s:%d)", + work->stationType, work->stationHost, work->stationPort); + } + else + { + sprintf(apath.stationType, "%s (%s)", + work->stationType, work->stationDevice); + } + } + + if (radMsgRouterMessageSend (WVIEW_MSG_TYPE_STATION_INFO, &apath, sizeof (apath)) + == ERROR) + { + radMsgLog (PRI_HIGH, "radMsgRouterMessageSend failed Archive Path"); + return ERROR; + } + + work->archiveRqstPending = FALSE; + return OK; +} + +int stationProcessIPM (WVIEWD_WORK *work, char *srcQueueName, int msgType, void *msg) +{ + WVIEW_MSG_REQUEST *msgRqst; + WVIEW_MSG_LOOP_DATA loop; + WVIEW_MSG_HILOW_DATA hilow; + WVIEW_MSG_ALERT* alert; + int retVal, i; + + switch (msgType) + { + case WVIEW_MSG_TYPE_REQUEST: + msgRqst = (WVIEW_MSG_REQUEST *)msg; + + switch (msgRqst->requestType) + { + case WVIEW_RQST_TYPE_STATION_INFO: + // flag that a request has been tendered + work->archiveRqstPending = TRUE; + + // are we currently in a serial cycle? + if (!work->runningFlag) + { + // yes, just bail out for now + return OK; + } + else + { + // no, send it now + return (stationProcessInfoResponses (work)); + } + + case WVIEW_RQST_TYPE_LOOP_DATA: + loop.loopData = work->loopPkt; + if (loop.loopData.sampleET == ARCHIVE_VALUE_NULL) + loop.loopData.sampleET = 0; + if (loop.loopData.radiation == 0xFFFF) + loop.loopData.radiation = 0; + if (loop.loopData.UV < 0) + loop.loopData.UV = 0; + if (loop.loopData.rxCheckPercent == 0xFFFF) + loop.loopData.rxCheckPercent = 0; + if (loop.loopData.wxt510Hail == ARCHIVE_VALUE_NULL) + loop.loopData.wxt510Hail = 0; + if (loop.loopData.wxt510Hailrate == ARCHIVE_VALUE_NULL) + loop.loopData.wxt510Hailrate = 0; + if (loop.loopData.wxt510HeatingTemp == ARCHIVE_VALUE_NULL) + loop.loopData.wxt510HeatingTemp = 0; + if (loop.loopData.wxt510HeatingVoltage == ARCHIVE_VALUE_NULL) + loop.loopData.wxt510HeatingVoltage = 0; + if (loop.loopData.wxt510SupplyVoltage == ARCHIVE_VALUE_NULL) + loop.loopData.wxt510SupplyVoltage = 0; + if (loop.loopData.wxt510ReferenceVoltage == ARCHIVE_VALUE_NULL) + loop.loopData.wxt510ReferenceVoltage = 0; + if (loop.loopData.wxt510RainDuration == ARCHIVE_VALUE_NULL) + loop.loopData.wxt510RainDuration = 0; + if (loop.loopData.wxt510RainPeakRate == ARCHIVE_VALUE_NULL) + loop.loopData.wxt510RainPeakRate = 0; + if (loop.loopData.wxt510HailDuration == ARCHIVE_VALUE_NULL) + loop.loopData.wxt510HailDuration = 0; + if (loop.loopData.wxt510HailPeakRate == ARCHIVE_VALUE_NULL) + loop.loopData.wxt510HailPeakRate = 0; + if (loop.loopData.wxt510Rain == ARCHIVE_VALUE_NULL) + loop.loopData.wxt510Rain = 0; + + if (radMsgRouterMessageSend (WVIEW_MSG_TYPE_LOOP_DATA, + &loop, + sizeof(loop)) + == ERROR) + { + radMsgLog (PRI_HIGH, "radMsgRouterMessageSend failed LOOP"); + return ERROR; + } + + return OK; + + case WVIEW_RQST_TYPE_HILOW_DATA: + hilow.hilowData = work->sensors; + if (radMsgRouterMessageSend (WVIEW_MSG_TYPE_HILOW_DATA, + &hilow, + sizeof(hilow)) + == ERROR) + { + radMsgLog (PRI_HIGH, "radMsgRouterMessageSend failed HILOW %d", sizeof(WVIEW_MSG_HILOW_DATA)); + return ERROR; + } + + return OK; + } + break; + + case WVIEW_MSG_TYPE_ALERT: + alert = (WVIEW_MSG_ALERT*)msg; + if (processAlertMessage(work, alert->alertType) == ERROR) + { + radMsgLog (PRI_HIGH, "Email Alert Send failed - are sendmail and sendEmail installed?"); + } + break; + + default: + // Pass it through to the station-specific function: + stationMessageIndicate(work, msgType, msg); + break; + } + + return OK; +} + +int stationPushDataToClients (WVIEWD_WORK *work) +{ + WVIEW_MSG_LOOP_DATA loop; + + if (!work->runningFlag) + { + return OK; + } + + memcpy (&loop.loopData, &work->loopPkt, sizeof (loop.loopData)); + + if (radMsgRouterMessageSend (WVIEW_MSG_TYPE_LOOP_DATA_SVC, &loop, sizeof(loop)) + == ERROR) + { + radMsgLog (PRI_HIGH, "radMsgRouterMessageSend failed for loop transmit!"); + return ERROR; + } + + radProcessTimerStart (work->pushTimer, work->pushInterval); + return OK; +} + +int stationPushArchiveToClients(WVIEWD_WORK *work, ARCHIVE_PKT* pktToSend) +{ + WVIEW_MSG_ARCHIVE_DATA arc; + + if (!work->runningFlag) + { + return OK; + } + + memcpy (&arc.archiveData, pktToSend, sizeof (*pktToSend)); + + if (radMsgRouterMessageSend (WVIEW_MSG_TYPE_ARCHIVE_DATA, &arc, sizeof(arc)) + == ERROR) + { + radMsgLog (PRI_HIGH, "radMsgRouterMessageSend failed for archive transmit!"); + return ERROR; + } + + return OK; +} + +void stationStartArchiveTimerUniform (WVIEWD_WORK *work) +{ + time_t ntime; + struct tm currTime; + int intSECS; + + // get the current time + ntime = time (NULL); + localtime_r (&ntime, &currTime); + + // get the next archive interval delta in seconds + intSECS = currTime.tm_min/work->archiveInterval; + intSECS *= work->archiveInterval; + intSECS += work->archiveInterval; + intSECS -= currTime.tm_min; // delta in minutes + intSECS *= 60; + + // we try to land 4 seconds after top-of-minute + intSECS += (4 - currTime.tm_sec); + + // now we have the delta in seconds + work->nextArchiveTime = ntime + intSECS; + radTimerStart (work->archiveTimer, intSECS*1000); +} + +void stationStartCDataTimerUniform (WVIEWD_WORK *work) +{ + time_t ntime; + struct tm locTime; + int moduloVal, cdataintSECS; + + ntime = time (NULL); + localtime_r (&ntime, &locTime); + cdataintSECS = work->cdataInterval/1000; + + // try to hit the start of next cdataInterval + moduloVal = locTime.tm_sec % cdataintSECS; + moduloVal = cdataintSECS - moduloVal; + + radTimerStart (work->cdataTimer, moduloVal*1000); +} + +int stationStartSyncTimerUniform (WVIEWD_WORK *work, int firstTime) +{ + time_t ntime; + struct tm locTime; + int tempVal; + + ntime = time (NULL); + localtime_r (&ntime, &locTime); + locTime.tm_sec %= 60; + + if (firstTime) + { + // Make sure the sync timer does not track with the archive interval + // by doing the sync at 2:30 of any 5 minute interval: + tempVal = locTime.tm_min % 5; + tempVal = 2 - tempVal; + if (tempVal < 0) + { + tempVal += 5; + } + tempVal *= 60; // Make it seconds + + if (locTime.tm_sec <= 30) + { + tempVal += 30 - locTime.tm_sec; + } + else + { + if (tempVal == 0) + { + tempVal = 5 * 60; // Add 5 minutes + } + tempVal -= (locTime.tm_sec - 30); + } + if (tempVal == 0) + { + tempVal = 1; + } + + radTimerStart (work->syncTimer, tempVal * 1000); + return FALSE; + } + + if (locTime.tm_sec < 10) + { + // Correct: + radTimerStart (work->syncTimer, ((30 - locTime.tm_sec) * 1000)); + } + else if (locTime.tm_sec > 45) + { + radTimerStart (work->syncTimer, ((90 - locTime.tm_sec) * 1000)); + } + else + { + // try to hit 30 secs into the minute + tempVal = 30 - locTime.tm_sec; + radTimerStart (work->syncTimer, WVD_TIME_SYNC_INTERVAL + (tempVal * 1000)); + return TRUE; + } + + return FALSE; +} + +int stationVerifyArchiveInterval (WVIEWD_WORK *work) +{ + ARCHIVE_PKT tempRec; + + // sanity check the archive interval against the most recent record + memset (&tempRec, 0, sizeof(tempRec)); + if ((int)dbsqliteArchiveGetNewestTime(&tempRec) == ERROR) + { + // there must be no archive records - just return OK + return OK; + } + + if ((int)tempRec.interval != (int)work->archiveInterval) + { + radMsgLog (PRI_HIGH, + "verifyArchiveInterval: station value of %d does NOT match archive value of %d", + work->archiveInterval, (int)tempRec.interval); + return ERROR; + } + else + { + return OK; + } +} + +int stationGetConfigValueInt (WVIEWD_WORK *work, char *configName, int *store) +{ + int value; + + wvconfigInit (FALSE); + value = wvconfigGetINTValue (configName); + wvconfigExit (); + *store = value; + return OK; +} + +int stationGetConfigValueBoolean (WVIEWD_WORK *work, char *configName, int *store) +{ + int value; + + wvconfigInit (FALSE); + value = wvconfigGetBooleanValue(configName); + wvconfigExit (); + *store = value; + return OK; +} + +int stationGetConfigValueFloat (WVIEWD_WORK *work, char *configName, float *store) +{ + float tempfloat; + + wvconfigInit (FALSE); + tempfloat = wvconfigGetDOUBLEValue (configName); + wvconfigExit (); + + tempfloat *= 100; + if (tempfloat < 0.0) + tempfloat -= 0.5; + else + tempfloat += 0.5; + tempfloat /= 100; + *store = tempfloat; + return OK; +} + +void stationClearLoopData (WVIEWD_WORK *work) +{ + work->loopPkt.sampleET = ARCHIVE_VALUE_NULL; + work->loopPkt.radiation = 0xFFFF; + work->loopPkt.UV = -1; + work->loopPkt.rxCheckPercent = 0xFFFF; + work->loopPkt.wxt510Hail = ARCHIVE_VALUE_NULL; + work->loopPkt.wxt510Hailrate = ARCHIVE_VALUE_NULL; + work->loopPkt.wxt510HeatingTemp = ARCHIVE_VALUE_NULL; + work->loopPkt.wxt510HeatingVoltage = ARCHIVE_VALUE_NULL; + work->loopPkt.wxt510SupplyVoltage = ARCHIVE_VALUE_NULL; + work->loopPkt.wxt510ReferenceVoltage = ARCHIVE_VALUE_NULL; + work->loopPkt.wxt510RainDuration = ARCHIVE_VALUE_NULL; + work->loopPkt.wxt510RainPeakRate = ARCHIVE_VALUE_NULL; + work->loopPkt.wxt510HailDuration = ARCHIVE_VALUE_NULL; + work->loopPkt.wxt510HailPeakRate = ARCHIVE_VALUE_NULL; + work->loopPkt.wxt510Rain = ARCHIVE_VALUE_NULL; + work->loopPkt.wmr918WindBatteryStatus = 0xFF; + work->loopPkt.wmr918RainBatteryStatus = 0xFF; + work->loopPkt.wmr918OutTempBatteryStatus = 0xFF; + work->loopPkt.wmr918InTempBatteryStatus = 0xFF; + + return; +} + diff --git a/stations/common/station.h b/stations/common/station.h new file mode 100755 index 0000000..76b7fd3 --- /dev/null +++ b/stations/common/station.h @@ -0,0 +1,250 @@ +#ifndef INC_stationh +#define INC_stationh +/*--------------------------------------------------------------------------- + + FILENAME: + station.h + + PURPOSE: + Provide the station abstraction utility. + + REVISION HISTORY: + Date Engineer Revision Remarks + 12/31/2005 M.S. Teel 0 Original + + NOTES: + + + LICENSE: + Copyright (c) 2005, Mark S. Teel (mark@teel.ws) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +/* ... System include files +*/ +#include +#include +#include +#include +#include +#include +#include + +/* ... Library include files +*/ +#include +#include +#include +#include + +/* ... Local include files +*/ +#include +#include +#include +#include +#include +#include + +#define SERIAL_BYTE_LENGTH_MAX 1024 + + +////////////////////////////////////////////////////////////////////////////// +// Station-Specific Prototypes (to be supplied by station interface) +////////////////////////////////////////////////////////////////////////////// + +// station-supplied init function +// -- Can Be Asynchronous - event indication required -- +// +// MUST (in this order): +// - set the 'stationGeneratesArchives' flag in WVIEWD_WORK: +// if the station generates archive records (TRUE) or they should be +// generated automatically by the daemon from the sensor readings (FALSE) +// - Initialize the 'stationData' store for station work area +// - Initialize the interface medium +// - determine the station archive interval - either from the station itself +// or from user configuration in wview.conf - and set the +// 'work->archiveInterval' variable (in minutes) in WVIEWD_WORK +// - VERIFY the archive interval by calling 'stationVerifyArchiveInterval' - +// If they don't match, indicate an errant start via the call: +// radProcessEventsSend (NULL, STATION_INIT_COMPLETE_EVENT, 1) +// and stopping further init activities here! +// - do any catch-up on archive records if there is a data logger (can be +// asynchronous) - the 'work->runningFlag' can be used for start up +// synchronization but should not be modified by the station interface code +// - do initial LOOP acquisition +// - indicate successful initialization is done via the call: +// radProcessEventsSend (NULL, STATION_INIT_COMPLETE_EVENT, 0) +// +// OPTIONAL: +// - Initialize a state machine or any other construct required for the +// station interface - these should be stored in the 'stationData' store +// +// 'archiveIndication' - indication callback used to pass back an archive record +// generated as a result of 'stationGetArchive' being called; should receive a +// NULL pointer for 'newRecord' if no record available; only used if +// 'stationGeneratesArchives' flag is set to TRUE by the station interface +// +// Returns: OK or ERROR +// +extern int stationInit +( + WVIEWD_WORK *work, + void (*archiveIndication)(ARCHIVE_PKT* newRecord) +); + + +// station-supplied exit function +// +// Returns: N/A +// +extern void stationExit (WVIEWD_WORK *work); + + +// station-supplied function to retrieve positional info (lat, long, elev) - +// should populate WVIEWD_WORK fields: latitude, longitude, elevation +// -- Synchronous -- +// +// - If station does not store these parameters, they can be retrieved from the +// wview.conf file (see the 'stationGetConfigValue' utilities below) - user +// must choose station type "Generic" when running the wviewconfig script +// +// Returns: OK or ERROR +// +extern int stationGetPosition (WVIEWD_WORK *work); + + +// station-supplied function to indicate a time sync should be performed if the +// station maintains time, otherwise may be safely ignored +// -- Can Be Asynchronous -- +// +// Returns: OK or ERROR +// +extern int stationSyncTime (WVIEWD_WORK *work); + + +// station-supplied function to indicate sensor readings should be performed - +// should populate 'work' struct: loopPkt (see datadefs.h for minimum field reqs) +// -- Can Be Asynchronous -- +// +// - indicate readings are complete by sending the STATION_LOOP_COMPLETE_EVENT +// event to this process (radProcessEventsSend (NULL, STATION_LOOP_COMPLETE_EVENT, 0)) +// +// Returns: OK or ERROR +// +extern int stationGetReadings (WVIEWD_WORK *work); + + +// station-supplied function to indicate an archive record should be generated - +// MUST populate an ARCHIVE_RECORD struct and indicate it to 'archiveIndication' +// function passed into 'stationInit' +// -- Asynchronous - callback indication required -- +// +// Returns: OK or ERROR +// +// Note: 'archiveIndication' should receive a NULL pointer for the newRecord if +// no record is available +// Note: This function will only be invoked by the wview daemon if the +// 'stationInit' function set the 'stationGeneratesArchives' to TRUE +// +extern int stationGetArchive (WVIEWD_WORK *work); + + +// station-supplied function to indicate data is available on the station +// interface medium (serial or ethernet) - +// It is the responsibility of the station interface to read the data from the +// medium and process appropriately. The data does not have to be read within +// the context of this function, but may be used to stimulate a state machine. +// -- Synchronous -- +// +// Returns: N/A +// +extern void stationDataIndicate (WVIEWD_WORK *work); + + +// station-supplied function to receive IPM messages - any message received by +// the generic station message handler which is not recognized will be passed +// to the station-specific code through this function. +// It is the responsibility of the station interface to process the message +// appropriately (or ignore it). +// -- Synchronous -- +// +// Returns: N/A +// +extern void stationMessageIndicate (WVIEWD_WORK *work, int msgType, void *msg); + + +// station-supplied function to indicate the interface timer has expired - +// It is the responsibility of the station interface to start/stop the interface +// timer as needed for the particular station requirements. +// The station interface timer is specified by the 'ifTimer' member of the +// WVIEWD_WORK structure. No other timers in that structure should be manipulated +// in any way by the station interface code. +// -- Synchronous -- +// +// Returns: N/A +// +extern void stationIFTimerExpiry (WVIEWD_WORK *work); + + +////////////////////////////////////////////////////////////////////////////// +// Station-Generic Definitions +////////////////////////////////////////////////////////////////////////////// + +#define STATION_PARM_ELEVATION "STATION_ELEVATION" +#define STATION_PARM_LATITUDE "STATION_LATITUDE" +#define STATION_PARM_LONGITUDE "STATION_LONGITUDE" +#define STATION_PARM_ARC_INTERVAL "STATION_ARCHIVE_INTERVAL" +#define STATION_PARM_OUTSIDE_CHANNEL "STATION_OUTSIDE_CHANNEL" + + + +////////////////////////////////////////////////////////////////////////////// +// Station-Generic Prototypes +////////////////////////////////////////////////////////////////////////////// + +// send shutdown notification +extern int stationSendShutdown (WVIEWD_WORK *work); + +// send archive record notification +extern int stationSendArchiveNotifications (WVIEWD_WORK *work, float sampleRain); + +// send the archive path message +extern int stationProcessInfoResponses (WVIEWD_WORK *work); + +// process received IPM messages +extern int stationProcessIPM (WVIEWD_WORK *work, char *srcQueueName, int msgType, void *msg); + +// push LOOP data to clients +extern int stationPushDataToClients (WVIEWD_WORK *work); + +// push Archive data to clients +extern int stationPushArchiveToClients(WVIEWD_WORK *work, ARCHIVE_PKT* pktToSend); + +// start the archive timer uniformly +extern void stationStartArchiveTimerUniform (WVIEWD_WORK *work); + +// start the data acquisition timer uniformly +extern void stationStartCDataTimerUniform (WVIEWD_WORK *work); + +// start the station time sync timer (if required) uniformly: +// Returns TRUE if sync should be done or FALSE otherwise +extern int stationStartSyncTimerUniform (WVIEWD_WORK *work, int firstTime); + +// verify the archive interval configured in the station or set by the user in +// wview-conf.sdb to confirm it matches the archive data +extern int stationVerifyArchiveInterval (WVIEWD_WORK *work); + +// several to retrieve config values out of wview.conf +extern int stationGetConfigValueInt (WVIEWD_WORK *work, char *configName, int *store); +extern int stationGetConfigValueFloat (WVIEWD_WORK *work, char *configName, float *store); +extern int stationGetConfigValueBoolean (WVIEWD_WORK *work, char *configName, int *store); + +// Clear optional station data in the loop packt: +extern void stationClearLoopData (WVIEWD_WORK *work); + +#endif + diff --git a/stations/common/stormRain.c b/stations/common/stormRain.c new file mode 100755 index 0000000..b37f099 --- /dev/null +++ b/stations/common/stormRain.c @@ -0,0 +1,185 @@ +/*--------------------------------------------------------------------------- + + FILENAME: + stormRain.c + + PURPOSE: + Provide utilities to compute "storm" rain and start date. + + REVISION HISTORY: + Date Engineer Revision Remarks + 01/08/2006 M.S. Teel 0 Original + + NOTES: + + + LICENSE: + Copyright (c) 2006, Mark S. Teel (mark@teel.ws) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +/* ... System include files +*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/* ... Library include files +*/ +#include +#include + +/* ... Local include files +*/ +#include + +/* ... global memory declarations +*/ + +/* ... local memory +*/ +static STORMRAIN_WORK stormWork; + + +static void arcRecordCallback(ARCHIVE_PKT* rec, void* data) +{ + float rate, rainfall; + int diffMins; + STORMRAIN_WORK* sWork = (STORMRAIN_WORK*)data; + + rainfall = (float)rec->value[DATA_INDEX_rain]; + if (rainfall <= ARCHIVE_VALUE_NULL) + { + // NULL rain in this record: + return; + } + rate = (float)rec->value[DATA_INDEX_rainRate]; + if (rate <= ARCHIVE_VALUE_NULL) + { + // NULL rate in this record: + return; + } + + if (!sWork->inStorm) + { + if (rate >= sWork->startTrigger) + { + // we have a start time + sWork->rain = rainfall; + sWork->startTime = sWork->lastRainTime = rec->dateTime; + sWork->inStorm = TRUE; + } + } + else + { + if (rainfall != 0) + { + // update the rain total, etc. + sWork->rain += rainfall; + sWork->lastRainTime = rec->dateTime; + } + else + { + // see if the storm is over + diffMins = (rec->dateTime - sWork->lastRainTime)/60; + if (diffMins >= (60*sWork->idleHours)) + { + // storm is over... + sWork->rain = 0; + sWork->startTime = sWork->lastRainTime = 0; + sWork->inStorm = FALSE; + } + } + } +} + +void stormRainInit (float startTrigger, int idleHours) +{ + time_t start, end; + ARCHIVE_PKT arcRecord; + char select[256]; + + memset (&stormWork, 0, sizeof(stormWork)); + stormWork.startTrigger = startTrigger; + stormWork.idleHours = idleHours; + + sprintf(select, "rain,rainRate"); + + // get our search start time/date + start = end = time(NULL); + start -= WV_SECONDS_IN_WEEK; // go back one week + + dbsqliteArchiveExecutePerRecord(arcRecordCallback, + (void*)&stormWork, + start, + end, + select); + + return; +} + +void stormRainUpdate (float rainRate, float rainFall) +{ + time_t ntime = time(NULL); + int diffMins; + + if (!stormWork.inStorm) + { + if (rainRate >= stormWork.startTrigger) + { + // we have a start time + stormWork.rain = rainFall; + stormWork.startTime = stormWork.lastRainTime = ntime; + stormWork.inStorm = TRUE; + } + } + else + { + if (rainFall != 0) + { + // update the rain total, etc. + stormWork.rain += rainFall; + stormWork.lastRainTime = ntime; + } + else + { + // see if the storm is over + diffMins = (int)(ntime - stormWork.lastRainTime); + diffMins /= 60; + if (diffMins >= (60*stormWork.idleHours)) + { + // storm is over... + stormWork.rain = 0; + stormWork.startTime = stormWork.lastRainTime = (time_t)0; + stormWork.inStorm = FALSE; + } + } + } + + return; +} + +time_t stormRainGetStartTimeT (void) +{ + return stormWork.startTime; +} + +float stormRainGet (void) +{ + return stormWork.rain; +} + diff --git a/stations/common/stormRain.h b/stations/common/stormRain.h new file mode 100644 index 0000000..a9fe8b9 --- /dev/null +++ b/stations/common/stormRain.h @@ -0,0 +1,78 @@ +#ifndef INC_stormrainh +#define INC_stormrainh +/*--------------------------------------------------------------------------- + + FILENAME: + stormRain.h + + PURPOSE: + Provide utilities to compute "storm" rain and start date. + + REVISION HISTORY: + Date Engineer Revision Remarks + 01/08/2006 M.S. Teel 0 Original + + NOTES: + We define a storm to begin when an effective rain rate of 0.05 in/hr + or greater is observed. A storm ends when 12 hours pass with no + recorded rainfall. + + LICENSE: + Copyright (c) 2006, Mark S. Teel (mark@teel.ws) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +/* ... System include files +*/ +#include +#include +#include +#include +#include +#include + +/* ... Library include files +*/ +#include +#include + +/* ... Local include files +*/ +#include +#include +#include +#include "daemon.h" + + + +// work area +typedef struct +{ + float startTrigger; // rain rate that starts a storm + int idleHours; // number of "no rain" hours that ends storm + int inStorm; // flag indicating if we are in a storm + time_t startTime; // when the storm start was triggered + time_t lastRainTime; // last recorded rain time + float rain; // accumulated storm rain + char strBuffer[32]; // place to return date/time requests +} STORMRAIN_WORK; + + +// function prototypes + +// called once at init +extern void stormRainInit (float startTrigger, int idleHours); + +// called for each new archive interval +extern void stormRainUpdate (float rainRate, float rainFall); + +extern time_t stormRainGetStartTimeT (void); + +// retrieve the storm rain amount +extern float stormRainGet (void); + +#endif + diff --git a/stations/common/usbcompat.c b/stations/common/usbcompat.c new file mode 100644 index 0000000..9199b56 --- /dev/null +++ b/stations/common/usbcompat.c @@ -0,0 +1,938 @@ +/*--------------------------------------------------------------------------- + + FILENAME: + usbcompat.c + + PURPOSE: + Provide a quick and dirty compatibility layer for libusb-0.1 to + libusb-1.0 interface API and utilities. + + REVISION HISTORY: + Date Engineer Revision Remarks + 02/13/2011 M.S. Teel 0 Original + + NOTES: + Based on source code for the libusb-compat-0.1 library. See the + copyright for that library below this header. + + LICENSE: + Copyright (c) 2011, Mark S. Teel (mteel2005@gmail.com) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ +/* + * Core functions for libusb-compat-0.1 + * Copyright (C) 2008 Daniel Drake + * Copyright (c) 2000-2003 Johannes Erdfelt + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include +#include +#include +#include + +#include "usbcompat.h" + +static libusb_context *ctx = NULL; +static int usbcompat_debug = 0; + +enum usbi_log_level { + LOG_LEVEL_DEBUG, + LOG_LEVEL_INFO, + LOG_LEVEL_WARNING, + LOG_LEVEL_ERROR, +}; + +#ifdef ENABLE_LOGGING +#define _usbi_log(level, fmt...) usbi_log(level, __FUNCTION__, fmt) +#else +#define _usbi_log(level, fmt...) +#endif + +#ifdef ENABLE_DEBUG_LOGGING +#define usbi_dbg(fmt...) _usbi_log(LOG_LEVEL_DEBUG, fmt) +#else +#define usbi_dbg(fmt...) +#endif + +#define usbi_info(fmt...) _usbi_log(LOG_LEVEL_INFO, fmt) +#define usbi_warn(fmt...) _usbi_log(LOG_LEVEL_WARNING, fmt) +#define usbi_err(fmt...) _usbi_log(LOG_LEVEL_ERROR, fmt) + +struct usbcompat_bus *usbcompat_busses = NULL; + +#define compat_err(e) -(errno=libusb_to_errno(e)) + +static int libusb_to_errno(int result) +{ + switch (result) { + case LIBUSB_SUCCESS: + return 0; + case LIBUSB_ERROR_IO: + return EIO; + case LIBUSB_ERROR_INVALID_PARAM: + return EINVAL; + case LIBUSB_ERROR_ACCESS: + return EACCES; + case LIBUSB_ERROR_NO_DEVICE: + return ENXIO; + case LIBUSB_ERROR_NOT_FOUND: + return ENOENT; + case LIBUSB_ERROR_BUSY: + return EBUSY; + case LIBUSB_ERROR_TIMEOUT: + return ETIMEDOUT; + case LIBUSB_ERROR_OVERFLOW: + return EOVERFLOW; + case LIBUSB_ERROR_PIPE: + return EPIPE; + case LIBUSB_ERROR_INTERRUPTED: + return EINTR; + case LIBUSB_ERROR_NO_MEM: + return ENOMEM; + case LIBUSB_ERROR_NOT_SUPPORTED: + return ENOSYS; + default: + return ERANGE; + } +} + +static void usbi_log(enum usbi_log_level level, const char *function, + const char *format, ...) +{ + va_list args; + FILE *stream = stdout; + const char *prefix; + +#ifndef ENABLE_DEBUG_LOGGING + if (!usbcompat_debug) + return; +#endif + + switch (level) { + case LOG_LEVEL_INFO: + prefix = "info"; + break; + case LOG_LEVEL_WARNING: + stream = stderr; + prefix = "warning"; + break; + case LOG_LEVEL_ERROR: + stream = stderr; + prefix = "error"; + break; + case LOG_LEVEL_DEBUG: + stream = stderr; + prefix = "debug"; + break; + default: + stream = stderr; + prefix = "unknown"; + break; + } + + fprintf(stream, "libusb-compat %s: %s: ", prefix, function); + + va_start (args, format); + vfprintf(stream, format, args); + va_end (args); + + fprintf(stream, "\n"); +} + +void usbcompat_init(void) +{ + int r; + usbi_dbg(""); + + if (!ctx) { + r = libusb_init(&ctx); + if (r < 0) { + usbi_err("initialization failed!"); + return; + } + + /* usbcompat_set_debug can be called before usbcompat_init */ + if (usbcompat_debug) + libusb_set_debug(ctx, 3); + } +} + +void usbcompat_set_debug(int level) +{ + usbcompat_debug = level; + + /* usbcompat_set_debug can be called before usbcompat_init */ + if (ctx) + libusb_set_debug(ctx, 3); +} + +char *usbcompat_strerror(void) +{ + return strerror(errno); +} + +static int find_busses(struct usbcompat_bus **ret) +{ + libusb_device **dev_list = NULL; + struct usbcompat_bus *busses = NULL; + struct usbcompat_bus *bus; + int dev_list_len = 0; + int i; + int r; + + r = libusb_get_device_list(ctx, &dev_list); + if (r < 0) { + usbi_err("get_device_list failed with error %d", r); + return compat_err(r); + } + + if (r == 0) { + libusb_free_device_list(dev_list, 1); + /* no buses */ + return 0; + } + + /* iterate over the device list, identifying the individual busses. + * we use the location field of the usbcompat_bus structure to store the + * bus number. */ + + dev_list_len = r; + for (i = 0; i < dev_list_len; i++) { + libusb_device *dev = dev_list[i]; + uint8_t bus_num = libusb_get_bus_number(dev); + + /* if we already know about it, continue */ + if (busses) { + bus = busses; + int found = 0; + do { + if (bus_num == bus->location) { + found = 1; + break; + } + } while ((bus = bus->next) != NULL); + if (found) + continue; + } + + /* add it to the list of busses */ + bus = malloc(sizeof(*bus)); + if (!bus) + goto err; + + memset(bus, 0, sizeof(*bus)); + bus->location = bus_num; + sprintf(bus->dirname, "%03d", bus_num); + LIST_ADD(busses, bus); + } + + libusb_free_device_list(dev_list, 1); + *ret = busses; + return 0; + +err: + bus = busses; + while (bus) { + struct usbcompat_bus *tbus = bus->next; + free(bus); + bus = tbus; + } + return -ENOMEM; +} + +int usbcompat_find_busses(void) +{ + struct usbcompat_bus *new_busses = NULL; + struct usbcompat_bus *bus; + int changes = 0; + int r; + + /* libusb-1.0 initialization might have failed, but we can't indicate + * this with libusb-0.1, so trap that situation here */ + if (!ctx) + return 0; + + usbi_dbg(""); + r = find_busses(&new_busses); + if (r < 0) { + usbi_err("find_busses failed with error %d", r); + return r; + } + + /* walk through all busses we already know about, removing duplicates + * from the new list. if we do not find it in the new list, the bus + * has been removed. */ + + bus = usbcompat_busses; + while (bus) { + struct usbcompat_bus *tbus = bus->next; + struct usbcompat_bus *nbus = new_busses; + int found = 0; + usbi_dbg("in loop"); + + while (nbus) { + struct usbcompat_bus *tnbus = nbus->next; + + if (bus->location == nbus->location) { + LIST_DEL(new_busses, nbus); + free(nbus); + found = 1; + break; + } + nbus = tnbus; + } + + if (!found) { + /* bus removed */ + usbi_dbg("bus %d removed", bus->location); + changes++; + LIST_DEL(usbcompat_busses, bus); + free(bus); + } + + bus = tbus; + } + + /* anything remaining in new_busses is a new bus */ + bus = new_busses; + while (bus) { + struct usbcompat_bus *tbus = bus->next; + usbi_dbg("bus %d added", bus->location); + LIST_DEL(new_busses, bus); + LIST_ADD(usbcompat_busses, bus); + changes++; + bus = tbus; + } + + return changes; +} + +static int find_devices(libusb_device **dev_list, int dev_list_len, + struct usbcompat_bus *bus, struct usbcompat_device **ret) +{ + struct usbcompat_device *devices = NULL; + struct usbcompat_device *dev; + int i; + + for (i = 0; i < dev_list_len; i++) { + libusb_device *newlib_dev = dev_list[i]; + uint8_t bus_num = libusb_get_bus_number(newlib_dev); + + if (bus_num != bus->location) + continue; + + dev = malloc(sizeof(*dev)); + if (!dev) + goto err; + + /* No need to reference the device now, just take the pointer. We + * increase the reference count later if we keep the device. */ + dev->dev = newlib_dev; + + dev->bus = bus; + dev->devnum = libusb_get_device_address(newlib_dev); + sprintf(dev->filename, "%03d", dev->devnum); + LIST_ADD(devices, dev); + } + + *ret = devices; + return 0; + +err: + dev = devices; + while (dev) { + struct usbcompat_device *tdev = dev->next; + free(dev); + dev = tdev; + } + return -ENOMEM; +} + +static void clear_endpoint_descriptor(struct usbcompat_endpoint_descriptor *ep) +{ + if (ep->extra) + free(ep->extra); +} + +static void clear_interface_descriptor(struct usbcompat_interface_descriptor *iface) +{ + if (iface->extra) + free(iface->extra); + if (iface->endpoint) { + int i; + for (i = 0; i < iface->bNumEndpoints; i++) + clear_endpoint_descriptor(iface->endpoint + i); + free(iface->endpoint); + } +} + +static void clear_interface(struct usbcompat_interface *iface) +{ + if (iface->altsetting) { + int i; + for (i = 0; i < iface->num_altsetting; i++) + clear_interface_descriptor(iface->altsetting + i); + free(iface->altsetting); + } +} + +static void clear_config_descriptor(struct usbcompat_config_descriptor *config) +{ + if (config->extra) + free(config->extra); + if (config->interface) { + int i; + for (i = 0; i < config->bNumInterfaces; i++) + clear_interface(config->interface + i); + free(config->interface); + } +} + +static void clear_device(struct usbcompat_device *dev) +{ + int i; + for (i = 0; i < dev->descriptor.bNumConfigurations; i++) + clear_config_descriptor(dev->config + i); +} + +static int copy_endpoint_descriptor(struct usbcompat_endpoint_descriptor *dest, + const struct libusb_endpoint_descriptor *src) +{ + memcpy(dest, src, USBCOMPAT_DT_ENDPOINT_AUDIO_SIZE); + + dest->extralen = src->extra_length; + if (src->extra_length) { + dest->extra = malloc(src->extra_length); + if (!dest->extra) + return -ENOMEM; + memcpy(dest->extra, src->extra, src->extra_length); + } + + return 0; +} + +static int copy_interface_descriptor(struct usbcompat_interface_descriptor *dest, + const struct libusb_interface_descriptor *src) +{ + int i; + int num_endpoints = src->bNumEndpoints; + size_t alloc_size = sizeof(struct usbcompat_endpoint_descriptor) * num_endpoints; + + memcpy(dest, src, USBCOMPAT_DT_INTERFACE_SIZE); + dest->endpoint = malloc(alloc_size); + if (!dest->endpoint) + return -ENOMEM; + memset(dest->endpoint, 0, alloc_size); + + for (i = 0; i < num_endpoints; i++) { + int r = copy_endpoint_descriptor(dest->endpoint + i, &src->endpoint[i]); + if (r < 0) { + clear_interface_descriptor(dest); + return r; + } + } + + dest->extralen = src->extra_length; + if (src->extra_length) { + dest->extra = malloc(src->extra_length); + if (!dest->extra) { + clear_interface_descriptor(dest); + return -ENOMEM; + } + memcpy(dest->extra, src->extra, src->extra_length); + } + + return 0; +} + +static int copy_interface(struct usbcompat_interface *dest, + const struct libusb_interface *src) +{ + int i; + int num_altsetting = src->num_altsetting; + size_t alloc_size = sizeof(struct usbcompat_interface_descriptor) + * num_altsetting; + + dest->num_altsetting = num_altsetting; + dest->altsetting = malloc(alloc_size); + if (!dest->altsetting) + return -ENOMEM; + memset(dest->altsetting, 0, alloc_size); + + for (i = 0; i < num_altsetting; i++) { + int r = copy_interface_descriptor(dest->altsetting + i, + &src->altsetting[i]); + if (r < 0) { + clear_interface(dest); + return r; + } + } + + return 0; +} + +static int copy_config_descriptor(struct usbcompat_config_descriptor *dest, + const struct libusb_config_descriptor *src) +{ + int i; + int num_interfaces = src->bNumInterfaces; + size_t alloc_size = sizeof(struct usbcompat_interface) * num_interfaces; + + memcpy(dest, src, USBCOMPAT_DT_CONFIG_SIZE); + dest->interface = malloc(alloc_size); + if (!dest->interface) + return -ENOMEM; + memset(dest->interface, 0, alloc_size); + + for (i = 0; i < num_interfaces; i++) { + int r = copy_interface(dest->interface + i, &src->interface[i]); + if (r < 0) { + clear_config_descriptor(dest); + return r; + } + } + + dest->extralen = src->extra_length; + if (src->extra_length) { + dest->extra = malloc(src->extra_length); + if (!dest->extra) { + clear_config_descriptor(dest); + return -ENOMEM; + } + memcpy(dest->extra, src->extra, src->extra_length); + } + + return 0; +} + +static int initialize_device(struct usbcompat_device *dev) +{ + libusb_device *newlib_dev = dev->dev; + int num_configurations; + size_t alloc_size; + int r; + int i; + + /* device descriptor is identical in both libs */ + r = libusb_get_device_descriptor(newlib_dev, + (struct libusb_device_descriptor *) &dev->descriptor); + if (r < 0) { + usbi_err("error %d getting device descriptor", r); + return compat_err(r); + } + + num_configurations = dev->descriptor.bNumConfigurations; + alloc_size = sizeof(struct usbcompat_config_descriptor) * num_configurations; + dev->config = malloc(alloc_size); + if (!dev->config) + return -ENOMEM; + memset(dev->config, 0, alloc_size); + + /* even though structures are identical, we can't just use libusb-1.0's + * config descriptors because we have to store all configurations in + * a single flat memory area (libusb-1.0 provides separate allocations). + * we hand-copy libusb-1.0's descriptors into our own structures. */ + for (i = 0; i < num_configurations; i++) { + struct libusb_config_descriptor *newlib_config; + r = libusb_get_config_descriptor(newlib_dev, i, &newlib_config); + if (r < 0) { + clear_device(dev); + free(dev->config); + return compat_err(r); + } + r = copy_config_descriptor(dev->config + i, newlib_config); + libusb_free_config_descriptor(newlib_config); + if (r < 0) { + clear_device(dev); + free(dev->config); + return r; + } + } + + /* libusb doesn't implement this and it doesn't seem that important. If + * someone asks for it, we can implement it in v1.1 or later. */ + dev->num_children = 0; + dev->children = NULL; + + libusb_ref_device(newlib_dev); + return 0; +} + +static void free_device(struct usbcompat_device *dev) +{ + clear_device(dev); + libusb_unref_device(dev->dev); + free(dev); +} + +int usbcompat_find_devices(void) +{ + struct usbcompat_bus *bus; + libusb_device **dev_list; + int dev_list_len; + int r; + int changes = 0; + + /* libusb-1.0 initialization might have failed, but we can't indicate + * this with libusb-0.1, so trap that situation here */ + if (!ctx) + return 0; + + usbi_dbg(""); + dev_list_len = libusb_get_device_list(ctx, &dev_list); + if (dev_list_len < 0) + return compat_err(dev_list_len); + + for (bus = usbcompat_busses; bus; bus = bus->next) { + struct usbcompat_device *new_devices = NULL; + struct usbcompat_device *dev; + + r = find_devices(dev_list, dev_list_len, bus, &new_devices); + if (r < 0) { + libusb_free_device_list(dev_list, 1); + return r; + } + + /* walk through the devices we already know about, removing duplicates + * from the new list. if we do not find it in the new list, the device + * has been removed. */ + dev = bus->devices; + while (dev) { + int found = 0; + struct usbcompat_device *tdev = dev->next; + struct usbcompat_device *ndev = new_devices; + + while (ndev) { + if (ndev->devnum == dev->devnum) { + LIST_DEL(new_devices, ndev); + free(ndev); + found = 1; + break; + } + ndev = ndev->next; + } + + if (!found) { + usbi_dbg("device %d.%d removed", + dev->bus->location, dev->devnum); + LIST_DEL(bus->devices, dev); + free_device(dev); + changes++; + } + + dev = tdev; + } + + /* anything left in new_devices is a new device */ + dev = new_devices; + while (dev) { + struct usbcompat_device *tdev = dev->next; + r = initialize_device(dev); + if (r < 0) { + usbi_err("couldn't initialize device %d.%d (error %d)", + dev->bus->location, dev->devnum, r); + dev = tdev; + continue; + } + usbi_dbg("device %d.%d added", dev->bus->location, dev->devnum); + LIST_DEL(new_devices, dev); + LIST_ADD(bus->devices, dev); + changes++; + dev = tdev; + } + } + + libusb_free_device_list(dev_list, 1); + return changes; +} + +struct usbcompat_bus *usbcompat_get_busses(void) +{ + return usbcompat_busses; +} + +usbcompat_dev_handle *usbcompat_open(struct usbcompat_device *dev) +{ + int r; + usbi_dbg(""); + + usbcompat_dev_handle *udev = malloc(sizeof(*udev)); + if (!udev) + return NULL; + + r = libusb_open((libusb_device *) dev->dev, &udev->handle); + if (r < 0) { + usbi_err("could not open device, error %d", r); + free(udev); + errno = libusb_to_errno(r); + return NULL; + } + + udev->last_claimed_interface = -1; + udev->device = dev; + return udev; +} + +int usbcompat_close(usbcompat_dev_handle *dev) +{ + usbi_dbg(""); + libusb_close(dev->handle); + free(dev); + return 0; +} + +struct usbcompat_device *usbcompat_device(usbcompat_dev_handle *dev) +{ + return dev->device; +} + +int usbcompat_set_configuration(usbcompat_dev_handle *dev, int configuration) +{ + usbi_dbg("configuration %d", configuration); + return compat_err(libusb_set_configuration(dev->handle, configuration)); +} + +int usbcompat_claim_interface(usbcompat_dev_handle *dev, int interface) +{ + int r; + usbi_dbg("interface %d", interface); + + r = libusb_claim_interface(dev->handle, interface); + if (r == 0) { + dev->last_claimed_interface = interface; + return 0; + } + + return compat_err(r); +} + +int usbcompat_release_interface(usbcompat_dev_handle *dev, int interface) +{ + int r; + usbi_dbg("interface %d", interface); + + r = libusb_release_interface(dev->handle, interface); + if (r == 0) + dev->last_claimed_interface = -1; + + return compat_err(r); +} + +int usbcompat_set_altinterface(usbcompat_dev_handle *dev, int alternate) +{ + usbi_dbg("alternate %d", alternate); + if (dev->last_claimed_interface < 0) + return -(errno=EINVAL); + + return compat_err(libusb_set_interface_alt_setting(dev->handle, + dev->last_claimed_interface, alternate)); +} + +int usbcompat_resetep(usbcompat_dev_handle *dev, unsigned int ep) +{ + return compat_err(usbcompat_clear_halt(dev, ep)); +} + +int usbcompat_clear_halt(usbcompat_dev_handle *dev, unsigned int ep) +{ + usbi_dbg("endpoint %x", ep); + return compat_err(libusb_clear_halt(dev->handle, ep & 0xff)); +} + +int usbcompat_reset(usbcompat_dev_handle *dev) +{ + usbi_dbg(""); + return compat_err(libusb_reset_device(dev->handle)); +} + +static int usbcompat_bulk_io(usbcompat_dev_handle *dev, int ep, char *bytes, + int size, int timeout) +{ + int actual_length; + int r; + usbi_dbg("endpoint %x size %d timeout %d", ep, size, timeout); + r = libusb_bulk_transfer(dev->handle, ep & 0xff, bytes, size, + &actual_length, timeout); + + /* if we timed out but did transfer some data, report as successful short + * read. FIXME: is this how libusb-0.1 works? */ + if (r == 0 || (r == LIBUSB_ERROR_TIMEOUT && actual_length > 0)) + return actual_length; + + return compat_err(r); +} + +int usbcompat_bulk_read(usbcompat_dev_handle *dev, int ep, char *bytes, + int size, int timeout) +{ + if (!(ep & USBCOMPAT_ENDPOINT_IN)) { + /* libusb-0.1 will strangely fix up a read request from endpoint + * 0x01 to be from endpoint 0x81. do the same thing here, but + * warn about this silly behaviour. */ + usbi_warn("endpoint %x is missing IN direction bit, fixing"); + ep |= USBCOMPAT_ENDPOINT_IN; + } + + return usbcompat_bulk_io(dev, ep, bytes, size, timeout); +} + +int usbcompat_bulk_write(usbcompat_dev_handle *dev, int ep, char *bytes, + int size, int timeout) +{ + if (ep & USBCOMPAT_ENDPOINT_IN) { + /* libusb-0.1 on BSD strangely fix up a write request to endpoint + * 0x81 to be to endpoint 0x01. do the same thing here, but + * warn about this silly behaviour. */ + usbi_warn("endpoint %x has excessive IN direction bit, fixing"); + ep &= ~USBCOMPAT_ENDPOINT_IN; + } + + return usbcompat_bulk_io(dev, ep, bytes, size, timeout); +} + +static int usbcompat_interrupt_io(usbcompat_dev_handle *dev, int ep, char *bytes, + int size, int timeout) +{ + int actual_length; + int r; + usbi_dbg("endpoint %x size %d timeout %d", ep, size, timeout); + r = libusb_interrupt_transfer(dev->handle, ep & 0xff, bytes, size, + &actual_length, timeout); + + /* if we timed out but did transfer some data, report as successful short + * read. FIXME: is this how libusb-0.1 works? */ + if (r == 0 || (r == LIBUSB_ERROR_TIMEOUT && actual_length > 0)) + return actual_length; + + return compat_err(r); +} + +int usbcompat_interrupt_read(usbcompat_dev_handle *dev, int ep, char *bytes, + int size, int timeout) +{ + if (!(ep & USBCOMPAT_ENDPOINT_IN)) { + /* libusb-0.1 will strangely fix up a read request from endpoint + * 0x01 to be from endpoint 0x81. do the same thing here, but + * warn about this silly behaviour. */ + usbi_warn("endpoint %x is missing IN direction bit, fixing"); + ep |= USBCOMPAT_ENDPOINT_IN; + } + return usbcompat_interrupt_io(dev, ep, bytes, size, timeout); +} + +int usbcompat_interrupt_write(usbcompat_dev_handle *dev, int ep, char *bytes, + int size, int timeout) +{ + if (ep & USBCOMPAT_ENDPOINT_IN) { + /* libusb-0.1 on BSD strangely fix up a write request to endpoint + * 0x81 to be to endpoint 0x01. do the same thing here, but + * warn about this silly behaviour. */ + usbi_warn("endpoint %x has excessive IN direction bit, fixing"); + ep &= ~USBCOMPAT_ENDPOINT_IN; + } + + return usbcompat_interrupt_io(dev, ep, bytes, size, timeout); +} + +int usbcompat_control_msg(usbcompat_dev_handle *dev, int bmRequestType, + int bRequest, int wValue, int wIndex, char *bytes, int size, int timeout) +{ + int r; + usbi_dbg("RQT=%x RQ=%x V=%x I=%x len=%d timeout=%d", bmRequestType, + bRequest, wValue, wIndex, size, timeout); + + r = libusb_control_transfer(dev->handle, bmRequestType & 0xff, + bRequest & 0xff, wValue & 0xffff, wIndex & 0xffff, bytes, size & 0xffff, + timeout); + + if (r >= 0) + return r; + + return compat_err(r); +} + +int usbcompat_get_string(usbcompat_dev_handle *dev, int desc_index, int langid, + char *buf, size_t buflen) +{ + int r; + r = libusb_get_string_descriptor(dev->handle, desc_index & 0xff, + langid & 0xffff, buf, (int) buflen); + if (r >= 0) + return r; + return compat_err(r); +} + +int usbcompat_get_string_simple(usbcompat_dev_handle *dev, int desc_index, + char *buf, size_t buflen) +{ + int r; + r = libusb_get_string_descriptor_ascii(dev->handle, desc_index & 0xff, + buf, (int) buflen); + if (r >= 0) + return r; + return compat_err(r); +} + +int usbcompat_get_descriptor(usbcompat_dev_handle *dev, unsigned char type, + unsigned char desc_index, void *buf, int size) +{ + int r; + r = libusb_get_descriptor(dev->handle, type, desc_index, buf, size); + if (r >= 0) + return r; + return compat_err(r); +} + +int usbcompat_get_descriptor_by_endpoint(usbcompat_dev_handle *dev, int ep, + unsigned char type, unsigned char desc_index, void *buf, int size) +{ + /* this function doesn't make much sense - the specs don't talk about + * getting a descriptor "by endpoint". libusb-1.0 does not provide this + * functionality so we just send a control message directly */ + int r; + r = libusb_control_transfer(dev->handle, + LIBUSB_ENDPOINT_IN | (ep & 0xff), LIBUSB_REQUEST_GET_DESCRIPTOR, + (type << 8) | desc_index, 0, buf, size, 1000); + if (r >= 0) + return r; + return compat_err(r); +} + +int usbcompat_get_driver_np(usbcompat_dev_handle *dev, int interface, + char *name, unsigned int namelen) +{ + int r = libusb_kernel_driver_active(dev->handle, interface); + if (r == 1) { + /* libusb-1.0 doesn't expose driver name, so fill in a dummy value */ + snprintf(name, namelen, "dummy"); + return 0; + } else if (r == 0) { + return -(errno=ENODATA); + } else { + return compat_err(r); + } +} + +int usbcompat_detach_kernel_driver_np(usbcompat_dev_handle *dev, int interface) +{ + return compat_err(libusb_detach_kernel_driver(dev->handle, interface)); +} + diff --git a/stations/common/usbcompat.h b/stations/common/usbcompat.h new file mode 100644 index 0000000..0119467 --- /dev/null +++ b/stations/common/usbcompat.h @@ -0,0 +1,400 @@ +/*--------------------------------------------------------------------------- + + FILENAME: + usbcompat.h + + PURPOSE: + Provide a quick and dirty compatibility layer for libusb-0.1 to + libusb-1.0 interface API and utilities. + + REVISION HISTORY: + Date Engineer Revision Remarks + 02/13/2011 M.S. Teel 0 Original + + NOTES: + Based on source code for the libusb-compat-0.1 library. See the + copyright for that library below this header. + + LICENSE: + Copyright (c) 2011, Mark S. Teel (mteel2005@gmail.com) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ +/* + * Copyright (c) 2000-2003 Johannes Erdfelt + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * This file (and only this file) may alternatively be licensed under the + * BSD license. See the LICENSE file shipped with the libusb-compat-0.1 source + * distribution for details. + */ + +#ifndef INC_usbcompath +#define INC_usbcompath + +#include +#include +#include + +#include +#include + + +/* + * USB spec information + * + * This is all stuff grabbed from various USB specs and is pretty much + * not subject to change + */ + +/* + * Device and/or Interface Class codes + */ +#define USBCOMPAT_CLASS_PER_INTERFACE 0 /* for DeviceClass */ +#define USBCOMPAT_CLASS_AUDIO 1 +#define USBCOMPAT_CLASS_COMM 2 +#define USBCOMPAT_CLASS_HID 3 +#define USBCOMPAT_CLASS_PRINTER 7 +#define USBCOMPAT_CLASS_PTP 6 +#define USBCOMPAT_CLASS_MASS_STORAGE 8 +#define USBCOMPAT_CLASS_HUB 9 +#define USBCOMPAT_CLASS_DATA 10 +#define USBCOMPAT_CLASS_VENDOR_SPEC 0xff + +/* + * Descriptor types + */ +#define USBCOMPAT_DT_DEVICE 0x01 +#define USBCOMPAT_DT_CONFIG 0x02 +#define USBCOMPAT_DT_STRING 0x03 +#define USBCOMPAT_DT_INTERFACE 0x04 +#define USBCOMPAT_DT_ENDPOINT 0x05 + +#define USBCOMPAT_DT_HID 0x21 +#define USBCOMPAT_DT_REPORT 0x22 +#define USBCOMPAT_DT_PHYSICAL 0x23 +#define USBCOMPAT_DT_HUB 0x29 + +/* + * Descriptor sizes per descriptor type + */ +#define USBCOMPAT_DT_DEVICE_SIZE 18 +#define USBCOMPAT_DT_CONFIG_SIZE 9 +#define USBCOMPAT_DT_INTERFACE_SIZE 9 +#define USBCOMPAT_DT_ENDPOINT_SIZE 7 +#define USBCOMPAT_DT_ENDPOINT_AUDIO_SIZE 9 /* Audio extension */ +#define USBCOMPAT_DT_HUB_NONVAR_SIZE 7 + +/* All standard descriptors have these 2 fields in common */ +struct usbcompat_descriptor_header { + u_int8_t bLength; + u_int8_t bDescriptorType; +}; + +/* String descriptor */ +struct usbcompat_string_descriptor { + u_int8_t bLength; + u_int8_t bDescriptorType; + u_int16_t wData[1]; +}; + +/* HID descriptor */ +struct usbcompat_hid_descriptor { + u_int8_t bLength; + u_int8_t bDescriptorType; + u_int16_t bcdHID; + u_int8_t bCountryCode; + u_int8_t bNumDescriptors; + /* u_int8_t bReportDescriptorType; */ + /* u_int16_t wDescriptorLength; */ + /* ... */ +}; + +/* Endpoint descriptor */ +#define USBCOMPAT_MAXENDPOINTS 32 +struct usbcompat_endpoint_descriptor { + u_int8_t bLength; + u_int8_t bDescriptorType; + u_int8_t bEndpointAddress; + u_int8_t bmAttributes; + u_int16_t wMaxPacketSize; + u_int8_t bInterval; + u_int8_t bRefresh; + u_int8_t bSynchAddress; + + unsigned char *extra; /* Extra descriptors */ + int extralen; +}; + +#define USBCOMPAT_ENDPOINT_ADDRESS_MASK 0x0f /* in bEndpointAddress */ +#define USBCOMPAT_ENDPOINT_DIR_MASK 0x80 + +#define USBCOMPAT_ENDPOINT_TYPE_MASK 0x03 /* in bmAttributes */ +#define USBCOMPAT_ENDPOINT_TYPE_CONTROL 0 +#define USBCOMPAT_ENDPOINT_TYPE_ISOCHRONOUS 1 +#define USBCOMPAT_ENDPOINT_TYPE_BULK 2 +#define USBCOMPAT_ENDPOINT_TYPE_INTERRUPT 3 + +/* Interface descriptor */ +#define USBCOMPAT_MAXINTERFACES 32 +struct usbcompat_interface_descriptor { + u_int8_t bLength; + u_int8_t bDescriptorType; + u_int8_t bInterfaceNumber; + u_int8_t bAlternateSetting; + u_int8_t bNumEndpoints; + u_int8_t bInterfaceClass; + u_int8_t bInterfaceSubClass; + u_int8_t bInterfaceProtocol; + u_int8_t iInterface; + + struct usbcompat_endpoint_descriptor *endpoint; + + unsigned char *extra; /* Extra descriptors */ + int extralen; +}; + +#define USBCOMPAT_MAXALTSETTING 128 /* Hard limit */ +struct usbcompat_interface { + struct usbcompat_interface_descriptor *altsetting; + + int num_altsetting; +}; + +/* Configuration descriptor information.. */ +#define USBCOMPAT_MAXCONFIG 8 +struct usbcompat_config_descriptor { + u_int8_t bLength; + u_int8_t bDescriptorType; + u_int16_t wTotalLength; + u_int8_t bNumInterfaces; + u_int8_t bConfigurationValue; + u_int8_t iConfiguration; + u_int8_t bmAttributes; + u_int8_t MaxPower; + + struct usbcompat_interface *interface; + + unsigned char *extra; /* Extra descriptors */ + int extralen; +}; + +/* Device descriptor */ +struct usbcompat_device_descriptor { + u_int8_t bLength; + u_int8_t bDescriptorType; + u_int16_t bcdUSB; + u_int8_t bDeviceClass; + u_int8_t bDeviceSubClass; + u_int8_t bDeviceProtocol; + u_int8_t bMaxPacketSize0; + u_int16_t idVendor; + u_int16_t idProduct; + u_int16_t bcdDevice; + u_int8_t iManufacturer; + u_int8_t iProduct; + u_int8_t iSerialNumber; + u_int8_t bNumConfigurations; +}; + +struct usbcompat_ctrl_setup { + u_int8_t bRequestType; + u_int8_t bRequest; + u_int16_t wValue; + u_int16_t wIndex; + u_int16_t wLength; +}; + +/* + * Standard requests + */ +#define USBCOMPAT_REQ_GET_STATUS 0x00 +#define USBCOMPAT_REQ_CLEAR_FEATURE 0x01 +/* 0x02 is reserved */ +#define USBCOMPAT_REQ_SET_FEATURE 0x03 +/* 0x04 is reserved */ +#define USBCOMPAT_REQ_SET_ADDRESS 0x05 +#define USBCOMPAT_REQ_GET_DESCRIPTOR 0x06 +#define USBCOMPAT_REQ_SET_DESCRIPTOR 0x07 +#define USBCOMPAT_REQ_GET_CONFIGURATION 0x08 +#define USBCOMPAT_REQ_SET_CONFIGURATION 0x09 +#define USBCOMPAT_REQ_GET_INTERFACE 0x0A +#define USBCOMPAT_REQ_SET_INTERFACE 0x0B +#define USBCOMPAT_REQ_SYNCH_FRAME 0x0C + +#define USBCOMPAT_TYPE_STANDARD (0x00 << 5) +#define USBCOMPAT_TYPE_CLASS (0x01 << 5) +#define USBCOMPAT_TYPE_VENDOR (0x02 << 5) +#define USBCOMPAT_TYPE_RESERVED (0x03 << 5) + +#define USBCOMPAT_RECIP_DEVICE 0x00 +#define USBCOMPAT_RECIP_INTERFACE 0x01 +#define USBCOMPAT_RECIP_ENDPOINT 0x02 +#define USBCOMPAT_RECIP_OTHER 0x03 + +/* + * Various libusb API related stuff + */ + +#define USBCOMPAT_ENDPOINT_IN 0x80 +#define USBCOMPAT_ENDPOINT_OUT 0x00 + +/* Error codes */ +#define USBCOMPAT_ERROR_BEGIN 500000 + +/* Data types */ +struct usbcompat_device; +struct usbcompat_bus; + +/* + * To maintain compatibility with applications already built with libusb, + * we must only add entries to the end of this structure. NEVER delete or + * move members and only change types if you really know what you're doing. + */ +struct usbcompat_device { + struct usbcompat_device *next, *prev; + + char filename[PATH_MAX + 1]; + + struct usbcompat_bus *bus; + + struct usbcompat_device_descriptor descriptor; + struct usbcompat_config_descriptor *config; + + void *dev; /* Darwin support */ + + u_int8_t devnum; + + unsigned char num_children; + struct usbcompat_device **children; +}; + +struct usbcompat_bus { + struct usbcompat_bus *next, *prev; + + char dirname[PATH_MAX + 1]; + + struct usbcompat_device *devices; + u_int32_t location; + + struct usbcompat_device *root_dev; +}; + +struct usbcompat_dev_handle; +typedef struct usbcompat_dev_handle usbcompat_dev_handle; + +/* Variables */ +extern struct usbcompat_bus *usbcompat_busses; + +#ifdef __cplusplus +extern "C" { +#endif + +/* Function prototypes */ + +/* usb.c */ +usbcompat_dev_handle *usbcompat_open(struct usbcompat_device *dev); +int usbcompat_close(usbcompat_dev_handle *dev); +int usbcompat_get_string(usbcompat_dev_handle *dev, int index, int langid, char *buf, + size_t buflen); +int usbcompat_get_string_simple(usbcompat_dev_handle *dev, int index, char *buf, + size_t buflen); + +/* descriptors.c */ +int usbcompat_get_descriptor_by_endpoint(usbcompat_dev_handle *udev, int ep, + unsigned char type, unsigned char index, void *buf, int size); +int usbcompat_get_descriptor(usbcompat_dev_handle *udev, unsigned char type, + unsigned char index, void *buf, int size); + +/* .c */ +int usbcompat_bulk_write(usbcompat_dev_handle *dev, int ep, char *bytes, int size, + int timeout); +int usbcompat_bulk_read(usbcompat_dev_handle *dev, int ep, char *bytes, int size, + int timeout); +int usbcompat_interrupt_write(usbcompat_dev_handle *dev, int ep, char *bytes, int size, + int timeout); +int usbcompat_interrupt_read(usbcompat_dev_handle *dev, int ep, char *bytes, int size, + int timeout); +int usbcompat_control_msg(usbcompat_dev_handle *dev, int requesttype, int request, + int value, int index, char *bytes, int size, int timeout); +int usbcompat_set_configuration(usbcompat_dev_handle *dev, int configuration); +int usbcompat_claim_interface(usbcompat_dev_handle *dev, int interface); +int usbcompat_release_interface(usbcompat_dev_handle *dev, int interface); +int usbcompat_set_altinterface(usbcompat_dev_handle *dev, int alternate); +int usbcompat_resetep(usbcompat_dev_handle *dev, unsigned int ep); +int usbcompat_clear_halt(usbcompat_dev_handle *dev, unsigned int ep); +int usbcompat_reset(usbcompat_dev_handle *dev); + +#define LIBUSBCOMPAT_HAS_GET_DRIVER_NP 1 +int usbcompat_get_driver_np(usbcompat_dev_handle *dev, int interface, char *name, + unsigned int namelen); +#define LIBUSBCOMPAT_HAS_DETACH_KERNEL_DRIVER_NP 1 +int usbcompat_detach_kernel_driver_np(usbcompat_dev_handle *dev, int interface); + +char *usbcompat_strerror(void); + +void usbcompat_init(void); +void usbcompat_set_debug(int level); +int usbcompat_find_busses(void); +int usbcompat_find_devices(void); +struct usbcompat_device *usbcompat_device(usbcompat_dev_handle *dev); +struct usbcompat_bus *usbcompat_get_busses(void); + +/* *** BEGIN added from libusb-compat-0.1/usbi.h *** */ +/* Some quick and generic macros for the simple kind of lists we use */ +#define LIST_ADD(begin, ent) \ + do { \ + if (begin) { \ + ent->next = begin; \ + ent->next->prev = ent; \ + } else \ + ent->next = NULL; \ + ent->prev = NULL; \ + begin = ent; \ + } while(0) + +#define LIST_DEL(begin, ent) \ + do { \ + if (ent->prev) \ + ent->prev->next = ent->next; \ + else \ + begin = ent->next; \ + if (ent->next) \ + ent->next->prev = ent->prev; \ + ent->prev = NULL; \ + ent->next = NULL; \ + } while (0) + +struct usbcompat_dev_handle { + libusb_device_handle *handle; + struct usbcompat_device *device; + + /* libusb-0.1 is buggy w.r.t. interface claiming. it allows you to claim + * multiple interfaces but only tracks the most recently claimed one, + * which is used for usbcompat_set_altinterface(). we clone the buggy behaviour + * here. */ + int last_claimed_interface; +}; +/* *** END added from libusb-compat-0.1/usbi.h *** */ + +#ifdef __cplusplus +} +#endif + +#endif /* __USBCOMPAT_H__ */ + diff --git a/stations/common/usbhid.c b/stations/common/usbhid.c new file mode 100755 index 0000000..eefe034 --- /dev/null +++ b/stations/common/usbhid.c @@ -0,0 +1,306 @@ +/*--------------------------------------------------------------------------- + + FILENAME: + usbhid.c + + PURPOSE: + Provide the weather station USB HID medium utilities. + + REVISION HISTORY: + Date Engineer Revision Remarks + 02/15/2011 M.S. Teel 0 Original + + NOTES: + wview medium-specific routines to be supplied: + + usbhidMediumInit - sets up function pointers and work area + usbhidInit - initialize + usbhidExit - exit + usbhidRead - blocking read until specified bytes are read + usbhidWrite - write on medium + + See daemon.h for details of the WVIEW_MEDIUM structure. + + LICENSE: + Copyright (c) 2011, Mark S. Teel (mark@teel.ws) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +/* ... System include files +*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/* ... Library include files +*/ +#include +#include + +/* ... Local include files +*/ +#include +#include +#include +#include + +/* ... global memory declarations +*/ + +/* ... local memory +*/ +static MEDIUM_USBHID mediumUSB; +static void usbhidExit (WVIEW_MEDIUM *med); + +////////////////////////////////////////////////////////////////////////////// +// ... medium callback functions +////////////////////////////////////////////////////////////////////////////// + +static int usbhidInit (WVIEW_MEDIUM *medium) +{ + MEDIUM_USBHID *usbhidWork = (MEDIUM_USBHID *)medium->workData; + + // Open the HID device: + medium->hidDevice = hid_open(usbhidWork->vendorId, usbhidWork->productId); + if (medium->hidDevice == NULL) + { + radMsgLog (PRI_HIGH, "USBHID: hid_open failed!"); + return ERROR; + } + + // Set to non-blocking so we can do timed read exact: + if (hid_set_nonblocking(medium->hidDevice, !usbhidWork->blocking) != 0) + { + radMsgLog (PRI_HIGH, "USBHID: hid_set_nonblocking failed!"); + usbhidExit(medium); + return ERROR; + } + + return OK; +} + +static void usbhidExit (WVIEW_MEDIUM *med) +{ + if (med->hidDevice != NULL) + hid_close(med->hidDevice); + med->hidDevice = NULL; + + return; +} + +static int usbhidRead +( + struct _wview_medium *medium, + void *buffer, + int length, + int msTimeout +) +{ + int rval, cumTime = 0, index = 0; + uint64_t readTime; + uint8_t *ptr = (uint8_t *)buffer; + MEDIUM_USBHID *usbhidWork = (MEDIUM_USBHID *)medium->workData; + + while (index < length) + { + // Bail out if non-blocking IO and timeout has expired. + if (!usbhidWork->blocking && cumTime >= msTimeout) + { + return index; + } + + readTime = radTimeGetMSSinceEpoch (); + rval = hid_read(medium->hidDevice, &ptr[index], length - index); + if (rval < 0) + { + if (errno != EINTR && errno != EAGAIN) + { + return ERROR; + } + } + else + { + if (rval > 0 && usbhidWork->debug) + { + radMsgLog (PRI_STATUS, "usbhidRead:"); + radMsgLogData(&ptr[index], rval); + } + index += rval; + } + + readTime = radTimeGetMSSinceEpoch () - readTime; + cumTime += (int)readTime; + if (!usbhidWork->blocking && index < length && cumTime < msTimeout) + { + readTime = radTimeGetMSSinceEpoch (); + radUtilsSleep (9); + readTime = radTimeGetMSSinceEpoch () - readTime; + cumTime += (int)readTime; + } + } + + return index; +} + +static int usbhidReadSpecial +( + struct _wview_medium *medium, + void *buffer, + int length, + int msTimeout +) +{ + int rval, cumTime = 0, index = 0, i, blockLen; + uint64_t readTime; + uint8_t *ptr = (uint8_t *)buffer; + uint8_t readBuf[8]; + MEDIUM_USBHID *usbhidWork = (MEDIUM_USBHID *)medium->workData; + + while (index < length && cumTime < msTimeout) + { + readTime = radTimeGetMSSinceEpoch (); + + // Read in 8-byte blocks: + rval = hid_read(medium->hidDevice, &readBuf[0], 8); + if (rval < 0) + { + if (errno != EINTR && errno != EAGAIN) + { + return ERROR; + } + } + else if (rval > 0) + { + // Now we should have an 8-byte block of data: + // Process the first byte as a length field: + blockLen = (int)readBuf[0]; + if ((blockLen > 7) || (blockLen > (rval-1))) + { + // Impossible: + return ERROR; + } + + // Copy the length given: + memcpy(&ptr[index], &readBuf[1], MIN(blockLen,(length-index))); + index += MIN(blockLen,(length-index)); + } + + readTime = radTimeGetMSSinceEpoch () - readTime; + cumTime += (int)readTime; + if ((index < length) && (cumTime < msTimeout)) + { + readTime = radTimeGetMSSinceEpoch (); + radUtilsSleep (9); + readTime = radTimeGetMSSinceEpoch () - readTime; + cumTime += (int)readTime; + } + } + + if (index > 0 && usbhidWork->debug) + { + radMsgLog (PRI_STATUS, "usbhidReadSpecial:"); + radMsgLogData(&ptr[0], index); + } + + return index; +} + +static int usbhidWrite +( + struct _wview_medium *medium, + void *buffer, + int length +) +{ + int retVal, index = 0; + MEDIUM_USBHID *usbhidWork = (MEDIUM_USBHID *)medium->workData; + uint8_t* sendPtr = (uint8_t*)buffer; + + // Write in 8-byte chunks: + while (index < length) + { + retVal = hid_write(medium->hidDevice, &sendPtr[index], 8); + if (retVal != 8) + { + if (retVal == -1) + { + if (usbhidWork->debug) + { + radMsgLog (PRI_HIGH, "USBHID: hid_write failed"); + } + return ERROR; + } + else + { + if (usbhidWork->debug) + { + radMsgLog (PRI_HIGH, "USBHID: hid_write is SHORT: %d of %d", + retVal, length); + } + return retVal; + } + } + + index += 8; + } + + if (usbhidWork->debug) + { + radMsgLog (PRI_STATUS, "usbhidWrite:"); + radMsgLogData(buffer, length); + } + + return length; +} + + +// ... ----- API methods ----- + +int usbhidMediumInit +( + WVIEW_MEDIUM *medium, + uint16_t vendor_id, + uint16_t product_id, + int enableDebug, + int blocking +) +{ + MEDIUM_USBHID *work = &mediumUSB; + + memset (medium, 0, sizeof (*medium)); + memset (work, 0, sizeof (*work)); + + work->vendorId = vendor_id; + work->productId = product_id; + work->debug = enableDebug; + work->blocking = blocking; + + medium->type = MEDIUM_TYPE_USBHID; + + // set our workData pointer for later use + medium->workData = (void *)work; + + medium->usbhidInit = usbhidInit; + medium->usbhidExit = usbhidExit; + medium->usbhidRead = usbhidRead; + medium->usbhidReadSpecial = usbhidReadSpecial; + medium->usbhidWrite = usbhidWrite; + + return OK; +} + diff --git a/stations/common/usbhid.h b/stations/common/usbhid.h new file mode 100644 index 0000000..553a570 --- /dev/null +++ b/stations/common/usbhid.h @@ -0,0 +1,71 @@ +#ifndef INC_usbhidh +#define INC_usbhidh +/*--------------------------------------------------------------------------- + + FILENAME: + usbhid.h + + PURPOSE: + Provide utilities for USB communications. + + REVISION HISTORY: + Date Engineer Revision Remarks + 02/15/11 M.S. Teel 0 Original + + NOTES: + + + LICENSE: + Copyright (c) 2011, Mark S. Teel (mark@teel.ws) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +/* ... System include files +*/ +#include +#include +#include +#include +#include +#include + +/* ... Library include files +*/ +#include +#include + +/* ... Local include files +*/ +#include +#include +#include + + +// define our work area +typedef struct +{ + uint16_t vendorId; + uint16_t productId; + int blocking; + int debug; +} MEDIUM_USBHID; + + +/* ... function prototypes +*/ + +// this is the only globally visible method +extern int usbhidMediumInit +( + WVIEW_MEDIUM *medium, + uint16_t vendor_id, + uint16_t product_id, + int enableDebug, + int blocking +); + +#endif + diff --git a/stations/common/usbif.c b/stations/common/usbif.c new file mode 100755 index 0000000..b2141da --- /dev/null +++ b/stations/common/usbif.c @@ -0,0 +1,276 @@ +/*--------------------------------------------------------------------------- + + FILENAME: + usbif.c + + PURPOSE: + Provide the weather station USB medium utilities. + + REVISION HISTORY: + Date Engineer Revision Remarks + 02/14/2011 M.S. Teel 0 Original + + NOTES: + wview medium-specific routines to be supplied: + + usbifMediumInit - sets up function pointers and work area + usbifInit - initialize + usbifExit - exit + usbifInterruptRead - interrupt driven reads + usbifInterruptWrite - interrupt driven writes + usbifSendControlMsg - send a control msg + usbifGetDescriptor - Get descriptor data + usbifClaimInterface - claim an interface + usbifReleaseInterface - release an interface + usbifSetAltInterface - set an alternate interface + usbifSetConfiguration - set configuration + + See daemon.h for details of the WVIEW_MEDIUM structure. + + LICENSE: + Copyright (c) 2011, Mark S. Teel (mark@teel.ws) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +/* ... System include files +*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/* ... Library include files +*/ +#include +#include + +/* ... Local include files +*/ +#include +#include +#include +#include + +/* ... global memory declarations +*/ + +/* ... local memory +*/ +static MEDIUM_USB mediumUSB; + + +static struct usbcompat_device *find_device(int vendor, int product) +{ + struct usbcompat_bus *bus; + + for (bus = usbcompat_get_busses(); bus != NULL; bus = bus->next) + { + struct usbcompat_device *dev; + + for (dev = bus->devices; dev != NULL; dev = dev->next) + { + if (dev->descriptor.idVendor == vendor && + dev->descriptor.idProduct == product) + { + // Found it! + return dev; + } + } + } + return NULL; +} + + +////////////////////////////////////////////////////////////////////////////// +// ... medium callback functions +////////////////////////////////////////////////////////////////////////////// + +static int usbifInit (WVIEW_MEDIUM *med) +{ + MEDIUM_USB *usbifWork = (MEDIUM_USB *)med->workData; + int retVal; + uint8_t buffer[1024]; + + usbcompat_init(); + usbcompat_find_busses(); + usbcompat_find_devices(); + + usbifWork->dev = find_device(usbifWork->vendorId, usbifWork->productId); + if (usbifWork->dev == NULL) + { + radMsgLog (PRI_HIGH, "USB: device for vendor %d, product %d not found!", + usbifWork->vendorId, usbifWork->productId); + return ERROR; + } + + med->usbHandle = usbcompat_open(usbifWork->dev); + if (med->usbHandle == NULL) + { + radMsgLog (PRI_HIGH, "USB: failed to obtain device handle"); + return ERROR; + } + + retVal = usbcompat_get_driver_np(med->usbHandle, 0, buffer, sizeof(buffer)); + if (retVal == 0) + { + retVal = usbcompat_detach_kernel_driver_np(med->usbHandle, 0); + } + + retVal = usbcompat_claim_interface(med->usbHandle, 0); + if (retVal != 0) + { + radMsgLog (PRI_HIGH, "USB: claim_interface failed: %d", retVal); + return ERROR; + } + + retVal = usbcompat_set_altinterface(med->usbHandle, 0); + if (retVal != 0) + { + radMsgLog (PRI_HIGH, "USB: set_altinterface failed: %d", retVal); + return ERROR; + } + + return OK; +} + +static void usbifExit (WVIEW_MEDIUM *med) +{ + usbcompat_release_interface(med->usbHandle, 0); + usbcompat_close(med->usbHandle); + med->usbHandle = NULL; + return; +} + +static int usbifInterruptRead +( + struct _wview_medium *medium, + int ep, + char *buffer, + int length, + int timeout +) +{ + return usbcompat_interrupt_read(medium->usbHandle, ep, buffer, length, timeout); +} + +static int usbifInterruptWrite +( + struct _wview_medium *medium, + int ep, + char *buffer, + int length, + int timeout +) +{ + return usbcompat_interrupt_write(medium->usbHandle, ep, buffer, length, timeout); +} + +static int usbifSendControlMsg +( + struct _wview_medium *medium, + int bmRequestType, + int bRequest, + int wValue, + int wIndex, + char *buffer, + int length, + int timeout +) +{ + return usbcompat_control_msg(medium->usbHandle, + bmRequestType, + bRequest, + wValue, + wIndex, + buffer, + length, + timeout); +} + +static int usbifGetDescriptor +( + struct _wview_medium *medium, + uint8_t type, + uint8_t desc_index, + void *buffer, + int length +) +{ + return usbcompat_get_descriptor(medium->usbHandle, + type, + desc_index, + buffer, + length); +} + +static int usbifClaimInterface(struct _wview_medium *medium, int interface) +{ + return usbcompat_claim_interface(medium->usbHandle, interface); +} + +static int usbifReleaseInterface(struct _wview_medium *medium, int interface) +{ + return usbcompat_release_interface(medium->usbHandle, interface); +} + +static int usbifSetAltInterface(struct _wview_medium *medium, int alternate) +{ + return usbcompat_set_altinterface(medium->usbHandle, alternate); +} + +static int usbifSetConfiguration(struct _wview_medium *medium, int configuration) +{ + return usbcompat_set_configuration(medium->usbHandle, configuration); +} + + +// ... ----- API methods ----- + +int usbifMediumInit +( + WVIEW_MEDIUM *medium, + uint16_t vendor_id, + uint16_t product_id +) +{ + MEDIUM_USB *work = &mediumUSB; + + memset (medium, 0, sizeof (*medium)); + memset (work, 0, sizeof (*work)); + + work->vendorId = vendor_id; + work->productId = product_id; + + medium->type = MEDIUM_TYPE_USB; + + // set our workData pointer for later use + medium->workData = (void *)work; + + medium->usbInit = usbifInit; + medium->usbExit = usbifExit; + medium->usbInterruptRead = usbifInterruptRead; + medium->usbInterruptWrite = usbifInterruptWrite; + medium->usbSendControlMsg = usbifSendControlMsg; + medium->usbGetDescriptor = usbifGetDescriptor; + medium->usbClaimInterface = usbifClaimInterface; + medium->usbReleaseInterface = usbifReleaseInterface; + medium->usbSetAltInterface = usbifSetAltInterface; + medium->usbSetConfiguration = usbifSetConfiguration; + + return OK; +} + diff --git a/stations/common/usbif.h b/stations/common/usbif.h new file mode 100644 index 0000000..893d776 --- /dev/null +++ b/stations/common/usbif.h @@ -0,0 +1,69 @@ +#ifndef INC_usbifh +#define INC_usbifh +/*--------------------------------------------------------------------------- + + FILENAME: + usbif.h + + PURPOSE: + Provide utilities for USB communications. + + REVISION HISTORY: + Date Engineer Revision Remarks + 02/14/11 M.S. Teel 0 Original + + NOTES: + + + LICENSE: + Copyright (c) 2011, Mark S. Teel (mark@teel.ws) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +/* ... System include files +*/ +#include +#include +#include +#include +#include +#include + +/* ... Library include files +*/ +#include +#include + +/* ... Local include files +*/ +#include +#include +#include +#include + + +// define our work area +typedef struct +{ + struct usbcompat_device* dev; + uint16_t vendorId; + uint16_t productId; +} MEDIUM_USB; + + +/* ... function prototypes +*/ + +// this is the only globally visible method +extern int usbMediumInit +( + WVIEW_MEDIUM *medium, + uint16_t vendor_id, + uint16_t product_id +); + +#endif + diff --git a/utilities/Makefile.am b/utilities/Makefile.am new file mode 100755 index 0000000..8449f1e --- /dev/null +++ b/utilities/Makefile.am @@ -0,0 +1,11 @@ +# Makefile - Utilities + +# add subdirectories to be included in the distribution +# (thus requiring Makefile.am files) +SUBDIRS = \ +archive-be2le \ +archive-le2be \ +wlk2sqlite \ +sqlite2wlk \ +hilowcreate + diff --git a/utilities/archive-be2le/Makefile.am b/utilities/archive-be2le/Makefile.am new file mode 100755 index 0000000..3ce3298 --- /dev/null +++ b/utilities/archive-be2le/Makefile.am @@ -0,0 +1,39 @@ +# Makefile - arc_be2le + +#define the executable to be built +bin_PROGRAMS = arc_be2le + +# define include directories +INCLUDES = \ + -I$(top_srcdir)/common \ + -I$(top_srcdir)/stations/VantagePro \ + -I$(top_srcdir)/utilities \ + -I$(prefix)/include \ + -DWV_CONFIG_DIR=\"$(sysconfdir)/wview\" \ + -DWV_RUN_DIR=\"$(localstatedir)/wview\" \ + -DBUILD_UTILITIES \ + -D_GNU_SOURCE + +# define the sources +arc_be2le_SOURCES = \ + $(top_srcdir)/common/sysdefs.h \ + $(top_srcdir)/common/datadefs.h \ + $(top_srcdir)/common/dbsqlite.h \ + $(top_srcdir)/stations/VantagePro/dbfiles.h \ + $(top_srcdir)/utilities/wvutilities.h \ + $(top_srcdir)/common/dbsqlite.c \ + $(top_srcdir)/common/wvutils.c \ + $(top_srcdir)/stations/VantagePro/dbfiles.c \ + $(top_srcdir)/utilities/wvutilities.c \ + $(top_srcdir)/utilities/archive-be2le/arc_be2le.c + +# define libraries +arc_be2le_LDADD = + +# define library directories +arc_be2le_LDFLAGS = -L$(prefix)/lib -L$(prefix)/usr/lib -L/usr/lib + +if CROSSCOMPILE +arc_be2le_LDFLAGS += $(prefix)/lib/crt1.o $(prefix)/lib/crti.o $(prefix)/lib/crtn.o +endif + diff --git a/utilities/archive-be2le/arc_be2le.c b/utilities/archive-be2le/arc_be2le.c new file mode 100755 index 0000000..7ecd7ac --- /dev/null +++ b/utilities/archive-be2le/arc_be2le.c @@ -0,0 +1,78 @@ +/*--------------------------------------------------------------------- + + FILE NAME: + arc_be2le.c + + PURPOSE: + wview archive file convertor utility: Little Endian to Big Endian. + + REVISION HISTORY: + Date Programmer Revision Function + 02/26/2006 M.S. Teel 0 Original + + ASSUMPTIONS: + None. + +------------------------------------------------------------------------*/ +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include "wvutilities.h" + + +static void USAGE (void) +{ + printf ("Usage: arc-be2le \n\n"); + printf (" Convert wview archive data in from big endian\n"); + printf (" to little endian then store the result in \n\n"); + return; +} + +int main (int argc, char *argv[]) +{ + char *SourceDir, *DestDir; + struct stat fileData; + + if (argc < 3) + { + USAGE (); + return ERROR; + } + + SourceDir = argv[1]; + DestDir = argv[2]; + + // sanity check the arguments + if (stat(SourceDir, &fileData) != 0) + { + printf ("Source directory %s does not exist!\n", SourceDir); + return ERROR; + } + else if (!(fileData.st_mode & S_IFDIR)) + { + printf ("Source directory %s is not a directory!\n", SourceDir); + return ERROR; + } + if (stat (DestDir, &fileData) != 0) + { + printf ("Destination directory %s does not exist, creating...\n", DestDir); + if (mkdir(DestDir, 0755) != 0) + { + printf ("Destination directory %s could not be created: %s\n", + DestDir, strerror(errno)); + return -1; + } + } + + // OK, args appear to be good, convert files (set 'doLE2BE' to 0) + wvuConvertWLKFiles (SourceDir, DestDir, 0); + + exit (0); +} diff --git a/utilities/archive-le2be/Makefile.am b/utilities/archive-le2be/Makefile.am new file mode 100755 index 0000000..19f2f70 --- /dev/null +++ b/utilities/archive-le2be/Makefile.am @@ -0,0 +1,39 @@ +# Makefile - arc_le2be + +#define the executable to be built +bin_PROGRAMS = arc_le2be + +# define include directories +INCLUDES = \ + -I$(top_srcdir)/common \ + -I$(top_srcdir)/stations/VantagePro \ + -I$(top_srcdir)/utilities \ + -I$(prefix)/include \ + -DWV_CONFIG_DIR=\"$(sysconfdir)/wview\" \ + -DWV_RUN_DIR=\"$(localstatedir)/wview\" \ + -DBUILD_UTILITIES \ + -D_GNU_SOURCE + +# define the sources +arc_le2be_SOURCES = \ + $(top_srcdir)/common/sysdefs.h \ + $(top_srcdir)/common/datadefs.h \ + $(top_srcdir)/common/dbsqlite.h \ + $(top_srcdir)/stations/VantagePro/dbfiles.h \ + $(top_srcdir)/utilities/wvutilities.h \ + $(top_srcdir)/common/dbsqlite.c \ + $(top_srcdir)/common/wvutils.c \ + $(top_srcdir)/stations/VantagePro/dbfiles.c \ + $(top_srcdir)/utilities/wvutilities.c \ + $(top_srcdir)/utilities/archive-le2be/arc_le2be.c + +# define libraries +arc_le2be_LDADD = + +# define library directories +arc_le2be_LDFLAGS = -L$(prefix)/lib -L$(prefix)/usr/lib -L/usr/lib + +if CROSSCOMPILE +arc_le2be_LDFLAGS += $(prefix)/lib/crt1.o $(prefix)/lib/crti.o $(prefix)/lib/crtn.o +endif + diff --git a/utilities/archive-le2be/arc_le2be.c b/utilities/archive-le2be/arc_le2be.c new file mode 100755 index 0000000..81d44a5 --- /dev/null +++ b/utilities/archive-le2be/arc_le2be.c @@ -0,0 +1,78 @@ +/*--------------------------------------------------------------------- + + FILE NAME: + arc_le2be.c + + PURPOSE: + wview archive file convertor utility: Little Endian to Big Endian. + + REVISION HISTORY: + Date Programmer Revision Function + 02/26/2006 M.S. Teel 0 Original + + ASSUMPTIONS: + None. + +------------------------------------------------------------------------*/ +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include "wvutilities.h" + + +static void USAGE (void) +{ + printf ("Usage: arc_le2be \n\n"); + printf (" Convert wview archive data in from little endian\n"); + printf (" to big endian then store the result in \n\n"); + return; +} + +int main (int argc, char *argv[]) +{ + char *SourceDir, *DestDir; + struct stat fileData; + + if (argc < 3) + { + USAGE (); + return ERROR; + } + + SourceDir = argv[1]; + DestDir = argv[2]; + + // sanity check the arguments + if (stat(SourceDir, &fileData) != 0) + { + printf ("Source directory %s does not exist!\n", SourceDir); + return ERROR; + } + else if (!(fileData.st_mode & S_IFDIR)) + { + printf ("Source directory %s is not a directory!\n", SourceDir); + return ERROR; + } + if (stat (DestDir, &fileData) != 0) + { + printf ("Destination directory %s does not exist, creating...\n", DestDir); + if (mkdir(DestDir, 0755) != 0) + { + printf ("Destination directory %s could not be created: %s\n", + DestDir, strerror(errno)); + return -1; + } + } + + // OK, args appear to be good, convert files (set 'doLE2BE' to 1) + wvuConvertWLKFiles (SourceDir, DestDir, 1); + + exit (0); +} diff --git a/utilities/hilowcreate/Makefile.am b/utilities/hilowcreate/Makefile.am new file mode 100755 index 0000000..6763720 --- /dev/null +++ b/utilities/hilowcreate/Makefile.am @@ -0,0 +1,36 @@ +# Makefile - hilowcreate + +#define the executable to be built +bin_PROGRAMS = hilowcreate + +# define include directories +INCLUDES = \ + -I$(top_srcdir)/common \ + -I$(prefix)/include \ + -DWV_CONFIG_DIR=\"$(sysconfdir)/wview\" \ + -DWV_RUN_DIR=\"$(localstatedir)/wview\" \ + -DBUILD_UTILITIES=1 \ + -D_GNU_SOURCE + +# define the sources +hilowcreate_SOURCES = \ + $(top_srcdir)/common/sysdefs.h \ + $(top_srcdir)/common/datadefs.h \ + $(top_srcdir)/common/dbsqlite.h \ + $(top_srcdir)/common/windAverage.h \ + $(top_srcdir)/common/dbsqlite.c \ + $(top_srcdir)/common/dbsqliteHiLow.c \ + $(top_srcdir)/common/windAverage.c \ + $(top_srcdir)/common/wvutils.c \ + $(top_srcdir)/utilities/hilowcreate/hilowcreate.c + +# define libraries +hilowcreate_LDADD = + +# define library directories +hilowcreate_LDFLAGS = -L$(prefix)/lib -L$(prefix)/usr/lib -L/usr/lib + +if CROSSCOMPILE +hilowcreate_LDFLAGS += $(prefix)/lib/crt1.o $(prefix)/lib/crti.o $(prefix)/lib/crtn.o +endif + diff --git a/utilities/hilowcreate/hilowcreate.c b/utilities/hilowcreate/hilowcreate.c new file mode 100755 index 0000000..7a595c0 --- /dev/null +++ b/utilities/hilowcreate/hilowcreate.c @@ -0,0 +1,128 @@ +/*--------------------------------------------------------------------- + + FILE NAME: + hilowcreate.c + + PURPOSE: + wview archive file convertor utility: SQLite3 database TO Davis WLK. + + REVISION HISTORY: + Date Programmer Revision Function + 03/01/2009 M.S. Teel 0 Original + + ASSUMPTIONS: + None. + +------------------------------------------------------------------------*/ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + + +static void USAGE (void) +{ + printf ("Usage: hilowcreate \n\n"); + printf (" Create wview HILOW data in /wview-hilow.sdb\n"); + printf (" using archive records in /wview-archive.sdb\n\n"); + printf ("Note: If wview-hilow.sdb already exists in , it will be overwritten;\n"); + printf (" cannot be the live wview archive, copy wview-archive.sdb to a work directory.\n\n"); + return; +} + + +// Create HILOW DB: +static void CreateHiLowDatabase (char *srcDir) +{ + time_t timeStamp, diffTime, retVal, startTime = time(NULL); + struct tm buildTime; + ARCHIVE_PKT sqlitePkt; + int inserts = 0, errors = 0; + char cmnd[_MAX_PATH]; + + dbsqliteArchiveSetPath(srcDir); + + // ... Initialize the archive database interface: + if (dbsqliteArchiveInit() == ERROR) + { + printf("dbsqliteArchiveInit failed"); + return; + } + + // Remove any old copies: + sprintf(cmnd, "rm -f %s/wview-hilow.sdb", srcDir); + if (system(cmnd) != 0) + { + printf("system error:%s:%s\n", cmnd, strerror(errno)); + } + + if (dbsqliteHiLowInit(TRUE) == ERROR) + { + printf("dbsqliteHiLowInit failed"); + dbsqliteArchiveExit(); + return; + } + + dbsqliteHiLowExit(); + dbsqliteArchiveExit(); + + // Output results: + printf ("%s/wview-hilow.sdb created successfully!\n", srcDir); + printf ("Now copy %s/wview-hilow.sdb to %s/archive and restart wview 5.1.0 or newer...\n", + srcDir, WV_RUN_DIR); + return; +} + +//////////////////////////////////////////////////////////////////////////////// +int main (int argc, char *argv[]) +{ + char *SourceDir; + struct stat fileData; + char tempPath[_MAX_PATH]; + + if (argc < 2) + { + USAGE (); + return ERROR; + } + + SourceDir = argv[1]; + sprintf(tempPath, "%s/archive", WV_RUN_DIR); + + // sanity check the arguments + if (stat(SourceDir, &fileData) != 0) + { + printf ("Source directory %s does not exist!\n", SourceDir); + return ERROR; + } + else if (!(fileData.st_mode & S_IFDIR)) + { + printf ("Source directory %s is not a directory!\n", SourceDir); + return ERROR; + } + else if (! strcmp(SourceDir, tempPath)) + { + printf ("ERROR: Source directory cannot be the active archive directory %s\n", + tempPath); + printf ("Copy your wview-archive.sdb database to a working directory\n"); + printf ("and use that as the source directory.\n"); + printf ("wview will add the new records created while this process is running\n"); + printf ("the next time it is started with the new HILOW database.\n"); + return ERROR; + } + + // OK, args appear to be good, create HILOW data: + printf("Creating HILOW database...(this takes ~ 30 seconds to 30 minutes per month of data according to your server platform)...\n"); + CreateHiLowDatabase (SourceDir); + + exit (0); +} + diff --git a/utilities/sqlite2wlk/Makefile.am b/utilities/sqlite2wlk/Makefile.am new file mode 100755 index 0000000..7df9420 --- /dev/null +++ b/utilities/sqlite2wlk/Makefile.am @@ -0,0 +1,37 @@ +# Makefile - sqlite2wlk + +#define the executable to be built +bin_PROGRAMS = sqlite2wlk + +# define include directories +INCLUDES = \ + -I$(top_srcdir)/common \ + -I$(top_srcdir)/stations/VantagePro \ + -I$(top_srcdir)/utilities \ + -I$(prefix)/include \ + -DWV_CONFIG_DIR=\"$(sysconfdir)/wview\" \ + -DWV_RUN_DIR=\"$(localstatedir)/wview\" \ + -DBUILD_UTILITIES \ + -D_GNU_SOURCE + +# define the sources +sqlite2wlk_SOURCES = \ + $(top_srcdir)/common/sysdefs.h \ + $(top_srcdir)/common/datadefs.h \ + $(top_srcdir)/common/dbsqlite.h \ + $(top_srcdir)/stations/VantagePro/dbfiles.h \ + $(top_srcdir)/common/dbsqlite.c \ + $(top_srcdir)/common/wvutils.c \ + $(top_srcdir)/stations/VantagePro/dbfiles.c \ + $(top_srcdir)/utilities/sqlite2wlk/sqlite2wlk.c + +# define libraries +sqlite2wlk_LDADD = + +# define library directories +sqlite2wlk_LDFLAGS = -L$(prefix)/lib -L$(prefix)/usr/lib -L/usr/lib + +if CROSSCOMPILE +sqlite2wlk_LDFLAGS += $(prefix)/lib/crt1.o $(prefix)/lib/crti.o $(prefix)/lib/crtn.o +endif + diff --git a/utilities/sqlite2wlk/sqlite2wlk.c b/utilities/sqlite2wlk/sqlite2wlk.c new file mode 100755 index 0000000..377198e --- /dev/null +++ b/utilities/sqlite2wlk/sqlite2wlk.c @@ -0,0 +1,223 @@ +/*--------------------------------------------------------------------- + + FILE NAME: + sqlite2wlk.c + + PURPOSE: + wview archive file convertor utility: SQLite3 database TO Davis WLK. + + REVISION HISTORY: + Date Programmer Revision Function + 09/13/2008 M.S. Teel 0 Original + + ASSUMPTIONS: + None. + +------------------------------------------------------------------------*/ +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include "wvutilities.h" + + +static void USAGE (void) +{ + printf ("Usage: sqlite2wlk \n\n"); + printf (" Convert wview archive data in /wview-archive.sdb\n"); + printf (" to Davis WLK files in \n"); + printf ("Note: If Davis WLK files in , they will be overwritten.\n"); + printf (" If is the live wview archive, you will need root access.\n\n"); + return; +} + +#define GETVALWITHOFFSET(x,y) ((x <= ARCHIVE_VALUE_NULL) ? 0xff : (uint8_t)(x+y)) +#define GETVALNOOFFSET(x) ((x <= ARCHIVE_VALUE_NULL) ? 0xff : (uint8_t)x) + +static void convertArchivePktToWLK(ARCHIVE_PKT* archivePkt, ARCHIVE_RECORD* newRecord, time_t timeStamp) +{ + uint16_t temp; + time_t tempTime; + + // create the silly packed time crap for the record: + // First check for 24:00 madness: + if (wvutilsGetHour(timeStamp) == 0 && wvutilsGetMin(timeStamp) == 0) + { + // Need to make it 24:00: + tempTime = timeStamp - (3*WV_SECONDS_IN_HOUR); // Avoid DST issues + newRecord->date = INSERT_PACKED_DATE(wvutilsGetYear(tempTime), + wvutilsGetMonth(tempTime), + wvutilsGetDay(tempTime)); + newRecord->time = (100 * 24); + } + else + { + newRecord->date = INSERT_PACKED_DATE(wvutilsGetYear(timeStamp), + wvutilsGetMonth(timeStamp), + wvutilsGetDay(timeStamp)); + newRecord->time = (100 * wvutilsGetHour(timeStamp)) + wvutilsGetMin(timeStamp); + } + + // Set the values we can: + newRecord->outTemp = (int16_t)(archivePkt->value[DATA_INDEX_outTemp]*10); + newRecord->highOutTemp = (int16_t)(archivePkt->value[DATA_INDEX_outTemp]*10); + newRecord->lowOutTemp = (int16_t)(archivePkt->value[DATA_INDEX_outTemp]*10); + temp = (uint16_t)(archivePkt->value[DATA_INDEX_rain]*100); + newRecord->rain = temp; + newRecord->highRainRate = (uint16_t)(archivePkt->value[DATA_INDEX_rainRate]*100); + newRecord->barometer = (uint16_t)(archivePkt->value[DATA_INDEX_barometer]*1000); + newRecord->radiation = (uint16_t)(archivePkt->value[DATA_INDEX_radiation]); + newRecord->windSamples = 0xffff; + newRecord->inTemp = (int16_t)(archivePkt->value[DATA_INDEX_inTemp]*10); + newRecord->inHumidity = (uint8_t)archivePkt->value[DATA_INDEX_inHumidity]; + newRecord->outHumidity = (uint8_t)archivePkt->value[DATA_INDEX_outHumidity]; + newRecord->avgWindSpeed = (uint8_t)archivePkt->value[DATA_INDEX_windSpeed]; + newRecord->highWindSpeed = (uint8_t)archivePkt->value[DATA_INDEX_windGust]; + newRecord->highWindDir = (uint8_t)(archivePkt->value[DATA_INDEX_windGustDir] / 22.5); + newRecord->prevWindDir = (uint8_t)(archivePkt->value[DATA_INDEX_windDir] / 22.5); + newRecord->UV = (uint8_t)(archivePkt->value[DATA_INDEX_UV]*10); + newRecord->ET = (uint8_t)(archivePkt->value[DATA_INDEX_ET]*1000); + newRecord->highRadiation = (uint16_t)(archivePkt->value[DATA_INDEX_radiation]); + newRecord->highUV = (uint8_t)(archivePkt->value[DATA_INDEX_UV]*10); + newRecord->fcstRule = 0xff; + newRecord->leafTemp1 = GETVALWITHOFFSET(archivePkt->value[DATA_INDEX_leafTemp1],90); + newRecord->leafTemp2 = GETVALWITHOFFSET(archivePkt->value[DATA_INDEX_leafTemp2],90); + newRecord->leafWet1 = GETVALNOOFFSET(archivePkt->value[DATA_INDEX_leafWet1]); + newRecord->leafWet2 = GETVALNOOFFSET(archivePkt->value[DATA_INDEX_leafWet2]); + newRecord->soilTemp1 = GETVALWITHOFFSET(archivePkt->value[DATA_INDEX_soilTemp1],90); + newRecord->soilTemp2 = GETVALWITHOFFSET(archivePkt->value[DATA_INDEX_soilTemp2],90); + newRecord->soilTemp3 = GETVALWITHOFFSET(archivePkt->value[DATA_INDEX_soilTemp3],90); + newRecord->soilTemp4 = GETVALWITHOFFSET(archivePkt->value[DATA_INDEX_soilTemp4],90); + newRecord->recordType = 0; + newRecord->extraHumid1 = GETVALNOOFFSET(archivePkt->value[DATA_INDEX_extraHumid1]); + newRecord->extraHumid2 = GETVALNOOFFSET(archivePkt->value[DATA_INDEX_extraHumid2]); + newRecord->extraTemp1 = GETVALWITHOFFSET(archivePkt->value[DATA_INDEX_extraTemp1],90); + newRecord->extraTemp2 = GETVALWITHOFFSET(archivePkt->value[DATA_INDEX_extraTemp2],90); + newRecord->extraTemp3 = GETVALWITHOFFSET(archivePkt->value[DATA_INDEX_extraTemp3],90); + newRecord->soilMoist1 = GETVALNOOFFSET(archivePkt->value[DATA_INDEX_soilMoist1]); + newRecord->soilMoist2 = GETVALNOOFFSET(archivePkt->value[DATA_INDEX_soilMoist2]); + newRecord->soilMoist3 = GETVALNOOFFSET(archivePkt->value[DATA_INDEX_soilMoist3]); + newRecord->soilMoist4 = GETVALNOOFFSET(archivePkt->value[DATA_INDEX_soilMoist4]); + + return; +} + +// Convert SQLite3 database to WLK database: +static void ConvertSqliteToWlk (char *srcDir, char *destDir) +{ + time_t timeStamp, diffTime, retVal, startTime = time(NULL); + struct tm buildTime; + ARCHIVE_RECORD wlkPkt; + ARCHIVE_PKT sqlitePkt; + int inserts = 0, errors = 0; + char cmnd[_MAX_PATH]; + + dbsqliteArchiveSetPath(srcDir); + + // ... Initialize the archive database interface: + if (dbsqliteArchiveInit() == ERROR) + { + printf("dbsqliteArchiveInit failed"); + return; + } + + sprintf(cmnd, "rm -rf %s", destDir); + if (system(cmnd) != 0) + { + printf("system error:%s:%s\n", cmnd, strerror(errno)); + } + if (mkdir(destDir, S_IFDIR | 00777) != 0) + { + printf("mkdir:%s:%s\n", destDir, strerror(errno)); + } + + // Set the start date to 1/1/2000: + // Build the sqlite timestamp: + memset (&buildTime, 0, sizeof(buildTime)); + buildTime.tm_min = 0; + buildTime.tm_hour = 0; + buildTime.tm_mday = 1; + buildTime.tm_mon = 0; + buildTime.tm_year = 100; + buildTime.tm_isdst = -1; + timeStamp = mktime(&buildTime); + + for ((retVal = dbsqliteArchiveGetNextRecord(timeStamp, &sqlitePkt)); + (int)retVal != ERROR; + (retVal = dbsqliteArchiveGetNextRecord(retVal, &sqlitePkt))) + { + // OK, need to insert this puppy: + convertArchivePktToWLK(&sqlitePkt, &wlkPkt, retVal); + + if (dbfStoreArchiveRecord(destDir, &wlkPkt, sqlitePkt.interval, 0x1000) == ERROR) + { + // Error: + errors ++; + } + else + { + // Good stuff: + inserts ++; + } + } + + diffTime = time(NULL) - startTime; + + dbsqliteArchiveExit(); + + // Output results: + printf("Conversion Stats:\n"); + printf(" Time : %d:%d\n", (int)diffTime/60, (int)diffTime%60); + printf(" Recs/sec : %d\n", (inserts+errors)/(int)diffTime); + printf(" Inserts : %d\n", inserts); + printf(" Errors : %d\n", errors); +} + +int main (int argc, char *argv[]) +{ + char *SourceDir, *DestDir; + struct stat fileData; + + if (argc < 3) + { + USAGE (); + return ERROR; + } + + SourceDir = argv[1]; + DestDir = argv[2]; + + // sanity check the arguments + if (stat(SourceDir, &fileData) != 0) + { + printf ("Source directory %s does not exist!\n", SourceDir); + return ERROR; + } + else if (!(fileData.st_mode & S_IFDIR)) + { + printf ("Source directory %s is not a directory!\n", SourceDir); + return ERROR; + } + if (stat (DestDir, &fileData) != 0) + { + printf ("Destination directory %s does not exist, creating...\n", DestDir); + if (mkdir(DestDir, 0755) != 0) + { + printf ("Destination directory %s could not be created: %s\n", + DestDir, strerror(errno)); + return -1; + } + } + + // OK, args appear to be good, convert SQLite DB to WLK files: + printf("Converting...(this takes ~25 minutes per year of data at 5 minute archive interval)...\n"); + ConvertSqliteToWlk (SourceDir, DestDir); + + exit (0); +} diff --git a/utilities/wlk2sqlite/Makefile.am b/utilities/wlk2sqlite/Makefile.am new file mode 100755 index 0000000..d998c97 --- /dev/null +++ b/utilities/wlk2sqlite/Makefile.am @@ -0,0 +1,37 @@ +# Makefile - wlk2sqlite + +#define the executable to be built +bin_PROGRAMS = wlk2sqlite + +# define include directories +INCLUDES = \ + -I$(top_srcdir)/common \ + -I$(top_srcdir)/stations/VantagePro \ + -I$(top_srcdir)/utilities \ + -I$(prefix)/include \ + -DWV_CONFIG_DIR=\"$(sysconfdir)/wview\" \ + -DWV_RUN_DIR=\"$(localstatedir)/wview\" \ + -DBUILD_UTILITIES \ + -D_GNU_SOURCE + +# define the sources +wlk2sqlite_SOURCES = \ + $(top_srcdir)/common/sysdefs.h \ + $(top_srcdir)/common/datadefs.h \ + $(top_srcdir)/common/dbsqlite.h \ + $(top_srcdir)/stations/VantagePro/dbfiles.h \ + $(top_srcdir)/common/dbsqlite.c \ + $(top_srcdir)/common/wvutils.c \ + $(top_srcdir)/stations/VantagePro/dbfiles.c \ + $(top_srcdir)/utilities/wlk2sqlite/wlk2sqlite.c + +# define libraries +wlk2sqlite_LDADD = + +# define library directories +wlk2sqlite_LDFLAGS = -L$(prefix)/lib -L$(prefix)/usr/lib -L/usr/lib + +if CROSSCOMPILE +wlk2sqlite_LDFLAGS += $(prefix)/lib/crt1.o $(prefix)/lib/crti.o $(prefix)/lib/crtn.o +endif + diff --git a/utilities/wlk2sqlite/wlk2sqlite.c b/utilities/wlk2sqlite/wlk2sqlite.c new file mode 100755 index 0000000..aa106d8 --- /dev/null +++ b/utilities/wlk2sqlite/wlk2sqlite.c @@ -0,0 +1,335 @@ +/*--------------------------------------------------------------------- + + FILE NAME: + wlk2sqlite.c + + PURPOSE: + wview archive file convertor utility: Davis WLK TO SQLite3 database. + + REVISION HISTORY: + Date Programmer Revision Function + 09/13/2008 M.S. Teel 0 Original + + ASSUMPTIONS: + None. + +------------------------------------------------------------------------*/ +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include "wvutilities.h" + + +static void USAGE (void) +{ + printf ("Usage: wlk2sqlite [destination_directory]\n\n"); + printf (" Convert WLK archive file data in to SQLite3 format in [destination_directory]/wview-archive.sdb\n"); + printf (" or to $prefix/var/wview/archive/wview-archive.sdb if [destination_directory] is not given\n"); + printf ("Note: Must be run as root if [destination_directory] is not given. Only unique records will be inserted.\n"); + printf ("Note: can be the same as [destination_directory]\n\n"); + return; +} + +static void convertWLKToArchivePkt(ArchiveRecord* newRecord, ARCHIVE_PKT* archivePkt, time_t timeStamp) +{ + Data_Indices index; + float click; + + // create the time_t time for the record: + archivePkt->dateTime = (int32_t)timeStamp; + + archivePkt->usUnits = 1; + archivePkt->interval = newRecord->archiveInterval; + + // Set all values to NULL by default: + for (index = DATA_INDEX_barometer; index < DATA_INDEX_MAX; index ++) + { + archivePkt->value[index] = ARCHIVE_VALUE_NULL; + } + + // Set the values we can: + archivePkt->value[DATA_INDEX_outTemp] = (float)newRecord->outsideTemp/10.0; + archivePkt->value[DATA_INDEX_barometer] = (float)newRecord->barometer/1000.0; + archivePkt->value[DATA_INDEX_inTemp] = (float)newRecord->insideTemp/10.0; + archivePkt->value[DATA_INDEX_inHumidity] = (float)newRecord->insideHum/10.0; + archivePkt->value[DATA_INDEX_outHumidity] = (float)newRecord->outsideHum/10.0; + archivePkt->value[DATA_INDEX_windSpeed] = (float)newRecord->windSpeed/10.0; + if (newRecord->windDirection < 16) + { + archivePkt->value[DATA_INDEX_windDir] = (float)newRecord->windDirection * 22.5; + } + archivePkt->value[DATA_INDEX_windGust] = (float)newRecord->hiWindSpeed/10.0; + if (newRecord->hiWindDirection < 16) + { + archivePkt->value[DATA_INDEX_windGustDir] = (float)newRecord->hiWindDirection * 22.5; + } + + if ((newRecord->rain & 0xF000) == 0x0000) + click = 10; + else if ((newRecord->rain & 0xF000) == 0x2000) //0.2 mm 0x2000 + click = 127; + else if ((newRecord->rain & 0xF000) == 0x3000) //1.0 mm 0x3000 + click = 25.4; + else if ((newRecord->rain & 0xF000) == 0x6000) //0.1 mm 0x6000 (not fully supported) + click = 254; + else + click = 100; + + archivePkt->value[DATA_INDEX_rainRate] + = (float)newRecord->hiRainRate/click; + archivePkt->value[DATA_INDEX_rain] + = ((float)(newRecord->rain & 0xFFF))/click; + archivePkt->value[DATA_INDEX_dewpoint] + = wvutilsCalculateDewpoint ((float)archivePkt->value[DATA_INDEX_outTemp], + (float)archivePkt->value[DATA_INDEX_outHumidity]); + archivePkt->value[DATA_INDEX_windchill] + = wvutilsCalculateWindChill ((float)archivePkt->value[DATA_INDEX_outTemp], + (float)archivePkt->value[DATA_INDEX_windSpeed]); + archivePkt->value[DATA_INDEX_heatindex] + = wvutilsCalculateHeatIndex ((float)archivePkt->value[DATA_INDEX_outTemp], + (float)archivePkt->value[DATA_INDEX_outHumidity]); + archivePkt->value[DATA_INDEX_ET] = (float)newRecord->ET/1000.0; + if ((uint16_t)newRecord->solarRad != 0x7FFF && + (uint16_t)newRecord->solarRad != 0xFFFF && + (float)newRecord->solarRad >= 0 && + (float)newRecord->solarRad <= 1800) + { + archivePkt->value[DATA_INDEX_radiation] = (float)newRecord->solarRad; + } + if (newRecord->UV != 0xFF) + { + archivePkt->value[DATA_INDEX_UV] = (float)newRecord->UV/10.0; + } + if (newRecord->extraTemp[0] != 0xFF) + { + archivePkt->value[DATA_INDEX_extraTemp1] = (float)(newRecord->extraTemp[0] - 90); + } + if (newRecord->extraTemp[1] != 0xFF) + { + archivePkt->value[DATA_INDEX_extraTemp2] = (float)(newRecord->extraTemp[1] - 90); + } + if (newRecord->extraTemp[2] != 0xFF) + { + archivePkt->value[DATA_INDEX_extraTemp3] = (float)(newRecord->extraTemp[2] - 90); + } + if (newRecord->soilTemp[0] != 0xFF) + { + archivePkt->value[DATA_INDEX_soilTemp1] = (float)(newRecord->soilTemp[0] - 90); + } + if (newRecord->soilTemp[1] != 0xFF) + { + archivePkt->value[DATA_INDEX_soilTemp2] = (float)(newRecord->soilTemp[1] - 90); + } + if (newRecord->soilTemp[2] != 0xFF) + { + archivePkt->value[DATA_INDEX_soilTemp3] = (float)(newRecord->soilTemp[2] - 90); + } + if (newRecord->soilTemp[3] != 0xFF) + { + archivePkt->value[DATA_INDEX_soilTemp4] = (float)(newRecord->soilTemp[3] - 90); + } + if (newRecord->leafTemp[0] != 0xFF) + { + archivePkt->value[DATA_INDEX_leafTemp1] = (float)(newRecord->leafTemp[0] - 90); + } + if (newRecord->leafTemp[1] != 0xFF) + { + archivePkt->value[DATA_INDEX_leafTemp2] = (float)(newRecord->leafTemp[1] - 90); + } + if (newRecord->extraHum[0] != 0xFF) + { + archivePkt->value[DATA_INDEX_extraHumid1] = (float)newRecord->extraHum[0]; + } + if (newRecord->extraHum[1] != 0xFF) + { + archivePkt->value[DATA_INDEX_extraHumid2] = (float)newRecord->extraHum[1]; + } + if (newRecord->soilMoisture[0] != 0xFF) + { + archivePkt->value[DATA_INDEX_soilMoist1] = (float)newRecord->soilMoisture[0]; + } + if (newRecord->soilMoisture[1] != 0xFF) + { + archivePkt->value[DATA_INDEX_soilMoist2] = (float)newRecord->soilMoisture[1]; + } + if (newRecord->soilMoisture[2] != 0xFF) + { + archivePkt->value[DATA_INDEX_soilMoist3] = (float)newRecord->soilMoisture[2]; + } + if (newRecord->soilMoisture[3] != 0xFF) + { + archivePkt->value[DATA_INDEX_soilMoist4] = (float)newRecord->soilMoisture[3]; + } + if (newRecord->leafWetness[0] != 0xFF) + { + archivePkt->value[DATA_INDEX_leafWet1] = (float)newRecord->leafWetness[0]; + } + if (newRecord->leafWetness[1] != 0xFF) + { + archivePkt->value[DATA_INDEX_leafWet2] = (float)newRecord->leafWetness[1]; + } + + return; +} + +static void ConvertWlkToSqlite (char *srcDir, char *destDir) +{ + time_t timeStamp, diffTime, startTime = time(NULL); + struct tm buildTime; + uint16_t recdate, rectime; + ArchiveRecord wlkPkt; + ARCHIVE_PKT sqlitePkt; + int dups = 0, inserts = 0, errors = 0, lastminute = 0; + + if (destDir != NULL) + { + dbsqliteArchiveSetPath(destDir); + } + + // ... Initialize the archive database interface: + if (dbsqliteArchiveInit() == ERROR) + { + printf("dbsqliteArchiveInit failed\n"); + return; + } + + // Avoid expensive fsyncs during conversion + if (dbsqliteArchivePragmaSet("synchronous", "off") == ERROR) + { + printf("dbsqliteArchivePragmaSet synchronous = off failed\n"); + return; + } + + // Avoid expensive journal operations during conversion + if (dbsqliteArchivePragmaSet("journal_mode", "off") == ERROR) + { + printf("dbsqliteArchivePragmaSet journal_mode = off failed\n"); + return; + } + + // Set the start date to 1/1/2000: + recdate = INSERT_PACKED_DATE(2000,1,1); + rectime = 0; + while (dbfGetNextArchiveRecord(srcDir, &recdate, &rectime, &wlkPkt) != ERROR) + { + // Build the sqlite timestamp: + memset (&buildTime, 0, sizeof(buildTime)); + buildTime.tm_min = EXTRACT_PACKED_MINUTE(rectime); + buildTime.tm_hour = EXTRACT_PACKED_HOUR(rectime); + buildTime.tm_mday = EXTRACT_PACKED_DAY(recdate); + buildTime.tm_mon = EXTRACT_PACKED_MONTH(recdate) - 1; + buildTime.tm_year = EXTRACT_PACKED_YEAR(recdate) - 1900; + buildTime.tm_isdst = -1; + timeStamp = mktime(&buildTime); + + // Check to see if this record exists in the sqlite DB: + if (dbsqliteArchiveGetRecord(timeStamp, &sqlitePkt) != ERROR) + { + // duplicate, skip it: + dups ++; + continue; + } + + // OK, need to insert this puppy: + convertWLKToArchivePkt(&wlkPkt, &sqlitePkt, timeStamp); + + if (dbsqliteArchiveStoreRecord(&sqlitePkt) == ERROR) + { + // Error: + errors ++; + } + else + { + // Good stuff: + inserts ++; + } + + diffTime = time(NULL) - startTime; + + // Output running stats + if ( (int)diffTime%60 == 0 && (int)diffTime/60 > 0 && (int)diffTime/60 != lastminute ) + { + printf("Conversion Stats:\n"); + printf(" Time : %d:%d minutes\n", (int)diffTime/60, (int)diffTime%60); + printf(" Recs/sec : %d\n", (inserts+dups+errors)/(int)diffTime); + printf(" Inserts : %d\n", inserts); + printf(" Duplicates: %d\n", dups); + printf(" Errors : %d\n", errors); + lastminute = (int)diffTime/60; + } + } + + dbsqliteArchiveExit(); + + diffTime = time(NULL) - startTime; + + // Output results: + printf("Conversion Stats:\n"); + printf(" Time : %d:%d\n", (int)diffTime/60, (int)diffTime%60); + printf(" Recs/sec : %d\n", (inserts+dups+errors)/(int)diffTime); + printf(" Inserts : %d\n", inserts); + printf(" Duplicates: %d\n", dups); + printf(" Errors : %d\n", errors); +} + +//////////////////////////////////////////////////////////////////////////////// +int main (int argc, char *argv[]) +{ + char *SourceDir, *DestDir; + struct stat fileData; + + if (argc < 2) + { + USAGE (); + exit (-1); + } + if (argc < 3) + { + printf("Using default $prefix/var/wview/archive destination...\n"); + DestDir = NULL; + } + else + { + DestDir = argv[2]; + } + + SourceDir = argv[1]; + + // sanity check the arguments + if (stat(SourceDir, &fileData) != 0) + { + printf ("Source directory %s does not exist!\n", SourceDir); + exit (-1); + } + else if (!(fileData.st_mode & S_IFDIR)) + { + printf ("Source directory %s is not a directory!\n", SourceDir); + exit (-1); + } + if (DestDir) + { + if (stat(DestDir, &fileData) != 0) + { + printf ("Destination directory %s does not exist!\n", DestDir); + exit (-1); + } + else if (!(fileData.st_mode & S_IFDIR)) + { + printf ("Destination directory %s is not a directory!\n", DestDir); + exit (-1); + } + } + + // OK, args appear to be good, convert WLK files to our SQLite DB: + printf("Converting...(this takes a while, longer on small devices)...\n"); + ConvertWlkToSqlite (SourceDir, DestDir); + exit (0); +} + diff --git a/utilities/wvutilities.c b/utilities/wvutilities.c new file mode 100755 index 0000000..ee98c4d --- /dev/null +++ b/utilities/wvutilities.c @@ -0,0 +1,509 @@ +/*--------------------------------------------------------------------- + + FILE NAME: + wvutilities.c + + PURPOSE: + Provide the wview utility program common definitions. + + REVISION HISTORY: + Date Programmer Revision Function + 02/26/2006 M.S. Teel 0 Original + + ASSUMPTIONS: + None. + +------------------------------------------------------------------------*/ +#include "wvutilities.h" + +static int DoLe2Be; +static HeaderBlock hostHeader; + +#define SHORT_SWAP(x) (((x << 8) & 0xFF00) | ((x >> 8) & 0x00FF)) +#define LONG_SWAP(x) (((x << 24) & 0xFF000000) | \ + ((x << 8) & 0x00FF0000) | \ + ((x >> 8) & 0x0000FF00) | \ + ((x >> 24) & 0x000000FF)) + +#ifdef WORDS_BIGENDIAN + +#define SHORT_READ(x) (DoLe2Be ? SHORT_SWAP(x) : (x)) +#define LONG_READ(x) (DoLe2Be ? LONG_SWAP(x) : (x)) +#define SHORT_WRITE(x) (DoLe2Be ? (x) : SHORT_SWAP(x)) +#define LONG_WRITE(x) (DoLe2Be ? (x) : LONG_SWAP(x)) + +#else + +#define SHORT_READ(x) (DoLe2Be ? (x) : SHORT_SWAP(x)) +#define LONG_READ(x) (DoLe2Be ? (x) : LONG_SWAP(x)) +#define SHORT_WRITE(x) (DoLe2Be ? SHORT_SWAP(x) : (x)) +#define LONG_WRITE(x) (DoLe2Be ? LONG_SWAP(x) : (x)) + +#endif + +//// Local methods +static char *buildArchiveFileName +( + char *path, + uint16_t date, + char *store, + int *yr, + int *mo +) +{ + *yr = (date/100) + 2000; + *mo = date%100; + + sprintf (store, "%s/%4.4d-%2.2d.wlk", path, *yr, *mo); + return store; +} + +static char *incrementArchiveFileName +( + char *oldName, + char *store, + int *yr, + int *mo +) +{ + uint16_t year, month; + char temp[32], path[256]; + int i; + + wvstrncpy (path, oldName, sizeof(path)); + for (i = strlen (oldName) - 1; i >= 0; i --) + { + if (path[i] == '/') + { + path[i] = 0; + break; + } + } + if (i < 0) + { + path[0] = '.'; + } + + memset (temp, 0, sizeof(temp)); + strncpy (temp, &oldName[strlen(path)+1], 4); + year = atoi (temp); + + memset (temp, 0, sizeof(temp)); + strncpy (temp, &oldName[strlen(path)+6], 2); + month = atoi (temp); + + if (month >= 12) + { + month = 1; + year ++; + } + else + { + month ++; + } + + sprintf (store, "%s/%4.4d-%2.2d.wlk", path, year, month); + *yr = year; + *mo = month; + return store; +} + +static int readArcHeader (FILE *inFile, HeaderBlock *header) +{ + int i; + int isSourceNative = TRUE; + + if (fread (header, sizeof(*header), 1, inFile) != 1) + { + printf ("read error\n"); + return ERROR; + } + + hostHeader = *header; + + // test to see if it is already the desired endianness + if (DoLe2Be) + { +#ifdef WORDS_BIGENDIAN + if ((unsigned int)header->totalRecords < 0x10000) + { + printf ("File is already in Big Endian format\n"); + return ERROR; + } + else + { + isSourceNative = FALSE; + } +#else + if ((unsigned int)header->totalRecords >= 0x10000) + { + printf ("File is already in Big Endian format\n"); + return ERROR; + } +#endif + } + else + { +#ifdef WORDS_BIGENDIAN + if ((unsigned int)header->totalRecords >= 0x10000) + { + printf ("File is already in Little Endian format\n"); + return ERROR; + } +#else + if ((unsigned int)header->totalRecords < 0x10000) + { + printf ("File is already in Little Endian format\n"); + return ERROR; + } + else + { + isSourceNative = FALSE; + } +#endif + } + + // Do header conversion (ID field is a string and does not require conversion) + header->totalRecords = LONG_READ(header->totalRecords); + + for (i = 1; i <= 31; i ++) + { + header->dayIndex[i].recordsInDay = SHORT_READ(header->dayIndex[i].recordsInDay); + header->dayIndex[i].startPos = LONG_READ(header->dayIndex[i].startPos); + } + + if (!isSourceNative) + { + hostHeader = *header; + } + + return OK; +} + +static int writeArcHeader (FILE *outFile, HeaderBlock *header) +{ + int i; + + header->totalRecords = LONG_WRITE(header->totalRecords); + + for (i = 1; i <= 31; i ++) + { + header->dayIndex[i].recordsInDay = SHORT_WRITE(header->dayIndex[i].recordsInDay); + header->dayIndex[i].startPos = LONG_WRITE(header->dayIndex[i].startPos); + } + + if (fwrite (header, sizeof(*header), 1, outFile) != 1) + { + printf ("write error\n"); + return ERROR; + } + + return OK; +} + +static int readSummaryRecord (FILE *inFile, DailySummaryRecord *record) +{ + if (fread (record, sizeof(*record), 1, inFile) != 1) + { + return ERROR; + } + + record->dataSpan = SHORT_READ(record->dataSpan); + record->hiOutTemp = SHORT_READ(record->hiOutTemp); + record->lowOutTemp = SHORT_READ(record->lowOutTemp); + record->hiInTemp = SHORT_READ(record->hiInTemp); + record->lowInTemp = SHORT_READ(record->lowInTemp); + record->avgOutTemp = SHORT_READ(record->avgOutTemp); + record->avgInTemp = SHORT_READ(record->avgInTemp); + record->hiChill = SHORT_READ(record->hiChill); + record->lowChill = SHORT_READ(record->lowChill); + record->hiDew = SHORT_READ(record->hiDew); + record->lowDew = SHORT_READ(record->lowDew); + record->avgChill = SHORT_READ(record->avgChill); + record->avgDew = SHORT_READ(record->avgDew); + record->hiOutHum = SHORT_READ(record->hiOutHum); + record->lowOutHum = SHORT_READ(record->lowOutHum); + record->hiInHum = SHORT_READ(record->hiInHum); + record->lowInHum = SHORT_READ(record->lowInHum); + record->avgOutHum = SHORT_READ(record->avgOutHum); + record->hiBar = SHORT_READ(record->hiBar); + record->lowBar = SHORT_READ(record->lowBar); + record->avgBar = SHORT_READ(record->avgBar); + record->hiSpeed = SHORT_READ(record->hiSpeed); + record->avgSpeed = SHORT_READ(record->avgSpeed); + record->dailyWindRunTotal = SHORT_READ(record->dailyWindRunTotal); + record->hi10MinSpeed = SHORT_READ(record->hi10MinSpeed); + record->dailyRainTotal = SHORT_READ(record->dailyRainTotal); + record->hiRainRate = SHORT_READ(record->hiRainRate); + record->dailyUVDose = SHORT_READ(record->dailyUVDose); + record->todaysWeather = SHORT_READ(record->todaysWeather); + record->numWindPackets = SHORT_READ(record->numWindPackets); + record->hiSolar = SHORT_READ(record->hiSolar); + record->dailySolarEnergy = SHORT_READ(record->dailySolarEnergy); + record->minSunlight = SHORT_READ(record->minSunlight); + record->dailyETTotal = SHORT_READ(record->dailyETTotal); + record->hiHeat = SHORT_READ(record->hiHeat); + record->lowHeat = SHORT_READ(record->lowHeat); + record->avgHeat = SHORT_READ(record->avgHeat); + record->hiTHSW = SHORT_READ(record->hiTHSW); + record->lowTHSW = SHORT_READ(record->lowTHSW); + record->hiTHW = SHORT_READ(record->hiTHW); + record->lowTHW = SHORT_READ(record->lowTHW); + record->integratedHeatDD65 = SHORT_READ(record->integratedHeatDD65); + record->hiWetBulb = SHORT_READ(record->hiWetBulb); + record->lowWetBulb = SHORT_READ(record->lowWetBulb); + record->avgWetBulb = SHORT_READ(record->avgWetBulb); + record->integratedCoolDD65 = SHORT_READ(record->integratedCoolDD65); + + return OK; +} + +static int writeSummaryRecord (FILE *outFile, DailySummaryRecord *record) +{ + record->dataSpan = SHORT_WRITE(record->dataSpan); + record->hiOutTemp = SHORT_WRITE(record->hiOutTemp); + record->lowOutTemp = SHORT_WRITE(record->lowOutTemp); + record->hiInTemp = SHORT_WRITE(record->hiInTemp); + record->lowInTemp = SHORT_WRITE(record->lowInTemp); + record->avgOutTemp = SHORT_WRITE(record->avgOutTemp); + record->avgInTemp = SHORT_WRITE(record->avgInTemp); + record->hiChill = SHORT_WRITE(record->hiChill); + record->lowChill = SHORT_WRITE(record->lowChill); + record->hiDew = SHORT_WRITE(record->hiDew); + record->lowDew = SHORT_WRITE(record->lowDew); + record->avgChill = SHORT_WRITE(record->avgChill); + record->avgDew = SHORT_WRITE(record->avgDew); + record->hiOutHum = SHORT_WRITE(record->hiOutHum); + record->lowOutHum = SHORT_WRITE(record->lowOutHum); + record->hiInHum = SHORT_WRITE(record->hiInHum); + record->lowInHum = SHORT_WRITE(record->lowInHum); + record->avgOutHum = SHORT_WRITE(record->avgOutHum); + record->hiBar = SHORT_WRITE(record->hiBar); + record->lowBar = SHORT_WRITE(record->lowBar); + record->avgBar = SHORT_WRITE(record->avgBar); + record->hiSpeed = SHORT_WRITE(record->hiSpeed); + record->avgSpeed = SHORT_WRITE(record->avgSpeed); + record->dailyWindRunTotal = SHORT_WRITE(record->dailyWindRunTotal); + record->hi10MinSpeed = SHORT_WRITE(record->hi10MinSpeed); + record->dailyRainTotal = SHORT_WRITE(record->dailyRainTotal); + record->hiRainRate = SHORT_WRITE(record->hiRainRate); + record->dailyUVDose = SHORT_WRITE(record->dailyUVDose); + record->todaysWeather = SHORT_WRITE(record->todaysWeather); + record->numWindPackets = SHORT_WRITE(record->numWindPackets); + record->hiSolar = SHORT_WRITE(record->hiSolar); + record->dailySolarEnergy = SHORT_WRITE(record->dailySolarEnergy); + record->minSunlight = SHORT_WRITE(record->minSunlight); + record->dailyETTotal = SHORT_WRITE(record->dailyETTotal); + record->hiHeat = SHORT_WRITE(record->hiHeat); + record->lowHeat = SHORT_WRITE(record->lowHeat); + record->avgHeat = SHORT_WRITE(record->avgHeat); + record->hiTHSW = SHORT_WRITE(record->hiTHSW); + record->lowTHSW = SHORT_WRITE(record->lowTHSW); + record->hiTHW = SHORT_WRITE(record->hiTHW); + record->lowTHW = SHORT_WRITE(record->lowTHW); + record->integratedHeatDD65 = SHORT_WRITE(record->integratedHeatDD65); + record->hiWetBulb = SHORT_WRITE(record->hiWetBulb); + record->lowWetBulb = SHORT_WRITE(record->lowWetBulb); + record->avgWetBulb = SHORT_WRITE(record->avgWetBulb); + record->integratedCoolDD65 = SHORT_WRITE(record->integratedCoolDD65); + + if (fwrite (record, sizeof(*record), 1, outFile) != 1) + { + return ERROR; + } + + return OK; +} + +static int readArchiveRecord (FILE *inFile, ArchiveRecord *record) +{ + if (fread (record, sizeof(*record), 1, inFile) != 1) + { + return ERROR; + } + + record->packedTime = SHORT_READ(record->packedTime); + record->outsideTemp = SHORT_READ(record->outsideTemp); + record->hiOutsideTemp = SHORT_READ(record->hiOutsideTemp); + record->lowOutsideTemp = SHORT_READ(record->lowOutsideTemp); + record->insideTemp = SHORT_READ(record->insideTemp); + record->barometer = SHORT_READ(record->barometer); + record->outsideHum = SHORT_READ(record->outsideHum); + record->insideHum = SHORT_READ(record->insideHum); + record->rain = SHORT_READ(record->rain); + record->hiRainRate = SHORT_READ(record->hiRainRate); + record->windSpeed = SHORT_READ(record->windSpeed); + record->hiWindSpeed = SHORT_READ(record->hiWindSpeed); + record->numWindSamples = SHORT_READ(record->numWindSamples); + record->solarRad = SHORT_READ(record->solarRad); + record->hisolarRad = SHORT_READ(record->hisolarRad); + + return OK; +} + +static int writeArchiveRecord (FILE *outFile, ArchiveRecord *record) +{ + record->packedTime = SHORT_WRITE(record->packedTime); + record->outsideTemp = SHORT_WRITE(record->outsideTemp); + record->hiOutsideTemp = SHORT_WRITE(record->hiOutsideTemp); + record->lowOutsideTemp = SHORT_WRITE(record->lowOutsideTemp); + record->insideTemp = SHORT_WRITE(record->insideTemp); + record->barometer = SHORT_WRITE(record->barometer); + record->outsideHum = SHORT_WRITE(record->outsideHum); + record->insideHum = SHORT_WRITE(record->insideHum); + record->rain = SHORT_WRITE(record->rain); + record->hiRainRate = SHORT_WRITE(record->hiRainRate); + record->windSpeed = SHORT_WRITE(record->windSpeed); + record->hiWindSpeed = SHORT_WRITE(record->hiWindSpeed); + record->numWindSamples = SHORT_WRITE(record->numWindSamples); + record->solarRad = SHORT_WRITE(record->solarRad); + record->hisolarRad = SHORT_WRITE(record->hisolarRad); + + if (fwrite (record, sizeof(*record), 1, outFile) != 1) + { + return ERROR; + } + + return OK; +} + +static void convertDayRecords (FILE *inFile, FILE *outFile, HeaderBlock *header, int dayNumber) +{ + int i; + DailySummaryRecord summary; + ArchiveRecord record; + + + if (header->dayIndex[dayNumber].recordsInDay == 0) + { + // nothing for us to do + return; + } + + // goto the beginning of the archive recs for this day + if (fseek (inFile, + sizeof(*header) + (DBFILES_RECORD_SIZE * header->dayIndex[dayNumber].startPos), + SEEK_SET) + == -1) + { + return; + } + + if (readSummaryRecord (inFile, &summary) == OK) + { + fseek (outFile, 0, SEEK_END); + writeSummaryRecord (outFile, &summary); + + for (i = 2; i < header->dayIndex[dayNumber].recordsInDay; i ++) + { + if (readArchiveRecord (inFile, &record) == OK) + { + writeArchiveRecord (outFile, &record); + } + } + } + + return; +} + + +//// API methods + +int wvuConvertWLKFiles (char *srcDir, char *destDir, int doLE2BE) +{ + char sourceName[512], destName[512]; + uint16_t fileDate; + struct stat fileStatus; + FILE *inFile, *outFile; + int retVal, year, month, dummyYear, dummyMonth; + int curyear, curmonth; + int i, done = FALSE; + time_t ntime; + struct tm locTime; + HeaderBlock Header; + + DoLe2Be = doLE2BE; + ntime = time (NULL); + localtime_r (&ntime, &locTime); + curmonth = locTime.tm_mon + 1; + curyear = locTime.tm_year + 1900; + + // start at 1/1/2000 + fileDate = 0; + buildArchiveFileName (srcDir, fileDate, sourceName, &year, &month); + buildArchiveFileName (destDir, fileDate, destName, &dummyYear, &dummyMonth); + + while (!done) + { + if ((year > curyear) || (year == curyear && month > curmonth)) + { + // we're done! + done = TRUE; + continue; + } + + if (stat (sourceName, &fileStatus) == -1) + { + // move on to the next month + incrementArchiveFileName (sourceName, sourceName, &year, &month); + incrementArchiveFileName (destName, destName, &dummyYear, &dummyMonth); + continue; + } + + // if we are here, we have a source file to work with + inFile = fopen (sourceName, "r"); + if (inFile == NULL) + { + printf ("Problem opening %s\n", sourceName); + incrementArchiveFileName (sourceName, sourceName, &year, &month); + incrementArchiveFileName (destName, destName, &dummyYear, &dummyMonth); + continue; + } + outFile = fopen (destName, "w"); + if (outFile == NULL) + { + printf ("Problem opening %s\n", destName); + fclose (inFile); + incrementArchiveFileName (sourceName, sourceName, &year, &month); + incrementArchiveFileName (destName, destName, &dummyYear, &dummyMonth); + continue; + } + + // now do the dirty work + if (readArcHeader (inFile, &Header) == ERROR) + { + printf ("Source File %s skipped\n", sourceName); + fclose (outFile); + unlink (destName); + fclose (inFile); + incrementArchiveFileName (sourceName, sourceName, &year, &month); + incrementArchiveFileName (destName, destName, &dummyYear, &dummyMonth); + continue; + } + + if (writeArcHeader (outFile, &Header) == ERROR) + { + printf ("Write error on %s - %s skipped\n", destName, sourceName); + fclose (outFile); + unlink (destName); + fclose (inFile); + incrementArchiveFileName (sourceName, sourceName, &year, &month); + incrementArchiveFileName (destName, destName, &dummyYear, &dummyMonth); + continue; + } + + // process each day + for (i = 1; i <= 31; i ++) + { + convertDayRecords (inFile, outFile, &hostHeader, i); + } + + fclose (outFile); + fclose (inFile); + incrementArchiveFileName (sourceName, sourceName, &year, &month); + incrementArchiveFileName (destName, destName, &dummyYear, &dummyMonth); + } + + return OK; +} + diff --git a/utilities/wvutilities.h b/utilities/wvutilities.h new file mode 100644 index 0000000..604ce93 --- /dev/null +++ b/utilities/wvutilities.h @@ -0,0 +1,55 @@ +#ifndef INC_wvutilitiesh +#define INC_wvutilitiesh +/*--------------------------------------------------------------------------- + + FILENAME: + wvutilities.h + + PURPOSE: + Provide the wview utility program common definitions. + + REVISION HISTORY: + Date Engineer Revision Remarks + 02/26/2006 M.S. Teel 0 Original + + NOTES: + + + LICENSE: + Copyright (c) 2006, Mark S. Teel (mark@teel.ws) + + This source code is released for free distribution under the terms + of the GNU General Public License. + +----------------------------------------------------------------------------*/ + +// System include files +#include +#include +#include +#include +#include +#include +#include +#include + +// Library include files + +// Local include files +#include +#include +#include +#include +#include + + +// data defs + + +// API function prototypes + +// Convert the endianness of WLK files as given by 'doLE2BE' +extern int wvuConvertWLKFiles (char *srcDir, char *destDir, int doLE2BE); + +#endif + diff --git a/wview-Debian-Quick-Start.html b/wview-Debian-Quick-Start.html new file mode 100755 index 0000000..d3ce921 --- /dev/null +++ b/wview-Debian-Quick-Start.html @@ -0,0 +1,38 @@ + + + +wview Debian Quick Start Guide + + + + + +

wview Debian Quick Start Guide

+

Oct. 4, 2009

+ +

+

Purpose

+ This guide will provide the basic procedure for a "standard" wview install on + a debian or derivative system using the "wview-install-debian" script. + Detailed description of the steps, advanced configuration of features and + troubleshooting tips are found in the + wview User Manual. +

+ +

+

Procedure

+
    +
  • Obtain the "wview-install-debian" script:
    +
      +
    • Download from + Sourceforge: [wget http://sourceforge.net/projects/wview/files/wview/wview-install-debian/download]
    • +
    • --OR--
    • +
    • Extract the wview distribution [tar zxvf wview-x.y.z.tar.gz] and locate the + script in the "scripts" subdirectory of the distro.
    • +

    +
  • Install wview and all prerequisites using the script: [sudo ./wview-install-debian]
  • +

    + + + + diff --git a/wview-Old-User-Manual.html b/wview-Old-User-Manual.html new file mode 100755 index 0000000..95f9d02 --- /dev/null +++ b/wview-Old-User-Manual.html @@ -0,0 +1,3603 @@ + + + +wview User Manual + + + + + +

    wview User Manual

    +

    Nov. 12, 2009

    + +

    +

    Contents


    +1. Overview
    +2. Distribution Contents
    +3. Prerequisites
    +4. Build and Install
    +5. Cross Compiling
    +6. Configuration
    +7. Weather Station Configuration
    +7.1 Station Simulator
    +7.2 Davis Vantage Pro/Pro2 Console
    +7.3 Vaisala WXT510
    +7.4 La Crosse WS-2300 Series
    +7.5 Oregon Scientific WMR918 Series
    +8. Metric Units and Internationalization
    +9. Run Environment Description
    +10. Configuration Files Description
    +11. Using a MySQL or PostgreSQL Server to Store Data
    +12. Setting Up FTP Transfers
    +13. Alarms and wview As a Data Feed Engine
    +14. Secure File Transfer (rsync/ssh)
    +15. Miscellaneous
    +16. CWOP - Submitting Your Data to NOAA and the CWOP System
    +17. Wunderground/Weatherforyou - Submitting Your Data to Weather Underground and/or Weatherforyou
    +18. AWEKAS - Providing Your Data to Awekas
    +19. Using a Linksys NSLU2 as the wview Host
    +20. Porting New Stations To wview
    +21. Sensor Calibration
    +22. Data Stored in SQLite3 (And How to Use It)
    +23. wviewmgmt - The New Way to Manage wview
    +24. Troubleshooting

    +

    + + + +
    +

    1. Overview

    +wview is a collection of linux/unix daemons which interface with a supported +weather station to retrieve archive records (if generated by the station) and +current conditions. The stations currently supported are:
    +Davis Vantage Pro/Pro2
    +Vaisala WXT510
    +La Crosse WS-23XX
    +Oregon Scientific WMR9X8/WMR928N
    +
    +If the station does not generate archive records internally, wview will auto-generate +archive records based on the sensor readings collected for that interval. The +archive records are stored in the Davis WLK file format (thus Weatherlink-generated +archive files can often be used by wview). At a configurable interval, wview will +utilize the archive history and current conditions to generate weather images +(buckets, dials and graphs) and HTML web pages based on user-configurable HTML +templates.

    + +Features:
    +
      +
    • 24x7x365 reliability.
    • +
    • Fast image and HTML/XML file generation.
    • +
    • Non-GUI, headless, lightweight (size and resources).
    • +
    • Embeddable - can be deployed on low-power embedded systems such as the Linksys + NSLU2.
    • +
    • Multi-Lingual - HTML/XML templates, labels and text.
    • +
    • US (Imperial) or Metric Units - can be easily configured for metric or US units + of measure.
    • +
    • SQLite Archive Storage - archive data is stored in an SQLite3 relational database.
    • +
    • Remote Upload - web pages and images can be transferred to a remote web server + via an ftp or secure ssh process included with wview.
    • +
    • Alarms - the wview alarm daemon wvalarmd can be enabled to deliver current + conditions to TCP socket clients as a near real-time data feed engine. wvalarmd + can also be configured to function as a weather data alarm generator to user + specified scripts or binaries.
    • +
    • CWOP - can be configured to submit data to CWOP.
    • +
    • Wunderground - can be configured to submit data to Wunderground.
    • +
    • Awekas - can be configured to submit data to Awekas.
    • +
    • Weatherforyou - can be configured to submit data to Weatherforyou.
    • +
    • RSS Feeds - processes XML template files and includes a default weather data + RSS feed template.
    • +
    + +
    + +
    +

    2. Distribution Contents

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + /stations/common + + Source directory for the station daemon +
    + /stations/VantagePro + + Source directory for the Davis Vantage Pro/Pro2 interface +
    + /stations/Simulator + + Source directory for the station simulator +
    + /stations/WXT510 + + Source directory for the Vaisala WXT510 interface +
    + /stations/WS-2300 + + Source directory for the La Crosse WS-23XX interface +
    + /stations/WMR918 + + Source directory for the Oregon Scientific WMR9X8 interface +
    + /htmlgenerator + + Source directory for the file generator +
    + /common + + Common source and build files +
    + /wviewmgmt + + Source directory for the wview management web site +
    + /examples/conf + + Example config files (see description below) +
    + /examples/html/classic + + Example HTML template files and tag description file parameters.txt + (see description below) +
    + /examples/html/chrome + + Example HTML template files and tag description file parameters.txt + (see description below) +
    + /utilities/archive-be2le + + Source directory for the wview archive directory Big-Endian to + Little-Endian command line convertor +
    + /utilities/archive-le2be + + Source directory for the wview archive directory Little-Endian to + Big-Endian command line convertor +
    + /utilities/wlk2sqlite + + Source directory for the wview WLK to SQLite3 archive convertor +
    + /utilities/sqlite2wlk + + Source directory for the wview SQLite3 to WLK archive convertor +
    + /configure + + build configuration script to be executed before building wview +
    + /cross-compile + + Contains example configure scripts for cross compilation. libz, libpng, + libgd, librad and wview scripts are included (and should be built in that + order). Use these scripts instead of "./configure" to configure these + libraries for cross compilation. These scripts configure for arm-linux + targets but could easily be edited for a different target. +
    + /scripts + + Contains example helper scripts for wview installation and update. +
    + /ChangeLog + + The release notes file +
    + /wview-User-Manual.html + + This file, the User Manual +
    + /wview-Quick-Start.html + + Quick Start Guide +
    + /wview-Quick-Start-MacOSX.html + + MacOSX-specific Quick Start Guide +
    + /wview-Quick-Start-Debian.html + + Debian-specific Quick Start Guide +
    + /UPGRADE + + Notes on upgrading from previous versions +
    + +

    + +
    +

    3. Prerequisites

    +

    Libraries

    +
    +  Note: Some of these libraries may be represented as "-dev" or similar in your 
    +        package management utility, i.e., "libcurl-dev". If they are, you should install
    +        the dev version in order to get the development libraries that radlib/wview needs.
    +
    +
      +
    • + libz - compression library +
    • +
    • + libpng - png image library +
    • +
    • + libreadline5-dev - readline library +
    • +
    • + gawk - gnu pattern processing language +
    • +
    • + libsqlite3 - SQLite3 database run time library - make sure + the older version (2.x.y) is not installed - it may be named libsqlite0 + (debian, kubuntu) or some other package name - you can only + have SQLite3 libraries and development environment installed +
    • +
    • + libsqlite3-dev - SQLite3 database development library - + make sure the older version (2.x.y) is not installed - it may be + named libsqlite0-dev (debian, kubuntu) or some other package name - you can only + have SQLite3 libraries and development environment installed +
    • +
    • + sqlite3 - SQLite3 database command line utility +
    • +
    • + libgd2 - graphics drawing library - may require installation - + http://www.boutell.com/gd/ +
    • +
    • + radlib - rapid application development library - + must be installed with SQLite support (--enable-sqlite) - + http://www.radlib.teel.ws +
    • +
    • + libssl - secure socket library - optional, required for libcurl +
    • +
    • + libcurl - "C" URL HTTP library - optional, required for Weather Underground or Weatherforyou - + http://curl.haxx.se/libcurl/ +
    • +
    • + sendmail - email transfer agent - optional, required for email alerts +
    • +
    • + sendEmail - command line email sending utility - optional, required for email alerts +
    • +
    • + mysql-client - MySQL client utilities (including mysqlimport) - optional, required for MySQL exports +
    • +
    +

    System

    +
      +
    • + ntp/xntp - Network Time Protocol - a configured and functional NTP + daemon should be enabled on the host system to keep system and weather + station time accurate and in sync +
    • +
    • + apache - in order to serve your weather site to the world, + an http server is required - other http servers will work too. If you are + going to export your site to another server or to your ISP account site, + then an http server is not required on the wview server. +
    • +
    • + php5 and php5-sqlite3 - optionally needed for browser-based + configuration (requires apache or similar) +
    • +
    • + Serial/Ethernet/USB port - an available interface port + is required to connect to the Weather Station

      +
        +
      • Serial
      • +
      • Ethernet - a terminal server or ethernet to serial server is required - + set up as decribed above for the serial port and configured in transparent + data mode (no control character processing) with no packet delimiter. + wview allows you to specify the hostname and port of your serial server. + The Neteon GW21E, Lantronix MSS1-T + and Xyplex Maxserver 1600 have been confirmed to work with wview. See + the Miscellaneous section for my + configuration notes on the GW21E and the MSS1-T.
      • +
      • USB
      • +
      +
    • +
    +

    Weather Station - one of the following:

    +
      +
    • + Vantage Pro or Vantage Pro2 Weather Station with Console
      +
        +
      • + WeatherLink for Vantage Pro or Pro2, Windows, Serial or USB - + the windows software is not required, but this is the only way to obtain the + serial or USB data logger +
      • +
      +
    • +
    • + Vaisala WXT510
      +
    • +
    • + La Crosse WS-2300 Series
      +
    • +
    • + Oregon Scientific WMR918/928N/968 Series
      +
    • +
    • + Station Simulator (for demos and testing)
      +
    • +
    • + +
    • +
    + +

    + +
    +

    4. Build and Install

    +
    +   Select one of the following methods based on your OS/distribution.
    +
    +4.1  Debian and Derivatives (ubuntu, kubuntu, xubuntu)
    +
    +     Use the scripts/wview-install-debian script. It is also available at:
    +     wview-install-debian
    +
    +     It will install all prerequisites (via apt-get) and the latest wview.
    +
    +4.2  Mac OSX >= 10.4
    +
    +4.2.1  Install XCode for your OSX version: 
    +       Download XCode
    +
    +4.2.2  Install MacPorts: Download MacPorts
    +
    +4.2.3  Execute the following from the command line:
    +       > sudo port -d selfupdate
    +       > sudo port install wview
    +
    +       This will install all prerequisites and wview.
    +
    +4.3  Manual Installation
    +
    +4.3.1  Extract in the location [wview_bld_path] of your choosing:
    +       > cd [wview_bld_path]
    +       > tar zxvf wview-x.y.z.tar.gz
    +
    +4.3.2  Change directory to the wview root source directory:
    +       > cd [wview_bld_path/wview-x.y.z]
    +
    +4.3.3  Run the configure script to create the build files for your platform:
    +
    +     Note: If the environment variable HTTP_DOC_ROOT is defined prior to 
    +           invocation of the configure script, the install target will 
    +           install wviewmgmt (The Web Management Tool) to the http document 
    +           root specified.
    +
    +     Note: [station_enable_str] refers to the appropriate enable string in the
    +           list of stations in the previous section *for your station*.
    +
    +     => Possible Configuration Arguments:
    +
    +     [station_enable_str] - one of:
    +         [none] --enable-station-vpro is default
    +         --enable-station-vpro
    +         --enable-station-wxt510
    +         --enable-station-ws2300
    +         --enable-station-wmr918
    +         --enable-station-sim
    +     
    +     Optional HTTP Services Support - one of:
    +         [none] No Wunderground/Weatherforyou support (default)
    +         --enable-http
    +
    +
    +     => Examples:
    +
    +     [(default)]
    +       > HTTP_DOC_ROOT=/var/www ./configure [station_enable_str]
    +
    +     [Wunderground]
    +       > HTTP_DOC_ROOT=/var/www/weather ./configure [station_enable_str] --enable-http
    +
    +
    +     => Note:
    +
    +     Execute "./configure --help" to see user configurable options. Be careful
    +     changing any of the prefix values, this has an effect on wview 
    +     configuration.
    +
    +4.3.4  Build:
    +       > make
    +
    +4.3.5  Install:
    +       > sudo make install
    +    
    +Note: The following build targets are available:
    +    make                - default target builds all source
    +    make clean          - deletes binaries and object files
    +    make install        - makes "default" then installs the binaries to 
    +                          ${exec-prefix}/bin (usually /usr/local/bin); copies 
    +                          example config files and run environment (from .../bin) 
    +                          to $prefix/etc/wview and $prefix/var/wview respectively, 
    +                          preserving existing data files and configuration if they
    +                          already exist
    +
    +4.3.6  Configure to run at system boot (if new install):
    +     Select the appropriate wview run script - 
    +     wview-x.y.z/examples/SuSE|RedHat|FreeBSD|FedoraCore|Debian|NSLU2 (based on your OS/distro)
    +      
    +     Copy it to the proper location for your system:
    +         SuSE:       cp wview-x.y.z/examples/SuSE/wview /etc/init.d
    +         FreeBSD:    cp wview-x.y.z/examples/FreeBSD/wview /etc/rc.d
    +         FedoraCore: cp wview-x.y.z/examples/FedoraCore/wview /etc/rc.d/init.d
    +         Debian:     cp wview-x.y.z/examples/Debian/wview /etc/init.d
    +   
    +     Make sure it is executable:
    +         SuSE:       chmod +x /etc/init.d/wview
    +         FreeBSD:    chmod +x /etc/rc.d/wview
    +         FedoraCore: chmod +x /etc/rc.d/init.d/wview
    +         Debian:     chmod +x /etc/init.d/wview
    +       
    +     Create a symbolic link in the runlevel directory you boot your server:
    +
    +         Fedora/Redhat to boot into runlevel 3: 
    +             > cd /etc/rc.d/rc3.d
    +             > ln -s ../init.d/wview S98wview
    +    
    +         Debian/ubuntu:
    +             > update-rc.d wview defaults 99
    +    
    +         Other distros will be some variation of this.
    +
    +         wview will now be started when the system is booted.
    +         Note: You can start/stop wview using this run script:
    +             > /etc/init.d/wview start   *or*
    +             > /etc/init.d/wview stop
    +
    +     Run Script Notes:
    +     
    +     1) These are example scripts - ergo their location in the distro under the 
    +        examples directory. Don't be afraid to edit the appropriate one for your 
    +        system to your configuration and install location.
    +
    +     2) Whatever "--prefix" you select during the configure step (default = 
    +        /usr/local), the wview start scripts are automatically updated to include
    +        the proper prefix for binary file and run directories.
    +
    +     3) You should use the same "--prefix=xxx" specification when configuring 
    +        radlib and wview. Further, this should be the default install location 
    +        for libraries and library headers in your system - i.e., prefix/lib and 
    +        prefix/include for libgd2.a, libpng.a, libz.a and gd.h respectively. Do 
    +        not select a non-default prefix (/usr/local) randomly.
    +
    +4.3.7  Add radlib location to shared library cache:
    +     If you get errors similar to:
    +        /usr/local/bin/wviewd: error while loading shared libraries: 
    +        librad.so.0: cannot open shared object file: No such file or directory
    +     you need to either copy the latest wview start script from the distro
    +     (examples//wview) to your start script location (see the 
    +     preceeding section), or add the radlib shared library location 
    +     (/usr/local/lib) to your shared library cache (see /etc/ld.so.conf) and run
    +     ldconfig.
    +
    + +

    + +
    +

    5. Cross Compiling

    +
    +See the scripts in /cross-compile for "./configure" alternatives.
    +This directory contains example configure scripts for cross compilation. libz,
    +libpng, libgd, librad and wview scripts are included (and should be built in 
    +that order). Use these scripts instead of "./configure" to configure these 
    +libraries for cross compilation. These scripts configure for arm-linux 
    +targets but can be edited for other targets.
    +
    +Note: The config-radlib-arm-linux and config-wview-arm-linux scripts will accept 
    +      up to 3 additional configure options.
    +
    +The general build sequence is:
    +
    +> ./config-[pkgname]-arm-linux
    +> make
    +[become root]
    +> make install
    +
    +All libs and applications are installed in the toolchain root, defined in the 
    +config scripts.
    +
    +The general build order is:
    +
    +libz
    +libpng
    +libgd2
    +libreadline5
    +libsqlite
    +librad
    +wview
    +
    + +

    + +
    +

    6. Configuration

    +
    +Note: See the appropriate station configuration section  7. Weather Station Configuration
    +      for a procedure to configure your weather station prior to running wview for 
    +      the first time.
    +
    +Note: See the Configuration Files Description and Run Environment Description 
    +      sections for detailed information concerning files and directories mentioned here.
    +
    +6.1  (Mandatory) Read the distro (or online) version of the file UPGRADE.
    +
    +6.2  (Mandatory) Configure wview
    +
    +     Use one of the following methods:
    +
    +6.2.1  Web Access ($distro/wviewmgmt)
    +       The wview SQLite3 configuration database can be updated using a browser and 
    +       hosting an http server on the wview server. The directory $distro/wviewmgmt
    +       provides a web interface for wview configuration. The default password is 
    +       "wview". See the section on the wviewmgmt Manager for setup and use details.
    +
    +6.2.2  wviewconfig 
    +       The wview SQLite3 configuration database can be updated using the configure 
    +       script "wviewconfig". This script asks questions interactively then updates 
    +       (backing up the existing file) the file $prefix/etc/wview/wview-conf.sdb based 
    +       on the answers. It uses your previous settings as the default selections where 
    +       appropriate. It will also allow interactive enabling/disabling of optional wview 
    +       capabilities such as alarms or file transfer via ftp or ssh. See the relevant 
    +       sections of this manual for details concerning configuration of the optional 
    +       capabilities.
    +       wviewconfig can be safely used for existing installs. It uses the current 
    +       settings as the default choices, so you don't have to remember what your 
    +       prior settings were. It will also allow the configuration of new wview 
    +       optional capabilities while remaining backward compatible with all versions 
    +       of wview since the 1.0.0 release.
    +
    +6.3  (Mandatory) Run the wviewhtmlconfig Script
    +
    +     Customizing HTML Templates For Your Site
    +
    +     Note: Changing HTML templates in $prefix/etc/wview/html does not require a restart
    +           of wview. The changes you make will take effect at the next htmlgend
    +           generation cycle.
    +     Note: Changing the config files images.conf, images-user.conf,
    +           html-templates.conf and (if supported) forecast.conf does not require 
    +           a restart of wview (as of version 3.1.0), but does require a HUP signal 
    +           to be sent to htmlgend to cause these files to be re-read. Do this as
    +           follows (this will also toggle log verbosity):
    +
    +               sudo kill -s HUP `cat $prefix/var/wview/htmlgend.pid`
    +
    +     As of version 3.5.0, a new configuration script for template setup has been
    +     added: wviewhtmlconfig.  You should run this first to configure which site
    +     skin you want to use and to setup the default imperial or metric graphics.
    +     To run it (as root) enter "wviewhtmlconfig" at the command line, it is interactive
    +     and will save your old $prefix/etc/wview/html directory to $prefix/etc/wview/html-DATE.TIME.
    +     As wview users contribute new skins they will be added to the wview distribution
    +     and supported by the wviewhtmlconfig script.
    +
    +     HTML template files (in $prefix/etc/wview/html) should be customized to your language and 
    +     your design preferences. The configuration file html-templates.conf specifies the 
    +     template files to be used for generation. You may add or remove from this list as 
    +     needed.
    +	 
    +     Edit copies placed in $prefix/etc/wview/html directory, either by use of the "wviewhtmlconfig" 
    +     script or manually copied for upgrades.
    +     
    +     The HTML tags "!--stationCity--" and "!--stationState--" should not be removed 
    +     from template files for which you want the station location included. wview will 
    +     replace these tags with the values you specify for "STATION_CITY" and "STATION_STATE" 
    +     in $prefix/etc/wview/wview-conf.sdb.
    +     
    +     The default wview "skin" is provided in the distribution at:
    +     examples/html/classic. For information on creating your own skin, see the file
    +     "examples/html/Template-Skins-HOWTO.txt".
    +     
    +     The default configuration supports different homepage templates based on day or night. 
    +     These templates are "index-day.htx" and "index-night.htx" in 
    +     $prefix/etc/wview/html-templates.conf. If you want one standard homepage template INSTEAD 
    +     of day/night templates, uncomment "index.htx" and comment the day/night templates
    +     in the $prefix/etc/wview/html-templates.conf file. See the example html-templates.conf file 
    +     in the distro for more details. You may either create soft links to the day and 
    +     night templates you want to use in $prefix/etc/wview/html 
    +     (ln -s index-clouds.htx index-day.htx), or copy the files you want to 
    +     index-day.htx and index-night.htx.
    +     
    +     There is now support for XML file generation. XML templates should be named
    +     "*.xtx" and placed in $prefix/etc/wview/html. An example RSS feed template is 
    +     included in the distro: examples/html/wxrss.xtx. An href to it is included
    +     in all home page template examples so the RSS feed may be discovered while 
    +     visiting your home page. wxrss.xtx should be edited for your site when placed 
    +     in $prefix/etc/wview/html either manually or via "make install".
    +     
    +     There is now support for "generic" template file types. For any template
    +     file named "example.[ext]x" and listed in html-templates.conf, wview will 
    +     generate a file named "example.[ext]". For example, "myscript.phpx" listed
    +     in html-templates.conf and found in $prefix/var/wview/html will have all wview 
    +     tags replaced and the resulting file will be named "myscript.php". Please 
    +     note that if you are using FTP to transfer your files you will need to add 
    +     rules for any new extensions you want to support in the wview-conf.sdb database.
    +
    +     There is support for two new html tags - forecast rule and forecast icon.
    +     The new tags are !--forecastRule-- and !--forecastIcon-- (brackets omitted) 
    +     respectively.
    +     The configuration file forecast.conf allows the user to define the icon 
    +     files and text associated with the forecast rules. See the example 
    +     forecast.conf file for details. The icon tag will be replaced by an image 
    +     html construct pointing at the appropriate icon image file. The rule tag will 
    +     be replaced by the corresponding text string defined in forecast.conf. 
    +     Because these capabilities require 35 KB or more of memory (and I think the 
    +     VP forecasts are hokey), they are disabled by default.
    +
    +     There is now support for html template macro file inclusion in html template
    +     files. The new meta-tag is !--include filename.xxx-- (brackets omitted). Any
    +     template macro file that is to be included in one or more html templates should
    +     be listed BEFORE any templates including it in the $prefix/etc/wview/html-templates.conf
    +     configuration file. There is no restriction on the levels of inclusion, just be 
    +     sure you specify macro templates early in the html-templates.conf file.
    +     
    +     Note that you can create any page template design you like - the file 
    +     examples/html/parameterlist.txt contains a list of all html tags supported by wview. 
    +     These tags are replaced by the actual data when the html page is generated by wview.
    +
    +6.4  (New Installs) Existing Weatherlink Archive Files
    +
    +     If you have existing archive files (*.wlk) that you want to import, use the new
    +     wlk2sqlite utility to import all archive records into the SQLite3 database
    +     prior to starting wview. It will find them and use them for historical charts 
    +     and NOAA reports. Be sure they were collected using the same archive interval 
    +     that you are going to use for your VP with wview. See the section 
    +     Preparing a New Vantage Pro Console for more details.
    +     
    +     Note: If you need to convert archive files from big endian to little endian,
    +           use the arc_be2le utility (specifying source and destination directories).
    +           This will convert all WLK files in the source 
    +           directory and place them in the destination directory specified. Folks
    +           moving from a PPC-based Mac to a PC host (for example) would need to do 
    +           this conversion on their existing archive directory.
    +     Note: If you need to convert archive files from little endian to big endian,
    +           use the arc_le2be utility (specifying source and destination directories).
    +           This will convert all WLK files in the source 
    +           directory and place them in the destination directory specified. Folks
    +           moving from a PC host to an NSLU2 embedded host or a PPC-based Mac 
    +           (for example) would need to do this conversion on their existing archive 
    +           directory.
    +     Note: Execution of wlk2sqlite may require modifying the dynamic library load
    +           path, i.e.:
    +           >export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib
    +           You'll know you need this if trying to execute wlk2sqlite produces the 
    +           following message:
    +           *wlk2sqlite: error while loading shared libraries: librad.so.0: 
    +            cannot open shared object file: No such file or directory*
    +           ... or similar message.
    +
    +6.5  (Optional) Alarms or Weather Data Feeds
    +
    +     If weather data alarm processing and/or TCP stream socket server for data 
    +     feeds is desired, edit $prefix/etc/wview/wview-conf.sdb - changing to fit your requirements.
    +     See Alarms and wview As a Data Feed Engine for details.
    +
    +6.6  (Optional) FTP File Transfer
    +
    +     If ftp of files to a remote host is desired, edit the wview configuration database
    +     $prefix/etc/wview/wview-conf.sdb - changing to fit your environment.
    +     See Setting Up FTP Transfers for details.
    +
    +6.7  (Optional) Secure rsync Remote Synchronization
    +
    +     If secure rsync synchronization of files to a remote host is desired, edit the 
    +     wview configuration database $prefix/etc/wview/wview-conf.sdb - changing to 
    +     fit your environment. 
    +     See Secure File Transfer (rsync/ssh) for details.
    +
    +6.8  Date Formats in Images and Generated Files
    +
    +     The format of the date in images and generated files can now be defined in 
    +     $prefix/etc/wview/wview-conf.sdb.
    +     Prior to version 3.2.2 the date format was fixed depending on whether US or metric units were used.
    +     Valid formats are those allowed by strftime(3).
    +
    +6.9  Pre/Post-Generation Scripts
    +
    +     The script file $prefix/etc/wview/pre-generate.sh (if it exists) will 
    +     be executed by the htmlgend daemon immediately before the generation of all images 
    +     and templates.  The script file $prefix/etc/wview/post-generate.sh (if it exists) will 
    +     be executed by the htmlgend daemon immediately following the generation of all images 
    +     and templates. This can be used to perform custom transformations of generated images 
    +     or files. It should NOT be used to perform network tasks such as FTP or other tasks
    +     which may block and interrupt execution of the htmlgend daemon.
    +     Note: Empty generation scripts should be renamed so wview does not execute them,
    +           which in rare cases can cause zombie processes.
    +
    + + +

    + +
    +

    7. Weather Station Configuration

    +
    +This chapter contains station-specific configuration instructions. 
    +
    +
    + + +
    +

    7.1 Station Simulator

    +
    +The Simulator station requires no physical weather station. It is a quick and 
    +easy way to demo wview. It also provides an excellent debug platform as cyclic 
    +data is generated. There is no station setup, just configure wview for the 
    +Simulator (--enable-station-sim) then choose Station Type *Simulator* when you run 
    +wviewconfig after build/install.
    +

    + + +
    +

    7.2 Davis Vantage Pro/Pro2 Console

    +7.2.1 Preparing a New Vantage Pro Console +
    +If you are installing a new VP console, there are a few initial settings 
    +that you need to set up so that your weather station and wview operate 
    +properly. Configuration of archive interval, station location (elevation, 
    +latitude and longitude) and your desired rain season start month must be
    +set up before you start wview for the first time. 
    +Below is a short description of each of these:
    +
    +Archive Interval  - this determines how often the VP console will generate
    +                    an archive record and store it in its internal memory.
    +                    These records are retrieved by wview from the console
    +                    and stored in the archive files (and the archive database,
    +                    if database archiving is enabled). The valid values are:
    +                    5, 10, 15, 30, 60 (minutes). Keep in mind that the 
    +                    shorter the archive interval, the more records that will be 
    +                    generated. More records means a shorter time span for 
    +                    internal storage in the VP console memory and larger archive
    +                    files on the wview server disk (and larger database tables 
    +                    if stored in a database). I use an archive interval of 5 
    +                    minutes. TO AVOID HAVING TO DELETE ALL OF YOUR ARCHIVE DATA
    +                    LATER IN ORDER TO CHANGE THE ARCHIVE INTERVAL, IT IS VERY
    +                    IMPORTANT THAT YOU MAKE THIS CHOICE CAREFULLY AND NOT 
    +                    CHANGE IT AFTER ARCHIVE DATA HAS BEEN STORED BY WVIEW.
    +                    This does NOT effect how often HTML files containing 
    +                    current conditions are generated or the up to the minute
    +                    values they contain, this is controlled by the configuration
    +                    value "HTMLGEN_GENERATE_INTERVAL" in wview-conf.sdb. It will effect
    +                    the granularity of your charts for the last 24 hours.
    +
    +Elevation         - this is given in feet above (or below) sea level. This is
    +                    the recommended way to calibrate your barometer.
    +
    +Lat/Long          - this describes the location of your weather station.
    +
    +Rain Season Start - this defines the first month of each year when yearly
    +                    rain totals will begin. Most people will use a value of
    +                    "1" here.
    +
    +In order to set these parameters in the VP console, the "vpinstall" script is
    +provided with the wview distribution. Note that all of these can be configured 
    +using the On-Screen VP console setup utility. After building and installing wview, 
    +the vpinstall script can be found in ${exec-prefix}/bin (usually /usr/local/bin). It 
    +is an interactive script which queries you for these initial values then uses the
    +vpconfig utility to commit them to your new VP console. Once completed, it is
    +advisable to wait 10 minutes or so before starting wview for the first time as
    +it takes the VP console a little while to "digest" the new settings, in
    +particular for the barometer.
    +
    + +
    +7.2.2 Vantage Pro Plus Extended Sensor Support +
    +7.2.2.1 Overview
    +
    +      Note: If you do not have a Vantage Pro Plus or Vantage Pro2 Plus and 
    +            you haven't added additional sensors ($$) to your VP, then there 
    +            is no reason to enable Plus extended data in wview-conf.sdb or to 
    +            enable extended sensor generation in images.conf. You will only be 
    +            wasting CPU cycles as the extended sensors will not be populated 
    +            with anything meaningful.
    +
    +      The Vantage Pro Plus adds Solar Radiation, UV and Evapotranspiration (ET) 
    +      sensors with the potential to add other sensors. wview provides support 
    +      for historical charts (day, month and year) for the following VP Plus 
    +      sensors:
    +
    +        Solar Radiation
    +        UV
    +        ET
    +        LeafTemp1
    +        LeafTemp2
    +        LeafWetness1
    +        LeafWetness2
    +        SoilTemp1
    +        SoilTemp2
    +        SoilTemp3
    +        SoilTemp4
    +        ExtraHumidity1
    +        ExtraHumidity2
    +        ExtraTemp1
    +        ExtraTemp2
    +        ExtraTemp3
    +        SoilMoisture1
    +        SoilMoisture2
    +        SoilMoisture3
    +        SoilMoisture4
    +
    +      wview also can generate current condition buckets for Radiation, 
    +      UV and ET.
    +
    +7.2.2.2 Configuration
    +
    +--------  wview-conf.sdb
    +
    +      --  Update $prefix/etc/wview/wview-conf.sdb (see configuration instructions)
    +      --  Modify the following parameter so it is enabled (yes):
    +          HTMLGEN_EXTENDED_DATA
    +
    +--------  images.conf
    +
    +      --  If this is a new install, skip to 12.2.2.2. To upgrade, you will need 
    +          to copy the new extended data image definitions from the distribution 
    +          example file images.conf found in .../wview-x.y.z/examples/conf. 
    +          The new definitions begin with the heading:
    +          "#################  V P  P L U S  D A T A  B U C K E T S  #################"
    +          Copy all definitions below this heading to your existing images.conf 
    +          file.
    +      --  Edit $prefix/etc/wview/images.conf
    +      --  Starting at the VP Plus Data Buckets header, enable all desired sensor
    +          images by removing all "#" characters from the beginning of the line.
    +          Remember, if you don't have the physical sensor installed on your 
    +          station, you should not enable the corresponding images for it - it 
    +          only wastes resources. At a minimum, it is safe to enable Radiation, 
    +          UV and ET images if you have a Plus station.
    +      --  For each new image you have enabled, translate or edit the title and 
    +          units strings as needed (no spaces in the units label).
    +      --  Save and Exit the editor
    +
    +--------  html-templates.conf
    +
    +      --  Edit $prefix/etc/wview/html-templates.conf
    +      --  Replace the corresponding old references with:
    +    
    +          almanac_Plus.htx
    +          Current_Plus.htx
    +          Daily_Plus.htx
    +          Monthly_Plus.htx
    +          Yearly_Plus.htx
    +        
    +          Leave index.htx unchanged.
    +      --  Save and Exit the editor
    +
    +--------  index.htx
    +
    +      --  Copy the Plus index.htx template over the standard file:
    +          cp $prefix/etc/wview/html/index_Plus.htx $prefix/etc/wview/html/index.htx
    +        
    +      --  Edit the index.htx file as usual to customize for your station,
    +          language and content preferences.
    +
    +--------  Current_Plus.htx
    +
    +      --  If upgrading, copy the example Current_Plus.htx file from
    +          the distribution to your wview config tree:
    +          cp wview-x.y.z/examples/html/Current_Plus.htx $prefix/etc/wview/html
    +      --  Edit $prefix/etc/wview/html/Current_Plus.htx as usual to customize 
    +          for your station, language and content preferences.
    +      --  Save and Exit the editor
    +
    +--------  Daily_Plus.htx
    +
    +      --  If upgrading, copy the example Daily_Plus.htx file from
    +          the distribution to your wview config tree:
    +          cp wview-x.y.z/examples/html/Daily_Plus.htx $prefix/etc/wview/html
    +      --  Edit $prefix/etc/wview/html/Daily_Plus.htx
    +      --  Delete all extended data image references that are NOT supported by
    +          your station. Find the comment lines:
    +          !-- ***** Extended Data Begin ***** --
    +          ...
    +            [extended sensor image references]
    +          ...
    +          !-- ***** Extended Data End ***** --
    +          and delete all non-supported sensor images. The extended sensor images
    +          remaining should match the "Day" images you enabled in images.conf.
    +      --  Edit $prefix/etc/wview/html/Daily_Plus.htx as usual to customize 
    +          for your station, language and content preferences.
    +      --  Save and Exit the editor
    +
    +--------  Monthly_Plus.htx
    +
    +      --  If upgrading, copy the example Monthly_Plus.htx file from
    +          the distribution to your wview config tree:
    +          cp wview-x.y.z/examples/html/Monthly_Plus.htx $prefix/etc/wview/html
    +      --  Edit $prefix/etc/wview/html/Monthly_Plus.htx
    +      --  Delete all extended data image references that are NOT supported by
    +          your station. Find the comment lines:
    +          !-- ***** Extended Data Begin ***** --
    +          ...
    +            [extended sensor image references]
    +          ...
    +          !-- ***** Extended Data End ***** --
    +          and delete all non-supported sensor images. The extended sensor images
    +          remaining should match the "Month" images you enabled in images.conf.
    +      --  Edit $prefix/etc/wview/html/Monthly_Plus.htx as usual to customize 
    +          for your station, language and content preferences.
    +      --  Save and Exit the editor
    +
    +--------  Yearly_Plus.htx
    +
    +      --  If upgrading, copy the example Yearly_Plus.htx file from
    +          the distribution to your wview config tree:
    +          cp wview-x.y.z/examples/html/Yearly_Plus.htx $prefix/etc/wview/html
    +      --  Edit $prefix/etc/wview/html/Yearly_Plus.htx
    +      --  Delete all extended data image references that are NOT supported by
    +          your station. Find the comment lines:
    +          !-- ***** Extended Data Begin ***** --
    +          ...
    +            [extended sensor image references]
    +          ...
    +          !-- ***** Extended Data End ***** --
    +          and delete all non-supported sensor images. The extended sensor images
    +          remaining should match the "Year" images you enabled in images.conf.
    +      --  Edit $prefix/etc/wview/html/Yearly_Plus.htx as usual to customize 
    +          for your station, language and content preferences.
    +      --  Save and Exit the editor
    +

    + +c +
    +

    7.3 Vaisala WXT510

    +7.3.1 Overview +
    +The Vaisala WXT510 Weather Transmitter is a semi-professional instrument 
    +about the size of a 2 liter bottle of Coke. Ultrasonic wind speed sensors, 
    +non-tipping rain/hail measurement and low power are a few of the nice features 
    +of this station. A very simple NMEA 0183 protocol implementation is provided 
    +to retrieve the readings. Choose Station Type *WXT510* when you run wviewconfig.
    +

    +7.3.2 Preparing a WXT510 For wview +
    +When you built wview for the WXT510 (configure option --enable-station-wxt510)
    +the wxt510config utility was built and installed as well. It simply autobauds 
    +the WXT510 until it finds the station's current comm settings then resets the
    +station to 19200-8-N-1. wview startup will do the remaining initialization of 
    +the station.
    +To run it, determine what serial device your station is connected to, then as 
    +root execute:
    +
    +    localhost> wxt510config [station_dev]
    +
    +where [station_dev] is something like /dev/ttyS0, /dev/cuaa0, /dev/ttyUSB0, etc.
    +
    +
    + + +
    +

    7.4 La Crosse WS-2300 Series

    +7.4.1 Overview +
    +The La Crosse WS-2300 Series (WS-2300/2308/2310/2315) Personal Weather Station 
    +is a low-cost consumer grade station. Although it is proud to be wireless, it is
    +best implemented "cabled" between the sensors and the console. When cabled, it
    +will update the console with new data every 8 seconds. When ran wirelessly, it 
    +updates on the order of minutes, which is nearly useless for our purposes.
    +Choose Station Type *WS-2300* when you run wviewconfig.
    +

    +7.4.2 Preparing a WS-2300 Series For wview +
    +There is no preparation required - just connect to your serial port and go!
    +

    +7.4.3 Notes +
    +- The La Crosse weather stations do not provide Rain Rate calculation, so wview
    +provides it for you. It is a very simple implementation which looks at the last 
    +10 minutes of rainfall and scales it up to one hour.
    +
    +- Choose Station Type *WS-2300* when you run wviewconfig.
    +
    +
    + + +
    +

    7.4 Oregon Scientific WMR918 Series

    +7.4.1 Overview +
    +The Oregon Scientific WMR918 Series (WMR918/968) Personal Weather Station 
    +is a low-cost consumer grade station.
    +Choose Station Type *WMR918* when you run wviewconfig.
    +

    +7.4.2 Oregon Scientific WMR918 Series For wview +
    +There is no preparation required - just connect to your serial port and go!
    +

    +7.4.3 Notes +
    +- The Oregon Scientific WMR918 weather stations do not provide Rain Rate calculation, 
    +so wview provides it for you. It is a very simple implementation which looks at the last 
    +10 minutes of rainfall and scales it up to one hour.
    +
    +- The WMR9X8 stations send sensor packets (usually corresponding to a sensor suite)
    +  periodically. wview requires one packet from each sensor suite before it can complete
    +  initialization. Thus, if your outside temperature transmitter is down or malfunctioning,
    +  wview will not function properly.
    +
    +- Choose Station Type *WMR918* when you run wviewconfig.
    +
    +
    + +

    + +
    +

    8. Metric Units and Internationalization

    +
    +A configuration parameter in wview-conf.sdb named "HTMLGEN_METRIC_UNITS" has been 
    +added that if set to "yes" will cause wview to output all images (buckets and 
    +charts) as well as all values for HTML tags in the metric equivalents. 
    +The conversions are:
    +
    +Temperature ........... Celsius
    +Barometer ............. Millibars
    +Wind Speed ............ Kilometers per hour
    +Rain .................. Centimeters
    +
    +images.conf can be edited to translate the English labels, titles, and units to 
    +any language. By editing this file and the HTML template files, any language can 
    +be supported by wview. For existing installations, if the "HTMLGEN_METRIC_UNITS" 
    +parameter is not found in wview-conf.sdb, wview will assume US units and no 
    +conversions will be done. 
    +In fact one can easily switch back and forth between US and metric units by 
    +toggling this configuration value and restarting wview. 
    +
    +NOTE:
    +All archive data in the SQLite3 database is still stored in US format - the 
    +conversions are only done for real time image and HTML file generation.
    +
    +
    + +

    + +
    +

    9. Run Environment Description

    +
    +$prefix/etc/wview - configuration files
    +
    +    *.conf                  - configuration files described below
    +    html/*.htx              - HTML template files to be customized for your 
    +                              site and language
    +    html/parameterlist.txt  - contains the list of all available HTML tags 
    +                              which can be placed in *.htx template files. 
    +                              These tags are replaced each time htmlgend 
    +                              generates new image and html files.
    +
    +$prefix/var/wview - run time data files
    +
    +    *.pid                   - run time pid files created/deleted by the wview
    +                              daemons. Do NOT alter or delete these files. They 
    +                              should only be present if the corresponding wview 
    +                              daemon is running.
    +    archive/wview-archive.sdb - SQLite 3 archive database file
    +    dev/*                   - FIFO device files created by wview - do NOT alter 
    +                              or delete them
    +    img/*                   - default destination for generated image and HTML
    +                              files - in particular if subsequent ftp/ssh of 
    +                              files is required
    +    noaa/noaa.dat           - NOAA data file
    +
    + +

    + +
    +

    10. Configuration Files Description

    +
    +Note: Most wview configuration is now stored in an SQLite3 database at:
    +      $prefix/etc/wview/wview-conf.sdb. Thus SQLite3 must be installed and radlib
    +      must be configured with "--enable-sqlite".
    +Note: There are now several ways to configure wview through the SQLite3 database:
    +      wviewconfig (command line interface), Web Server ($HTTP_DOC_ROOT/wviewmgmt/login.php),
    +      Firefox (SQLite Manager Add-On) and SQLite Database Browser (QT-based GUI
    +      application).
    +
    +1) wview-conf.sdb       - SQLite3 configuration database for wview.
    +                          Automatically updated by the wviewconfig script.
    +
    +2) wview-conf.sql       - default SQLite3 wview configuration database backup
    +                          file. Can be used with the sqlite3 ".read" command to
    +                          recreate the default database.
    +
    +3) images.conf          - defines which built-in images should be generated -
    +                          do NOT place user defined images in this file. This
    +                          file provides a list of the image files generated by
    +                          wview which can be used in HTML templates.
    +                          Requires manual editing by user.
    +
    +4) images-user.conf     - defines user-defined images to be generated - note
    +                          that the corresponding image generator must be 
    +                          provided in the ".../htmlgenerator/images-user.c"
    +                          source file and listed in the "user_generators"
    +                          jump table at the bottom of the source file. The
    +                          index within the jump table should be referenced
    +                          from the "images-user.conf" file. These files can be
    +                          saved from the source tree when upgrading and copied
    +                          back to preserve user image definitions.
    +                          Requires manual editing by user.
    +
    +5) html-templates.conf  - defines the html templates to be used for html
    +                          generation. User-defined templates may be listed
    +                          here so long as they utilize the tags as defined
    +                          in ".../examples/html/parameterlist.txt".
    +                          Requires manual editing by user.
    +
    +6) forecast.conf        - defines the icon files and forecast text strings to 
    +                          be associated with the VP forecast icon and forecast 
    +                          rule values. Icon image file examples are found in 
    +                          wview-x.y.z/bin/img and can be copied to your weather 
    +                          web site. If you change the defaults in forecast.conf,
    +                          you will need to copy your new icon files to your 
    +                          weather web site.
    +                          See wview-x.y.z/examples/html/parameterlist.txt to 
    +                          find all available html tags including the forecast 
    +                          icon and rule tags. If absent (or renamed), no 
    +                          forecast icon or rule text will be stored or 
    +                          substituted for the corresponding html tags. This is 
    +                          the default and the way to disable the forecast 
    +                          storage and processing.
    +                          Requires manual editing by user.
    +
    +7) arcrec-header.conf   - allows user configuration of the header written to 
    +                          each day's browser file. The default is English, but 
    +                          any language can be supported by changing the contents
    +                          of this file. Remember to keep column alignment in 
    +                          mind when changing this file. Total header size is 
    +                          limited to 1024 characters - this is the sum of all 
    +                          lines in arcrec-header.conf.
    +                          Requires manual editing by user.
    +
    +8) graphics.conf        - Allows configuration of graphic colors and other 
    +                          properties.
    +
    +Note: See the example config files for format descriptions.
    +
    + +

    + +
    +

    11. Using a MySQL or PostgreSQL Server to Store Data

    +
    +    MySQL and PostgreSQL storage of weather data is now accomplished through export
    +    scripts which are provided in the wview distribution. These scripts are run via
    +    CRON periodically to export data from the SQLite3 databases to the export databases.
    +
    +11.1  MySQL Installation and Setup
    +
    +11.1.1  Ensure you have a functioning MySQL server running either local to the
    +        wview server or remote. In either case, you will need an account on the
    +        MySQL server which has create and grant privileges.
    +
    +11.1.2  Prerequisites On the wview Server - be sure you have sqlite3 and mysqlimport
    +        installed on the wview server. Even if the MySQL server is remote, you need
    +        mysqlimport installed locally.
    +
    +11.1.3  Local Server:
    +
    +        Create the MySQL Database, tables and user:
    +        (Be sure you have set your MySQL account settings using wviewmgmt or wviewconfig
    +        before creating locally).
    +        > wview-mysql-export create 
    +          (This runs wview-mysql-create for you locally using wview-conf.sdb settings)
    +
    +11.1.4  Remote Server:
    +
    +        Copy the wview-mysql-create script to the remote server:
    +        > scp $exec_prefix/wview-mysql-create @:/downloads
    +          (copy wherever you like on the remote server)
    +          (wview-mysql-create can be found by executing: whereis wview-mysql-create)
    +
    +        Login to the remote server:
    +        > ssh @
    +
    +        Execute the create script (make sure the admin user has the right privileges):
    +        > wview-mysql-create help  (to see usage text)
    +        > wview-mysql-create     
    +          (admin_username can be the same as db_username)
    +
    +        Log out of the remote server:
    +        > exit
    +
    +11.2   PostgreSQL Installation and Setup
    +
    +       NOTE: The PostgreSQL user created when you setup PostgreSQL will 
    +             either be pgsql or postgres. postgres is used for this example. We
    +             will use the unix root account for all export activities so the
    +             create and export scripts should be run as root or from the root
    +             CRON table.
    +
    +11.2.1  Ensure you have a functioning PostgreSQL server running either local to the
    +        wview server or remote.
    +
    +11.2.2  Prerequisites On the wview Server - be sure you have psql installed on 
    +        the wview server. Even if the PostgreSQL server is remote, you need psql 
    +        installed locally.
    +
    +11.2.3  Create a PostgreSQL user for the unix root user:
    +
    +          Login to the PostgreSQL account:
    +          user@wview_server> su
    +          root@wview_server:# su postgres
    +          
    +          Create an administrative user based on the root unix account:
    +          postgres@wview_server:# createuser root
    +          
    +          Answer the question:
    +          Shall the new role be a superuser? (y/n) y
    +          
    +          Logout of the PostgreSQL account:
    +          postgres@wview_server:# exit
    +          root@wview_server:#
    +
    +11.2.4  Create the wview database:
    +
    +          Create the wview database:
    +          root@wview_server:# createdb wviewDB
    +
    +          Note: wviewDB should be the same value specified in wview-conf.sdb for 
    +          STATION_SQLDB_DB_NAME.
    +
    +11.2.5  Create the export tables and 'replace' triggers:
    +
    +          root@wview_server:# wview-pgsql-create wviewDB
    +          
    +
    +11.3  Test Run and Debug
    +
    +      The end game is to create a cron job which runs wview-mysql-export periodically
    +      (every 5 minutes seems reasonable although the export is surprisingly fast so
    +      1 minute periods are not out of the question). First, let's run it from the
    +      command line and confirm it is working.
    +      Note: If using PostgreSQL, substitute "wview-pgsql-export" for "wview-mysql-export".
    +
    +11.3.1  Execute with debugs enabled:
    +        > wview-mysql-export debug
    +
    +11.3.2  Examine the CSV export files:
    +        > cat $prefix/etc/wview/export/archive.csv
    +        > cat $prefix/etc/wview/export/outTemp.csv
    +        > cat $prefix/etc/wview/export/noaaHistory.csv
    +
    +        These will probably be large given this was the first export. They should
    +        contain all records up to the present time.
    +
    +11.3.3  Examine the MySQL tables:
    +        (login to the remote server if not local)
    +        > mysql --user= --password=
    +        mysql> use ;
    +        mysql> select count(*) from archive;
    +        (This should match the record count in your SQLite3 database)
    +        mysql> select * from windDir;
    +        (The 20 wind direction bins should display direction counts = 60/archive_interval
    +        for each row)
    +        mysql> select * from noaaHistory;
    +        (There should be one record for each day of archive data)
    +
    +11.3.4  Examine the Import Logs:
    +        > cat $prefix/etc/wview/export/mysql_import.log
    +
    +11.4  Production Setup
    +
    +11.4.1  Add to the root crontab on the wview server:
    +        > sudo crontab -e
    +        Add the following line and save the crontab:
    +        1,6,11,16,21,26,31,36,41,46,51,56 * * * * $exec_prefix/wview-mysql-export
    +        This runs the wview-mysql-export script every 5 minutes (1 minute past 
    +        archive record reception for 5 minute archive interval)
    +
    +        Example where $exec_prefix is "/usr/local/bin":
    +        1,6,11,16,21,26,31,36,41,46,51,56 * * * * /usr/local/bin/wview-mysql-export
    +
    +        Note: You must include a full path for the wview-mysql-export script in the
    +              cron table.
    +
    +11.4.2  Done - you can check the $prefix/etc/wview/export directory and the MySQL
    +        database to confirm the updates.
    +
    +11.5  Notes
    +
    +      The records imported into the MySQL database tables are set to replace any
    +      existing records with the same dateTime stamp. This is so we get the latest
    +      HILOW records for the current hour and so if better/more complete data is
    +      re-exported, it will take precedence over older data. You will notice in the
    +      debug log file $prefix/etc/wview/export/mysql_import.log that records are 
    +      deleted and inserted at incremental updates. This is the HILOW last record
    +      phenomenon.
    +
    +      If you need to cause a complete re-export of all data, delete the file
    +      $prefix/etc/wview/export/mysql_export_marker. The next time the wview-mysql-export
    +      script runs, all data will be exported (and replace existing data in the MySQL 
    +      database).
    +
    +11.6  PostGreSQL and FreeBSD Semaphore Resources
    +
    +      FreeBSD starts with a limited number of semaphores available for application 
    +      use. radlib/wview use some semaphores and so does PostgreSQL. Often the
    +      default number is not sufficient. Here is how to increase the number of
    +      semaphores available:
    +
    +      1) In /boot/loader.conf add the lines:
    +          kern.ipc.semmni="256"
    +          kern.ipc.semmns="512"
    +          kern.ipc.semmnu="256"
    +
    +      2) In /etc/sysctl.conf add these lines:
    +          kern.ipc.shmmax=536870912
    +          kern.ipc.semmap=256
    +          kern.ipc.shm_use_phys=1
    +          kern.ipc.shmall=131072 
    +
    +      3) Reboot
    +
    +
    + + +

    + +
    +

    12. Setting Up FTP Transfers

    +
    +12.1  tnftp
    +
    +   wview's ftp daemon requires the tnftp utility which is standard for all BSD
    +   distributions and some linux distributions such as SuSE. Others, such as 
    +   Fedora Core and Debian, have the vanilla FTP client which is almost useless.
    +   In this case you are going to have to replace the old FTP client with tnftp.
    +   
    +   To determine if you have tnftp installed, execute the following:
    +
    +    localhost:~> /usr/bin/ftp
    +    ftp> status
    +    Not connected.
    +    No proxy connection.
    +    Gate ftp: off, server (none), port ftpgate.
    +    Passive mode: on; fallback to active mode: on.
    +    Mode: ; Type: ; Form: ; Structure: .
    +    Verbose: on; Bell: off; Prompting: on; Globbing: on.
    +    Store unique: off; Receive unique: off.
    +    Preserve modification times: on.
    +    Case: off; CR stripping: on.
    +    Ntrans: off.
    +    Nmap: off.
    +    Hash mark printing: off; Mark count: 1024; Progress bar: on.
    +    Get transfer rate throttle: off; maximum: 0; increment 1024.
    +    Put transfer rate throttle: off; maximum: 0; increment 1024.
    +    Socket buffer sizes: send 32768, receive 65536.
    +    Use of PORT cmds: on.
    +    Use of EPSV/EPRT cmds for IPv4: on.
    +    Command line editing: on.
    +    Version: NetBSD-ftp 20040410  -OR-
    +    Version: tnftp                -OR-
    +    Version: lukemftp
    +    ftp> quit
    +    
    +    The key in this output is the Version line: NetBSD-ftp or tnftp or lukemftp is 
    +    what we are looking for. If you don't get similar output, you need to install tnftp.
    +
    +   To install tnftp, do the following:
    +   
    +   Debian/Ubuntu/Kubuntu:
    +   > sudo apt-get install tnftp
    +   And use /usr/bin/tnftp as your FTP binary.
    +
    +   Source Install:
    +   Download from here: ftp://ftp.netbsd.org/pub/NetBSD/misc/lukemftp/tnftp-20050625.tar.gz
    +
    +   Extract it: tar zxvf tnftp-20050625.tar.gz
    +    
    +   cd to the directory: cd tnftp-20050625
    +    
    +   Then execute the following as root (you can cut and paste these commands):
    +    ./configure
    +    make
    +    make install
    +    mv /usr/bin/ftp /usr/bin/ftp-old
    +    ln -s /usr/local/bin/ftp /usr/bin/ftp
    +    
    +    If /usr/kerberos/bin/ftp exists:
    +    
    +    mv /usr/kerberos/bin/ftp /usr/kerberos/bin/ftp-old
    +    ln -s /usr/local/bin/ftp /usr/kerberos/bin/ftp
    +
    +12.2  Remote Server Directory Setup
    +
    +   wviewftpd will place all files it transfers into the [conf_directory]
    +   directory under your ftp login directory on the remote server.
    +   [conf_directory] is the value of the "directory" configuration value in
    +   wview-conf.sdb.
    +
    +      localhost:~> telnet [remote_host]
    +      username: [remote_username]
    +      password: [remote_password]
    +      [remote_host]:~> mkdir -p [conf_directory]
    +      [remote_host]:~> mkdir -p [conf_directory]/NOAA
    +      [remote_host]:~> mkdir -p [conf_directory]/Archive
    +      [remote_host]:~> logout
    +
    +12.3  Move Static Files to Server
    +
    +   Certain files like background images and other support files are not 
    +   transferred by wviewftpd to conserve bandwidth, so you should place them on 
    +   the remote server manually before running wview with FTP enabled but after 
    +   having executed "make install" for new installs. The following command 
    +   is also of the same format as what wview uses, thus testing your ftp setup.
    +   Replace remote_username, remote_password and remote_host with the proper 
    +   values for your remote ftp host account. Do not add or remove spaces in this
    +   command!
    +
    +      cd $prefix/var/wview/img
    +      /usr/bin/ftp -iV -u ftp://remote_username:remote_password@remote_host/[conf_directory] *.*
    +
    +12.4  Configure wviewftpd
    +
    +   Edit $prefix/etc/wview/wview-conf.sdb as described earlier.
    +
    +12.5  Start wview
    +      [your_distro_start_script_location]/wview start
    +
    +12.6  Debug
    +
    +   If you are having any problems and wish to get more feedback concerning the tnftp 
    +   command line or stderr output, see the file wviewftp.debug.sh in the distro ftp
    +   directory.  It contains instructions for how to gain access to the tnftp command line
    +   being executed and the stderr output from it.
    +
    + + +

    + +
    +

    13. Alarms and wview As a Data Feed Engine

    +
    +13.1  Overview
    +   To implement wview alarm capability, a new daemon process has been added: 
    +   wvalarmd. Like the wviewftpd daemon, if the configuration database wview-conf.sdb
    +   has the "ENABLE_ALARMS" paremeter enabled when wvalarmd starts, it will exit and alarms 
    +   will be disabled. wvalarmd "registers" with the wviewd daemon to receive 
    +   LOOP data at an interval determined by the "STATION_PUSH_INTERVAL" configuration
    +   value for wviewd in wview-conf.sdb (the default is every 60 seconds). When 
    +   a new LOOP data packet is received by wvalarmd, it checks all defined alarms
    +   to determine if an alarm has been triggered. If so, the alarm abatement 
    +   counter is initialized and the alarm binary/script is invoked with the 
    +   following arguments:
    +
    +       argv[0]/$0         full path of binary/script being run
    +       argv[1]/$1         alarm type (see definitions below)
    +       argv[2]/$2         alarm threshold
    +       argv[3]/$3         value which triggered the alarm
    +       
    +   It is not hard to conclude the following:
    +   
    +       1) Multiple alarm scripts may be defined for the same LOOP data value.
    +       2) Multiple LOOP data values may use the same binary/script since the 
    +          alarm type, threshold and triggering value are all passed to the
    +          binary/script.
    +       3) wview alarms could be used as a data feed to another application
    +          which requires weather data updates, you could even run an 
    +          "html-less" wview that does nothing more than archive weather data
    +          and feed current values to another application via wvalarmd.
    +          
    +13.1.1  Alarm Abatement - to control the frequency for which an alarm 
    +        script/binary is invoked while the data type is exceeding the threshold,
    +        an alarm configuration parameter called Abatement is provided. This is 
    +        simply the number of seconds after an alarm triggers to "hold off" or 
    +        not invoke the alarm binary/script. Once this abatement period expires,
    +        a new alarm binary/script invocation will occur if the alarm is 
    +        triggered.
    +
    +13.1.2  Alarm Update Frequency - this is controlled by the "STATION_PUSH_INTERVAL"
    +        parameter in wview-conf.sdb. It is given in milliseconds. One should 
    +        avoid setting it to anything less than 10000 (10 seconds) - I'm 
    +        sure it would be fine, but you are not going to get new data updates
    +        from the VP Console any faster than once every 10 seconds, because
    +        that is controlled by the parameter described in the next section.
    +
    +13.1.3  Weather Data Update Frequency - this is controlled by the 
    +        "STATION_ARCHIVE_INTERVAL" parameter in wview-conf.sdb. This determines how often
    +        the wviewd daemon polls the VP Console for current conditions. It is 
    +        given in milliseconds and should not reasonably be set to anything 
    +        less than 10000 (10 seconds), and must be an even divisor of 60000.
    +
    +13.1.4  Raw Data Feeds - wvalarmd implements a TCP socket server to accept 
    +        connections for clients wanting a raw feed of loop packet data. See 
    +        the section "Socket-Based Data Feeds" below for details.
    +
    +13.2  Configuration
    +
    +13.2.1  $prefix/etc/wview/wview-conf.sdb
    +
    +13.2.1.1  Edit $prefix/etc/wview/wview-conf.sdb
    +13.2.1.2  Configure the PUSH_INTERVAL parameter to your requirements. This
    +          value is given in seconds, so 60 seconds would be specified
    +          by a value of 60. This determines how often wvalarmd will receive
    +          current conditions data.
    +13.2.1.3  Save and Exit the editor
    +
    +13.2.2  $prefix/etc/wview/wview-conf.sdb
    +
    +13.2.2.1  Edit $prefix/etc/wview/wview-conf.sdb
    +13.2.2.2  Choose your station units type by indicating "STATION_US" or
    +          "STATION_METRIC" in wview-conf.sdb.
    +13.2.2.3  List your alarm definitions for up to 10 distinct alarm definitions.
    +13.2.2.4  Save and Exit the editor
    +
    +13.2.3  Scripts: $prefix/etc/wview/alarms/*.sh
    +
    +13.2.3.1  If using notification scripts, they should be placed in 
    +          $prefix/etc/wview/alarms and will receive the arguments described above
    +          when invoked.
    +13.2.3.2  There are a few example scripts in the distribution directory
    +          .../wview-x.y.z/examples/alarms - these will be copied to 
    +          $prefix/etc/wview/alarms for new installs which build with the "install"
    +          target or can be copied there when upgrading an existing installation.
    +13.2.3.3  If using custom binaries, make note of the arguments passed to the
    +          binary above.
    +13.2.3.4  The example scripts log alarm events to a log file: 
    +          $prefix/var/wview/alarms/alarm.log. This may be a feature you want to keep -
    +          even if you are doing other things in your alarm notification 
    +          scripts.
    +
    +13.3  Alarm Type Definitions
    +
    +   Type (wview-conf.sdb)     Value (passed to script/binary)
    +   ----------------------    -------------------------------
    +   Barometer                 0
    +   InsideTemp                1
    +   InsideHumidity            2
    +   OutsideTemp               3
    +   WindSpeed                 4
    +   TenMinuteAvgWindSpeed     5
    +   WindDirection             6
    +   OutsideHumidity           7
    +   RainRate                  8
    +   StormRain                 9
    +   DayRain                   10
    +   MonthRain                 11
    +   YearRain                  12
    +   TxBatteryStatus           13
    +   ConsoleBatteryVoltage     14
    +   DewPoint                  15
    +   WindChill                 16
    +   HeatIndex                 17
    +   Radiation                 18
    +   UV                        19
    +   ET                        20
    +   ExtraTemp1                21
    +   ExtraTemp2                22
    +   ExtraTemp3                23
    +   SoilTemp1                 24
    +   SoilTemp2                 25
    +   SoilTemp3                 26
    +   SoilTemp4                 27
    +   LeafTemp1                 28
    +   LeafTemp2                 29
    +   ExtraHumid1               30
    +   ExtraHumid2               31
    +
    +
    +13.4  Getting a "Warm Fuzzy"
    +
    +   To get an idea how it works and to gain familiarity with configuration, do
    +   the following:
    +   
    +13.4.1  Stop wview as you normally would:
    +            [FreeBSD]   /etc/rc.d/wview stop            -OR-
    +            [SuSE]      /etc/init.d/wview stop          -OR-
    +            [RH/Fedora] /etc/rc.d/init.d/wview stop
    +
    +13.4.2  Enable Alarms - update wview-conf.sdb to enable alarms
    +
    +13.4.3  Configure the Update Interval - update wview-conf.sdb and set the
    +        update (PUSH) interval to 60 seconds
    +
    +13.4.4  Get Example Alarm Scripts
    +        New Installs: 
    +            make install
    +        
    +        Upgrades: 
    +            mkdir $prefix/etc/wview/alarms
    +            mkdir $prefix/var/wview/alarms
    +            cp wview-x.y.z/examples/alarms/* $prefix/etc/wview/alarms
    +            
    +13.4.5  Edit the Thresholds - update wview-conf.sdb for thresholds very near 
    +        (or already "past") your current outside temperature
    +
    +13.4.6  Start wview as you normally would:
    +        [FreeBSD]   /etc/rc.d/wview start           -OR-
    +        [SuSE]      /etc/init.d/wview start         -OR-
    +        [RH/Fedora] /etc/rc.d/init.d/wview start
    +
    +13.4.7  Watch the magic:
    +            tail -f $prefix/var/wview/alarms/alarm.log
    +
    +13.5  Socket-Based Data Feeds
    +
    +13.5.1  Overview
    +      For true data feeds, wvalarmd provides a socket server which listens on 
    +      port 11011 of the wview server for connections. When a datafeed client 
    +      connects on that port, wvalarmd adds the client to the datafeed client 
    +      list. Then when loop data is received from wviewd, wvalarmd will write 
    +      the loop packet into each datafeed client's socket, preceded by a start 
    +      frame sequence. It is a one-way interface and the client may disconnect 
    +      at any time by calling "shutdown" followed by "close" to close the 
    +      socket. The number of active client connections is only limited by system 
    +      resources. The datafeed client will receive the full loop packet as 
    +      defined in common/datadefs.h - see the structure "LOOP_PKT".
    +
    +13.5.2  Configuration
    +      None required for socket data feeds, just the normal PUSH_INTERVAL setup 
    +      described above. No alarm entries are required in wview-conf.sdb for 
    +      datafeed clients.
    +
    +13.5.2  Client Requirements
    +      The datafeed sockets are regular TCP/stream PF_INET sockets. The general 
    +      connection steps (in C) are as follows:
    +        
    +        socket (...)        // create the socket descriptor
    +        [setup server address and port in socckaddr_in structure]
    +        connect (...)
    +        
    +      The client then may add the socket descriptor to an fdset for select 
    +      calls, simply block on the socket for reads, however you want to design 
    +      it. Other programming language procedures may differ, but the general 
    +      approach should be the same.
    +      
    +      Normal Processing "Loop":
    +      
    +      1) Wait for data on the socket
    +      2) Sync to the start frame sequence: 0xF388, 0xC6A2, 0xDADA, 0xE7CF
    +      3) Read loop packet
    +      4) Process loop packet
    +      5) Goto #1
    +      
    +      To disconnect:
    +      
    +        shutdown (sockfd, 2)
    +        close (sockfd)
    +      
    +      Note: There is a handy new radlib socket API which is illustrated in the 
    +            sample datafeed client source code which takes care of most of the 
    +            above procedure for you.
    +
    +13.5.3  Running the Example Datafeed Client
    +      The directory alarms/sample-datafeed-client contains a simple client 
    +      example including Makefile. It accepts an argument for host but will 
    +      use "localhost" if none is given. It connects to the wvalarmd server 
    +      and logs the current temperature when loop packets are received.
    +
    +13.5.3.1  Build the example client:
    +        (radlib-2.1.0 or higher is required)
    +        cd alarms/sample-datafeed-client
    +        make (or "gmake" for *BSD)
    +
    +13.5.3.2  Run it:
    +        (with wview already running)
    +        ./datafeedClient [wview_hostname]
    +
    +13.5.3.3  Watch the system log for wvalarmd connection messages and update 
    +          messages from the example client:
    +        tail -f /var/log/messages
    +
    +13.5.3.4  Kill the example client:
    +        Either "ctrl-c" if you ran it in the foreground, or "kill -15 [pid]" 
    +        if you backgrounded it when you started it.
    +
    +13.5.3.5  Testing multiple clients:
    +        Just repeatedly run the datafeed client application - each client 
    +        will have its own socket connection to wview. Kill them as described 
    +        above when you are done.
    +
    + +

    + +
    +

    14. Secure File Transfer (rsync/ssh)

    +
    +14.1  Overview
    +   A new wview daemon has been added to support secure file transfers to 
    +   remote servers: wviewsshd. It is enabled or disabled in the same way that
    +   the wviewftpd and wvalarmd daemons are - by editing the SQLite3 database file
    +   $prefix/etc/wview/wview-conf.sdb. All three of these utility daemons are disabled
    +   in the standard distribution. The secure updates are performed using rsync over 
    +   an ssh session. To work properly, the wview server must be able to login and/or 
    +   execute commands on the destination server via ssh WITHOUT entering a password. 
    +   This is accomplished by copying the wview server's root account shared ssh key 
    +   to the remote server's login account.
    +   
    +   Suggestion: Don't mix hostname with IP address for the remote server during
    +               the configuration below. Decide NOW whether you are going to use
    +               a hostname or an IP address, and use it consistently for all
    +               references to [remote_host] below. ssh does make a distinction
    +               when storing and verifying shared keys.
    +
    +   Placeholders:
    +   
    +   [remote_host]             - the host we want to update
    +   [ssh_login_user]          - the user account name on the [remote_host] we
    +                               want to use for the ssh logins
    +   [remote_test_dir]         - remote directory to receive files, relative
    +                               to the [ssh_login_user] login home directory
    +                               *[ssh_login_user] must have write access to this 
    +                               directory*
    +   [wview_server]            - the wview host
    +   
    +   Note: I have included prompts of the form "username@host:# " to help
    +         clarify what is being executed on what host. Your actual shell
    +         prompts may be different, this is only for clarity in this procedure.
    +
    +   Note: This procedure assumes compatible versions of ssh - version 1 and 
    +         version 2 of openssh have compatability problems as well as with 
    +         ssh.com versions 1 and 2. If you are having problems with the setup,
    +         I strongly suggest going to the following URL for advanced help:
    +         http://gentoo-wiki.com/SECURITY_SSH_without_a_password.
    +
    +14.2  Prerequisites
    +
    +14.2.1  Verify rsync is installed on the wview server:
    +        
    +        root@[wview_server]:# whereis rsync
    +        
    +        If that doesn't produce /usr/bin/rsync or similar, install rsync.
    +
    +14.2.2  Verify rsync is installed on the remote host:
    +
    +        root@[wview_server]:# ssh -l [ssh_login_user] [remote_host]
    +        [enter password]
    +        [ssh_login_user]@[remote_host]:# whereis rsync
    +        
    +        If that doesn't produce /usr/bin/rsync or similar, install rsync.
    +        
    +        Logout:
    +        
    +        [ssh_login_user]@[remote_host]:# exit 
    +
    +14.3  ssh Shared Key Setup
    +
    +14.3.1  Generate a public/private key pair for the root user on the wview
    +        server (if it does not already exist) (execute as the root user):
    +        
    +        root@[wview_server]:# mkdir -p ~/.ssh
    +        root@[wview_server]:# ssh-keygen -t rsa
    +        
    +        Just hit enter for default values when asked questions.
    +        This will create two files, we are interested in the ~/.ssh/id_rsa.pub
    +        file.
    +
    +14.3.2  Transfer id_rsa.pub to [remote_host]. Use ftp, scp, email, floppy 
    +        disk, whatever you want to get this file to the remote host.
    +
    +14.3.3  Login to [remote_host] as [ssh_login_user] and append the contents of 
    +        id_rsa.pub into ~/.ssh/authorized_keys (if authorized_keys does not 
    +        exist, just rename id_rsa.pub to authorized_keys).
    +        
    +        root@[wview_server]:# ssh -l [ssh_login_user] [remote_host]
    +        [enter password]
    +        [ssh_login_user]@[remote_host]:# mkdir -p ~/.ssh
    +        [ssh_login_user]@[remote_host]:# cd ~/.ssh
    +        [ssh_login_user]@[remote_host]:# cat id_rsa.pub >> authorized_keys
    +        [ssh_login_user]@[remote_host]:# chmod 600 authorized_keys
    +        
    +        Stay logged in - we need it for the next step.
    +
    +14.3.4  As root on the wview server, ssh into [remote_host] as [ssh_login_user],
    +        answer "yes" if this is the first time, then logout. This sets the 
    +        [remote_host] up in the wview server root account's "known_hosts" file.
    +        
    +        root@[wview_server]:# ssh -l [ssh_login_user] [remote_host]
    +        [no password should be required!]
    +        [ssh_login_user]@[remote_host]:# exit
    +
    +14.3.5  Finally, we need to set up ssh for the root user so that when 
    +        [remote_host] is logged into, ssh uses [ssh_login_user] instead of
    +        root for the login on [remote_host]. This must be done for every 
    +        [remote_host] entry in wview-conf.sdb. This is what specifies which 
    +        user account is used for the ssh login to each [remote_host].
    +        
    +        Edit /root/.ssh/config and put the following at the top of the file
    +        (just create a new file if it does not exist):
    +        
    +        [text start]
    +        Host [remote_host]
    +        User [ssh_login_user]
    +        [text end]
    +        
    +        Save and exit that file.
    +
    +14.3.6  Mandatory Tests - these must succeed before going any further:
    +
    +        As root on the wview server execute:
    +        root@[wview_server]:# ssh [remote_host]
    +
    +        You should be logged in to [remote_host] as [ssh_login_user] without 
    +        entering a password.
    +        
    +        Exit the remote host shell:
    +        [ssh_login_user]@[remote_host]:# exit
    +
    +        You should now be able to remotely execute commands over ssh. Verify 
    +        this by executing the date command from the wview server as root: 
    +        
    +        root@[wview_server]:# ssh [remote_host] date
    +
    +        This MUST execute without requiring a password. If it does not, go back
    +        to the beginning of shared key setup and double check your steps.
    +        
    +        DO NOT proceed if you cannot login/execute commands remotely as
    +        [ssh_login_user] without a password. This is critical! There is much 
    +        online documentation concerning ssh setup, this is only a bare-bones
    +        treatment of the subject.
    +
    +14.4  Confirming rsync Functionality
    +
    +14.4.1  Place files in $prefix/var/wview/img (if it is not already your "HTMLGEN_IMAGE_PATH"
    +        in wview-conf.sdb):
    +        
    +        root@[wview_server]:# cp [some_test_files] $prefix/var/wview/img
    +
    +14.4.2  Create/Verify the destination path on the remote server 
    +        (as [ssh_login_user]):
    +        
    +        root@[wview_server]:# ssh [remote_host]
    +        
    +        [you are now logged in as [ssh_login_user] without a password, right?]
    +        
    +        [ssh_login_user]@[remote_host]:# cd ~  
    +        
    +        [this is your ssh login directory - all wview-conf.sdb destination
    +        paths are relative to this directory]
    +        
    +        [ssh_login_user]@[remote_host]:# mkdir -p [remote_test_dir]
    +        
    +        Note: [remote_test_dir] is a relative path from the [ssh_login_user] 
    +              login directory - it CANNOT contain a leading "slash".
    +
    +        Logout:
    +
    +        [ssh_login_user]@[remote_host]:# exit
    +        
    +14.4.3  Test rsync over ssh from the wview server:
    +        root@[wview_server]:# rsync -aqz --rsh=ssh $prefix/var/wview/img/ [remote_host]:[remote_test_dir]
    +
    +        This should transfer the files you placed in $prefix/var/wview/img to the 
    +        remote server in the [remote_test_dir] directory without a password
    +        being required.
    +        
    +        *****!!!!!!!!!!!!!!!!!!!!!!!!! IMPORTANT !!!!!!!!!!!!!!!!!!!!!!!!!*****
    +        wview ssh file transfer capability will NOT work until you can
    +        successfully execute this command from the wview server and verify 
    +        the file transfers on the remote server.
    +        *****!!!!!!!!!!!!!!!!!!!!!!!!! IMPORTANT !!!!!!!!!!!!!!!!!!!!!!!!!*****
    +
    +14.5  Configuring wview For Secure Transfers
    +
    +14.5.1  Stop wview as you normally would:
    +            [FreeBSD]   /etc/rc.d/wview stop            -OR-
    +
    +            [SuSE]      /etc/init.d/wview stop          -OR-
    +            [RH/Fedora] /etc/rc.d/init.d/wview stop
    +
    +14.5.2  Update $prefix/etc/wview/wview-conf.sdb adding up to 5 rsync rules.
    +
    +        Each [remote_host] entry must have a corresponding "Host" entry in
    +        /root/.ssh/config which specifies the login account ([ssh_login_user]) 
    +        to use for ssh. See section 14.3 above. Further, the procedures outlined 
    +        in sections 14.2-14.4 MUST be followed for each unique [remote_host] 
    +        [ssh_login_user] combination.
    +
    +14.5.5  Start wview as you normally would:
    +            [FreeBSD]   /etc/rc.d/wview start           -OR-
    +            [SuSE]      /etc/init.d/wview start         -OR-
    +            [RH/Fedora] /etc/rc.d/init.d/wview start
    +
    + +

    + +
    +

    15. Miscellaneous

    +
    +15.1  Observing wview Operation
    +
    +   wview logs quite a bit of information about what is being initialized, 
    +   daemons started, etc. in the system log file (/var/log/messages, /var/log/syslog,
    +   etc.). This is where to go to try to investigate a startup problem or just observe
    +   what wview is "doing".
    +   On the other hand, once you get everything set up the way you want, you 
    +   can disable informational logs if you are concerned about how quickly your 
    +   /var/log/messages file is rolling over (you are rotating your log files, 
    +   right?). Set STATION_VERBOSE_MSGS=0 in $prefix/etc/wview/wview-conf.sdb to disable 
    +   informational log entries. You can always re-enable it if you need the 
    +   verbosity.
    +
    +15.2  Notes On Console Poll Timer Settings
    +
    +   The STATION_POLL_INTERVAL setting in wview-conf.sdb MUST be less than or equal 
    +   to 60000 (60 seconds), and it must be an even divisor of 60000.
    +
    +   If the value found in wview-conf.sdb does not meet these criteria, wviewd
    +   will default to 30 seconds.
    +
    +15.3  Notes On Generation Timer Settings
    +
    +   The wview-conf.sdb variables HTMLGEN_START_OFFSET and HTMLGEN_GENERATE_INTERVAL control when
    +   and how often the image and HTML generation occurs. Some examples:
    +
    +START_OFFSET[1]=0
    +GENERATE_INTERVAL[1]=1
    +   Start at 0 minutes past the next 5 minute mark (0, 5, 10, ...) and then
    +   generate every 1 minutes - Once it starts, it will generate ~10 seconds 
    +   past every minute of each hour
    +
    +START_OFFSET[1]=2
    +GENERATE_INTERVAL[1]=5
    +   Start at 2 minutes past the next 5 minute mark (0, 5, 10, ...) and then
    +   generate every 5 minutes - Once it starts, it will generate ~10 seconds 
    +   past the 5 minute plus 2 mark (2, 7, 12, 17, 22, ...) of each hour
    +
    +START_OFFSET[1]=3
    +GENERATE_INTERVAL[1]=10
    +   Start at 3 minutes past the next 5 minute mark (0, 5, 10, ...) and then
    +   generate every 10 minutes - Once it starts, it will generate ~10 seconds 
    +   past one of the following: (3, 13, 23, ...) or (8, 18, 28, ...) minutes of 
    +   each hour
    +
    +Note: Archive records are received within the first minute of the 
    +      archive interval position in the hour.
    +          For 5 minute archive interval: 0, 5, 10, 15, 20, ...
    +          For 30 minute archive interval: 0, 30
    +      Thus in order to insure you get the newest archive data in the 
    +      generation, unless you are using a 1 minute GENERATE_INTERVAL, you 
    +      should use an offset of 1 or greater.
    +
    +15.4  Notes on Image Generation
    +
    +   The daily historical charts are generated using the archive records from 
    +   the last 24 hours. The granularity depends upon your archive interval 
    +   choice. These charts are updated at the same frequency as the archive 
    +   interval to include the last archive interval.
    +
    +   The 28-day historical charts are generated using the hourly average of 
    +   archive records for the last 28 days. These charts are updated once per
    +   hour to include the last hour's averages.
    +
    +   The 365-day historical charts are generated using the daily average of
    +   archive records for the last 365 days. These charts are updated once per 
    +   day (12:01AM) to include the last day's averages.
    +
    +
    +15.5  Tweaking Your Barometric Pressure
    +
    +      Sometimes it is necessary to calibrate your VP console barometer. To do 
    +      this we use the vpconfig utility.
    +
    +15.5.1  Stop wview.
    +        /etc/rc.d/init.d/wview stop (or where your wview start script is located)
    +
    +15.5.2  Change your elevation.
    +        /usr/local/bin/vpconfig /dev/ttyS0 setelevation [new value in feet]
    +        (this assumes a prefix of /usr/local and serial device being /dev/ttyS0)
    +
    +15.5.3  Restart wview.
    +        /etc/rc.d/init.d/wview start
    +
    +      Note: the VP console takes a few minutes to "digest" the new elevation 
    +            and barometric pressure values my be skewed until it normalizes.
    +
    +15.6  vpconfig Usage
    +
    +      Note 1: It is not advisable to use vpconfig to change the archive 
    +              interval or rain season start after the initial configuration 
    +              (using vpinstall). This will skew your rain season totals or 
    +              render your archived data unusable. If you do change the archive 
    +              interval after records have been stored by wview, you will need 
    +              to delete all archive data in $prefix/var/wview/archive/wview-archive.sdb
    +              before restarting wview.
    +      
    +      Note 2: You MUST stop wview before using vpconfig to view or change 
    +              settings!
    +
    +      Usage: vpconfig [station_dev] [command] [cmnd_args]
    +
    +      station_dev  serial device the VP console is connected to:
    +                    FreeBSD: /dev/cuaa0 - /dev/cuaa4
    +                    Linux:   /dev/ttyS0 - /dev/ttyS4
    +
    +      command      command to execute, one of:
    +                     show
    +                       - retrieve and display VP console config
    +                     cleararchive
    +                       - clear the archive memory without changing the interval
    +                     setinterval [interval in minutes]
    +                       - set the archive interval (this clears the archive memory)
    +                     setelevation [elevation in feet]
    +                       - set the station elevation (feet) - use this to adjust barometer
    +                     setgain [0 for off, 1 for on]
    +                       - sets the gain of the radio receiver
    +                     setlatlong [latitude (negative for S)] [longitude (negative for W)]
    +                       - set the station latitude and longitude in tenths of a degree
    +                     setrainseasonstart [month (1-12)]
    +                       - set the month that the yearly rain total begins
    +
    +15.7  Ethernet Setup for the GW21E Serial Server
    +
    +15.7.1  Configure your GW21E IP address as described in the GW21E Quickstart 
    +        Guide.
    +
    +15.7.2  Telnet into the GW21E (admin,NULL)
    +
    +15.7.3  Set Serial Server parameters and Packet Delimiter so that your config
    +        looks like:
    +
    +        [snip]
    +        root@localhost:/root/dev# telnet 10.10.10.22
    +        Trying 10.10.10.22...
    +        Connected to 10.10.10.22.
    +        Escape character is '^]'.
    +        ABLELink Ethernet-Serial Server
    +        User name:admin
    +        Password:
    +        
    +        Login ok
    +        
    +        0.Exit  1.Overview  2.Networking  3.Security  4.COM1
    +        
    +        Input choice and enter(0~4): 4
    +        4
    +        
    +        COM0:
    +        1.  Link Mode (TCP Server/Virtual COM Disabled/Pair Connection Disabled/Filter disabled/3001 /Alive=10*10 sec)
    +        2.  COM Port (VP/RS-232/19200,None,8,1/RTS/CTS)
    +        3.  Keep Serial Buffer's Data While Connecting(Disabled)
    +        4.  Packet Delimiter (1 ms)
    +        5.  Accept Control Command from COM port (Disabled)
    +        
    +        Input choice and enter(1~5):
    +        [snip]
    +
    +        Remember to use "CTRL-]" to get to the telnet prompt and type "quit" to
    +        exit - their utility does not have an exit path in the menu! I had to 
    +        quit and re-login multiple times while configuring it.
    +        
    +        Given the above configuration, your wview ethernet configuration should
    +        be:
    +        Host: 10.10.10.22    (whatever the IP address is that you assigned it)
    +        Port: 3001           (set in section 4 of the GW21E config utility)
    +
    +15.8  Ethernet Setup for the Lantronix MSS1-T
    +
    +15.8.1  Connect to the MSS1-T via the serial port - factory defaults are
    +        9600, 8N1. Login as admin, then enter "set privileged" with password 
    +        "system".
    +
    +15.8.2  Configure the MSS1-T so that your "show server" command displays:
    +
    +        Lantronix MSS1 Version V3.5/5(980529)
    +        
    +        Type HELP at the 'Local_2> ' prompt for assistance.
    +        
    +        Username> admin
    +        
    +        Local_2> show server
    +        
    +           MSS1 Version V3.5/5(980529)            Uptime:                  0:06:16
    +           Hardware Addr: 00-80-a3-0f-f3-e2       Name/Nodenum:      MSS_0FF3E2/ 0
    +           Ident String: Micro Serial Server
    +        
    +           Inactive Timer (min):           30     Serial Delay (msec):          10
    +           Password Limit:                  3     Session Limit:                 4
    +           Queue Limit:                    32     Node/Host Limits:          50/20
    +        
    +        LAT  Circuit Timer (msec):         80     Keepalive Timer (sec):        20
    +           Multicast Timer (sec):          30     Retrans Limit:                10
    +        
    +        TCP/IP Address:           10.10.10.22     Subnet Mask:       255.255.255.0
    +           Nameserver:            (undefined)     Backup Nameserver:   (undefined)
    +           TCP/IP Gateway:         10.10.10.1     Backup Gateway:      (undefined)
    +           Domain Name:           (undefined)     Daytime Queries:         Enabled
    +                                                  TCP Keepalives:          Enabled
    +           DHCP Server:                  None     Lease Time:                 0:00
    +        
    +           Load Address:    00-00-00-00-00-00     Prompt:              Local_%n%P>
    +        
    +           Characteristics:
    +           Incoming Logins:  Telnet   (No Passwords Required)
    +           LAT Groups: 0
    +        
    +        Local_2>
    +
    +        The port to use on the MSS1-T is the raw mode port 3001.
    +        
    +        Given the above configuration, your wview ethernet configuration should
    +        be:
    +        Host: 10.10.10.22    (whatever the IP address is that you assigned it)
    +        Port: 3001           (not configurable)
    +
    +15.9  Endian Conversion Utilities
    +
    +      wview provides two command-line utilities to convert wview archive directories
    +      from Big Endian-to-Little Endian or Little Endian-to-Big Endian. These are 
    +      necessary if you want to change your wview host from a Mac/NSLU2 to a PC or
    +      vice versa.
    +
    +15.9.1  arc_be2le
    +
    +      Usage: arc-be2le [source_directory] [dest_directory]
    +
    +      Convert wview archive data in [source_directory] from big endian
    +      to little endian then store the result in [dest_directory]
    +
    +15.9.2  arc_le2be
    +
    +      Usage: arc_le2be [source_directory] [dest_directory]
    +
    +      Convert wview archive data in [source_directory] from little endian
    +      to big endian then store the result in [dest_directory]
    +
    + +

    + +
    +

    16. CWOP - Submitting Your Data to NOAA and the CWOP System

    +
    +16.1  Overview
    +
    +   CWOP (Citizen Weather Observer Program, http://www.wxqa.com/) is a system by 
    +   which individuals with weather stations and the proper software can submit 
    +   their weather data to an APRS-based data storage system so that anyone, 
    +   including NOAA, can use the data however they see fit. There are some really 
    +   neat station display web sites including some java apps to look up station 
    +   data, position, maps, etc. 
    +   
    +   As an example, see http://www.findu.com/cgi-bin/wxpage.cgi?call=CW4097 
    +   for my weather station.
    +   
    +   CWOP participation requires registering for an APRS "Call Sign". Once you 
    +   have configured wview for CWOP properly and confirmed your data online, you
    +   must contact the maintainers via email to confirm your registration. Then 
    +   your data will be available for anyone to see and possibly be used in NOAA 
    +   forecast models, etc. Pretty cool, eh?
    +
    +   When CWOP support is enabled and configured properly, wview will transmit 
    +   a new WX packet to the APRS server each time a new archive record is 
    +   generated. Thus archive interval drives the frequency that your CWOP data 
    +   will be updated.
    +   
    +   Supports the APRS-IS Rollover functionality by enforcing the definition of 
    +   3 APRS-IS server:port pairs - the goal is to avoid data loss to the CWOP 
    +   system caused by connection errors. Select 3 different servers from the list 
    +   at http://www.wxqa.com/activecwd.html - be advised, the arizona server is 
    +   heavily burdened and often refuses connections.
    +
    +16.2  Prerequisites
    +
    +   - Internet connection/firewall which allows TCP port 23 connections out
    +   - Accurate Latitude and Longitude values in the proper format (this is 
    +     not the same location info used for general station configuration, higher
    +     precision is required by CWOP)
    +   - 3 APRS Servers (see http://www.wxqa.com/activecwd.html for the list)
    +   - 3 APRS Port Numbers, one for each server (usually 23)
    +   - APRS Call Sign (CWnnnn)
    +
    +16.3  Register for an APRS Call Sign (unless you already have one):
    +
    +      http://www.findu.com/citizenweather/signup.html
    +
    +16.4  Determine your accurate latitude and longitude:
    +
    +      http://www.topozone.com/viewmaps.asp
    +
    +16.4  Enable CWOP support in wview:
    +
    +16.4.1  You must have wview version 1.6.0 or later
    +
    +16.4.2  Execute the wviewconfig script:
    +
    +        $prefix/bin/wviewconfig
    +   
    +        [just hit return to keep your current settings when prompted]
    +   
    +        > Enable CWOP (Citizen Weather Observer Program) support?"
    +        > (no):  
    +
    +        Answer with a "yes" to this question - then specify the CWOP parameters as 
    +        prompted.
    +        Note: You must specify all 3 APRS servers - the goal is to avoid data loss
    +
    +16.5  If you are upgrading from a version prior to 1.6.0, copy the new wview 
    +      run script including CWOP daemon support:
    +
    +      cp wview-x.y.z/examples/[your_distro]/wview [your_distro_start_script_location]
    +
    +16.6  Start wview
    +
    +      [your_distro_start_script_location]/wview start
    +
    +16.7  Check the wview logs for wvcwopd activity
    +
    +      As said before, your archive interval determines how often wview will 
    +      update your weather data to the CWOP system. The first record should be 
    +      generated at the next archive record generation after wview has started.
    +      Confirm that wvcwop logs are indicating successful connections to the 
    +      APRS server. 
    +      
    +      Look in /var/log/messages for something similar to:
    +      
    +      "CWOP: configured to submit station CW4097 data to:"
    +      "CWOP: Primary:   cwop.aprs.net:23"
    +      "CWOP: Secondary: cwop.aprs.net:23"
    +      "CWOP: Tertiary:  cwop.aprs.net:23"
    +      
    +      during initialization, and:
    +      
    +      "CWOP-sending: CW4097>APRS,TCPXX*,qAX,CW4097:@132235z3334.14N/09654.66W_284/002g005t093P002h40b10131.wview160"
    +      
    +      at the arrival of each new archive record.
    +
    +16.8  Confirm your data in the CWOP stream:
    +
    +      Goto http://www.findu.com/cgi-bin/wxpage.cgi?call=CWxxxx where CWxxxx is 
    +      your Call Sign. This should start displaying your weather data graphically 
    +      with links on the left for looking at your raw data. Make sure it is all 
    +      good - there is much information and many sites to visit to help you verify 
    +      your data. Just start at http://www.wxqa.com/activecwd.html.
    +
    +16.9  When all is good, send an email to the CWOP maintainers. Congratulations, 
    +      you are now contributing data for the greater good!
    +
    +

    + +
    +

    17. Wunderground/Weatherforyou - Submitting Your Data to Weather Underground and/or Weatherforyou

    +
    +17.1  Overview
    +
    +   The Weather Underground (www.wunderground.com/) (Wunderground) is a privately 
    +   held organization which provides many weather services - some free and some 
    +   not. Among the free services is the ability to register your weather station 
    +   and submit your data to them so that you can access your data and some nice 
    +   graphs from their site. Weatherforyou.com is also a privately held outfit
    +   with similar capabilities to wunderground.
    +
    +17.2  Prerequisites
    +
    +   - Internet connection/firewall which allows HTTP connections out
    +   - Accurate Latitude and Longitude values in the proper format
    +   - libcurl, a "C" URL library distributed with the command line "curl" utility 
    +     (see the Prerequisites section above for details)
    +   - A registered Weather Underground/Weatherforyou Station ID
    +
    +17.3  Wunderground
    +
    +17.3.1  Register for a Weather Underground Station ID (unless you already have one):
    +
    +        http://www.wunderground.com/weatherstation/usersignup.asp
    +
    +17.3.2  Determine your accurate latitude and longitude:
    +
    +        http://www.topozone.com/viewmaps.asp
    +
    +17.3.3  Enable Wunderground support in wview:
    +
    +17.3.3.1  You must have wview version 3.4.0 or later
    +
    +17.3.3.2  Configure wview to include Wunderground support (libcurl required):
    +
    +          ./configure [station_enable_str] --enable-http
    +          make install
    +
    +17.3.3.3  Execute the wviewconfig script:
    +
    +          /usr/local/bin/wviewconfig
    +   
    +          [just hit return to keep your current settings when prompted]
    +   
    +          > Enable WUNDERGROUND support?
    +          > (no):  
    +
    +          Answer with a "yes" to this question - then specify the Wunderground 
    +          parameters as prompted.
    +
    +17.3.4  If you are upgrading from a version prior to 3.4.0, copy the new wview 
    +        run script including Wunderground/Weatherforyou daemon support:
    +
    +        cp wview-x.y.z/examples/[your_distro]/wview [your_distro_start_script_location]
    +
    +17.3.5  Start wview
    +
    +        [your_distro_start_script_location]/wview start
    +
    +17.4  Weatherforyou
    +
    +17.4.1  Register for a Weatherforyou Station ID (unless you already have one):
    +
    +        http://www.hamweather.net/weatherstations/
    +        Login there and click on 'Add New' on the 'User Stations' line. 
    +        Then enter your station's information.
    +
    +17.4.2  Enable Weatherforyou support in wview:
    +
    +17.4.2.1  You must have wview version 3.4.0 or later
    +
    +17.4.2.2  Configure wview to include Weatherforyou support (libcurl required):
    +
    +          ./configure [station_enable_str] --enable-http
    +          make install
    +
    +17.4.2.3  Execute the wviewconfig script:
    +
    +          /usr/local/bin/wviewconfig
    +   
    +          [just hit return to keep your current settings when prompted]
    +   
    +          > Enable WEATHERFORYOU support?
    +          > (no):  
    +
    +          Answer with a "yes" to this question - then specify the Weatherforyou 
    +          parameters as prompted.
    +
    +17.4.3  If you are upgrading from a version prior to 3.4.0, copy the new wview 
    +        run script including Wunderground/Weatherforyou daemon support:
    +
    +        cp wview-x.y.z/examples/[your_distro]/wview [your_distro_start_script_location]
    +
    +17.4.4  Start wview
    +
    +        [your_distro_start_script_location]/wview start
    +
    +17.5  Check the wview logs for wvhttpd activity
    +
    +      Your archive interval determines how often wview will update your weather 
    +      data to the Wunderground/Weatherforyou systems. The first record should be 
    +      generated at the next archive record generation after wview has started.
    +      
    +      Look in /var/log/messages for something similar to:
    +      
    +      "WUNDERGROUND: configured to submit station KTXCOLLI1 data to wunderground.com"
    +      "WEATHERFORYOU: configured to submit station KTXCOLLI1 data to weatherforyou.com"
    +      
    +17.6  Confirm your data at the Wunderground server:
    +
    +      Goto http://www.wunderground.com/weatherstation/WXDailyHistory.asp?ID=XXXXXXX
    +      where XXXXXXX is your Wunderground Station ID. This should start displaying 
    +      your weather data graphically and as a packet list.
    +
    +17.7  Confirm your data at the Weatherforyou server:
    +
    +      Goto http://www.hamweather.net/weatherstations/pwsupdate.php?ID=XXXXXXX
    +      where XXXXXXX is your Weatherforyou Station ID. This should start displaying 
    +      your weather data as a packet list.
    +
    + + +

    + +
    +

    18. AWEKAS - Providing Your Data to Awekas

    +
    +18.1  Overview
    +
    +   From the AWEKAS site (http://www.awekas.at/en/index.php):
    +   "AWEKAS is an acronym for "Automatisches WEtterKArten System" (= automatic 
    +   weather map system) and is, as the name already states, a system which produces 
    +   overview maps from the data from private weather stations. The values are made 
    +   available by the participants on their websites as Text or CSV files and are 
    +   collected by the programme. The process is fully automatic and always provides 
    +   maps with up to date weather situations."
    +
    +   Simply put, if we provide the properly formatted HTML template to wview, AWEKAS
    +   will periodically poll the resulting HTML file for data and store it in their 
    +   database for online retrieval.
    +
    +18.2  Configure wview to Generate the AWEKAS File
    +
    +   Copy the appropriate template file (this assumes you have the latest distro HTML
    +   templates installed in $prefix/etc/wview/html):
    +   
    +   If you have a metric setup do the following:
    +       cp $prefix/etc/wview/html/awekas_wl.htx-metric $prefix/etc/wview/html/awekas_wl.htx
    +
    +   Else if you have a US (Imperial) setup do the following:
    +       cp $prefix/etc/wview/html/awekas_wl.htx-us $prefix/etc/wview/html/awekas_wl.htx
    +
    +   Add (or uncomment if already there) this template in $prefix/etc/wview/html-templates.conf:
    +       vi $prefix/etc/wview/html-templates.conf
    +       
    +       [Add the following lines or just remove the '#' from awekas_wl.htx if already there]
    +       [snip]
    +       ###############################################################################
    +       ### AWEKAS Data Submission Template
    +       ###############################################################################
    +
    +       awekas_wl.htx
    +       [/snip]
    +
    +   Restart wview:
    +       [your_start_script_location]/wview restart
    +
    +   That's it! Once you have registered with AWEKAS this file should be polled by them
    +   periodically to update your station data.
    +
    +
    + + +

    + +
    +

    19. Using a Linksys NSLU2 as the wview Host

    +
    +19.1  Overview
    +
    +   The Linksys NSLU2 is a network storage link which uses USB external hard 
    +   drives or flash drives and an ethernet interface to provide Network Attached
    +   Storage (NAS) to the masses. This is a perfect embedded platform to use as a
    +   wview host. It is small, inexpensive (~$70) and uses little power. In addition,
    +   there exists a mature linux hacking community which has developed an entire open 
    +   source distribution including a simple package management system. We will 
    +   use one USB port for our boot disk and root partition hard drive and the 
    +   other USB port for the USB-to-serial interface to the VP console. One could 
    +   also use one of the wview-approved terminal servers to interface the VP 
    +   console via ethernet.
    +
    +   The distribution we will use is SlugOS: 
    +   (http://www.nslu2-linux.org/wiki/SlugOS/HomePage).
    +   Big-Endian (BE) and Little-Endian (LE) variants are available.  If you use the LE
    +   version you will be able to use the archive files generated by wview directly on a
    +   PC, whereas with the BE version you would need to run the conversion utilities first.
    +
    +   Another alternative is Debian - see (http://www.nslu2-linux.org/wiki/Debian/HomePage).
    +   
    +   On all of these platforms it is fairly easy to set up a development environment
    +   and install wview from source just like on any other distro 
    +   (see  http://www.nslu2-linux.org/wiki/HowTo/SetupANativeCompiler), but we also provide precompiled binaries for SlugOS.
    +
    +19.2  Install Openslug
    +
    +   Download the SlugOS 4.8-beta binary: 
    +       http://www.slug-firmware.net/s-dls.php
    +
    +   Flash the NSLU2 with the SlugOS binary: 
    +       http://www.nslu2-linux.org/wiki/SlugOS/UsingTheBinary
    +
    +   Initialize your hard disk and transfer the root filesystem:
    +       http://www.nslu2-linux.org/wiki/OpenSlug/InitialisingOpenSlug
    +
    +    For SlugOS the flash/disk requirements are quite modest - a 512MB flash drive 
    +    should suffice.
    +
    +    It is recommended to set up a swap area.  In normal use wview installed alone
    +    on an NSLU2 does not cause swapping, but sometimes things like 'ipkg update' 
    +    need more memory than is physically available.
    +
    +19.3  Prepare ipkg to use the wview repository
    +
    +   Once you have your slug (NSLU2) booting from the USB hard drive/flash, do the 
    +   following to enable the wview ipkg repository:
    +
    +   Obtain the wview ipkg config file:
    +       cd /etc/ipkg
    +       wget http://www.wviewweather.com/ipkg/wview.conf
    +
    +   Configure ipkg:
    +       ipkg update
    +
    +19.4  Install a USB-to-Serial Adapter Driver
    +
    +   Note: If you are using a terminal server via ethernet, you can skip this step.
    +
    +   There are a wide range of supported USB Serial devices, including:
    +
    +    Module Name                ipkg Name
    +    -----------                -------------------------------
    +    BELKIN                     kernel-module-belkin-sa
    +    CP2101                     kernel-module-cp2101
    +    CYPRESS_M8                 kernel-module-cypress-m8
    +    DIGI_ACCELEPORT            kernel-module-digi-acceleport
    +    EDGEPORT                   kernel-module-io-edgeport
    +    EDGEPORT-TI                kernel-module-io-ti
    +    EMPEG                      kernel-module-empeg
    +    FTDI_SIO                   kernel-module-ftdi-sio
    +    KEYSPAN                    kernel-module-keyspan
    +    MCT_U232                   kernel-module-mct-u232
    +    PL2303                     kernel-module-pl2303
    +    SAFE                       kernel-module-safe-serial
    +    TI                         kernel-module-ti-usb-3410-5052
    +    WHITEHEAT                  kernel-module-whiteheat
    +
    +   Choose the ipkg name corresponding to your USB-to-Serial adapter - 
    +   to generalize we'll refer to it as [usb-pkg-name]. Use "ipkg list"
    +   to see descriptions of the individual packages if unsure which one 
    +   is right for your adapter. You will have to look in the "/dev"
    +   directory of your slug to determine the proper device name for your
    +   adapter/driver to give the wviewconfig script (after the driver is 
    +   installed and the USB-to-serial adapter is connected). USB adapters
    +   are considered serial devices by wview.
    +
    +   Install the kernel module driver:
    +       # ipkg install [usb-pkg-name]
    +
    +   Setup the module to be loaded at boot:
    +       (Note: [module name] is the ipkg Name above without the leading 
    +        "kernel-module-")
    +
    +       # echo [module name] > /etc/modutils/[module name]
    +       # /usr/sbin/update-modules
    +       # ln -s /etc/init.d/modutils.sh /etc/rc3.d/S90modutils
    +
    +19.5  Installing wview and All Prerequisites
    +
    +   Determine the type of wview to install:
    +       # ipkg list | grep wview
    +       wview-sim-mysql - 3.5.0-r0 - Version 3.5.0 of package wview
    +       wview-vpro-mysql - 3.5.0-r0 - Version 3.5.0 of package wview
    +       wview-wxt510-mysql - 3.5.0-r0 - Version 3.5.0 of package wview
    +
    +   Install wview and dependencies:
    +       # ipkg install wview-[type]
    +
    +   This will install radlib, gd, libpng, libjpeg, libz, libcurl and a few 
    +   other necessities. It will install an appropriate wview start/stop script 
    +   in /etc/init.d and configure it to run at boot. It will install default 
    +   config files in $prefix/etc/wview and a default run environment in $prefix/var/wview. 
    +   Note that wview binaries are installed in /usr/bin on the slug.
    +
    +   Install the ntpdate crontab:
    +   cd /etc/cron/crontabs
    +   crontab root
    +
    +   Set your timezone:
    +       $ ln -s /usr/share/zoneinfo/[your continent]/[Your timezone] /etc/localtime
    +
    +   Optional: If you plan to ftp your files off the slug to your webserver, you 
    +   will need to download the tnftp binary built for openslug 2.7-beta:
    +       # cd /usr/bin
    +       # mv /usr/bin/ftp /usr/bin/ftp-old
    +
    +       (OpenSlug BE):
    +       # wget http://www.wviewweather.com/ipkg/tnftp
    +       (OpenSlug LE):
    +       # wget -O tnftp http://www.wviewweather.com/ipkg/tnftp-LE
    +
    +       # chmod +x tnftp
    +       # ln -s /usr/bin/tnftp /usr/bin/ftp
    +   You can also download the tnftp tarball at:
    +   http://www.wviewweather.com/ipkg/tnftp-20050625.tar.gz
    +   and build tnftp natively on the slug (development setup on the slug required).
    +
    +19.6  Finishing Up
    +
    +   Now you should proceed to the Configuration
    +   section of this manual (skipping the first two sections) and configure your 
    +   wview installation. When you are done, reboot the slug and confirm proper 
    +   operation. You will probably want to install the nfs-utils package and/or 
    +   samba so you can transfer files to/from your slug. These are described on the 
    +   openslug Wiki - look around and have fun with your slug!
    +
    +19.7  Upgrading
    +
    +   If upgrading a previous version of wview:
    +       # /etc/init.d/wview stop
    +       # mv $prefix/etc/wview $prefix/etc/wview-OLD
    +       # mv $prefix/var/wview/img $prefix/var/wview/img-OLD
    +       # cd /etc/ipkg
    +       # wget http://www.wviewweather.com/ipkg/wview.conf
    +       # ipkg update
    +       # ipkg install wview-[type]
    +       (merge your old configuration files into the new directories)
    +       # wviewconfig
    +
    + + +

    + +
    +

    20. Porting New Stations To wview

    +
    +20.1  Station API
    +
    +   The wview station API is prototyped in ../stations/common/station.h. For 
    +   full details of the functional requirements please refer to station.h and
    +   existing station interfaces. The required functions of any station interface
    +   are (from station.h):
    +   
    +// station-supplied init function
    +// -- Can Be Asynchronous - event indication required --
    +//
    +// MUST (in this order):
    +//   - set the 'stationGeneratesArchives' flag in WVIEWD_WORK:
    +//     if the station generates archive records (TRUE) or they should be 
    +//     generated automatically by the daemon from the sensor readings (FALSE)
    +//   - Initialize the 'stationData' store for station work area
    +//   - Initialize the interface medium
    +//   - determine the station archive interval - either from the station itself
    +//     or from user configuration in wview-conf.sdb - and set the 
    +//     'work->archiveInterval' variable (in minutes) in WVIEWD_WORK
    +//   - VERIFY the archive interval by calling 'stationVerifyArchiveInterval' -
    +//     If they don't match, indicate an errant start via the call:
    +//     radProcessEventsSend (NULL, STATION_INIT_COMPLETE_EVENT, 1)
    +//     and stopping further init activities here!
    +//   - do any catch-up on archive records if there is a data logger (can be 
    +//     asynchronous) - the 'work->runningFlag' can be used for start up 
    +//     synchronization but should not be modified by the station interface code
    +//   - do initial LOOP acquisition
    +//   - indicate successful initialization is done via the call:
    +//     radProcessEventsSend (NULL, STATION_INIT_COMPLETE_EVENT, 0)
    +//
    +// OPTIONAL:
    +//   - Initialize a state machine or any other construct required for the 
    +//     station interface - these should be stored in the 'stationData' store
    +//
    +// 'archiveIndication' - indication callback used to pass back an archive record
    +//   generated as a result of 'stationGetArchive' being called; should receive a
    +//   NULL pointer for 'newRecord' if no record available; only used if 
    +//   'stationGeneratesArchives' flag is set to TRUE by the station interface
    +//
    +// Returns: OK or ERROR
    +//
    +extern int stationInit
    +(
    +    WVIEWD_WORK     *work,
    +    void            (*archiveIndication)(ARCHIVE_RECORD *newRecord)
    +);
    +
    +
    +// station-supplied exit function
    +//
    +// Returns: N/A
    +//
    +extern void stationExit (WVIEWD_WORK *work);
    +
    +
    +// station-supplied function to retrieve positional info (lat, long, elev) -
    +// should populate WVIEWD_WORK fields: latitude, longitude, elevation
    +// -- Synchronous --
    +//
    +// - If station does not store these parameters, they can be retrieved from the
    +//   wview-conf.sdb file (see the 'stationGetConfigValue' utilities below) - user 
    +//   must choose station type "NameOfStation" when running the wviewconfig script
    +//
    +// Returns: OK or ERROR
    +//
    +extern int stationGetPosition (WVIEWD_WORK *work);
    +
    +
    +// station-supplied function to indicate a time sync should be performed if the
    +// station maintains time, otherwise may be safely ignored
    +// -- Can Be Asynchronous --
    +//
    +// Returns: OK or ERROR
    +//
    +extern int stationSyncTime (WVIEWD_WORK *work);
    +
    +
    +// station-supplied function to indicate sensor readings should be performed -
    +// should populate 'work' struct: loopPkt (see datadefs.h for minimum field reqs)
    +// -- Can Be Asynchronous --
    +//
    +// - indicate readings are complete by sending the STATION_LOOP_COMPLETE_EVENT
    +//   event to this process (radProcessEventsSend (NULL, STATION_LOOP_COMPLETE_EVENT, 0))
    +//
    +// Returns: OK or ERROR
    +//
    +extern int stationGetReadings (WVIEWD_WORK *work);
    +
    +
    +// station-supplied function to indicate an archive record should be generated -
    +// MUST populate an ARCHIVE_RECORD struct and indicate it to 'archiveIndication'
    +// function passed into 'stationInit'
    +// -- Asynchronous - callback indication required --
    +//
    +// Returns: OK or ERROR
    +//
    +// Note: 'archiveIndication' should receive a NULL pointer for the newRecord if
    +//       no record is available
    +// Note: This function will only be invoked by the wview daemon if the 
    +//       'stationInit' function set the 'stationGeneratesArchives' to TRUE
    +//
    +extern int stationGetArchive (WVIEWD_WORK *work);
    +
    +
    +// station-supplied function to indicate data is available on the station 
    +// interface medium (serial or ethernet) -
    +// It is the responsibility of the station interface to read the data from the 
    +// medium and process appropriately. The data does not have to be read within
    +// the context of this function, but may be used to stimulate a state machine.
    +// -- Can Be Asynchronous --
    +//
    +// Returns: N/A
    +//
    +extern void stationDataIndicate (WVIEWD_WORK *work);
    +
    +
    +// station-supplied function to indicate the interface timer has expired -
    +// It is the responsibility of the station interface to start/stop the interface
    +// timer as needed for the particular station requirements.
    +// The station interface timer is specified by the 'ifTimer' member of the
    +// WVIEWD_WORK structure. No other timers in that structure should be manipulated
    +// in any way by the station interface code.
    +// -- Can Be Asynchronous --
    +//
    +// Returns: N/A
    +//
    +extern void stationIFTimerExpiry (WVIEWD_WORK *work);
    +
    +20.2  Notes
    +
    +   - Two events are used for notification to the generic wviewd daemon:
    +     STATION_INIT_COMPLETE_EVENT    used to asynchronously indicate station-
    +                                    specific initialization is complete
    +     STATION_LOOP_COMPLETE_EVENT    used to asynchronously indicate that a 
    +                                    current readings acquisition is complete
    +
    +   - If the station does not include a data logger, the generic wviewd daemon
    +     will generate archive records based on the current readings samples for the
    +     archive interval. See the WXT510 implementation for an example of a
    +     straightforward synchronous station interface. It does not include a data 
    +     logger and requires the wview daemon to generate archive records. The 
    +     Simulator station implementation also demonstrates a synchronous interface.
    +
    +   - If the station does include a data logger, the archiveIndication function
    +     allows an asynchronous way to indicate the archive record to the wview 
    +     daemon. See the VantagePro station implementation for an asynchronous, 
    +     state-machine driven station interface.
    +
    +   - A station-specific work data store pointer is provided in the WVIEWD_WORK
    +     structure to allow the definition and reference of station-specific data
    +     (including state machines, etc.).
    +
    +   - If the station does not configure/store location data, the station config
    +     parameter utility can be used to extract them from wview-conf.sdb (see 
    +     stations/Simulator/simulator.c for example usage).
    +
    +
    + +

    + +
    +

    21. Sensor Calibration

    +
    +21.1  Overview
    +
    +   Software calibration of basic sensors for any station type are configured using
    +   the wview-conf.sdb configuration database. Archive data and loop data used for 
    +   highs/lows/averages is calibrated before being stored to file or sent to external
    +   data consumers (CWOP, WeatherUnderground, Alarms, etc.).
    +   The format of the calibration equation is:
    +   CSV = M * X + C, where:
    +       CSV  is the calibrated sensor value
    +       M    is the multiplier configuration value in wview-conf.sdb
    +       X    is the raw sensor value
    +       C    is the constant configuration value in wview-conf.sdb
    +
    +21.2  Sensor List
    +
    +   The following sensors can be calibrated:
    +   Sensor                   Units
    +   ------------------       -----------------
    +   Barometer                US inches
    +   Station Pressure         US inches
    +   Altimeter                US inches
    +   Inside Temp              degrees fahrenheit
    +   Outside Temp             degrees fahrenheit
    +   Inside Humidity          %
    +   Outside Humidity         %
    +   Wind Speed               miles per hour (mph)
    +   Wind Direction           degrees (0-359)
    +   Rain                     US inches
    +   Rain Rate                US inches per hour
    +
    +21.3  Pressure Calibration
    +
    +   Each station type produces one type of pressure sensor: Barometric Pressure (BP),
    +   Station Pressure (SP) or Altimeter (ALT). wview converts the sensor provided
    +   to the other two types algorithmically. As of the 5.0.0 release, it is only 
    +   necessary to calibrate the pressure type provided by your station - wview will
    +   apply the calibration prior to the conversion to the other two types. Calibration 
    +   values entered for the two derived types will be ignored by wview.
    +
    +   Station           Pressure Type to Calibrate
    +   ----------------  --------------------------
    +   Vantage Pro       BP
    +   WXT510            SP
    +   WS-2300           SP
    +   WMR918            SP
    +
    +   Regardless of the type your are calibrating, you will generally be using the
    +   BP value to compare to local stations. Just remember to adjust the sensor
    +   calibration for the native pressure of your station.
    +
    +21.4  Notes
    +
    +   Calibration is done in US units only. Yes, imperial. Station data is always
    +   received/stored in imperial units. Thus calibration is done in imperial units.
    +   If you are a metric station, *figure out* what the imperial equivalent for 
    +   your metric calibration would be, and use that for the calibration settings.
    +
    +
    + + +

    + +
    +

    22. Data Stored in SQLite3 (And How to Use It)

    +
    +22.1  Overview
    +
    +   With the release of wview version 5.0.0 archive data is no longer stored in
    +   the Weatherlink WLK file format. wview now stores archive data in an SQLite3
    +   database, namely $prefix/var/wview/archive/wview-archive.sdb. This will allow
    +   much easier data viewing, modifying and post-processing. Version 5.1.0 added
    +   a new HILOW data store in the database $prefix/var/wview/archive/wview-hilow.sdb.
    +   Version 5.2.0 added NOAA data to the database $prefix/var/wview/archive/wview-noaa.sdb.
    +   This section will describe each of these databases including when and from what
    +   data they are created/updated.
    +
    +22.2  Database Origins
    +
    +   Database             Data Source                     How Generated
    +   ------------------   ------------------------------- -------------------------------
    +   wview-archive.sdb    WLK conversion via wlk2sqlite   Not autogenerated - requires
    +                        or stored run time by wview     an existing template (found at
    +                                                        $distro/bin/archive/wview-archive.sdb)
    +
    +   wview-hilow.sdb      Archive records for initial     Autogenerated during wview init 
    +                        creation; LOOP packets during   (can be very slow on some platforms);
    +                        run time by wview               Offline creation via the hilowcreate
    +                                                        utility
    +
    +   wview-noaa.sdb       HILOW records for initial       Autogenerated during wview init
    +                        creation and during run time    (not too long to generate)
    +
    +22.3  What to Protect and Why
    +
    +   wview-archive.sdb    - do NOT delete this database without a backup copy; this file
    +                          should be backed up for data security
    +
    +   wview-hilow.sdb      - if you delete this file, wview will regenerate it but you
    +                          will lose the higher resolution of LOOP data as wview can
    +                          only use archive records for historical generation; this
    +                          file should be backed up for data security
    +
    +   wview-noaa.sdb       - there is no data loss associated with deleting this database
    +                          and letting wview regenerate it during init
    +
    +22.4  Archive Data (wview-archive.sdb)
    +
    +22.4.1  Conversion from WLK to SQLite3
    +
    +   A conversion utility, wlk2sqlite is now provided as part of the wview
    +   distribution.
    +   
    +   > wlk2sqlite source_directory [destination_directory]
    +
    +   source_directory is a required argument but can be the same as [destination_directory] -
    +   I typically use the default archive file location for my install, 
    +   $prefix/var/wview/archive, since there is nothing destructive about the 
    +   conversion with respect to the old WLK files, it only reads them.
    +
    +   So an example using the default prefix /usr/local would be:
    +   > sudo wlk2sqlite /usr/local/var/wview/archive
    +   This places the newly populated SQLite3 archive database file in the proper
    +   location for wview 5.0.0 + operation.
    +
    +   Note: The conversion can take some time depending upon your platform and how
    +         much archive data you have.
    +
    +   When the conversion is complete, you have one coherent archive database.
    +
    +22.4.2  Viewing/Modifying Your Data With the sqlite3 CLI Utility
    +
    +   Let's look at your data.
    +   > sudo sqlite3 /usr/local/var/wview/archive/wview-archive.sdb
    +
    +   sqlite> .help
    +   (This displays available commands; non-SQL commands begin with '.')
    +
    +   sqlite> select * from archive;
    +   (This displays every column of every row)
    +
    +   sqlite> .schema archive
    +   (This displays the CREATE statement for the archive table including column
    +    names)
    +
    +   sqlite> update archive set altimeter='777.88' where altimeter is NULL;
    +   (This changes the value of column altimeter wherever it is NULL)
    +
    +   sqlite> .dump archive
    +   (This dumps every row of the archive table in SQL format - very handy for 
    +   moving your table from one platform to another)
    +
    +   Note: SQL syntax as recognized by SQLite3 is documented at: 
    +         http://www.sqlite.org/lang.html
    +
    +22.4.3  Moving Your Data to a Different Platform
    +
    +   This is where having our data in an SQL database starts to pay off.
    +
    +   Dump the archive table in SQL format:
    +   > sudo sqlite3 /usr/local/var/wview/archive/wview-archive.sdb .dump > ~/temp/[my_sql_filename]
    +
    +   Copy [my_sql_filename] to the target platform.
    +
    +   On the target platform:
    +   > sudo sqlite3 [new_database_filename] .read [my_sql_filename]
    +   (This will execute each SQL statement in [my_sql_filename] for the new database
    +   [new_database_filename])
    +
    +   Done!
    +
    +22.4.4  Why Can't I Just Move the Database File?
    +
    +   Physical platform and version of SQLite3 effect the format of the data in your
    +   archive database.
    +
    +22.5  HILOW Data (wview-hilow.sdb)
    +
    +22.5.1  Overview
    +
    +   The database $prefix/var/wview/archive/wview-hilow.sdb will be created and 
    +   back-filled using archive records for all records in the archive database. 
    +   This initialization process can take some time (8-12 minutes per year) but 
    +   is only required during the first run of wview after an upgrade >= 5.1.0.
    +   Once $prefix/var/wview/archive/wview-hilow.sdb has been created and back-filled,
    +   wview will add each LOOP sample to the HILOW database.
    +
    +22.5.2  Structure
    +
    +   The $prefix/var/wview/archive/wview-hilow.sdb database has one table for each
    +   of the supported sensor types (i.e., inTemp, outTemp, ... rain, rainRate).
    +   Each table contains a record for each hour. The data starts with your earliest 
    +   archive record.
    +
    +   The columns for each table are:
    +   dateTime         - timestamp for the beginning of the hour for this record
    +   low              - low value for this hour
    +   timeLow          - timestamp when the low occured
    +   high             - high value for this hour
    +   timeHigh         - timestamp when the high occured
    +   whenHigh         - additional value at the time of the high
    +                      (currently only used for wind direction when high wind occured)
    +   cumulative       - the sum of all samples in this record
    +   samples          - the number of samples in this record
    +
    +   Thus the average for an hour is cumulative/samples.
    +
    +   The windDir table has a unique structure.  Each row includes the dateTime stamp
    +   and 20 integer bins. Each wind direction reading is tallied in one of the 20 bins,
    +   with each bin representing an 18-degree window. These bins are used for consensus
    +   averaging (the algorithm is found in windAverage.c).
    +
    +22.5.3  Samples Stored
    +
    +   During creation, wview uses archive records to back-fill the HILOW database.
    +   During normal wview operation, LOOP samples are used to populate the HILOW 
    +   database, providing a much higher granularity for detecting low and high events.
    +
    +22.5.4  Regeneration
    +
    +   To regenerate the HILOW database:
    +   > /etc/init.d/wview stop
    +   > sudo rm $prefix/var/wview/archive/wview-hilow.sdb
    +   > /etc/init.d/wview start
    +
    +   Note: Doing this will delete the LOOP sample resolution stored while wview is
    +         running, replacing it with back-filled archive data. For a station with
    +         a 5 minute archive interval and 30 second LOOP interval, that is the 
    +         difference between 120 samples per hour and 12 samples per hour.
    +
    +22.6  NOAA Data (wview-noaa.sdb)
    +
    +22.6.1  Overview
    +
    +   The NOAA data is stored in an SQLite3 database. The database is generated 
    +   based on records in the HILOW database (much faster and more accurate than 
    +   archive records).
    +
    +22.6.2  Structure
    +
    +   Each record in wview-noaa.sdb represents one day. The columns for the 
    +   noaaHistory table are:
    +   dateTime         - timestamp for the beginning of the day for this record
    +   meanTemp         - average temperature for the day
    +   highTemp         - high temperature for the day
    +   highTempTime     - unix time of high
    +   lowTemp          - low temperature for the day
    +   lowTempTime      - unix time of low
    +   heatDegDays      - heat degree days for the day
    +   coolDegDays      - cool degree days for the day
    +   rain             - rain for the day
    +   avgWind          - average wind for the day
    +   highWind         - high wind for the day
    +   highWindTime     - unix time of high wind
    +   domWindDir       - dominant wind direction for the day
    +
    +22.6.3  Regeneration
    +
    +   To regenerate the NOAA database:
    +   > /etc/init.d/wview stop
    +   > sudo rm $prefix/var/wview/archive/wview-noaa.sdb
    +   > /etc/init.d/wview start
    +
    +22.7  Day history Table (wview-history.sdb)
    +
    +22.7.1  Overview
    +
    +   This table is generated internally by wview to avoid having to compute the 
    +   daily summary records used for the yearly charts every time wview starts.
    +   This was not a time concern with the flat WLK files but became more costly
    +   when we started storing data in SQLite3. The external use or utility of this 
    +   table is limited at best.
    +
    +22.7.2  Regeneration
    +
    +   To cause the day history table to be regenerated, stop wview, delete 
    +   wview-history.sdb and start wview.
    +
    +22.8  SQLite3 Tips
    +
    +   To display the dateTime timestamp of any table as local time:
    +   sqlite> select datetime(dateTime, 'unixepoch', 'localtime') from archive;
    +
    +   To fix any archive entries having 255 for the outHumidity:
    +   sqlite> update archive set outHumidity = '50' where outHumidity = '255';
    +   (Note: If you modify records in the archive table, you will probably want to
    +    regenerate the HILOW tables.)
    +
    +    
    +
    + +

    + +
    +

    23. wviewmgmt - The New Way to Manage wview

    +
    +23.1  Overview
    +
    +   Included in the wview distribution ($distro/wviewmgmt) is a PHP/Java/HTML web site
    +   for managing your wview installation via a web interface.
    +   Features include:
    +
    +   - Graphical configuration of wview (running or not)
    +   - Ability to remotely Start/Stop wview
    +   - Run status display
    +
    +23.2  Setup
    +
    +   Note: Replace $prefix, $distro and $documentRoot with the appropriate paths 
    +         for your server.
    +
    +   1) Configure wview with HTTP_DOC_ROOT defined (see the build and install section).
    +
    +   2) Use the make install build target:
    +      > make install
    +
    +   3) Be sure PHP support is included with the http server:
    +      For apache2, I have /etc/apache2/mods-enabled/php5.conf and 
    +      /etc/apache2/mods-enabled/php5.load.
    +
    +   4) Be sure the SQLite3 module for php is installed (php5-sqlite or similar).
    +
    +   5) If you want start/stop control of wview from the Management Web Site
    +      (and you are comfortable giving the http server user account sudo privileges),
    +
    +      a) Add the http user to the sudo group:
    +         > sudo adduser www-data sudo
    +
    +      b) Make sure the sudo group has no password required privileges in /etc/sudoers:
    +         > sudo visudo
    +         (Make sure the line [%sudo ALL=NOPASSWD: ALL] is at the bottom of the
    +          /etc/sudoers file)
    +
    +   6) Point your browser to: 
    +      http://[wview_URL_or_IP_address]/wviewmgmt/login.php
    +
    +   7) The default password is "wview". You should change this the first time you 
    +      login. There is no reminder capability, so if you forget it, delete the row
    +      in the wview-config.sdb database with name "ADMIN_PASSWORD", logout and start
    +      again with "wview".
    +
    +23.3  Notes
    +
    +   The addition of the http user to the sudoers file may be a security vulnerability -
    +   use it at your own risk.
    +   
    +   The Start/Stop button is not a toy - every effort has been made to harden it
    +   so you don't punch it like a child - but if you refresh your browser while you
    +   should be waiting patiently for wview to start or stop then push the button
    +   again, you will probably hose up your installation. So use it with care.
    +
    +
    + +

    + +
    +

    24. Troubleshooting

    +
    +24.1  wview Processes
    +
    +   Please note there are two unix processes for each wview daemon: this is 
    +   normal radlib operation. One is the actual daemon and the other is a 
    +   "reflector" process which allows the daemon to send events to itself and 
    +   to open the message queue FIFO properly. Don't let that bother you. 
    +   
    +   Here are the processes running on my server (no ssh/ftp because I don't 
    +   need it):
    +
    +    root@localhost:/root# ps aux | grep wv
    +    root     73863  0.0  0.2  2320 1808  ??  Ss    7:08AM   0:00.02 /usr/local/bin/radmrouted 1 $prefix/var/wview
    +    root     73865  0.0  0.2  2300 1780  ??  I     7:08AM   0:00.00 /usr/local/bin/radmrouted 1 $prefix/var/wview
    +    root     73868  0.0  0.1  2380 1472  ??  Ss    7:08AM   0:00.36 /usr/local/bin/wviewd
    +    root     73870  0.0  0.1  2356 1196  ??  I     7:08AM   0:00.00 /usr/local/bin/wviewd
    +    root     73873  0.0  0.1  2376 1524  ??  Is    7:08AM   0:35.43 /usr/local/bin/wviewsqld
    +    root     73875  0.0  0.1  2324 1140  ??  I     7:08AM   0:00.00 /usr/local/bin/wviewsqld
    +    root     73879  0.0  0.1  2336 1468  ??  Ss    7:08AM   0:00.01 /usr/local/bin/wvalarmd
    +    root     73881  0.0  0.1  2312 1136  ??  I     7:08AM   0:00.00 /usr/local/bin/wvalarmd
    +    root     73882  0.0  0.1  2392 1504  ??  Is    7:08AM   0:00.01 /usr/local/bin/wvcwopd
    +    root     73884  0.0  0.1  2304 1136  ??  I     7:08AM   0:00.00 /usr/local/bin/wvcwopd
    +    root     73885  0.0  0.3  4944 3100  ??  Is    7:08AM   0:00.03 /usr/local/bin/wvhttpd
    +    root     73886  0.0  0.2  4788 2216  ??  I     7:08AM   0:00.00 /usr/local/bin/wvhttpd
    +    root@localhost:/root# ps aux | grep htmlgen
    +    root     73876  0.0  0.3  3960 2800  ??  Is    7:08AM   0:05.82 /usr/local/bin/htmlgend
    +    root     73878  0.0  0.2  3860 1964  ??  I     7:08AM   0:00.00 /usr/local/bin/htmlgend
    +    root@localhost:/root#
    +    
    +    This indicates I have 6 wview daemons running: wviewd, htmlgend, wviewsqld,
    +    wvalarmd, wvcwopd and wvhttpd. It also displays the radlib message router
    +    process radmrouted.
    +
    +24.2  SYS V IPC (Interprocess Communication)
    +
    +   wview uses many radlib facilities which are based on the system V IPC 
    +   concepts of shared memory and semaphores.
    +   
    +   Here is a listing of IPC objects while wview is running (FreeBSD):
    +
    +    root@localhost:/root# ipcs
    +    Message Queues:
    +    T     ID     KEY        MODE       OWNER    GROUP
    +    
    +    Shared Memory:
    +    T     ID     KEY        MODE       OWNER    GROUP
    +    m 4128768     126979 --rw-rw-r--     root    wheel
    +    m 4128769     126977 --rw-rw-r--     root    wheel
    +    m 4128770  267386882 --rw-rw-r--     root    wheel
    +    
    +    Semaphores:
    +    T     ID     KEY        MODE       OWNER    GROUP
    +    s 4128768  267386881 --rw-r--r--     root    wheel
    +    s 4128769     126978 --rw-r--r--     root    wheel
    +    root@localhost:/root#
    +    
    +    And, after running /etc/rc.d/wview stop:
    +    
    +    root@localhost:/root# /etc/rc.d/wview stop
    +    Shutting down weather daemons...
    +    root@localhost:/root# ipcs
    +    Message Queues:
    +    T     ID     KEY        MODE       OWNER    GROUP
    +    
    +    Shared Memory:
    +    T     ID     KEY        MODE       OWNER    GROUP
    +    
    +    Semaphores:
    +    T     ID     KEY        MODE       OWNER    GROUP
    +    root@localhost:/root#
    +
    +    Linux systems tend to have other IPC objects defined - you do not want to 
    +    disturb those. The wview objects are discernable by the "KEY" values:
    +    126977 (0x1F001) - 126979 (0x1F003)             (wview-specific) 
    +    267386881 (0xFF00001) - 267386882 (0xFF00002)   (radlib general)
    +
    +    If, after shutting down wview, any of the IPC objects associated with wview
    +    (or radlib) still remain, you will need to remove them manually using the
    +    "ipcrm" command. This is normally caused by failed wview starts when 
    +    initially installing wview with improper configuration or by a faulty wview
    +    start/stop script which is leaving wview processes running.
    +
    +24.3  wview Startup
    +
    +   wview generates a number of log messages, in particular during 
    +   initialization. The system log file is normally /var/log/messages. I 
    +   typically run "tail -f /var/log/messages" in a separate console when 
    +   starting up wview. You should also when you first install it. Informational 
    +   logs can later be disabled by editing wview-conf.sdb then sending a HUP signal
    +   to wviewd and htmlgend: (kill -s HUP `cat $prefix/var/wview/wviewd.pid`) and 
    +   (kill -s HUP `cat $prefix/var/wview/htmlgend.pid`) respectively.
    +   
    +   If you have errors in your startup log, you will need to stop all wview 
    +   daemons using the wview start script (/etc/init.d/wview stop). These are 
    +   independent processes which will not shut down automatically if there is
    +   an error in one of them. You must use the wview script.
    +   
    +   Here is what the log file looks like on my server when wview starts up, so 
    +   you know what a good start looks like:
    +
    +Apr 18 08:48:44 linuxquad radmrouted[11676]: <1240062524756> : radlib: radmrouted started as a daemon ...
    +Apr 18 08:48:44 linuxquad radmrouted[11676]: <1240062524764> : started on radlib system 1, workdir /usr/local/var/wview
    +Apr 18 08:48:44 linuxquad radmrouted[11676]: <1240062524764> : running...                                              
    +Apr 18 08:48:45 linuxquad wviewd[11680]: <1240062525759> : radlib: wviewd started as a daemon ...                      
    +Apr 18 08:48:45 linuxquad wviewd[11680]: <1240062525759> : wview 5.4.0 starting ...                                    
    +Apr 18 08:48:45 linuxquad wviewd[11680]: <1240062525777> : station interface: serial ...                               
    +Apr 18 08:48:45 linuxquad wviewd[11680]: <1240062525777> : Rain Season Start Month set to 1                            
    +Apr 18 08:48:45 linuxquad wviewd[11680]: <1240062525777> : Rain Storm Start Trigger set to  0.05 in/hr                 
    +Apr 18 08:48:45 linuxquad wviewd[11680]: <1240062525778> : Rain Storm Stop Time set to 12 hours                        
    +Apr 18 08:48:45 linuxquad wviewd[11680]: <1240062525778> : Rain YTD preset set to 0.00 inches                          
    +Apr 18 08:48:45 linuxquad wviewd[11680]: <1240062525778> : ET YTD preset set to 0.000 inches                           
    +Apr 18 08:48:45 linuxquad wviewd[11680]: <1240062525778> : bad rain/ET YTD Year given, disabling...                                                           
    +Apr 18 08:48:45 linuxquad wviewd[11680]: <1240062525778> : station polling interval set to 30 seconds                                                         
    +Apr 18 08:48:45 linuxquad wviewd[11680]: <1240062525837> : HILOW: database OK                                                                                 
    +Apr 18 08:48:45 linuxquad wviewd[11680]: <1240062525837> : HILOW: beginning normal LOOP operation                                                             
    +Apr 18 08:48:45 linuxquad wviewd[11680]: <1240062525837> : -- Station Init Start --                                                                           
    +Apr 18 08:48:45 linuxquad wviewd[11680]: <1240062525838> : station archive interval: 5 minutes                                                                
    +Apr 18 08:48:45 linuxquad wviewd[11680]: <1240062525838> : Simulator station opened: 720 minute data generation period...                                     
    +Apr 18 08:48:45 linuxquad wviewd[11680]: <1240062525839> : running...                                                                                         
    +Apr 18 08:48:45 linuxquad wviewd[11680]: <1240062525839> : station location: elevation: 751 feet                                                              
    +Apr 18 08:48:45 linuxquad wviewd[11680]: <1240062525839> : station location: latitude: 33.6 N  longitude: 96.9 W                                              
    +Apr 18 08:48:45 linuxquad wviewd[11680]: <1240062525839> : initializing computed data values...                                                               
    +Apr 18 08:48:45 linuxquad wviewd[11680]: <1240062525841> : computeDataAllTime: 200904                                                                         
    +Apr 18 08:48:45 linuxquad wviewd[11680]: <1240062525846> : computeDataYear: 200904                                                                            
    +Apr 18 08:48:45 linuxquad wviewd[11680]: <1240062525851> : computeDataMonth: 200904                                                                           
    +Apr 18 08:48:45 linuxquad wviewd[11680]: <1240062525856> : computeDataWeek: 20090414                                                                          
    +Apr 18 08:48:46 linuxquad wviewd[11680]: <1240062526054> : -- Station Init Complete --                                                                        
    +Apr 18 08:48:46 linuxquad wviewd[11680]: <1240062526055> : newest archive record: 2009-04-14 22:40                                                            
    +Apr 18 08:48:46 linuxquad htmlgend[11686]: <1240062526745> : radlib: htmlgend started as a daemon ...                                                         
    +Apr 18 08:48:46 linuxquad wvalarmd[11691]: <1240062526746> : radlib: wvalarmd started as a daemon ...                                                         
    +Apr 18 08:48:46 linuxquad htmlgend[11686]: <1240062526746> : generating to /usr/local/var/wview/img                                                           
    +Apr 18 08:48:46 linuxquad htmlgend[11686]: <1240062526746> : templates at /usr/local/etc/wview/html                                                           
    +Apr 18 08:48:46 linuxquad htmlgend[11686]: <1240062526747> : !! Dual units will be displayed !!                                                               
    +Apr 18 08:48:46 linuxquad htmlgend[11686]: <1240062526747> : Tag Search red-black tree: max black node tree height: 7                                         
    +Apr 18 08:48:46 linuxquad wvalarmd[11691]: <1240062526747> : ALARM daemon not enabled - exiting...                                                            
    +Apr 18 08:48:46 linuxquad wvcwopd[11696]: <1240062526747> : radlib: wvcwopd started as a daemon ...                                                           
    +Apr 18 08:48:46 linuxquad wvcwopd[11696]: <1240062526748> : CWOP daemon is NOT enabled - exiting...                                                           
    +Apr 18 08:48:46 linuxquad wviewftpd[11701]: <1240062526749> : radlib: wviewftpd started as a daemon ...                                                       
    +Apr 18 08:48:46 linuxquad wviewftpd[11701]: <1240062526750> : ftp daemon disabled - exiting...                                                                
    +Apr 18 08:48:46 linuxquad wviewsshd[11706]: <1240062526751> : radlib: wviewsshd started as a daemon ...                                                       
    +Apr 18 08:48:46 linuxquad wviewsshd[11706]: <1240062526752> : ssh daemon disabled - exiting...                                                                
    +Apr 18 08:48:46 linuxquad wvpmond[11711]: <1240062526753> : radlib: wvpmond started as a daemon ...                                                           
    +Apr 18 08:48:46 linuxquad wvpmond[11711]: <1240062526829> : PMON: wviewd: 0                                                                                   
    +Apr 18 08:48:46 linuxquad wvpmond[11711]: <1240062526829> : PMON: wviewd process monitoring is disabled                                                       
    +Apr 18 08:48:46 linuxquad wvpmond[11711]: <1240062526829> : PMON: htmlgend: 0                                                                                 
    +Apr 18 08:48:46 linuxquad wvpmond[11711]: <1240062526829> : PMON: htmlgend process monitoring is disabled                                                     
    +Apr 18 08:48:46 linuxquad wvpmond[11711]: <1240062526829> : PMON: wvalarmd: 120                                                                               
    +Apr 18 08:48:46 linuxquad wvpmond[11711]: <1240062526829> : PMON: pid file /usr/local/var/wview/wvalarmd.pid not present, disable monitoring...               
    +Apr 18 08:48:46 linuxquad wvpmond[11711]: <1240062526829> : PMON: wvcwopd: 120                                                                                
    +Apr 18 08:48:46 linuxquad wvpmond[11711]: <1240062526829> : PMON: pid file /usr/local/var/wview/wvcwopd.pid not present, disable monitoring...                
    +Apr 18 08:48:46 linuxquad wvpmond[11711]: <1240062526829> : PMON: wvhttpd: 120                                                                                
    +Apr 18 08:48:46 linuxquad wvpmond[11711]: <1240062526829> : PMON: pid file /usr/local/var/wview/wvhttpd.pid not present, disable monitoring...                
    +Apr 18 08:48:46 linuxquad wvpmond[11711]: <1240062526829> : PMON: wviewsqld: 600                                                                              
    +Apr 18 08:48:46 linuxquad wvpmond[11711]: <1240062526829> : PMON: pid file /usr/local/var/wview/wviewsqld.pid not present, disable monitoring...              
    +Apr 18 08:48:46 linuxquad wvpmond[11711]: <1240062526829> : running...                                                                                        
    +Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527294> : running...                                                                                       
    +Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527294> : received station info from wviewd: 20090414 22:40:00                                             
    +Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527298> : htmlmgrInit: 51 built-in image definitions added                                                 
    +Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527306> : htmlmgrInit: 0 user image definitions added                                                      
    +Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527314> : htmlmgrInit: 14 templates added                                                                  
    +Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527314> : htmlmgrInit: forecast html tags are disabled - /usr/local/etc/wview/forecast.conf not found...   
    +Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527316> : initializing barometric pressure trend                                                           
    +Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527316> : initializing historical stores (this may take some time...)                                      
    +Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527316> : htmlHistoryInit: DAY: samples=0                                                                  
    +Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527325> : htmlHistoryInit: DAY: samples=50
    +Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527333> : htmlHistoryInit: DAY: samples=100
    +Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527341> : htmlHistoryInit: DAY: samples=150
    +Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527349> : htmlHistoryInit: DAY: samples=200
    +Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527357> : htmlHistoryInit: DAY: samples=250
    +Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527364> : htmlHistoryInit: DAY: samples=288
    +Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527364> : htmlHistoryInit: WEEK: samples=0
    +Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527372> : htmlHistoryInit: WEEK: samples=50
    +Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527393> : htmlHistoryInit: WEEK: samples=100
    +Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527401> : htmlHistoryInit: WEEK: samples=150
    +Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527404> : htmlHistoryInit: WEEK: samples=168
    +Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527404> : htmlHistoryInit: MONTH: samples=0
    +Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527412> : htmlHistoryInit: MONTH: samples=50
    +Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527421> : htmlHistoryInit: MONTH: samples=100
    +Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527429> : htmlHistoryInit: MONTH: samples=150
    +Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527437> : htmlHistoryInit: MONTH: samples=200
    +Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527445> : htmlHistoryInit: MONTH: samples=250
    +Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527453> : htmlHistoryInit: MONTH: samples=300
    +Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527461> : htmlHistoryInit: MONTH: samples=350
    +Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527470> : htmlHistoryInit: MONTH: samples=400
    +Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527478> : htmlHistoryInit: MONTH: samples=450
    +Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527486> : htmlHistoryInit: MONTH: samples=500
    +Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527494> : htmlHistoryInit: MONTH: samples=550
    +Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527515> : htmlHistoryInit: MONTH: samples=600
    +Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527523> : htmlHistoryInit: MONTH: samples=650
    +Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527527> : htmlHistoryInit: MONTH: samples=672
    +Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527550> : htmlHistoryInit: YEAR: samples=0
    +Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527578> : htmlHistoryInit: YEAR: samples=50
    +Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527607> : htmlHistoryInit: YEAR: samples=100
    +Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527635> : htmlHistoryInit: YEAR: samples=150
    +Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527664> : htmlHistoryInit: YEAR: samples=200
    +Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527692> : htmlHistoryInit: YEAR: samples=250
    +Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527721> : htmlHistoryInit: YEAR: samples=300
    +Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527749> : htmlHistoryInit: YEAR: samples=350
    +Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527769> : htmlHistoryInit: storing day history for Tue Apr 14 00:05:00 2009
    +Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527783> : htmlHistoryInit: YEAR: samples=365
    +Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527784> : HILOW: OK
    +Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527785> : NOAA DB: OK
    +Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527785> : NOAA: initilizing reports (this may take some time...)
    +Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527785> : NOAA DB: syncing 19700101 => 20090417
    +Apr 18 08:49:01 linuxquad htmlgend[11686]: <1240062541977> : NOAA DB: done: 4 HILOW records => 1 NOAA records
    +Apr 18 08:49:02 linuxquad htmlgend[11686]: <1240062542006> : NOAA Generate: creating monthly NOAA reports starting 200904
    +Apr 18 08:49:02 linuxquad htmlgend[11686]: <1240062542029> : NOAA Generate: creating yearly NOAA reports starting 2009
    +Apr 18 08:49:02 linuxquad htmlgend[11686]: <1240062542033> : ARCREC: initializing archive browser files (this may take some time...)
    +Apr 18 08:49:03 linuxquad htmlgend[11686]: <1240062543000> : starting html generation in 1 mins 7 secs
    +Apr 18 08:49:03 linuxquad htmlgend[11686]: <1240062543000> : doing initial html generation now...
    +Apr 18 08:49:03 linuxquad htmlgend[11686]: <1240062543113> : Generated: 113 ms: 51 images, 14 template files
    +
    +
    +24.4  Log Verbosity During Normal Operation
    +
    +   Configuration Parameter: STATION_VERBOSE_MSGS in wview-conf.sdb
    +
    +   Log Verbosity refers to status level logs - "normal" operation informational 
    +   messages. These can be quite interesting when first starting wview or after 
    +   an upgrade. But when enabled it can generate a bit of log file volume.
    +
    +   VERBOSE_MSGS controls the default state for all wview daemons at startup.
    +
    +   While wview is running, you can toggle the log verbosity for a given wview
    +   daemon by sending a HUP signal to that process (this also causes a reload of
    +   image/html configuration for htmlgend). Below are the suitable shell commands
    +   for each daemon:
    +
    +    kill -s HUP `cat $prefix/var/wview/wviewd.pid`
    +    kill -s HUP `cat $prefix/var/wview/htmlgend.pid`
    +    kill -s HUP `cat $prefix/var/wview/wviewftpd.pid`
    +    kill -s HUP `cat $prefix/var/wview/wviewsshd.pid`
    +    kill -s HUP `cat $prefix/var/wview/wvalarmd.pid`
    +    kill -s HUP `cat $prefix/var/wview/wvcwopd.pid`
    +    kill -s HUP `cat $prefix/var/wview/wvhttpd.pid`
    +
    +
    +24.5  Vantage Pro
    +
    +24.5.1  Wireless Reception
    +
    +   If you are missing archive records, receiving bad archive records, or your 
    +   current conditions values don't update as expected, you may have problems 
    +   with the wireless reception from the Vantage Pro Integrated Sensor Suite 
    +   (ISS). If you have version 1.7.2 or later of wview, you can access a chart 
    +   which displays the percentage of successful packets received at: 
    +   http://[your_weather_site_url]/rxcheck.png.
    +   If the graph displays points equal to zero or at very low values, you should 
    +   investigate why your wireless reception is so poor. Possible solutions 
    +   include: changing the gain on your VP Console, moving your VP Console to 
    +   improve reception or perhaps moving your ISS for better reception.
    +
    +
    +24.5.2  Serial Interface Problems
    +
    +   If you see a log message similar to:
    +
    +daemonStartProcState: lat and long failed
    +
    +   or periodic logs similar to:
    +
    +wakeupConsole: Read ERROR!
    +
    +   or:
    +
    +wakeupConsole: Invalid data: FE 13     (data values "FE 13" will vary)
    +
    +   you have a serial interface problem. The best test is to stop wview if it is 
    +   running and use vpconfig to verify your serial interface.
    +   
    +   You should be able to run "vpconfig [device] show" repeatedly without any 
    +   failures being reported (see section 15.6 for vpconfig usage).
    +   
    +   Possible culprits are:
    +   
    +   Bad serial cable
    +   Bad serial port on your PC
    +   Incorrect terminal server settings (when using a terminal server)
    +   Improper USB driver installation or operation (if using USB and driver)
    +
    +   wview will not work properly without a reliable serial interface.
    +
    +24.6  WXT510
    +
    +24.6.1  Comm Problems
    +
    +   If you see error messages like "NMEA: nmea0183Init: XXX failed!" when 
    +   starting wview, it is a good indication that the comm settings on the WXT510
    +   have not been set correctly. Be sure you run wxt510config BEFORE your first
    +   wview start. wxt510config searches through all possible comm setting 
    +   combinations until it "fimds" the WXT510, then it sets the comm parameters
    +   properly for wview.
    +
    +24.6.2  Sensor Problems
    +
    +   If you see an error similar to:
    +   "NMEA: nmea0183GetReadings: 0x7FC1: not all sensors updated..."
    +   then there is the possibility that the THP sensor connector header under the
    +   very top of the WXT510 is not making good contact. You can remove the long 
    +   screws which hold the entire station together then remove the 3 very small
    +   screws which retain the upper PCB cover. Take care to mate the small header 
    +   in the middle well then hold the cover in place while screwing the retaining
    +   screws back in. Make sure you do not dislodge the ribbon cable connections.
    +
    +24.7  Using Extended Sensors
    +
    +   If you are using extended sensors for your installation be sure that you do 
    +   not have invalid (not installed) sensor data being generated by htmlgend.
    +   Check your images.conf file for any enabled sensors which you do not have.
    +   Uninitialized sensor data can cause htmlgend to crash.
    +
    +24.8  Using raddebug
    +
    +   raddebug is a general debug utility which is installed when you install radlib.
    +   It can be used to display semaphore usage, buffer usage and radmrouted 
    +   statisitics:
    +   
    +       # raddebug 1 $prefix/var/wview
    +
    +   It will output something like:
    +
    +Attached to radlib system 1: UP 0 years, 0 months, 0 days, 5 hours, 17 minutes, 26 seconds
    +
    +Buffer Allocation by Size:
    +Dumping index 0: size 64: Free/Total 64/64
    +Dumping index 1: size 128: Free/Total 128/128
    +Dumping index 2: size 256: Free/Total 244/256
    +Dumping index 3: size 512: Free/Total 256/256
    +Dumping index 4: size 1024: Free/Total 128/128
    +Dumping index 5: size 2048: Free/Total 64/64
    +Dumping index 6: size 4096: Free/Total 32/32
    +
    +Buffer Summary:
    +        Total Free: 916
    +        Total Allocated: 12
    +        Total Allocations Since Started: 121628
    +
    +Semaphore Info:
    +INDEX   COUNT  WAITERS  ZCNT   PID
    +  0       0      0        0     0
    +  1       0      0        0     0
    +  2       1      0        0     63615
    +  3       0      0        0     0
    +  4       1      0        0     61597
    +  5       0      0        0     0
    +  6       0      0        0     0
    +  7       0      0        0     0
    +  8       0      0        0     0
    +  9       0      0        0     0
    + 10       0      0        0     0
    + 11       0      0        0     0
    + 12       0      0        0     0
    + 13       0      0        0     0
    + 14       0      0        0     0
    + 15       0      0        0     0
    +
    +raddebug[63615]: <3310611880> : radlib: raddebug started ...
    +Dumping message router stats to the system log file...
    +
    +   Then view the message router (radmrouted) statistics:
    +
    +       # tail -n 40 -f /var/log/messages
    +
    +   With output like:
    +
    +raddebug[63615]: <3310611880> : radlib: raddebug started ...
    +radmrouted[61576]: <3310611917> : ---------- Message Router Totals:  TX:0000001851  RX:0000001662 ----------
    +radmrouted[61576]: <3310611917> :      Name       MSGS TX         MSGS RX          TXERRS          RXERRS
    +radmrouted[61576]: <3310611917> : -------------- ----------      ----------      ----------      ----------
    +radmrouted[61576]: <3310611917> : wviewd               1021             641               0               0
    +radmrouted[61576]: <3310611917> : wviewsqld               1              64               0               0
    +radmrouted[61576]: <3310611917> : wvalarmd                1             318               0               0
    +radmrouted[61576]: <3310611917> : htmlgend              637             700               0               0
    +radmrouted[61576]: <3310611917> : wvcwopd                 1              64               0               0
    +radmrouted[61576]: <3310611917> : wvhttpd               1              64               0               0
    +radmrouted[61576]: <3310611917> : raddebug                0               0               0               0
    +radmrouted[61576]: <3310611917> : --------------------------------------------------------------------------
    +
    +
    + + + + diff --git a/wview-Quick-Start-FreeBSD.html b/wview-Quick-Start-FreeBSD.html new file mode 100755 index 0000000..f02791f --- /dev/null +++ b/wview-Quick-Start-FreeBSD.html @@ -0,0 +1,127 @@ + + + +wview Quick Start Guide For FreeBSD + + + + + +

    wview Quick Start Guide For FreeBSD >= 8.0

    +

    Feb. 16, 2011

    + +

    +

    Purpose

    + This guide will provide the basic procedure for a "standard" wview install. + Detailed description of the steps, advanced configuration of features and + troubleshooting tips are found in the + wview User Manual. +

    + +

    +

    Prerequisites

    + The following packages should be installed. Become root or use sudo.
    +

    Libraries

    +
      +
    • + gmake - GNU make
      + > pkg_add -r gmake +
    • +
    • + libgd2 - graphics drawing library + http://www.boutell.com/gd/
      + > pkg_add -r gd
      +
    • +
    • + readline - GNU library to edit command lines
      + > pkg_add -r readline
      +
    • +
    • + gawk - GNU awk
      + > pkg_add -r gawk +
    • +
    • + wget - ftp command line file fetcher
      + > pkg_add -r wget +
    • +
    • + libsqlite3 - SQLite3 database library
      + > pkg_add -r sqlite3
      +
    • +
    • + libcurl - "C" URL HTTP library - optional, required for Weather Underground - + http://curl.haxx.se/libcurl/
      + > pkg_add -r curl
      +
    • +
    • + radlib - rapid application development library - must be installed + with SQLite support (--enable-sqlite) - + http://www.radlib.teel.ws
      + Download version 2.10.1 or newer.
      + > wget http://downloads.sourceforge.net/radlib/radlib-2.10.1.tar.gz
      + > tar xzf radlib-X.Y.Z.tar.gz
      + > cd radlib-X.Y.Z
      + > ./configure --enable-sqlite
      + > sudo make install +
    • +
    +

    Optional Web-Based Configuration

    +
      +
    • + apache22 - HTTP Server
      + > pkg_add -r apache22 +
    • +
    • + PHP5 and PHP5-Sqlite - Scripting language with Sqlite3 support + > cd /usr/ports/databases/php5-sqlite; make install clean
      + (enable building the Apache module) +
    • +
    +

    System

    +
      +
    • + ntp/xntp - Network Time Protocol - a configured and functional NTP + daemon should be enabled on the host system to keep system and weather + station time accurate +
    • +
    • + apache - in order to serve your weather site to the world, + an http server is required - other http servers will work too. If you are + going to export your site to another server or to your ISP account site, + then an http server is not required on the wview server +
    • +
    • + php5 and php5-sqlite3 - optionally needed for browser-based + configuration (requires apache or similar) +
    • +
    • + Serial/Ethernet/USB port - an available interface port + is required to connect to the weather station +
    • +
    +

    Weather Station

    + See wview User Manual + for a current list of supported stations and special instructions. +

    + +

    +

    Procedure

    +
      +
    • Get the latest wview tarball:wget http://downloads.sourceforge.net/wview/wview-x.y.z.tar.gz
    • +
    • Extract the wview distribution: tar zxvf wview-x.y.z.tar.gz
    • +
    • Configure the distribution:
      + cd wview-x.y.z
      + export CFLAGS="-I/usr/local/include"; export LDFLAGS="-L/usr/local/lib";
      + ./configure
    • +
    • Build wview: make
    • +
    • Install wview: sudo make install
    • +
    • See wview-User-Manual.html#StationConfig + for station setup.
    • +
    • See wview-User-Manual.html#Configuration + for configuration instructions.
    • +
    • See wview startup and run logs: tail -n 100 -f /var/log/messages
    • +

      + + + + diff --git a/wview-Quick-Start-MacOSX.html b/wview-Quick-Start-MacOSX.html new file mode 100755 index 0000000..40bc47a --- /dev/null +++ b/wview-Quick-Start-MacOSX.html @@ -0,0 +1,170 @@ + + + +wview Quick Start Guide for Macintosh + + + + + +

      wview Quick Start Guide for Macintosh

      +

      Oct. 20, 2009

      + +

      +

      Purpose

      + This guide will provide the basic procedure for a "standard" wview install. + Detailed description of the steps, advanced configuration of features and + troubleshooting tips are found in the + wview User Manual. Also, please see + the wview homepage for a detailed description of wview, the latest news + on releases, the wview User's Forum and other interesting links. +

      + +

      +

      Mac OSX Requirements

      +
        +
      • + xcode - development tools including gcc required to build + source packages - see + http://developer.apple.com/tools/xcode/ to download the dmg installer

        +
      • +
      • + MacPorts - package management system - used to install + the library prerequisites listed below - see + http://www.macports.org/ to download the latest version - + once installed, add /opt/local/bin to your path by adding the following line to + your ~/.profile file: "export PATH=$PATH:/opt/local/bin" - then execute the following:

        + sudo port -d selfupdate (this may take a while)
        + Note: All wview binaries and scripts will be found at /opt/local/bin instead of + /usr/local/bin, so all instructions in the User Manual should have "/usr/local" + replaced with "/opt/local" for Mac installs

        +
      • +
      +

      System Requirements

      +
        +
      • + Sufficient Kernel Share Memory - check the following line in the + /etc/rc file, making sure the two sizes (shmmax and shmall) are similar in magnitude + to this line:
        + sysctl -w kern.sysv.shmmax=536870912 kern.sysv.shmmin=1 kern.sysv.shmmni=32 kern.sysv.shmseg=8 kern.sysv.shmall=131072 +
      • +
      • + ntp/xntp - Network Time Protocol - a configured and functional NTP + daemon should be enabled on the host system to keep system and weather + station time accurate +
      • +
      • + apache - in order to serve your weather site to the world, + an http server is required - other http servers will work too. If you are + going to export your site to another server or to your ISP account site, + then an http server is not required on the wview server +
      • +
      • + php5 and php5-sqlite3 - optionally needed for browser-based + configuration (requires apache or similar) +
      • +
      • + Serial/Ethernet/USB port - an available interface port + is required to connect to the weather station +
      • +
      +

      Weather Station - one of the following:

      +
        +
      • + Vantage Pro or Vantage Pro2 Weather Station with Console
        +
          +
        • + WeatherLink for Vantage Pro or Pro2, Macintosh Version - + the java software is not required, but this is the only way to obtain the + USB-to-Serial convertor, driver and serial data logger

          + Optionally, you can use the windows version with the serial data logger if + you obtain separately a USB-to-Serial convertor with a MacOSX compatible + driver - the Prolific chipset PL-2303 and + driver + have been confirmed to work with wview on a Mac Mini - Note: USB data + loggers are NOT supported by MacOSX + (in particular, there is no CP2101 driver for Darwin/FreeBSD) - unless you + have a serial port on your Mac, you must use a USB-to-Serial adapter connected + to a Weatherlink serial data logger (or just use the adapter provided with the + Macintosh version of Weatherlink) +
        • +
        +
      • +
      • + Vaisala WXT-510
        +
      • +
      • + La Crosse WS-2300 Series
        +
      • +
      • + Oregon Scientific WMR918 Series
        +
      • +
      • + Station Simulator (for demos and testing)
        +
      • +
      +

      + +

      +

      Install Procedure

      +
      +      Note: execute these commands in a terminal session while logged into an account 
      +            with administration privileges - the sudo command requires you to enter your 
      +            password before it will execute the command following it.
      +      
      +      1)  Install wview directly from MacPorts: 
      +
      +          sudo port install wview
      +      
      +      2)  If a new Vantage Pro station, use the on-screen console configuration 
      +          wizard
      +      
      +      3)  Configure wview: 
      +
      +          Note: Refer to Chapter 6 of the wview User Manual for new browser-based
      +                configuration options.
      +
      +          > sudo /opt/local/bin/wviewconfig
      +
      +      4)  Configure wview HTML Templates:
      +
      +          > sudo /opt/local/bin/wviewhtmlconfig
      +
      +      5)  Run wview: 
      +
      +          > sudo /sbin/SystemStarter start "wview"
      +          
      +          Note: You can stop or restart wview as follows:
      +          sudo /sbin/SystemStarter stop "wview"
      +          sudo /sbin/SystemStarter restart "wview"
      +      
      +      6)  See wview startup and run logs:
      +
      +          > sudo tail -n 100 -f /var/log/system.log
      +          
      +          Note: Unless you configured wview to include ftp, ssh, alarms or CWOP 
      +                support, these daemons will start up, determine they are disabled, 
      +                then exit. This is normal behavior. 
      +
      +      7)  Confirm proper operation:
      +
      +          > sudo ps aux | grep wv
      +          (you should see at least "wviewd_[station]" - maybe others if you enabled them)
      +
      +          > sudo ps aux | grep htmlgend
      +          (you should see "htmlgend")
      +
      +          You should see the log message "doing initial html generation now..." 
      +          at the end of the wview initialization log messages.
      +
      +          > ls -al /opt/local/var/wview/img/index.html
      +          (you should see the file listed)
      +
      +          Open /opt/local/var/wview/img/index.html with your favorite browser - this is your 
      +          new wview homepage.
      +    
      +

      + + + + diff --git a/wview-Quick-Start.html b/wview-Quick-Start.html new file mode 100755 index 0000000..6b4f557 --- /dev/null +++ b/wview-Quick-Start.html @@ -0,0 +1,137 @@ + + + +wview Quick Start Guide + + + + + +

      wview Quick Start Guide

      +

      Feb. 16, 2011

      + +

      +

      Purpose

      + This guide will provide the basic procedure for a "standard" wview install. + Detailed description of the steps, advanced configuration of features and + troubleshooting tips are found in the + wview User Manual. +

      + +

      +

      Prerequisites

      + The following package installations should be verified on your system. If a + package requires installation, you should use the package management utility + appropriate for your OS and distribution. Examples are:
      +
      +FreeBSD           /usr/ports
      +Fedora Core       yum
      +SuSE              yast (or apt4rpm)
      +Gentoo            emerge
      +Debian            apt
      +    
      +
      +Note: Some of these libraries may be represented as "-devel" or similar in your 
      +    package management utility, i.e., "libcurl-devel". If they are, you should install
      +    the devel version in order to get the development libraries that radlib/wview needs.
      +
      +

      Libraries

      +
        +
      • + libz - compression library - normally included in OS distribution +
      • +
      • + libpng - png image library - normally included in OS distribution +
      • +
      • + libreadline5-dev - normally included in OS distribution +
      • +
      • + gawk - GNU awk - normally included in OS distribution +
      • +
      • + libgd2 - graphics drawing library - may require installation - + http://www.boutell.com/gd/ +
      • +
      • + libsqlite3 - SQLite3 database library +
      • +
      • + radlib - rapid application development library - must be installed + with SQLite support (--enable-sqlite) - + http://www.radlib.teel.ws +
      • +
      • + libcurl - "C" URL HTTP library - optional, required for Weather Underground - + http://curl.haxx.se/libcurl/ +
      • +
      +

      System

      +
        +
      • + ntp/xntp - Network Time Protocol - a configured and functional NTP + daemon should be enabled on the host system to keep system and weather + station time accurate +
      • +
      • + apache - in order to serve your weather site to the world, + an http server is required - other http servers will work too. If you are + going to export your site to another server or to your ISP account site, + then an http server is not required on the wview server +
      • +
      • + php5 and php5-sqlite3 - optionally needed for browser-based + configuration (requires apache or similar) +
      • +
      • + Serial/Ethernet/USB port - an available interface port + is required to connect to the weather station +
      • +
      +

      Weather Station - one of the following:

      +
        +
      • + Vantage Pro or Vantage Pro2 Weather Station with Console
        +
          +
        • + WeatherLink for Vantage Pro or Pro2, Windows, Serial or USB or IP - + the windows software is not required, but this is the only way to obtain the + serial or USB data logger +
        • +
        +
      • +
      • + Vaisala WXT-510
        +
      • +
      • + La Crosse WS-2300 Series
        +
      • +
      • + Oregon Scientific WMR918 Series
        +
      • +
      • + Station Simulator (for demos and testing)
        +
      • +
      +

      + +

      +

      Procedure

      +
        +
      • Extract the wview distribution: tar zxvf wview-x.y.z.tar.gz
      • +
      • Configure the distribution: cd wview-x.y.z; ./configure
      • +
      • Build wview: make
      • +
      • Install wview: sudo make install
      • +
      • If new Vantage Pro, use the on-screen console configuration wizard
      • +
      • If new Vantage Pro, configure the console: /usr/local/bin/vpinstall
      • +
      • Configure wview: $prefix/bin/wviewconfig
      • +
      • Configure wview HTML Templates: $prefix/bin/wviewhtmlconfig
      • +
      • If new install, copy the run script: cp ./examples/[your_distro]/wview /etc/[your_distro_rc.d_dir]
      • +
      • If new install, make the run script executable: chmod +x /etc/[your_distro_rc.d_dir]/wview
      • +
      • Run wview: $prefix/etc/[your_distro_rc.d_dir]/wview start
      • +
      • See wview startup and run logs: tail -n 100 -f /var/log/syslog
      • +

        + + + + diff --git a/wview-User-Manual.html b/wview-User-Manual.html new file mode 100755 index 0000000..1a39647 --- /dev/null +++ b/wview-User-Manual.html @@ -0,0 +1,5182 @@ + + + + + wview User Manual + + + + + + + + + +
        + + wview Logo + +
        + + +

        + +  wview User Manual     +   March 5, 2014

        + + +

        +  Table of Contents +

        + + + + +
        + + + +
        + + +

        +  Chapter 1: Introduction +

        + + + + +

        +  What is wview? +

        + + +

        + wview is a collection of linux/unix daemons which interface with a supported + weather station to retrieve archive records (if generated by the station) and + current conditions. If the station does not generate archive records internally, + wview will auto-generate archive records based on the sensor readings collected + for that interval. The archive records and High/Low (HILOW) data are stored in + SQLite3 databases. At a configurable interval, wview will utilize the archive + history and current conditions to generate weather images (buckets, dials and + graphs) and HTML web pages based on user-configurable HTML templates. XML, PHP, + Perl, Python, etc. template files can also be defined using wview meta-tags which + will be replaced with current values when generated by wview. +

        + +

        + Features:
        +

        +
          +
        • 24x7x365 reliability.
        • +
        • Fast image and HTML/XML file generation.
        • +
        • Non-GUI, headless, lightweight (size and resources).
        • + +
        • Embeddable - can be deployed on low-power embedded systems such as the Linksys + NSLU2.
        • +
        • Multi-Lingual - HTML/XML templates, labels and text.
        • +
        • US (Imperial) or Metric Units - can be easily configured for metric or US units + of measure.
        • +
        • SQLite Archive Storage - archive data is stored in an SQLite3 relational database.
        • +
        • Remote Upload - web pages and images can be transferred to a remote web server or other + dedicated server + via an ftp or secure ssh process included with wview.
        • +
        • Alarms - the wview alarm daemon wvalarmd can be enabled to deliver current + conditions to TCP socket clients as a near real-time data feed engine. wvalarmd + can also be configured to function as a weather data alarm generator to user + specified scripts or binaries.
        • + +
        • CWOP - can be configured to submit data to CWOP.
        • +
        • Wunderground - can be configured to submit data to Wunderground.
        • +
        • Awekas - can be configured to submit data to Awekas.
        • +
        • Weatherforyou - can be configured to submit data to Weatherforyou.
        • +
        • RSS Feeds - processes XML template files and includes a default weather data + RSS feed template.
        • +
        + + + + +

        +  System Requirements +

        + + +

        + wview should work on any system with a unix or unix derivative operating system. + These include: linux, Mac OSX (Darwin), FreeBSD, NetBSD, Solaris. Your platform + should support ethernet and either RS-232 or USB for the station interface. +

        + +
          +
        • + ntp/xntp - Network Time Protocol - a configured and functional NTP + daemon should be enabled on the host system to keep system and weather + station time accurate and in sync. +
        • +
        • + apache - in order to serve your weather site to the world, + an http server is required - other http servers will work too. If you are + going to export your site to another server or to your ISP account site, + then an http server is not required on the wview server. +
        • +
        • + + php5 and php5-sqlite - optionally needed for browser-based + configuration (requires apache or similar). +
        • +
        • + Serial/Ethernet/USB port - an available interface port + is required to connect to the Weather Station.

          +
            +
          • Serial
          • + +
          • Ethernet - a terminal server or ethernet + to serial server or WeatherlinkIP datalogger is required - + set up as decribed above for the serial port and configured in transparent + data mode (no control character processing) with no packet delimiter. + wview allows you to specify the hostname and port of your serial server or WeatherlinkIP datalogger. + The Neteon + GW21E, Lantronix MSS1-T + and Xyplex Maxserver 1600 have been confirmed to work with wview. See + the Advanced Topics section for configuration + notes on the GW21E and the MSS1-T.
          • +
          • USB
          • +
          +
        • +
        + + + +

        +  Where to Get wview +

        + + +

        + wview and some utility install and upgrade scripts can be obtained from the + + + wview Sourceforge Homepage. +

        + + + +

        +  Getting Help and Reporting Problems +

        + + + +

        + The wview user's group is hosted on Google Groups: + + wview User's Group +

        + To report problems or vote on enhancements you can use the + + + wview Mantis Database. +

        +

        + + +

        +  Chapter 2: Installation +

        + + + + +

        +  Overview +

        + + +

        + To use wview, you must: +

          + +
        • Obtain a binary package for your operating system, or
        • +
        • Obtain the source and build wview for your operating system.
        • +
        + Currently the binary distributions available are:
        +
          +
        • MacPorts for OSX
        • +
        • RedHat/Fedora repositories
        • + +
        • Debian Packages (radlib and wview) via the wviewweather.com repository
        • +
        + If your platform is not one of these or you want to install from source, + you will need to know where to get the latest version of wview and how + to install it.

        + This chapter shows you how to obtain source and binary packages and how to + build wview from source should you choose to do so.

        + The following are the general steps you would use:
        +
          +
        1. Download the relevant package for your needs, e.g. source or binary + distribution.
        2. + +
        3. Build the source into a binary, if you have downloaded the source. + This may involve building and/or installing other necessary packages.
        4. +
        5. Install the binaries into their final destinations.
        6. +

        +

        + Run Environment:
        + There are several configuration variables which affect the location of the + wview run environment. "--prefix=xxx" (defaults to /usr/local for source + builds) is the top-level root for wview install. If "--sysconfdir=xxx" is + given to the configure script, it will override the $prefix value for + location of the configuration files. If "--localstatedir=xxx" is given, it + will override the $prefix value for location of the data files for wview.
        + Most source installs will want to use the default prefix "/usr/local" (by + not specifying any location parameters to configure).

        + + There are two file system installation locations of importance for wview. + The configuration "tree" is found at: $prefix/etc/wview and the data "tree" + is located at: $prefix/var/wview. The configuration tree includes the + SQLite3 configuration database, file generation configuration including + templates and several file to support SQL export activities. The data tree + contains the generated web site (img), all archived weather data (archive) + and NOAA files.
        + Each installation section below will indicate the default locations for + these trees. +

        + + + +

        +  Debian/ubuntu/kubuntu +

        + + + +

        + The Debian Advanced Packaging Tool (APT) is the most comprehensive and + mature unix package management system in existence. APT is the compelling + reason many linux users (such as me) use Debian or a Debian derivative for + their linux distribution of choice.
        + radlib and wview are now available in the wviewweather.com APT repository.
        + You may also install radlib and wview from source.
        +

        + +

        + +  APT Installation +

        +

        + The easiest method to install wview on a debian or ubuntu/kubuntu + server is to use the debian packages provided by the wviewweather.com + APT repository: +

          +
        1. Architectures supported: i386, amd64, armel, powerpc, armhf
        2. +
        3. Add the wviewweather.com repository to your /etc/apt/sources.list:
          + Edit /etc/apt/sources.list and add the following lines to the bottom of + the file:

          + + + + + + +
          + For debian squeeze:
          + deb http://www.wviewweather.com/apt/squeeze squeeze main
          + deb-src http://www.wviewweather.com/apt/squeeze squeeze main

          + For debian wheezy:
          + deb http://www.wviewweather.com/apt/wheezy wheezy main
          + deb-src http://www.wviewweather.com/apt/wheezy wheezy main

          + For ubuntu/kubuntu lucid:
          + deb http://www.wviewweather.com/apt/lucid lucid main
          + deb-src http://www.wviewweather.com/apt/lucid lucid main

          + For ubuntu/kubuntu precise:
          + deb http://www.wviewweather.com/apt/precise precise main
          + deb-src http://www.wviewweather.com/apt/precise precise main

          + For ubuntu/kubuntu quantal:
          + deb http://www.wviewweather.com/apt/quantal quantal main
          + deb-src http://www.wviewweather.com/apt/quantal quantal main

          + For ubuntu/kubuntu raring:
          + deb http://www.wviewweather.com/apt/raring raring main
          + deb-src http://www.wviewweather.com/apt/raring raring main

          + For ubuntu/kubuntu saucy:
          + deb http://www.wviewweather.com/apt/saucy saucy main
          + deb-src http://www.wviewweather.com/apt/saucy saucy main +
          + +
        4. +
        5. Note: important messages are output as part of the wview package install + which might be hidden when using a GUI tool for APT such as synaptic. + The command line tool apt-get allows you to see all installation messages.
          + Use your favorite APT tool, such as apt-get, aptitude, synaptic, adept, + etc. to upgrade your package list and install wview:

          + + + + + + +
          + apt-get method:
          + #> sudo apt-get update
          + #> apt-cache search wview
          + (wview should be listed now)

          + #> sudo apt-get install wview
          + (this will install radlib and any other missing prerequisites)
          + (when done, wview will be fully installed, configured and running + as the station simulator)
          + (you can stop it any time using "sudo /etc/init.d/wview stop") +
          + +
        6. + +
        7. wview will be running the simulator once installation is complete. When + you are ready to "go live" with your station, use the "wviewcleardata" + utility to purge all simulated data:

          + + + + + +
          + #> sudo wviewcleardata
          + (this will purge all archive records, delete the HILOW, history and NOAA databases)
          + + (leaving a clean weather data environment for your station) +
          + + WARNING - take care using this utility, it does not backup data so you should + do that yourself prior to running it if there is any possibility you want + to recover/keep the old weather data! +
        8. +
        + That's it! It could not be easier and all tasks included in the source script + described below have been done for you. It has the added benefit of extremely + easy upgrades (the debian APT way). This is the recommended installation + method.

        + + + + + + +
        + wview Run Environment:
        + Configuration: /etc/wview
        + Data: /var/lib/wview
        + + wviewmgmt: /var/lib/wviewmgmt linked at /var/www/wviewmgmt
        + Weather web site: /var/lib/wview/img linked at /var/www/weather +
        +

        + Upgrading an APT Install

        + + + + + + + +
        + #> sudo apt-get update (this updates the package list)
        + #> sudo apt-get -u upgrade (this upgrades any out of date packages, + including wview or radlib)
        + (if you are asked if you want to replace certain "config" files, + it is safe to say "Y") +
        +

        + Converting a Source Install (tarball) to an APT Install

        + The debian packages install to different locations than the old default + /usr/local prefix. Avoid running "make uninstall" with the old distro + until you have secured your data. Make backups of your config and data + trees to somewhere safe - like your home directory.
        + For example:

        + + + + + + +
        + #> sudo mkdir -p ~/wview-save
        + #> sudo mkdir -p ~/wview-save/etc
        + #> sudo mkdir -p ~/wview-save/var
        + #> sudo /etc/init.d/wview stop
        + #> sudo rm -rf /usr/local/var/wview/archive/wview-history.sdb
        + #> sudo rm -rf /etc/init.d/wview
        + #> sudo cp -rf /usr/local/etc/wview ~/wview-save/etc
        + #> sudo cp -rf /usr/local/var/wview ~/wview-save/var
        + #> sudo rm -f /var/www/weather (this should be a symbolic link)
        + #> sudo rm -rf /var/www/wviewmgmt (this should be a symbolic link)
        + #> cd [radlib_old_source_location] (the old distro source directory, typically /usr/local/src/radlib-X.Y.Z)
        + #> sudo make uninstall
        + #> cd [wview_old_source_location] (the old distro source directory, typically /usr/local/src/wview-X.Y.Z)
        + #> sudo make uninstall
        + #> sudo apt-get install wview
        + (if it asks about replacing /etc/init.d/wview, say "Y")
        + (at this point the simulator will be configured and running)
        + #> sudo /etc/init.d/wview stop
        + #> sudo cp -f ~/wview-save/var/wview/archive/*.* /var/lib/wview/archive
        + #> sudo cp -rf ~/wview-save/etc/wview/* /etc/wview
        + #> sudo wviewconfig
        + (or use wviewmgmt to update the generation and html paths)
        + HTMLGEN_IMAGE_PATH - /var/lib/wview/img
        + HTMLGEN_HTML_PATH - /etc/wview/html
        + (You may need to update Alarm or FTP or SSH paths as well)
        + #> sudo /etc/init.d/wview start
        +
        +
        + + That should do it. Just don't use uninstall without securing your config + and database files first.

        + Removing or Purging APT Installs

        + You can remove the wview installation *without* removing your data and + configuration files:

        + + + + + +
        + + #> sudo apt-get remove wview +
        +
        + You can also purge everything including generated data and configuration:

        + + + + + + +
        + #> sudo apt-get --purge remove wview +
        +
        +

        + Just be aware that "purge" removes everything. It will prompt you first + to make sure that is what you want to do. It is pretty idiot-proof.
        +

        + +
        + + +

        +  Source Installation +

        +

        + The wview distribution includes a Debian install script and an upgrade + script which greatly simplify installing and keeping wview up to date + the old-fashioned way: from source. The latest versions of these scripts + can always be downloaded from Sourceforge:

        + + + + wview-install-debian
        + + wview-update +


        + There is no need to download or build wview or its prerequisites directly + as the install script will take care of all that for you.
        + + The wview-update script can be used for any unix system with tar, wget and + a reasonable build environment installed.

        + To use the wview-install-debian script: +

          +
        1. Download + wview-install-debian

        2. +
        3. Make the script executable: + + + + + + +
          + #> chmod +x wview-install-debian +
          +
          +
        4. +
        5. Execute the script with root permissions: + + + + + + +
          + #> sudo ./wview-install-debian +
          +
          +
        6. + +
        +

        + There will be a few choices to make along the way. Answer "yes" to any + package management questions.
        + The script will invoke wviewconfig and wviewhtmlconfig to do initial + configuration for your station, environment and desired features. These + will also require input in order to proceed.

        +

        + + + + + + + +
        + wview Run Environment:
        + Configuration: /usr/local/etc/wview
        + Data: /usr/local/var/wview
        + wviewmgmt: /usr/local/var/wviewmgmt linked at /var/www/wviewmgmt
        + Weather web site: /usr/local/var/wview/img linked at /var/www/weather +
        + + +

        + To update wview installations of version 5.4.0 or later: +

        +
          +
        1. Download + + wview-update

        2. +
        3. Make the script executable: + + + + + + +
          + #> chmod +x wview-update +
          +
          +
        4. +
        5. Stop wview: + + + + + + +
          + #> sudo /etc/init.d/wview stop +
          +
          +
        6. +
        7. Execute the script with root permissions: + + + + + +
          + + #> sudo ./wview-update +
          +
          +
        8. +
        9. Start wview: + + + + + + +
          + #> sudo /etc/init.d/wview start +
          +
          +
        10. +
        + + + + +

        +  Mac OSX +

        + + +

        + The MacPorts package management system for Mac OSX (Darwin) includes source + packages for radlib and wview. This greatly simplifies installing wview and + keeping it up to date.

        + + Note: For a step-by-step procedure to install wview and all prerequisites + (including configuration of apache2 and PHP) please see the excellent + tutorial:
        + + Mac OS X (10.6.x) Installation Instructions (Advanced)

        + To install wview: +

          +
        1. Install XCode for your OSX version: + + Download XCode

        2. + +
        3. Install MacPorts: + + Download MacPorts

        4. +
        5. Update the ports list: + + + + + + +
          + #> sudo port -d selfupdate +
          +
          +
        6. +
        7. Update your ports: + + + + + +
          + + #> sudo port upgrade outdated +
          +
          +
        8. +
        9. Install wview (and all prerequisites): + + + + + + +
          + #> sudo port install wview +
          +
          +
        10. +
        +

        + + This will install all prerequisites and wview.

        +

        + + + + + +
        + wview Run Environment:
        + Configuration: /opt/local/etc/wview
        + + Data: /opt/local/var/wview
        + wviewmgmt: /opt/local/var/wviewmgmt
        + Weather web site: /opt/local/var/wview/img +
        +
        +

        + + To update OSX wview installations: +

        +
          +
        1. Stop wview: + + + + + + +
          + #> sudo /sbin/SystemStarter stop "wview" +
          +
          +
        2. +
        3. Update the ports list: + + + + + +
          + + #> sudo port -d selfupdate +
          +
          +
        4. +
        5. Update your ports: + + + + + + +
          + #> sudo port upgrade outdated +
          +
          +
        6. +
        7. Start wview: + + + + + + +
          + #> sudo /sbin/SystemStarter start "wview" +
          +
          +
        8. +
        + + + +

        +  Build/Install From Sources +

        + + +

        + + If you are going to install from source, please take care to install the + prerequisites and follow these instructions carefully.

        + Note: +

        +
          +
        • Whatever "--prefix" you select during the configure step (default = + /usr/local), the wview start scripts are automatically updated to include + the proper prefix for binary file and run directories.
        • +
        • You should use the same prefix specification when configuring + radlib and wview. Further, this should be the default install location + for libraries and library headers in your system - i.e., $prefix/lib and + $prefix/include for libgd2.a, libpng.a, libz.a and gd.h respectively. Do + not select a prefix randomly.
        • +
        + + + +

        +  Prerequisites +

        + + +

        + Note: Some of these libraries may be represented as "-dev" or "-devel" or similar in your + package management utility, i.e., "libcurl-dev". If they are, you should install + the dev version in order to get the development libraries that radlib/wview needs. +

        + +
          +
        • + libz - compression library +
        • +
        • + libpng - png image library +
        • +
        • + + libreadline5-dev - readline library +
        • +
        • + gawk - gnu pattern processing language +
        • +
        • + libsqlite3 - SQLite3 database run time library - make sure + the older version (2.x.y) is not installed - it may be named libsqlite0 + (debian, kubuntu) or some other package name - you can only + have SQLite3 libraries and development environment installed +
        • + +
        • + libsqlite3-dev - SQLite3 database development library - + make sure the older version (2.x.y) is not installed - it may be + named libsqlite0-dev (debian, kubuntu) or some other package name - you can only + have SQLite3 libraries and development environment installed +
        • +
        • + sqlite3 - SQLite3 database command line utility +
        • +
        • + libgd2 - graphics drawing library - may require installation - + + + http://www.boutell.com/gd +
        • +
        • + libusb-1.0-0-dev - USB interface library - may require installation - + + + http://www.libusb.org/ +
        • +
        • + radlib - rapid application development library - + must be installed with SQLite support (--enable-sqlite) - + + http://www.radlib.teel.ws +
        • + +
        • + libssl - secure socket library - optional, required for libcurl +
        • +
        • + libcurl - "C" URL HTTP library - optional, required for Weather Underground or Weatherforyou - + + http://curl.haxx.se/libcurl/ + +
        • +
        • + sendmail - email transfer agent - optional, required for email alerts +
        • +
        • + sendEmail - command line email sending utility - optional, required for email alerts +
        • +
        • + + mysql-client - MySQL client utilities (including mysqlimport) - optional, required for MySQL exports +
        • +
        + + + +

        +  Procedure + +

        + + +

        + For purposes of these instructions, the following conventions are used:
        + [wview_bld_path] is the staging area for building wview (i.e., /usr/local/src).
        + wview-x.y.z is the version of wview you are installing.
        + +

        +
          +
        1. Extract in the staging location [wview_bld_path]: + + + + + + +
          + #> cd [wview_bld_path]
          + #> tar zxvf wview-x.y.z.tar.gz +
          + +
          +
        2. +
        3. Change directory to the wview root source directory: + + + + + + +
          + #> cd [wview_bld_path]/wview-x.y.z +
          + +
          +
        4. +
        5. Run the configure script to create the build files for your platform.
          + + Important configure options:
          +
          + --prefix   (default: /usr/local) Controls + the installation root for wview.
          + --prefix=/ --bindir=/usr/bin --exec-prefix=/usr --sysconfdir=/etc --localstatedir=/var/lib --libdir=/usr/lib    + Configure options to use when building from source after a Debian APT installation. This places all binaries + and support files in the same locations as the Debian package. This is handy when modifying source code or + testing proposed releases.
          +
          + A typical new installation:
          + + + + + + +
          + #> ./configure +
          + + +
          + A typical upgrade:
          + + + + + + +
          + #> ./configure +
          + +
          +
        6. +
        7. Build: + + + + + +
          + + #> make +
          + +
          +
        8. +
        9. Install: + + + + + + +
          + #> sudo make install +
          + +
          +
        10. + +
        11. Configure to run at system boot (if new install):

          + Copy the appropriate wview start script to the proper location for + your system:
          +
          + + + + + + + + + + + + + + + + + + + +
          SuSEcp wview-x.y.z/examples/SuSE/wview /etc/init.d
          FreeBSDcp wview-x.y.z/examples/FreeBSD/wview /etc/rc.d
          FedoraCorecp wview-x.y.z/examples/FedoraCore/wview /etc/rc.d/init.d
          Debiancp wview-x.y.z/examples/Debian/wview /etc/init.d
          + +
          + + Make sure it is executable:
          +
          + + + + + + + + + + + + + + + + + + + +
          SuSEchmod +x /etc/init.d/wview
          FreeBSDchmod +x /etc/rc.d/wview
          FedoraCorechmod +x /etc/rc.d/init.d/wview
          Debianchmod +x /etc/init.d/wview
          + +
          + + Create a symbolic link in the runlevel directory you boot your server:
          + +
          + + + + + + + + + + + + + + + +
          SuSEcd /etc/init.d/rc3.d; ln -s ../init.d/wview S99wview
          FedoraCorecd /etc/rc.d/rc3.d; ln -s ../init.d/wview S99wview
          Debianupdate-rc.d wview defaults 99
          +
          +
          +
        12. +
        13. Add radlib location to shared library cache:
          + If you get errors similar to:
          + + /usr/local/bin/wviewd: error while loading shared libraries:
          + ibrad.so.0: cannot open shared object file: No such file or directory
          + you need to either copy the latest wview start script from the distro + (examples/[your_distro]/wview) to your start script location (see the + preceeding section), or add the radlib shared library location + (/usr/local/lib) to your shared library cache (see /etc/ld.so.conf) + and run ldconfig. +
        14. +
        15. Add rsyslog configuration file:
          + If your OS/distribution uses rsyslog as the system log utility, you can + configure it to create a wview log file.
          + #> sudo cp wview-x.y.z/examples/rsyslog/99-wview.conf /etc/rsyslog.d
          + Then restart rsyslog:
          + #> sudo /etc/init.d/rsyslog restart (or similar based on your run environment)
          +
        16. +
        + + + + + + +
        + wview Run Environment:
        + Configuration: /usr/local/etc/wview
        + Data: /usr/local/var/wview
        + wviewmgmt: /usr/local/var/wviewmgmt linked at /var/www/wviewmgmt
        + Weather web site: /usr/local/var/wview/img linked at /var/www/weather +
        +
        + + + +

        +  Building for Non-Native Targets (Cross Compilation) +

        + + +

        + See the scripts in the cross-compile folder for "./configure" alternatives. + This directory contains example configure scripts for cross compilation. libz, + libpng, libgd, librad and wview scripts are included (and should be built in + that order). Use these scripts instead of "./configure" to configure these + libraries for cross compilation. These scripts configure for arm-linux + targets but can be edited for other targets.

        + + Note: The config-radlib-arm-linux and config-wview-arm-linux scripts will accept + up to 3 additional configure options.

        + + The general build sequence is: +

        + + + + + + +
        + #> ./config-[pkgname]-arm-linux
        + #> make
        + #> sudo make install +
        + + +

        + All libs and applications are installed in the toolchain root, defined in the + config scripts.

        + The general build order is:
        + libz
        + libpng
        + libgd2
        + + libreadline5
        + libsqlite
        + libusb
        + librad
        + wview
        +

        + + + + +

        +  Troubleshooting +

        + + +

        + If your build fails it is almost certainly because you are missing a + prerequisite. Messages such as "unable to open gd.h" followed by a host + of other errors means you are missing libgd2-dev. It is not sufficient + to have the run time library only installed (libgd2), you also need + the development package installed.
        + + Also note that when a library or other dependency is missing, the first + error message is generally the one which will tell you what you are + missing.

        +

        + + + +

        +  Chapter 3: Configuration +

        +

        + wview configuration is often where users cause themselves problems. wview is + extremely configurable but with that flexibility comes more complexity and + more opportunities to misunderstand the purpose of the parameters and their + range of possible values. It is best to not change a parameter's value until + you are confident you know what/why you are doing it. New wview installations + default to the station simulator and a base working configuration. Use that + as a starting point and limit the number of parameters you change simultaneously + until you become more comfortable with wview. +

        + + + +

        +  Overview +

        + + + +

        + wview configuration can be accomplished from the command line (wviewconfig) + or using the web interface (wviewmgmt). wviewmgmt requires a working http + server with PHP and sqlite3 support. It is the easiest method once the + http/PHP/SQLite3 prerequisites are installed and confirmed. Many linux + distributions come with apache2+PHP+SQLite3 support already enabled.

        +

        + + + +

        +  Command Line Interface + +

        + + +

        + The command line interface (CLI) for wview configuration is wviewconfig. + It is installed when wview is installed. +

        + + + + + + +
        + + #> sudo wviewconfig
        + (this will start an interactive session - take your time) +

        + + + +

        + +  HTML Interface (wviewmgmt) +

        + + +

        + wviewmgmt is an html/java/php web site designed to provide a graphical + interface for wview configuration. It also provides system status information. +

        + + + + + + + + +
        + Local server access:
        + Enter the URL: http://localhost/wviewmgmt/login.php
        + The default password is "wview" (you should change this).

        + Remote server access: + Enter the URL: http://[remote_host_or_IP]/wviewmgmt/login.php +

        + + + +

        +  Getting It to Work +

        + + +
          +
        • Note where wviewmgmt was installed:
          + For Debian package install: already linked properly

          + For OSX MacPorts install: already linked properly

          + + For source install: wviewmgmt will be installed at: + $prefix/var/wviewmgmt
          + ($prefix was specified during the "configure" step)
          + You may need to soft link that location in your http document root:
          + + + + + + +
          + #> sudo ln -s $prefix/var/wviewmgmt [your_document_root]/wviewmgmt
          + (of course replacing $prefix and [your_document_root] appropriately) +
          + +
          +
        • + +
        • Ensure you have an http server (Apache or similar) running on + the wview host.

        • +
        • Ensure you have the PHP and PHP-SQLite3 modules for your http + server installed and enabled.

        • +
        • If you want start/stop control of wview from the Management + Web Site (and you are comfortable giving the http server user + account sudo privileges): + + + + + +
          + Add the http user to the sudo group:
          + #> sudo adduser www-data sudo

          + Update /etc/sudoers to allow start/stop control:
          + #> sudo visudo
          + (Add the following lines to the bottom of the file):
          + Cmnd_Alias WVIEW = /etc/init.d/wview
          + www-data ALL= NOPASSWD: WVIEW
          +
          + + +
          + Notes:
          + The addition of the http user to the sudoers file may be a + security vulnerability - use it at your own risk.

          + The Start/Stop button is not a toy - every effort has been made + to harden it so you don't punch it like a child - but if you + refresh your browser while you should be waiting patiently for + wview to start or stop then push the button again, you will + probably hose up your installation. So use it with care. +
        • +
        + + + + +

        +  Logging In +

        + + +

        + There is only one wviewmgmt user so only a password is required to + log in. By default the password is "wview". +

        + + + + +

        +  System Status Page +

        + + +

        + This page displays the current status (running/not running) of wview + as well as certain database record counts. You may also change the + wviewmgmt password on this page. +

        + + + +

        +  Services Page +

        + + +

        + + This page allows you to select which wview services to be run. If + you disable all services, you will have an archiving-only wview + server which stores archive and HILOW data which could be retrieved + periodically for post processing.
        + It also allows system log verbosity to be selected by service.
        + You can configure which wview processes are monitored by the wview + process monitor. +

        + + + +

        +  Station Page + +

        + + +

        + This page allows the configuration of the station type and interface + settings. Station presets, timing parameters and location can be + configured. See Chapter 4: Station Setup for + station-specific information.

        + Note: If you have a WeatherlinkIP datalogger, you will need to select a static + IP address and configure the datalogger with it. See the section + Davis Vantage Pro for more details on this. +

        +

        + Station-specific Parameters: Some parameters are specific to a particular + station type. They are displayed or hidden based on the station type selected.
        +

          +
        • + Enable WeatherlinkIP Interface- visible if "Davis Vantage Pro" is selected + as the station type. Configures wview to relax interface timing for the amazingly + slow Davis ethernet implementation. +
        • +
        • + Do RX Check- visible if "Davis Vantage Pro" is selected as the station type. + Configures wview to poll the console for RF packet statistics which can be displayed + graphically (if enabled in images.conf). Use this option with care as the Davis + consoles can behave erratically when the statistics packet is routinely polled. + It is suggested that you enable this while investigating RF problems from the + sensor suite and disable it after you are finished. +
        • +
        • + Outside Temperature Channel- visible if "Oregon Scientific WMR9XX" is selected + as the station type. Configures wview to use an alternate sensor channel for + outside temperature readings. +
        • +
        • + Enable serial DTR toggle during initialization?- visible if "Vaisala WXT510" + or "Texas Weather Instruments" is selected as the station type. Configures wview to + toggle the DTR signal during initialization of the serial interface. This is the + default behavior for these stations but in some cases it needs to be disabled if + you are having problems communication with the station. +
        • +
        +

        + + + +

        + +  File Generation Page +

        + + +

        + Station name and location can be set for file generation. Other + configuration settings for generation and certain text settings + are also configured here. +

        + + + + +

        +  Alarms Page +

        + + +

        + Up to 10 alarms may be defined on this page. +

        + + + + +

        +  FTP Page +

        + + +

        + This page allows configuration for FTP export of generated files and + images. +

        + + + +

        +  SSH Page +

        + + +

        + This page allows configuration for SSH export of generated files and + images. +

        + + + +

        +  CWOP Page +

        + + +

        + + This page allows configuration for Citizens Weather Observer Program + (CWOP) participation. +

        + + + +

        +  HTTP Services Page +

        + + + +

        + Weather Underground and Weather For You participation is configured + on this page. +

        + + + +

        +  Calibration Page +

        + + + +

        + wview supports calibration of certain weather data to compensate for + poor or faulty station sensors. +

        + + + +

        +  SQL Export Page + +

        + + +

        + wview supports the export of the archive and HILOW databases to MySQL + or PostgreSQL database servers. +

        + + +

        +  Chapter 4: Station Setup + +

        +

        + This chapter describes any specific setup necessary for supported weather + stations. It also describes any data tags that are unique for a given station. +

        + + + +

        +  Simulator + +

        + + +

        + The station simulator provides a way to demo the wview software without a + connection to an external station. It generates fake sensor readings so the + capabilities of wview including the weather web site generation can be + observed before going live with a station.

        + There is no specific setup for the station simulator.

        +

        + + + + +

        +  Virtual +

        + + +

        + The Virtual weather station allows you to run a wview server which + connects to another remote wview server via the datafeed TCP/IP socket + interface. Using the datafeed capabilities of the remote server, the virtual + server can generate files and web sites based on the remote station's data. + The remote station can be a low power device just running the station interface + process (wviewd) and the alarm process to serve the data to the virtual + wview server.

        + Note: It is imperative that both the remote and local wview servers are + running the same version of wview in case LOOP or ARCHIVE data definitions + have been modified.

        + To configure the virtual and remote servers (on the virtual server's + wviewmgmt Station page): +

        + +
          +
        • The virtual server must be configured as Station Type "Virtual"
        • +
        • The Station Interface must be "Ethernet"
        • +
        • The hostname or IP address of the remote wview server must be specified
        • +
        • The remote port (usually 11011 for the wview datafeed server) must be specified
        • +
        + On the remote server: +
          + +
        • "Enable Alarms" must be enabled on the Services page of wviewmgmt + to activate the datafeed server
        • +
        • Set "Data Push Interval" on the wviewmgmt Station page to 15 or 30 + seconds
        • +
        + Notes:
        +
          +
        • The archive record sync can take a very long time if there is a + large descrepancy in time between the remote and the virtual server + archive records. It can be more than an hour per year of archive + records. For this reason it is best to export your wview-archive.sdb + and wview-hilow.sdb databases from the remote server and import them + on the virtual server BEFORE running the virtual server for the first + time (see the Advanced Topics section for SQLite3 to learn how the + export/import process works).
        • +
        • Both the datafeed server and the Virtual station interface will try + hard to recover lost connections. The Virtual station also syncs all + archive records since the last epoch time that it received one from + the remote wview server when it starts up or recovers a connection.
        • + +
        • In theory wview data could be forwarded through multiple "hops" or + wview virtual servers.
        • +

        + + + +

        +  Davis Vantage Pro +

        + + +

        + If you are installing a new VP console, there are a few initial settings + that you need to set up so that your weather station and wview operate + properly. Configuration of archive interval, station location (elevation, + latitude and longitude) and your desired rain season start month must be + set up before you start wview for the first time.
        + Below is a short description of each of these:

        + + Archive Interval - this determines how often the VP console will generate + an archive record and store it in its internal memory. + These records are retrieved by wview from the console + and stored in the archive files (and the archive database, + if database archiving is enabled). The valid values are: + 5, 10, 15, 30, 60 (minutes). Keep in mind that the + shorter the archive interval, the more records that will be + generated. More records means a shorter time span for + internal storage in the VP console memory and larger archive + files on the wview server disk (and larger database tables + if stored in a database). I use an archive interval of 5 + minutes. TO AVOID HAVING TO DELETE ALL OF YOUR ARCHIVE DATA + LATER IN ORDER TO CHANGE THE ARCHIVE INTERVAL, IT IS VERY + IMPORTANT THAT YOU MAKE THIS CHOICE CAREFULLY AND NOT + CHANGE IT AFTER ARCHIVE DATA HAS BEEN STORED BY WVIEW. + This does NOT effect how often HTML files containing + current conditions are generated or the up to the minute + values they contain, this is controlled by the configuration + value "Generate Interval" on the wviewmgmt File Generation page. It will + effect the granularity of your charts for the last 24 hours.

        + + + Elevation - this is given in feet above (or below) sea level. This is + the recommended way to calibrate your barometer.

        + + Lat/Long - this describes the location of your weather station.

        + + Rain Season Start - this defines the first month of each year when yearly + rain totals will begin. Most people will use a value of + "1" here.

        + + In order to set these parameters in the VP console, the "vpinstall" script is + provided with the wview distribution. Note that all of these can be configured + using the On-Screen VP console setup utility. After building and installing wview, + the vpinstall script can be found in ${exec-prefix}/bin (usually /usr/local/bin). It + is an interactive script which queries you for these initial values then uses the + vpconfig utility to commit them to your new VP console. Once completed, it is + advisable to wait 10 minutes or so before starting wview for the first time as + it takes the VP console a little while to "digest" the new settings, in + particular for the barometer. +

        + +
        +

        + WeatherlinkIP Datalogger Setup: If you have purchased the WeatherlinkIP + datalogger you will need to do some additional setup.
        + You must configure the WeatherlinkIP datalogger with a static IP address + on your local network segment. By default the datalogger is configured for + DHCP to obtain its IP address. The trick is to find what address it has been + assigned by your wired or wireless router. I used the utility on my router's + DHCP status page to find the IP address given to the datalogger after I had + connected the ethernet cable and powered it on. My intranet is + 10.0.0.0/255.255.255.0 and the datalogger had been assigned 10.0.0.116. + Unfortunately the Vantage Vue console does not provide this information directly. + Once you have the IP address (a.b.c.d), enter the URL "http://a.b.c.d" in your favorite + browser. A very simple configuration screen is provided by the datalogger. + wview defaults to port 22222 for ethernet interfaces (as of 5.18.0) so no need + to modify it. Disable the "Upload to weatherlink.com" option and select + "Save Configuration". Then select "Use the following IP Address" and enter + your local network information. Select "Save IP Settings". You will need to + modify the URL to use the new static IP address you selected if you need to + get back to this configuration (which you probably will not). Remember the + IP address you selected so you can enter it in the station configuration page + of wviewmgmt. Note: you cannot access the datalogger configuration page while + any software is connected to the datalogger (wview or Weatherlink). +

        + +
        +

        + Extended Sensors: If you do not have a Vantage Pro Plus or Vantage Pro2 + Plus and you haven't added additional sensors ($$) to your VP, then there + is no reason to enable Plus extended data on the wviewmgmt Station page or to + enable extended sensor generation in images.conf. You will only be + wasting CPU cycles as the extended sensors will not be populated + with anything meaningful.

        + + The Vantage Pro Plus adds Solar Radiation, UV and Evapotranspiration (ET) + sensors with the potential to add other sensors. wview provides support + for historical charts (day, month and year) for the following VP Plus + sensors:

        + + Solar Radiation
        + UV
        + + ET
        + LeafTemp1
        + LeafTemp2
        + LeafWetness1
        + LeafWetness2
        + SoilTemp1
        + + SoilTemp2
        + SoilTemp3
        + SoilTemp4
        + ExtraHumidity1
        + ExtraHumidity2
        + ExtraTemp1
        + + ExtraTemp2
        + ExtraTemp3
        + SoilMoisture1
        + SoilMoisture2
        + SoilMoisture3
        + SoilMoisture4

        + + + wview also can generate current condition buckets for Radiation, + UV and ET. See the $prefix/etc/wview/images.conf file for optional + images which can be generated.

        + + To enable extended sensors:
        +

        +
          +
        1. Update wview configuration on the File Generation Page, enabling + "Generate Extended Sensor Values?".
        2. +
        3. Reconfigure the HTML templates by executing "sudo wviewhtmlconfig".
        4. +
        5. Customize the templates in $prefix/etc/wview/html if needed.
        6. + +
        +
        +

        + vpconfig Usage

        + Note 1: It is not advisable to use vpconfig to change the archive + interval or rain season start after the initial configuration + (using vpinstall). This will skew your rain season totals or + render your archived data unusable. If you do change the archive + interval after records have been stored by wview, you will need + to delete all archive data in $prefix/var/wview/archive/wview-archive.sdb + before restarting wview.

        + Note 2: You MUST stop wview before using vpconfig to view or change + settings!

        + Usage: vpconfig [station_dev] [command] [cmnd_args]
        + +

        +
        +    station_dev  serial device the VP console is connected to:
        +                  FreeBSD: /dev/cuaa0 - /dev/cuaa4
        +                  Linux:   /dev/ttyS0 - /dev/ttyS4
        +
        +    command      command to execute, one of:
        +                   show
        +                     - retrieve and display VP console config
        +                   cleararchive
        +                     - clear the archive memory without changing the interval
        +                   setinterval [interval in minutes]
        +                     - set the archive interval (this clears the archive memory)
        +                   setelevation [elevation in feet]
        +                     - set the station elevation (feet) - use this to adjust barometer
        +                   setgain [0 for off, 1 for on]
        +                     - sets the gain of the radio receiver
        +                   setlatlong [latitude (negative for S)] [longitude (negative for W)]
        +                     - set the station latitude and longitude in tenths of a degree
        +                   setrainseasonstart [month (1-12)]
        +                     - set the month that the yearly rain total begins
        +        
        + + + +

        +  Vaisala WXT510 + +

        + + +

        + The Vaisala WXT510 Weather Transmitter is a semi-professional instrument + about the size of a 2 liter bottle of Coke. Ultrasonic wind speed sensors, + non-tipping rain/hail measurement and low power are a few of the nice features + of this station. A very simple NMEA 0183 protocol implementation is provided + to retrieve the readings.
        +

        +

        + Preparing a WXT510 For wview:
        + The wxt510config utility simply autobauds the WXT510 until it finds the + station's current comm settings then resets the station to 19200-8-N-1. + wview startup will do the remaining initialization of the station.
        + + To run it, determine what serial device your station is connected to, + then as root execute: +

        + + + + + + +
        + #> sudo wxt510config [station_dev]
        + (where [station_dev] is something like /dev/ttyS0, /dev/cuaa0, /dev/ttyUSB0, etc.) +
        +
        +

        + Additional Data Tags (see examples/html/parameterlist.txt for tag format):
        + wxt510Hail
        + wxt510Hailrate
        + wxt510HeatingTemp
        + wxt510HeatingVoltage
        + wxt510SupplyVoltage
        + wxt510ReferenceVoltage
        + wxt510RainDuration
        + wxt510RainPeakRate
        + wxt510HailDuration
        + wxt510HailPeakRate
        + wxt510Rain
        + wxt510RainDurationMin
        + wxt510HailDurationMin

        +

        + + + +

        +  Texas Weather Instruments (TWI) +

        + + +

        + All Texas Weather Instruments stations with serial interface support + use the same simple protocol. wview autobauds the station each time it + starts so there is literally nothing to configure on the station's + console. wview should just work. The station should be configured to output + Imperial (US) units. Select the "TWI" station type when configuring wview.
        +

        + + + +

        + +  Oregon Scientific WMR918/928NX/968 +

        + + +

        + The Oregon Scientific WMR918 Series (WMR918/928N/968) Personal Weather Station + is a low-cost consumer grade station.

        + There is no preparation required - just connect to your serial port and go!

        + Notes:
        + +

        +
          +
        • The Oregon Scientific WMR918 weather stations do not provide Rain + Rate calculation, so wview provides it for you. It is a very simple + implementation which looks at the last 5 minutes of rainfall and + scales it up to one hour.
        • +
        • The WMR9X8 stations send sensor packets (usually corresponding to + a sensor suite) periodically. wview requires one packet from each + sensor suite before it can complete initialization. Thus, if your + outside temperature transmitter is down or malfunctioning, wview + will not function properly.
        • +
        +

        + Additional Data Tags (see examples/html/parameterlist.txt for tag format):
        + wmr918Humid3
        + wmr918Pool
        + wmr918Tendency
        + wmr918WindBatteryStatus
        + wmr918RainBatteryStatus
        + wmr918OutTempBatteryStatus
        + wmr918InTempBatteryStatus
        + wmr918poolTempBatteryStatus
        + wmr918extra1BatteryStatus
        + wmr918extra2BatteryStatus
        + wmr918extra3BatteryStatus

        +

        + + + +

        + +  Oregon Scientific WMR88/WMR88A/WMR100/WMR100N/WMR200/WMRS200/RMS300/RMS600 +

        + + +

        + The Oregon Scientific WMR88/WMR88A/WMR100/WMR100N/WMR200/WMRS200/RMS300/RMS600 Series Personal Weather Stations + are a low-cost consumer grade station. Select the "WMRUSB" station type + when configuring wview.
        + Like their older serial interface brethren, the WMR USB stations "blast out" + the sensor data as opposed to responding to commands like most reasonable + protocols for other stations. This fact coupled with the reality that the + USB HID interface allows no event-driven mechanism to know when there is + data ready to be read requires wview to poll the USB interface periodically in + order to receive the unsolicited sensor data packets from the console. wview + requires one packet from each sensor suite (Temp, Wind, Pressure and Rain) before + it can complete initialization. If one or more of your sensors is out of range, + the battery is weak or the sensor is malfunctioning, wview will not function + properly, i.e., it will not complete initialization.

        + There is no preparation required - just connect to your USB port and go!

        + Notes:
        + +

        +

        + Additional Data Tags (see examples/html/parameterlist.txt for tag format):
        + The WMR stations can support up to 8 additional Temperature/Humidity sensors. The + tags will be populated if you have the sensors. Sensor IDs 0 and 1 are used for inside + and outside main sensors respectively, so WMR sensor ID 2 is stored in extraTemp + sensor 1, etc.
        + genExtraTemp1 - genExtraTemp8
        + genExtraHumidity1 - genExtraHumidity8

        +

        + + + + +

        +  La Crosse WS-2300/2308/2310/2315 +

        + + +

        + The La Crosse WS-2300 Series (WS-2300/2308/2310/2315) Personal Weather + Station is a low-cost consumer grade station. Although it is proud to be + wireless, it is best implemented "cabled" between the sensors and the + console. When cabled, it will update the console with new data every + 8 seconds. When ran wirelessly, it updates on the order of minutes, + which is nearly useless for our purposes.

        + There is no preparation required - just connect to your serial port and + go!

        + + Note: The La Crosse weather stations do not provide Rain Rate calculation, + so wview provides it for you. It is a very simple implementation which + looks at the last 5 minutes of rainfall and scales it up to one hour.

        +

        + + + + +

        +  Fine Offset WH1080 (and many variants) +

        + + +

        + The Fine Offset 1080 Series (including Fine Offset: WH-1080, WH-1081; Watson: W-8681, WX-2008; + National Geographic: 265 NE; Elecsa: 6975, 6976; Ambient Weather: WS-1080, WS-1090, WS-2080; + Tycon: TP1080WC) Personal Weather Station is a low-cost consumer grade station.
        + The sensors will update no faster than once per minute so there is no need for + a faster "STATION_POLL_INTERVAL" than 60 seconds.

        + Note: The Fine Offset weather stations do not provide Rain Rate calculation, + so wview provides it for you. It is a very simple implementation which + looks at the last 5 minutes of rainfall and scales it up to one hour.
        +

        + + + + +

        +  Honeywell TE923W (and many variants) +

        + + +

        + The Honeywell TE923W (including Hideki, Nexus, Mebus, Irox, Honeywell, + Cresta TE923, TE923W, TE821W, WXR810, DV928) Personal Weather Station is a + low-cost consumer grade station sold under many brand names and models + around the world. These have by far the best quality of sensors and console + for stations in the $200 or less category. It is highly recommended.
        + The sensor data from the console will update no faster than once per minute + so there is no need for a faster "STATION_POLL_INTERVAL" than 60 seconds.

        + Note: The TE923 weather stations do not provide Rain Rate calculation, + so wview provides it for you. It is a very simple implementation which + looks at the last 5 minutes of rainfall and scales it up to one hour.
        +

        +

        + Additional Data Tags (see examples/html/parameterlist.txt for tag format):
        + The TE923 stations can support up to 4 additional Temperature/Humidity sensors. The + tags will be populated if you have the sensors. Sensor ID 0 is used for the + primary outside temperature sensor, so sensor ID 2 is stored in extraTemp + sensor 1, etc.
        + genExtraTemp1 - genExtraTemp4
        + genExtraHumidity1 - genExtraHumidity4
        + genExtraTempBatteryStatus1 - genExtraTempBatteryStatus4
        + genExtraUVBatteryStatus
        + genExtraWindBatteryStatus
        + genExtraRainBatteryStatus

        +

        + + +

        +  Chapter 5: File Generation +

        +

        + + wview file generation is the process of replacing well-defined wview data + tags (defined in parameterlist.txt) found in template files with their + current values. These template files can be the HTML templates used for + the default wview weather data web site or they can be any user-defined files + containing one or more data tags (PHP, XML, etc.). +

        + + + + + +
        + Configuration files referenced in this chapter:

        + $prefix/etc/wview/html-templates.conf - lists the template + files in $prefix/etc/wview/html to be generated.

        + + $prefix/etc/wview/images.conf - lists the weather images + (buckets, graphs, dials) to be generated.

        + $prefix/etc/wview/forecast.conf - configures forecast text + for Vantage Pro stations. +
        + + + + +

        +  Overview +

        + + +

        + Generic File Generation
        + + For any template file named "example.[ext]x" and listed in html-templates.conf, + wview will generate a file named "example.[ext]". For example, "myscript.phpx" + listed in html-templates.conf and found in $prefix/var/wview/html will have + all wview tags replaced and the resulting file will be named "myscript.php". + The resulting files are stored at the location specified on the wviewmgmt + File Generation page: "Generation Target Path".
        + Please note that if you are using FTP/SSH to transfer your files you will need + to add rules for any new extensions you want to support in wview configuration. +

        +

        + HTML File Generation and the wview Web Site
        + The default generation model for wview is to generate a weather web site + based on a series of HTML file templates and images defined in images.conf. + For any template file named "example.htx" and listed in html-templates.conf, + wview will generate a file named "example.htm". +

        + + + + +

        +  Customization +

        + + +

        + Notes:
        + + Changing HTML templates in $prefix/etc/wview/html does not require a + restart of wview. The changes you make will take effect at the next + htmlgend generation cycle.

        + Changing the config files images.conf, html-templates.conf and + (if supported) forecast.conf does not require a restart of wview + but does require a HUP signal to be sent to htmlgend to cause these + files to be re-read. Do this as follows + (this will also toggle log verbosity): +

        + + + + + + +
        + #> sudo kill -s HUP `cat $prefix/var/wview/htmlgend.pid` +
        + +
        + Changes to extended data or web site skin selection is done using the + wviewhtmlconfig script provided with wview. If changing to extended data + (adding UV, ET, radiation) first enable "Generate Extended Sensor Values?" + on the wviewmgmt File Generation page (and save changes) prior to running + wviewhtmlconfig. It will automatically determine the extended data setting + from the configuration database. It is interactive and will save your old + $prefix/etc/wview/html directory to $prefix/etc/wview/html-DATE.TIME.
        + As wview users contribute new skins they will be added to the wview distribution + and supported by the wviewhtmlconfig script. For information on creating + your own skin, see the file "$prefix/etc/wview/html/Template-Skins-HOWTO.txt". + + + + + +
        + + #> sudo wviewhtmlconfig +
        + +
        +

        + Macro File Inclusion
        + wview supports template macro file inclusion in template files. The meta-tag + is !--include filename.xxx-- (brackets omitted). Any template macro file + that is to be included in one or more template files should be listed BEFORE + any templates including it in the $prefix/etc/wview/html-templates.conf + configuration file. There is no restriction on the levels of inclusion, + just be sure you specify macro templates early in the html-templates.conf + file. The wview default web site templates utilize several header macro + files. +

        + +
        +

        + Language
        + HTML template files (in $prefix/etc/wview/html) should be customized to + your language and your design preferences. The configuration file + html-templates.conf specifies the template files to be used for generation. + You may add or remove from this list as needed.
        + Weather image captions can be edited in the $prefix/etc/wview/images.conf + file for your language preferences. +

        +
        +

        + + Metric Units
        + The configuration parameter on the File Generation Page "Enable Metric + Units For Generation?" allows configuration of metric units. If set to + "yes" it will cause wview to output all images (buckets and charts) as + well as all values for HTML tags in metric units.

        + The units are:
        + Temperature ........... Celsius
        + Barometer ............. Millibars
        + Wind Speed ............ Kilometers per hour
        + + Rain .................. Millimeters or Centimeters

        + images.conf can be edited to translate the English labels, titles, and + units to any language. By editing this file and the HTML template files, + any language can be supported by wview. In fact one can easily switch + back and forth between US and metric units by toggling this configuration + parameter and restarting wview.
        + Note:
        + All weather data in the SQLite3 database is still stored in US format - the + conversions are only done for real time image and HTML file generation. +

        +
        +

        + XML and RSS Feed
        + + XML templates should be named "*.xtx" and placed in $prefix/etc/wview/html. + An example RSS feed template is included in the distro: + $prefix/etc/wview/html/wxrss.xtx. An href to it is included in all home + page template examples so the RSS feed may be discovered while visiting + your home page. wxrss.xtx should be edited for your site. +

        +
        +

        + Forecast Support
        + The Vantage Pro stations produce a forecast rule value and an icon specifier. + The data tags are !--forecastRule-- and !--forecastIcon-- (brackets omitted) + respectively. The configuration file forecast.conf allows the user to + define the icon files and text associated with the forecast rules. See + the example forecast.conf file for details. The icon tag will be replaced + by an image html construct pointing at the appropriate icon image file. + The rule tag will be replaced by the corresponding text string defined in + forecast.conf. Because these capabilities require 35 KB or more of memory + (and I think the VP forecasts are hokey), they are disabled by default. + To enable them, rename the file $prefix/etc/wview/forecast.conf-no-forecast + to $prefix/etc/wview/forecast.conf. You must restart wview for this change + to take effect. +

        +
        +

        + + Generic Tag Generation
        + The template file $prefix/etc/wview/html/parameterlist.htx is provided as + an example of how to obtain all data tags and values through file generation. + It could be copied/modified to a PHP template such that the generated file + is appropriate for PHP inclusion. An XML template could also be developed. + In this way wview can be used as a minimal generation engine to provide + weather tags and post processing can be used in any environment you choose. +

        +
        +

        + Pre/Post-Generation Scripts
        + The script file $prefix/etc/wview/pre-generate.sh (if it exists) will be + executed by the htmlgend daemon immediately before the generation of all + images and templates. The script file $prefix/etc/wview/post-generate.sh + (if it exists) will be executed by the htmlgend daemon immediately + following the generation of all images and templates. This can be used to + perform custom transformations of generated images or files. It should NOT + be used to perform network tasks such as FTP or other tasks which may + block and interrupt execution of the htmlgend daemon.
        + Note: Empty generation scripts should be renamed so wview does not execute them, + which in rare cases can cause zombie processes.

        + +

        + + +

        +  Chapter 6: Exporting Generated Files +

        +

        + Based on your intended use for generated files, you may need to export them + to a remote server. If your wview server is also the HTTP server in your + setup, then a proper definition of the location specified on the wviewmgmt + File Generation page: "Generation Target Path" may be all you require to + publish your web site. This is the most straightforward approach. However, + if it is not possible or desirable to run your HTTP server locally or if + you have other plans for the files you generate, you may need to export + them. wview supports FTP or SSH processes for file exportation. The wview + ftp service tries to be somewhat intelligent: it maintains a "marker" + file which contains the date/time of the last FTP update. It will only transfer + files in your defined rules which are newer than that date. This conserves + time and bandwidth. +

        + + + +

        +  FTP +

        + + +

        + + wview's ftp daemon no longer requires the tnftp utility. The curl library + is used for direct transfer of files including a 20 second timeout per + file transferred to avoid hang ups when the remote server stops responding.
        +

        +
        +

        + Remote Server Directory Setup
        + wviewftpd will place all files it transfers into the [conf_directory] + directory under your ftp login directory on the remote server.
        + [conf_directory] is the value of the "Remote Directory" configuration + parameter on the FTP page. The curl library facilitates creating any remote + directories needed so manual setup is not required.
        + +

        +
        +

        + Move Static Files to Server
        + It is no longer necessary to move static files manually. The new wview FTP + service only sends files which have changed since the transfer, so all + static files are sent on the initial transfer.
        +

        +
        +

        + + Configure wview FTP Service
        + The wviewmgmt FTP page allows configuration of remote_username, + remote_password and remote_host as well as the conf_directory parameters. + You should also enable the FTP service on the Services page. +

        +
        +

        + Troubleshooting
        + If you are having problems and wish to get more feedback concerning + the FTP transfers, do the following: +

        +
          + +
        • Enable verbose logging for the FTP service on the wviewmgmt Services page.
        • +
        • Follow the system log in a shell console: sudo tail -f /var/log/syslog
        • +
        • Restart wview: sudo /etc/init.d/wview restart
        • +
        • Watch the log output. There will be logs with the heading "FTP_OUTPUT" + which will contain the stdout and stderr output from the tnftp utility. + Here is where you will learn why any transfers are failing. FTP transfers + begin at one minute past the next 5 minute boundary and proceed based + on your configuration from there. The wviewftpd startup logs will + indicate how long before the first FTP transfer.
        • +

        + Common FTP Problems
        + +
          +
        • Remote host wrong or remote user does not have FTP privileges.
        • +
        • Remote directory structure: the Remote Directory configuration + parameter is relative to the remote user's FTP base directory. This + could be /home/[user]/public_html or similar. FTP to user@remote_host + from the command line and issue "ftp> pwd" to find out what the base + directory on the remote host is.
        • +
        • Local files do not exist: if you specify "Archive/*.txt" in an FTP + rule as the source, if $prefix/var/wview/img/Archive is empty or + does not exist, this transfer will obviously fail.
        • +
        • Conflicting rule definitions: it is possible to define rules and + update intervals such that a file with a longer update interval may + be "skipped" for transfer because the marker file was updated for a rule + with finer update granularity. Given the use of the marker file, + it is safe and encouraged that you simplify your rules to one per + subdirectory of the form "*.*". Then no files will be passed over + and only updated files will be transferred. This is the default + setting in the distro example configuration.
        • +
        + +
        +

        + Re-Transmitting All Local Files
        + The wview ftp service tries to be somewhat intelligent: it maintains a + "marker" file which contains the date of the last FTP update. It will only + transfer files in your defined rules which are newer than that date. This + conserves time and bandwidth. To cause the wview ftp service to start over, + resending all files in your defined rules, you must delete the marker file: + $prefix/var/wview/ftp_marker. wview will send all files and save the new + marker file at the next defined update interval. +


        + + + +

        + +  Secure File Transfer (rsync/ssh) +

        + + +

        + Notes:
        + wview also supports the secure shell protocol for secure file transfers. + Using rsync, the wviewsshd service can export files or directories. rsync + is more efficient than FTP because only changed files are transmitted. + To work properly, the wview server must be able to login and/or execute + commands on the destination server via ssh WITHOUT entering a password. + This is accomplished by copying the wview server's root account shared + ssh key to the remote server's login account.

        + Suggestion: Don't mix hostname with IP address for the remote + server during the configuration below. Decide NOW whether you are going + to use a hostname or an IP address, and use it consistently for all + references to [remote_host] below. ssh does make a distinction when + storing and verifying shared keys.

        + + Placeholders:
        + [remote_host] - the host we want to update
        + [ssh_login_user] - the user account name on the [remote_host] we want to + use for the ssh logins
        + [remote_test_dir] - remote directory to receive files, relative to the + [ssh_login_user] login home directory ([ssh_login_user] must have write + access to this directory)
        + [wview_server] - the wview host

        + I have included prompts of the form "username@host:# " to help clarify + what is being executed on what host. Your actual shell prompts may be + different, this is only for clarity in this procedure.

        + + This procedure assumes compatible versions of ssh - version 1 and + version 2 of openssh have compatability problems as well as with + ssh.com versions 1 and 2. If you are having problems with the setup, + I strongly suggest going to the following URL for advanced help: + http://gentoo-wiki.com/SECURITY_SSH_without_a_password. +


        +

        + Prerequisites
        + Verify rsync is installed on the wview server: +

        + + + + + + +
        + root@[wview_server]:# whereis rsync +
        + + If that doesn't produce /usr/bin/rsync or similar, install rsync.

        + Verify rsync is installed on the remote host: + + + + + + + +
        + root@[wview_server]:# ssh -l [ssh_login_user] [remote_host]
        + [enter password]
        + [ssh_login_user]@[remote_host]:# whereis rsync + Logout:
        + [ssh_login_user]@[remote_host]:# exit +
        + +

        + If that doesn't produce /usr/bin/rsync or similar, install rsync. +

        +
        +

        + Shared Key Setup
        + + Generate a public/private key pair for the root user on the wview + server (if it does not already exist) (execute as the root user): +

        + + + + + +
        + root@[wview_server]:# mkdir -p ~/.ssh + root@[wview_server]:# ssh-keygen -t rsa +
        + + +

        + Just hit enter for default values when asked questions. + This will create two files, we are interested in the ~/.ssh/id_rsa.pub + file.

        + Transfer id_rsa.pub to [remote_host]. Use ftp, scp, email, floppy + disk, whatever you want to get this file to the remote host.

        + Login to [remote_host] as [ssh_login_user] and append the contents of + id_rsa.pub into ~/.ssh/authorized_keys (if authorized_keys does not + exist, just rename id_rsa.pub to authorized_keys). +

        + + + + + + +
        + root@[wview_server]:# ssh -l [ssh_login_user] [remote_host]
        + [enter password]
        + [ssh_login_user]@[remote_host]:# mkdir -p ~/.ssh
        + [ssh_login_user]@[remote_host]:# cd ~/.ssh
        + [ssh_login_user]@[remote_host]:# cat id_rsa.pub >> authorized_keys
        + + [ssh_login_user]@[remote_host]:# chmod 600 authorized_keys +
        + +

        + Stay logged in - we need it for the next step.

        + As root on the wview server, ssh into [remote_host] as [ssh_login_user], + answer "yes" if this is the first time, then logout. This sets the + [remote_host] up in the wview server root account's "known_hosts" file. +

        + + + + + + +
        + root@[wview_server]:# ssh -l [ssh_login_user] [remote_host]
        + [no password should be required!]
        + [ssh_login_user]@[remote_host]:# exit
        + +
        + +
        +

        + Proper Login Access
        + Finally, we need to set up ssh for the root user so that when + [remote_host] is logged into, ssh uses [ssh_login_user] instead of + root for the login on [remote_host]. This must be done for every + [remote_host] entry on the wviewmgmt SSH page. This is what specifies which + user account is used for the ssh login to each [remote_host].

        + + Edit /root/.ssh/config and put the following at the top of the file + (just create a new file if it does not exist): +

        + + + + + + +
        + Host [remote_host]
        + User [ssh_login_user] +
        + + Save and exit the file. + +
        +

        + Mandatory Tests
        + These tests must succeed before proceeding.

        + + As root on the wview server execute: +

        + + + + + +
        + root@[wview_server]:# ssh [remote_host] +
        + + +

        + You should be logged in to [remote_host] as [ssh_login_user] without + entering a password.

        + Exit the remote host shell: +

        + + + + + +
        + + [ssh_login_user]@[remote_host]:# exit +
        + +

        +
        + You should now be able to remotely execute commands over ssh. Verify + this by executing the date command from the wview server as root: +

        + + + + + + +
        + root@[wview_server]:# ssh [remote_host] date +
        + +
        + +

        + This MUST execute without requiring a password. If it does not, go back + to the beginning of shared key setup and double check your steps.

        + DO NOT proceed if you cannot login/execute commands remotely as + [ssh_login_user] without a password. This is critical! There is much + online documentation concerning ssh setup, this is only a bare-bones + treatment of the subject. +

        +
        +

        + Confirming rsync Functionality
        + + Place files in $prefix/var/wview/img (if it is not already your + "Generation Target Path" on the wviewmgmt File Generation page): +

        + + + + + +
        + root@[wview_server]:# cp [some_test_files] $prefix/var/wview/img +
        + + +

        +
        + Create/Verify the destination path on the remote server + (as [ssh_login_user]): +

        + + + + + +
        + + root@[wview_server]:# ssh [remote_host]
        + (you are now logged in as [ssh_login_user] without a password, right?)
        + [ssh_login_user]@[remote_host]:# cd ~
        + (this is your ssh login directory - all destination + paths are relative to this directory)
        + [ssh_login_user]@[remote_host]:# mkdir -p [remote_test_dir]
        + ([remote_test_dir] is a relative path from the [ssh_login_user] + login directory - it CANNOT contain a leading "slash")

        + + Logout:
        + [ssh_login_user]@[remote_host]:# exit +
        + +

        +

        + Test rsync over ssh from the wview server: +

        + + + + + + +
        + root@[wview_server]:# rsync -aqz --rsh=ssh $prefix/var/wview/img/ [remote_host]:[remote_test_dir] +
        + + +

        +
        + This should transfer the files you placed in $prefix/var/wview/img to the + remote server in the [remote_test_dir] directory without a password + being required. If it prompts for a password, you need to fix it. Left + in this state, the wview ssh daemon will not provide any negative feedback + because the stdin pipe that the rsync command prompts for a password on + will not exist when invoked within wview and rsync will just exit quietly.

        + *****!!!!!!!!!!!!!!!!!!!!!!!!! IMPORTANT !!!!!!!!!!!!!!!!!!!!!!!!!*****
        + wview ssh file transfer capability will NOT work until you can
        + successfully execute this command from the wview server and verify
        + the file transfers on the remote server.
        + + *****!!!!!!!!!!!!!!!!!!!!!!!!! IMPORTANT !!!!!!!!!!!!!!!!!!!!!!!!!*****
        +

        +
        +

        + Configuring wview For Secure Transfers
        + Add transfer rules on the wviewmgmt SSH page.

        + Note: Each [remote_host] entry must have a corresponding "Host" entry in + /root/.ssh/config which specifies the login account ([ssh_login_user]) + to use for ssh. See section 14.3 above. Further, the procedures outlined + above MUST be followed for each unique [remote_host] [ssh_login_user] + combination.

        + + You may change the SSH remote port number (default is 22) if required.
        + You may change the "-l" argument passed to ssh during remote login (this + should not be required under normal conditions).

        +

        + + +

        +  Chapter 7: Advanced Topics +

        + + + +

        +  Data Stored in SQLite3 (And How to Use It) +

        + + +

        + + wview stores archive data in an SQLite3 database, namely + $prefix/var/wview/archive/wview-archive.sdb. This allows easy data viewing, + modifying and post-processing.
        + HILOW data is stored in the database $prefix/var/wview/archive/wview-hilow.sdb.
        + NOAA data is stored in the database $prefix/var/wview/archive/wview-noaa.sdb.
        + This section will describe each of these databases including when and from + what data they are created/updated. +

        +
        +

        + Database Origins + +

        +
        +   Database             Data Source                     How Generated
        +   ------------------   ------------------------------- -------------------------------
        +   wview-archive.sdb    WLK conversion via wlk2sqlite   Not autogenerated - requires
        +                        or stored run time by wview     an existing template (found at
        +                                                        $distro/bin/archive/wview-archive.sdb)
        +
        +   wview-hilow.sdb      Archive records for initial     Autogenerated during wview init 
        +                        creation; LOOP packets during   (can be very slow on some platforms);
        +                        run time by wview               Offline creation via the hilowcreate
        +                                                        utility
        +
        +   wview-noaa.sdb       HILOW records for initial       Autogenerated during wview init
        +                        creation and during run time    (not too long to generate)
        +        
        +
        +

        + What to Protect and Why +

        +
        +
        +   wview-archive.sdb    - do NOT delete this database without a backup copy; this file
        +                          should be backed up for data security
        +
        +   wview-hilow.sdb      - if you delete this file, wview will regenerate it but you
        +                          will lose the higher resolution of LOOP data as wview can
        +                          only use archive records for historical generation; this
        +                          file should be backed up for data security
        +
        +   wview-noaa.sdb       - there is no data loss associated with deleting this database
        +                          and letting wview regenerate it during init
        +        
        +
        +

        + Archive Data

        + + Conversion from WLK to SQLite3

        + A conversion utility, wlk2sqlite, is provided as part of the wview + distribution.
        + + Usage: wlk2sqlite source_directory [destination_directory]

        + source_directory is a required argument but can be the same as + [destination_directory] - typically you can use the default archive + file location for your installation, $prefix/var/wview/archive, since there + is nothing destructive about the conversion with respect to the old + WLK files, it only reads them.

        + So an example using the default prefix /usr/local would be:

        +

        + + + + + + +
        + #> sudo wlk2sqlite /usr/local/var/wview/archive +
        + +
        +

        + This places the newly populated SQLite3 archive database file in the + proper location for wview 5.0.0 + operation.
        + + Note: The conversion can take some time depending upon your platform + and how much archive data you have.
        + When the conversion is complete, you have one coherent SQL archive database.


        + Viewing/Modifying Your Data With the sqlite3 CLI Utility

        + Let's look at your data:

        +

        + + + + + + +
        + #> sudo sqlite3 /usr/local/var/wview/archive/wview-archive.sdb
        + sqlite> .help
        + (This displays available commands; non-SQL commands begin with '.')

        + sqlite> select * from archive;
        + (This displays every column of every row)

        + + sqlite> .schema archive
        + (This displays the CREATE statement for the archive table including + column names)

        + sqlite> update archive set altimeter='777.88' where altimeter is NULL;
        + (This changes the value of column altimeter wherever it is NULL)

        + sqlite> .dump archive
        + (This dumps every row of the archive table in SQL format - very + handy for moving your table from one platform to another)
        + +
        + +

        +
        + Note: SQL syntax as recognized by SQLite3 is documented at: + http://www.sqlite.org/lang.html


        + Moving Your Data to a Different Platform

        + + This is where having our data in an SQL database starts to pay off.
        +

        + + + + + + +
        + Dump the archive table in SQL format:
        + #> sudo sqlite3 /usr/local/var/wview/archive/wview-archive.sdb .dump > ~/temp/[my_sql_filename]
        + +
        + Copy [my_sql_filename] to the target platform.
        +
        + On the target platform:
        + #> sudo sqlite3 [new_database_filename] .read [my_sql_filename]
        + (This will execute each SQL statement in [my_sql_filename] for + the new database [new_database_filename])
        +
        +
        + Done!

        + Why Can't I Just Move the Database File?

        + Physical platform and version of SQLite3 effect the format of the + data in your archive database. + +
        +
        + +

        + HILOW Data

        + Overview

        + The database $prefix/var/wview/archive/wview-hilow.sdb will be created + and back-filled using archive records for all records in the archive + database.
        + This initialization process can take some time (8-12 minutes per year) + but is only required during the first run of wview after an upgrade >= + 5.1.0.
        + Once $prefix/var/wview/archive/wview-hilow.sdb has been created and + back-filled, wview will add each LOOP sample to the HILOW database.

        + + Structure

        + The $prefix/var/wview/archive/wview-hilow.sdb database has one table + for each of the supported sensor types (i.e., inTemp, outTemp, ... + rain, rainRate). Each table contains a record for each hour. The data + starts with your earliest archive record.

        + The columns for each table are:
        +

        +
        +            dateTime         - timestamp for the beginning of the hour for this record
        +            low              - low value for this hour
        +            timeLow          - timestamp when the low occured
        +            high             - high value for this hour
        +            timeHigh         - timestamp when the high occured
        +            whenHigh         - additional value at the time of the high
        +                              (currently only used for wind direction when high wind occured)
        +            cumulative       - the sum of all samples in this record
        +            samples          - the number of samples in this record
        +        
        +

        + + Thus the average for an hour is cumulative/samples.

        + The windDir table has a unique structure. Each row includes the dateTime + stamp and 20 integer bins. Each wind direction reading is tallied in + one of the 20 bins, with each bin representing an 18-degree window. + These bins are used for consensus averaging (the algorithm is found in + windAverage.c).

        + Samples Stored

        + During creation, wview uses archive records to back-fill the HILOW + database. During normal wview operation, LOOP samples are used to + populate the HILOW database, providing a much higher granularity for + detecting low and high events.

        + Regeneration

        + To regenerate the HILOW database:
        + +

        + + + + + + +
        +
        +#> /etc/init.d/wview stop
        +#> sudo rm $prefix/var/wview/archive/wview-hilow.sdb
        +#> /etc/init.d/wview start
        +
        +
        +

        + Note: Doing this will delete the LOOP sample resolution stored while + wview is running, replacing it with back-filled archive data. For a + station with a 5 minute archive interval and 30 second LOOP interval, + that is the difference between 120 samples per hour and 12 samples + per hour. +

        +
        +
        +

        + NOAA Data

        + + Overview

        + The NOAA data is stored in an SQLite3 database. The database is generated + based on records in the HILOW database (much faster and more accurate than + archive records).

        + Structure

        + Each record in wview-noaa.sdb represents one day. The columns for the + noaaHistory table are:
        +

        +
        +            dateTime         - timestamp for the beginning of the day for this record
        +            meanTemp         - average temperature for the day
        +            highTemp         - high temperature for the day
        +            highTempTime     - unix time of high
        +            lowTemp          - low temperature for the day
        +            lowTempTime      - unix time of low
        +            heatDegDays      - heat degree days for the day
        +            coolDegDays      - cool degree days for the day
        +            rain             - rain for the day
        +            avgWind          - average wind for the day
        +            highWind         - high wind for the day
        +            highWindTime     - unix time of high wind
        +            domWindDir       - dominant wind direction for the day
        +        


        + +

        + Regeneration

        + To regenerate the NOAA database:
        +

        + + + + + +
        + +
        +#> /etc/init.d/wview stop
        +#> sudo rm $prefix/var/wview/archive/wview-noaa.sdb
        +#> /etc/init.d/wview start
        +
        +
        +
        +
        +

        + + Day history Table (wview-history.sdb)

        + + Overview

        + This table is generated internally by wview to avoid having to compute + the daily summary records used for the yearly charts every time wview + starts. This was not a time concern with the flat WLK files but became + more costly when we started storing data in SQLite3. The external use + or utility of this table is limited at best.

        + Regeneration

        + To regenerate the NOAA database:
        +

        + + + + + + +
        +
        +#> /etc/init.d/wview stop
        +#> sudo rm $prefix/var/wview/archive/wview-history.sdb
        +#> /etc/init.d/wview start
        +
        + +
        + +
        +
        +

        + SQLite3 Tips

        + To display the dateTime timestamp of any table as local time:
        +

        + + + + + + +
        + sqlite> select datetime(dateTime, 'unixepoch', 'localtime') from archive; +
        +

        + +

        + To fix any archive entries having 255 for the outHumidity:
        +

        + + + + + + +
        + sqlite> update archive set outHumidity = '50' where outHumidity = '255'; +
        +
        +

        + Note: If you modify records in the archive table, you will probably want + to regenerate the HILOW tables

        +

        +
        +
        + +

        + Purging All Weather Data

        + wview will be running the simulator once installation is complete. When + you are ready to "go live" with your station or you just want to purge + all weather data and start over, use the "wviewcleardata" utility to purge + all weather data: +

        + + + + + +
        + #> sudo wviewcleardata
        + + (this will purge all archive records, delete the HILOW, history and NOAA databases)
        + (leaving a clean weather data environment for your station) +
        + +

        + WARNING - take care using this utility, it does not backup data so you should + do that yourself prior to running it if there is any possibility you want + to recover/keep the old weather data! + +

        + + + +

        +  CWOP - Submitting Your Data to NOAA + and the CWOP System +

        + + + +

        + Overview

        + CWOP (Citizen Weather Observer Program, http://www.wxqa.com/) is a system + by which individuals with weather stations and the proper software can + submit their weather data to an APRS-based data storage system so that + anyone, including NOAA, can use the data however they see fit. There are + some really neat station display web sites including some java apps to + look up station data, position, maps, etc.

        + As an example, see http://www.findu.com/cgi-bin/wxpage.cgi?call=CW4097 + for my weather station.

        + CWOP participation requires registering for an APRS "Call Sign". Once you + have configured wview for CWOP properly and confirmed your data online, you + must contact the maintainers via email to confirm your registration. Then + your data will be available for anyone to see and possibly be used in NOAA + forecast models, etc. Pretty cool, eh?

        + When CWOP support is enabled and configured properly, wview will transmit + a new WX packet to the APRS server every 10 minutes based on the last digit + of your callsign.

        + + wview supports the APRS-IS Rollover functionality by enforcing the + definition of 3 APRS-IS server:port pairs - the goal is to avoid data + loss to the CWOP system caused by connection errors. Select 3 different + servers from the list at http://www.wxqa.com/activecwd.html - be advised, + the arizona server is heavily burdened and often refuses connections.

        + Prerequisites
        +

        +
          +
        • Internet connection/firewall which allows TCP port 23 connections + out
        • +
        • Accurate Latitude and Longitude values in the proper format (this + is not the same location info used for general station configuration, + higher precision is required by CWOP)
        • +
        • 3 APRS Servers (see http://www.wxqa.com/activecwd.html for the list)
        • + +
        • 3 APRS Port Numbers, one for each server (usually 23)
        • +
        • APRS Call Sign (CWnnnn/DWnnnn)
        • +

        +

        + Setup

        + Register for an APRS Call Sign (unless you already have one):
        + + + http://www.findu.com/citizenweather/signup.html

        + Determine your accurate latitude and longitude:
        + + http://www.topozone.com/viewmaps.asp

        + Enable CWOP support in wview:
        + Enable the CWOP service on the wviewmgmt Services page.

        + + Configure the CWOP settings on the wviewmgmt CWOP page.

        + Check the wview logs for wvcwopd activity:

        + Look in /var/log/messages for something similar to:
        + "CWOP: configured to submit station CW4097 data to:"
        + "CWOP: Primary: cwop.aprs.net:23"
        + "CWOP: Secondary: cwop.aprs.net:23"
        + + "CWOP: Tertiary: cwop.aprs.net:23"
        + during initialization, and:

        + "CWOP-sending: CW4097>APRS,TCPXX*,qAX,CW4097:@132235z3334.14N/09654.66W_284/002g005t093P002h40b10131.wview160"
        + every 10 minutes at the minute of your last CWOP ID digit.

        + Confirm your data in the CWOP stream:

        + Goto http://www.findu.com/cgi-bin/wxpage.cgi?call=CWxxxx where CWxxxx is + your Call Sign. This should start displaying your weather data graphically + with links on the left for looking at your raw data. Make sure it is all + good - there is much information and many sites to visit to help you verify + your data. Just start at http://www.wxqa.com/activecwd.html.

        + + When all is good, send an email to the CWOP maintainers. Congratulations, + you are now contributing data for the greater good!

        +

        + + + +

        +  Wunderground/Weatherforyou - + Submitting Your Data to Weather Underground and/or Weatherforyou +

        + + + +

        + Overview

        + The Weather Underground (www.wunderground.com/) (Wunderground) is a privately + held organization which provides many weather services - some free and some + not. Among the free services is the ability to register your weather station + and submit your data to them so that you can access your data and some nice + graphs from their site. Weatherforyou.com is also a privately held outfit + with similar capabilities to wunderground.

        + Prerequisites
        +

        +
          + +
        • Internet connection/firewall which allows HTTP connections out
        • +
        • Accurate Latitude and Longitude values in the proper format
        • +
        • libcurl, a "C" URL library distributed with the command line "curl" + utility
        • +
        • A registered Weather Underground/Weatherforyou Station ID
        • +

        +

        + Weather Underground

        + + Register for a Weather Underground Station ID (unless you already have one):
        + + http://www.wunderground.com/weatherstation/usersignup.asp

        + Determine your accurate latitude and longitude:
        + + http://www.topozone.com/viewmaps.asp

        + Enable Wunderground support in wview:
        + + Enable the HTTP service on the wviewmgmt Services page.

        + Configure the HTTP settings on the wviewmgmt HTTP Services page.

        + WeatherForYou

        + Register for a Weatherforyou Station ID (unless you already have one):
        + + http://www.hamweather.net/weatherstations/

        + + Determine your accurate latitude and longitude:
        + + http://www.topozone.com/viewmaps.asp

        + Enable Weatherforyou support in wview:
        + Enable the HTTP service on the wviewmgmt Services page.

        + Configure the HTTP settings on the wviewmgmt HTTP Services page.

        + + Check the wview logs for wvhttpd activity:

        + Your archive interval determines how often wview will update your weather + data to the Wunderground/Weatherforyou systems. The first record should be + generated at the next archive record generation after wview has started.

        + Look in /var/log/messages for something similar to:
        + "WUNDERGROUND: configured to submit station KTXCOLLI1 data to wunderground.com"
        + "WEATHERFORYOU: configured to submit station KTXCOLLI1 data to weatherforyou.com"

        + Confirm your data at the Wunderground server:

        + + Goto http://www.wunderground.com/weatherstation/WXDailyHistory.asp?ID=XXXXXXX + where XXXXXXX is your Wunderground Station ID. This should start displaying + your weather data graphically and as a packet list.

        + Confirm your data at the Weatherforyou server:

        + Goto http://www.hamweather.net/weatherstations/pwsupdate.php?ID=XXXXXXX + where XXXXXXX is your Weatherforyou Station ID. This should start displaying + your weather data as a packet list.

        +

        + + + +

        + +  AWEKAS - Providing Your Data to + Awekas +

        + + +

        + Overview

        + From the AWEKAS site ( + http://www.awekas.at/en/index.php): + "AWEKAS is an acronym for "Automatisches WEtterKArten System" (= automatic + weather map system) and is, as the name already states, a system which + produces overview maps from the data from private weather stations. + The values are made available + by the participants on their websites as Text or CSV files and are + collected by the programme. The process is fully automatic and always + provides maps with up to date weather situations." + + Simply put, if we provide the properly formatted HTML template to wview, + AWEKAS will periodically poll the resulting HTML file for data and store + it in their database for online retrieval.

        + + Setup

        + Add (or uncomment if already there) the Awekas template in + $prefix/etc/wview/html-templates.conf:
        +

        + + + + + +
        + #> sudo vi $prefix/etc/wview/html-templates.conf

        + + (Add the following lines or just remove the '#' from awekas_wl.htx if + already there)
        + [snip]
        + ###############################################################################
        + ### AWEKAS Data Submission Template
        + ###############################################################################
        +
        + + awekas_wl.htx
        + [/snip]

        + Save the file. +
        +

        +

        + + That's it! Once you have registered with AWEKAS this file should be polled + by them periodically to update your station data.

        +

        + + + +

        +  Sensor Calibration +

        + + +

        + Overview

        + Software calibration of basic sensors for any station type are configured + using wviewconfig or the wviewmgmt web interface. Archive data and loop data + used for highs/lows/averages is calibrated before being stored to file or + sent to external data consumers (CWOP, WeatherUnderground, Alarms, etc.).

        + The format of the calibration equation is:
        + CSV = M * X + C, where:
        + + CSV is the calibrated sensor value
        + M is the multiplier configuration value on the wviewmgmt Calibration page
        + X is the raw sensor value
        + C is the constant configuration value on the wviewmgmt Calibration page

        + Sensor List

        + The following sensors can be calibrated:
        + +

        +
        +            Sensor                   Units
        +            ------------------       -----------------
        +            Barometer                US inches
        +            Station Pressure         US inches
        +            Altimeter                US inches
        +            Inside Temp              degrees fahrenheit
        +            Outside Temp             degrees fahrenheit
        +            Inside Humidity          %
        +            Outside Humidity         %
        +            Wind Speed               miles per hour (mph)
        +            Wind Direction           degrees (0-359)
        +            Rain                     US inches
        +            Rain Rate                US inches per hour
        +      

        +

        + Pressure Calibration

        + Each station type produces one type of pressure sensor: Barometric + Pressure (BP), Station Pressure (SP) or Altimeter (ALT). wview converts + the sensor provided to the other two types algorithmically. It is only + necessary to calibrate the pressure type provided by your station - wview + will apply the calibration prior to the conversion to the other two types. + Calibration values entered for the two derived types will be ignored by + wview.

        +

        + +
        +            Station           Pressure Type to Calibrate
        +            ----------------  --------------------------
        +            Vantage Pro       BP
        +            WXT510            SP
        +            WS-2300           SP
        +            WMR918            SP
        +      
        +

        + Regardless of the type you are calibrating, you will generally be using + the BP value to compare to local stations. Just remember to adjust the + sensor calibration for the native pressure of your station.

        + Notes

        + Calibration is done in US units only. Station data is always received/ + stored in imperial units. Thus calibration is done in imperial units. + If you are a metric station, *figure out* what the imperial equivalent + for your metric calibration would be, and use that for the calibration + settings.

        +

        + + + +

        +  Alarms +

        + + +

        + + Overview

        + If the wviewmgmt Services page has "Enable Alarms (wvalarmd)" disabled + when wvalarmd starts, it will exit and alarms will be disabled. + If enabled, wvalarmd "registers" with the wviewd daemon to receive + LOOP data at an interval determined by the "Data Push Interval (seconds)" + parameter of the wviewmgmt Station page (the default is every 60 seconds). + When a new LOOP data packet is received by wvalarmd, it checks all defined + alarms to determine if an alarm has been triggered. If so, the alarm + abatement counter is initialized and the alarm binary/script is invoked + with the following arguments:

        +

        +
        +            argv[0]/$0         full path of binary/script being run
        +            argv[1]/$1         alarm type (see definitions below)
        +            argv[2]/$2         alarm threshold
        +            argv[3]/$3         value which triggered the alarm
        +      
        +

        + It is not hard to conclude the following:
        + +

        +
          +
        1. Multiple alarm scripts may be defined for the same LOOP data value.
        2. +
        3. Multiple LOOP data values may use the same binary/script since the + alarm type, threshold and triggering value are all passed to the + binary/script.
        4. +
        5. wview alarms could be used as a data feed to another application + which requires weather data updates, you could even run an + "html-less" wview that does nothing more than archive weather data + and feed current values to another application via wvalarmd.
        6. +
        +

        + +
        + Alarm Abatement

        + To control the frequency for which an alarm script/binary is invoked + while the data type is exceeding the threshold, an alarm configuration + parameter called Abatement is provided. This is simply the number of + seconds after an alarm triggers to "hold off" or not invoke the alarm + binary/script. Once this abatement period expires, a new alarm + binary/script invocation will occur if the alarm is triggered.

        + Alarm Update Frequency

        + This is controlled by the "Data Push Interval (seconds)" parameter of + the wviewmgmt Station page (the default is every 60 seconds). It is + given in seconds. One should avoid setting it to anything less than 10 - + I'm sure it would be fine, but you are not going to get new data updates + from the VP Console any faster than once every 10 seconds.

        + Weather Data Update Frequency

        + + This is controlled by the "Sensor Polling Interval (seconds)" parameter + on the wviewmgmt Station page. This determines how often the wviewd + daemon polls the station for current conditions. It is given in seconds + and should not reasonably be set to anything less than 10 seconds, and + must be an even divisor of 60.

        + Raw Data Feeds

        + wvalarmd implements a TCP socket server to accept connections for clients + wanting a raw feed of loop packet data. See the section "Socket-Based + Data Feeds" below for details.

        +

        +
        +

        + Configuration

        + + Configure the "Data Push Interval (seconds)" parameter of the wviewmgmt + Station page to your requirements. This value is given in seconds. This + determines how often wvalarmd will receive current conditions data.

        + Choose your station units type by indicating "Enable Metric Units for + Alarms?" on the wviewmgmt Alarms page.

        + Specify your alarm definitions for up to 10 distinct alarms on the wviewmgmt + Alarms page.

        + Scripts: $prefix/etc/wview/alarms/*.sh

        + If using notification scripts, they should be placed in + $prefix/etc/wview/alarms and will receive the arguments described above + when invoked.
        + There are a few example scripts in the distribution directory + examples/alarms - these will be copied to $prefix/etc/wview/alarms for new + installs.
        + + If using custom binaries, make note of the arguments passed to the binary + above.
        + The example scripts log alarm events to a log file: + $prefix/var/wview/alarms/alarm.log. This may be a feature you want to keep - + even if you are doing other things in your alarm notification scripts.

        + Alarm Type Definitions
        +

        +
        +            Type (wview-conf.sdb)     Value (passed to script/binary)
        +            ----------------------    -------------------------------
        +            Barometer                 0
        +            InsideTemp                1
        +            InsideHumidity            2
        +            OutsideTemp               3
        +            WindSpeed                 4
        +            TenMinuteAvgWindSpeed     5
        +            WindDirection             6
        +            OutsideHumidity           7
        +            RainRate                  8
        +            StormRain                 9
        +            DayRain                   10
        +            MonthRain                 11
        +            YearRain                  12
        +            TxBatteryStatus           13
        +            ConsoleBatteryVoltage     14
        +            DewPoint                  15
        +            WindChill                 16
        +            HeatIndex                 17
        +            Radiation                 18
        +            UV                        19
        +            ET                        20
        +            ExtraTemp1                21
        +            ExtraTemp2                22
        +            ExtraTemp3                23
        +            SoilTemp1                 24
        +            SoilTemp2                 25
        +            SoilTemp3                 26
        +            SoilTemp4                 27
        +            LeafTemp1                 28
        +            LeafTemp2                 29
        +            ExtraHumid1               30
        +            ExtraHumid2               31
        +      
        +
        + +

        + Trying It Out

        + To get an idea how it works and to gain familiarity with configuration, + do the following:
        +

        +
          +
        1. Enable and configure alarms (Edit the Thresholds - configure a few + alarms for thresholds very near or already "past" your current outside + temperature.
        2. +
        3. Watch the magic:
          + + + + + + +
          + #> tail -f $prefix/var/wview/alarms/alarm.log +
          +
        4. + +

        + + + +

        +  Socket-Based Data Feeds +

        + + + +

        + Overview +

        +

        + For true data feeds, wvalarmd provides a socket server which listens on + port 11011 of the wview server for connections. When a datafeed client + connects on that port, wvalarmd adds the client to the datafeed client + list. Then when loop data is received from wviewd, wvalarmd will write + the loop packet into each datafeed client's socket, preceded by a start + frame sequence. When an archive record is generated by the wviewd daemon + wvalarmd will write it into each datafeed client's socket, preceded by a + start frame sequence unique to archive data. Newly connected clients may + request archive records in order to synchronize any records generated + since the last connection. There is a unique start frame for this archive + request packet which just contains the epoch time to indicate "get the + next record after this time" (or alternatively, "this is the last record + I have, give me the next one"). Other than this synchronization of archive + records process, it is a one-way interface and the client may disconnect + at any time by calling "shutdown" followed by "close" to close the socket. + The number of active client connections is only limited by system + resources. +

        +

        + The datafeed client will receive modified loop and archive packets as + defined in common/datafeed.h - see the structures "LOOP_PKT_FP" and + "ARCHIVE_PKT_FP". Conversion utilities are provided in common/datafeed.c + to convert the fixed point representations transmitted over the socket + back to the floating point format used internally by wview. See the + alarms/sample-datafeed-client/datafeedClient.c file for an example + implementation of a client and the use of the conversion utilities. + Note that "HTON" denotes "host to network" and "NTOH" denotes "network + to host" in the conversion utility function names. The Virtual station + type uses the remote datafeed server for its data source and is another + good example of the use of the datafeed server interface.

        + +

        +

        + Configuration

        + None required for socket data feeds, just the wviewmgmt Station page + "Data Push Interval" which controls how often LOOP data is pushed to the + clients. Archive packets will be pushed each time a new archive record + is generated. No alarm entries are required in the configuration database + for datafeed clients.

        + Client Requirements

        + The datafeed sockets are regular TCP/stream PF_INET sockets. The general + connection steps (in C) are as follows:
        +

        + + + socket (...) // create the socket descriptor
        + [setup server address and port in socckaddr_in structure]
        + connect (...)

        +
        +

        + The client then may add the socket descriptor to an fdset for select + calls, simply block on the socket for reads, however you want to design + it. Other programming language procedures may differ, but the general + approach should be the same.

        + + Normal Processing "Loop":
        +

        +
          +
        1. Wait for data on the socket
        2. +
        3. Sync to the start frame sequence: 0xF388, 0xC6A2, 0xDADA, 0x000X + (where "X" can be 1 or 2 indicating "LOOP" or "ARCHIVE" packet)
        4. +
        5. Read loop or archive packet
        6. +
        7. Convert packet from network to host format (floating point and + endianess) - see the example and common/datafeed.[ch] +
        8. Process the loop or archive packet
        9. + +
        10. Goto #1
        11. +
        +

        + To disconnect:
        +

        + + shutdown (sockfd, 2)
        + + close (sockfd)
        +

        +

        + Note: There is a handy radlib socket API which is illustrated in the + sample datafeed client source code which takes care of most of the + above connection procedure for you.

        + Running the Example Datafeed Client

        + The directory alarms/sample-datafeed-client contains a simple client + example including Makefile. It accepts an argument for host but will + use "localhost" if none is given. It connects to the wvalarmd server + and logs the current temperature when loop packets are received and + logs the dateTime of the archive when archive records are received.

        +

        + + + + + + + +
        + Build the example client:
        + #> cd alarms/sample-datafeed-client
        + #> make (or "gmake" for *BSD)

        + + Run it:
        + (with wview already running)
        + #> ./datafeedClient [wview_hostname]

        + Watch the system log for wvalarmd connection messages
        + Watch the console for client update messages from the example client:
        + #> tail -f /var/log/syslog +
        +
        +

        + Kill the example client:
        + Either "ctrl-c" if you ran it in the foreground, or "kill -15 [pid]" + if you backgrounded it when you started it.

        + Testing multiple clients:
        + + Just repeatedly run the datafeed client application - each client + will have its own socket connection to wview. Kill them as described + above when you are done.

        +

        + + + +

        +  Using a MySQL or PostgreSQL Server + to Store Data +

        + + +

        + Overview

        + MySQL and PostgreSQL storage of weather data is now accomplished through + export scripts which are provided in the wview distribution. These scripts + are run via CRON periodically to export data from the SQLite3 databases + to the export databases.

        + MySQL Installation and Setup

        +

          +
        1. Ensure you have a functioning MySQL server running either local to + the wview server or remote. In either case, you will need an account on + the MySQL server which has create and grant privileges.
        2. + +
        3. Prerequisites On the wview Server - be sure you have sqlite3 and + mysqlimport installed on the wview server. Even if the MySQL server is + remote, you need mysqlimport installed locally.
        4. +
        5. Local Server:
          + + + + + +
          + Create the MySQL Database, tables and user:
          + + (Be sure you have set your MySQL account settings using wviewmgmt + or wviewconfig before creating locally).
          + #> wview-mysql-export create [root_mysql_user_password]
          + (This runs wview-mysql-create for you locally using configuration + database settings)

          +
          + +
        6. +
        7. Remote Server:
          + + + + + +
          + Copy the wview-mysql-create script to the remote server:
          + #> scp $exec_prefix/wview-mysql-create [username]@[mysql_host]:/downloads
          + + (copy wherever you like on the remote server)
          + (wview-mysql-create can be found by executing: whereis + wview-mysql-create)

          + Login to the remote server:
          + #> ssh [username]@[mysql_host]

          + Execute the create script (make sure the admin user has the right + privileges):
          + #> wview-mysql-create help (to see usage text)
          + + #> wview-mysql-create [admin_username] [admin_password] [db_username] + [db_password] [db_name]
          + (admin_username can be the same as db_username)

          + Log out of the remote server:
          + #> exit
          +
          + +
        8. +

        +

        + PostgreSQL Installation and Setup

        +

        +
          +
        1. NOTE: The PostgreSQL user created when you setup PostgreSQL will + either be pgsql or postgres. postgres is used for this example. We + will use the unix root account for all export activities so the + create and export scripts should be run as root or from the root + CRON table.
        2. +
        3. Ensure you have a functioning PostgreSQL server running either + local to the wview server or remote.
        4. + +
        5. Prerequisites On the wview Server - be sure you have psql installed + on the wview server. Even if the PostgreSQL server is remote, you need + psql installed locally.
        6. +
        7. Create a PostgreSQL user for the unix root user:
          + + + + + +
          + Login to the PostgreSQL account:
          + + user@wview_server> su
          + root@wview_server:# su postgres

          + Create an administrative user based on the root unix account:
          + postgres@wview_server:# createuser root

          + Answer the question:
          + Shall the new role be a superuser? (y/n) y

          + + Logout of the PostgreSQL account:
          + postgres@wview_server:# exit
          +
          +
        8. +
        9. Create the wview database:
          + + + + + + +
          + Create the wview database:
          + root@wview_server:# createdb wviewDB
          + Note: wviewDB should be the same value specified on the wviewmgmt + SQL Export page for "SQL Database Name".
          + +
          +
        10. +
        11. Create the export tables and 'replace' triggers:
          + + + + + + +
          + root@wview_server:# wview-pgsql-create wviewDB
          +
          +
        12. +
        +

        + + Test Run and Debug

        + The end game is to create a cron job which runs wview-mysql-export + periodically (every 5 minutes seems reasonable although the export is + surprisingly fast so 1 minute periods are not out of the question). First, + let's run it from the command line and confirm it is working.
        + Note: If using PostgreSQL, substitute "wview-pgsql-export" for + "wview-mysql-export".
        +

        + + + + + + +
        + Execute with debugs enabled:
        + #> wview-mysql-export debug

        + Examine the CSV export files:
        + #> cat $prefix/etc/wview/export/archive.csv
        + #> cat $prefix/etc/wview/export/outTemp.csv
        + + #> cat $prefix/etc/wview/export/noaaHistory.csv

        + These will probably be large given this was the first export. They + should contain all records up to the present time.

        + Examine the MySQL tables:
        + (login to the remote server if not local)
        + #> mysql --user=[db_username] --password=[db_password]
        + mysql> use [db_name];
        + + mysql> select count(*) from archive;
        + (This should match the record count in your SQLite3 database)

        + mysql> select * from windDir;
        + (The 20 wind direction bins should display direction counts = + 60/archive_interval for each row)

        + mysql> select * from noaaHistory;
        + (There should be one record for each day of archive data)

        + + Examine the Import Logs:
        + #> cat $prefix/etc/wview/export/mysql_import.log
        +
        +
        +

        + Production Setup

        + +

        + + + + + +
        + Add to the root crontab on the wview server:
        + #> sudo crontab -e

        + Add the following line and save the crontab:
        + + 1,6,11,16,21,26,31,36,41,46,51,56 * * * * $exec_prefix/wview-mysql-export

        + This runs the wview-mysql-export script every 5 minutes (1 minute past + archive record reception for 5 minute archive interval)

        + Example where $exec_prefix is "/usr/local/bin":
        + 1,6,11,16,21,26,31,36,41,46,51,56 * * * * /usr/local/bin/wview-mysql-export
        +
        + + +

        + Done - you can check the $prefix/etc/wview/export directory and the MySQL + database to confirm the updates.

        + Notes

        + The records imported into the MySQL database tables are set to replace any + existing records with the same dateTime stamp. This is so we get the latest + HILOW records for the current hour and so if better/more complete data is + re-exported, it will take precedence over older data. You will notice in the + debug log file $prefix/etc/wview/export/mysql_import.log that records are + deleted and inserted at incremental updates. This is the HILOW last record + phenomenon.
        + If you need to cause a complete re-export of all data, delete the file + $prefix/etc/wview/export/mysql_export_marker. The next time the wview-mysql-export + script runs, all data will be exported (and replace existing data in the MySQL + database).

        + PostGreSQL and FreeBSD Semaphore Resources

        + + FreeBSD starts with a limited number of semaphores available for application + use. radlib and wview use some semaphores and so does PostgreSQL. Often the + default number is not sufficient. Here is how to increase the number of + semaphores available:

        +

        +
          +
        1. In /boot/loader.conf add the lines:
          + kern.ipc.semmni="256"
          + kern.ipc.semmns="512"
          + kern.ipc.semmnu="256"
        2. + +
        3. In /etc/sysctl.conf add these lines:
          + kern.ipc.shmmax=536870912
          + kern.ipc.semmap=256
          + kern.ipc.shm_use_phys=1
          + kern.ipc.shmall=131072
        4. +
        5. Reboot
        6. + +
        + + + +

        +  Existing Weatherlink Archive Files +

        + + + +

        + Overview

        + If you have existing archive files (*.wlk) that you want to import, use + the wlk2sqlite utility to import all archive records into the SQLite3 + database prior to starting wview. It will find them and use them for + historical charts and NOAA reports. Be sure they were collected using the + same archive interval that you are going to use for your VP with wview.

        + Configuration
        +

        +
          +
        • If you need to convert archive files from big endian to little endian, + use the arc_be2le utility (specifying source and destination directories). + This will convert all WLK files in the source + directory and place them in the destination directory specified. Folks + moving from a PPC-based Mac to a PC host (for example) would need to do + this conversion on their existing archive directory.
        • + +
        • If you need to convert archive files from little endian to big endian, + use the arc_le2be utility (specifying source and destination directories). + This will convert all WLK files in the source + directory and place them in the destination directory specified. Folks + moving from a PC host to an NSLU2 embedded host or a PPC-based Mac + (for example) would need to do this conversion on their existing archive + directory.
        • +
        • Execution of wlk2sqlite may require modifying the dynamic library load + path, i.e.:
          + >export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib
          + You'll know you need this if trying to execute wlk2sqlite produces the + following message:
          + *wlk2sqlite: error while loading shared libraries: librad.so.0: + cannot open shared object file: No such file or directory* + ... or similar message.

        • +
        + + + +

        +  Run Environment Description +

        + + +

        + + Configuration Files ($prefix/etc/wview)
        +

        +
        +        *.conf                  - configuration files described below
        +        html/*.htx              - HTML template files to be customized for your 
        +                                  site and language
        +        html/parameterlist.txt  - contains the list of all available HTML tags 
        +                                  which can be placed in *.htx template files. 
        +                                  These tags are replaced each time htmlgend 
        +                                  generates new image and html files.
        +      


        +

        + Data Files ($prefix/var/wview)
        +

        + +
        +        *.pid                   - run time pid files created/deleted by the wview
        +                                  daemons. Do NOT alter or delete these files. They 
        +                                  should only be present if the corresponding wview 
        +                                  daemon is running.
        +        archive/wview-archive.sdb - SQLite 3 archive database file
        +        dev/*                   - FIFO device files created by wview - do NOT alter 
        +                                  or delete them
        +        img/*                   - default destination for generated image and HTML
        +                                  files - in particular if subsequent ftp/ssh of 
        +                                  files is required
        +        noaa/noaa.dat           - NOAA data file
        +      

        + + + +

        +  Ethernet Setup for the GW21E Serial + Server +

        + + +
          +
        1. Configure your GW21E IP address as described in the GW21E Quickstart + Guide.
        2. +
        3. Telnet into the GW21E (user=admin, passwd=NULL).
        4. +
        5. Set Serial Server parameters and Packet Delimiter so that your config + looks like:
          +
          +        [snip]
          +        root@localhost:/root/dev# telnet 10.10.10.22
          +        Trying 10.10.10.22...
          +        Connected to 10.10.10.22.
          +        Escape character is '^]'.
          +        ABLELink Ethernet-Serial Server
          +        User name:admin
          +        Password:
          +        
          +        Login ok
          +        
          +        0.Exit  1.Overview  2.Networking  3.Security  4.COM1
          +        
          +        Input choice and enter(0~4): 4
          +        4
          +        
          +        COM0:
          +        1.  Link Mode (TCP Server/Virtual COM Disabled/Pair Connection Disabled/Filter disabled/3001 /Alive=10*10 sec)
          +        2.  COM Port (VP/RS-232/19200,None,8,1/RTS/CTS)
          +        3.  Keep Serial Buffer's Data While Connecting(Disabled)
          +        4.  Packet Delimiter (1 ms)
          +        5.  Accept Control Command from COM port (Disabled)
          +        
          +        Input choice and enter(1~5):
          +        [snip]
          +          

          + + Remember to use "CTRL-]" to get to the telnet prompt and type "quit" to + exit - their utility does not have an exit path in the menu! I had to + quit and re-login multiple times while configuring it.

          + Given the above configuration, your wview ethernet configuration should + be:
          + Host: 10.10.10.22 (whatever the IP address is that you assigned it)
          + Port: 3001 (set in section 4 of the GW21E config utility)

          +
        6. +
        + + + +

        +  Ethernet Setup for the Lantronix + MSS1-T +

        + + +
          +
        1. Connect to the MSS1-T via the serial port - factory defaults are + 9600, 8N1. Login as admin, then enter "set privileged" with password + "system".
        2. + +
        3. Configure the MSS1-T so that your "show server" command displays:
          +
          +        Lantronix MSS1 Version V3.5/5(980529)
          +        
          +        Type HELP at the 'Local_2> ' prompt for assistance.
          +        
          +        Username> admin
          +        
          +        Local_2> show server
          +        
          +           MSS1 Version V3.5/5(980529)            Uptime:                  0:06:16
          +           Hardware Addr: 00-80-a3-0f-f3-e2       Name/Nodenum:      MSS_0FF3E2/ 0
          +           Ident String: Micro Serial Server
          +        
          +           Inactive Timer (min):           30     Serial Delay (msec):          10
          +           Password Limit:                  3     Session Limit:                 4
          +           Queue Limit:                    32     Node/Host Limits:          50/20
          +        
          +        LAT  Circuit Timer (msec):         80     Keepalive Timer (sec):        20
          +           Multicast Timer (sec):          30     Retrans Limit:                10
          +        
          +        TCP/IP Address:           10.10.10.22     Subnet Mask:       255.255.255.0
          +           Nameserver:            (undefined)     Backup Nameserver:   (undefined)
          +           TCP/IP Gateway:         10.10.10.1     Backup Gateway:      (undefined)
          +           Domain Name:           (undefined)     Daytime Queries:         Enabled
          +                                                  TCP Keepalives:          Enabled
          +           DHCP Server:                  None     Lease Time:                 0:00
          +        
          +           Load Address:    00-00-00-00-00-00     Prompt:              Local_%n%P>
          +        
          +           Characteristics:
          +           Incoming Logins:  Telnet   (No Passwords Required)
          +           LAT Groups: 0
          +        
          +        Local_2>
          +        
          +        The port to use on the MSS1-T is the raw mode port 3001.
          +        
          +        Given the above configuration, your wview ethernet configuration should
          +        be:
          +        Host: 10.10.10.22    (whatever the IP address is that you assigned it)
          +        Port: 3001           (not configurable)
          +          

        4. +
        + + + +

        + +  Endian Conversion Utilities +

        + + +

        + wview provides two command-line utilities to convert wview archive directories + from Big Endian-to-Little Endian or Little Endian-to-Big Endian. These are + necessary if you want to change your wview host from a Mac/NSLU2 to a PC or + vice versa.

        + arc_be2le
        + Usage: arc-be2le [source_directory] [dest_directory]

        + + Convert wview archive data in [source_directory] from big endian + to little endian then store the result in [dest_directory].

        + arc_le2be
        + Usage: arc_le2be [source_directory] [dest_directory]

        + Convert wview archive data in [source_directory] from little endian + to big endian then store the result in [dest_directory].


        +

        + + + +

        +  Chapter 8: Troubleshooting +

        +

        + This chapter provides invaluable information for understanding wview + operation and diagnosing problems you may encounter. +

        + + + + +

        +  wview Processes +

        + + +

        + Please note there are two unix processes for each wview daemon: this is + normal radlib operation. One is the actual daemon and the other is a + "reflector" process which allows the daemon to send events to itself and + to open the message queue FIFO properly. Don't let that bother you.

        + Here are the processes running on my server (no ssh/ftp because I don't + need it):
        + +

        +
        +    root@localhost:/root# ps aux | grep wv
        +    root     73863  0.0  0.2  2320 1808  ??  Ss    7:08AM   0:00.02 /usr/local/bin/radmrouted 1 $prefix/var/wview
        +    root     73865  0.0  0.2  2300 1780  ??  I     7:08AM   0:00.00 /usr/local/bin/radmrouted 1 $prefix/var/wview
        +    root     73868  0.0  0.1  2380 1472  ??  Ss    7:08AM   0:00.36 /usr/local/bin/wviewd
        +    root     73870  0.0  0.1  2356 1196  ??  I     7:08AM   0:00.00 /usr/local/bin/wviewd
        +    root     73873  0.0  0.1  2376 1524  ??  Is    7:08AM   0:35.43 /usr/local/bin/wviewsqld
        +    root     73875  0.0  0.1  2324 1140  ??  I     7:08AM   0:00.00 /usr/local/bin/wviewsqld
        +    root     73879  0.0  0.1  2336 1468  ??  Ss    7:08AM   0:00.01 /usr/local/bin/wvalarmd
        +    root     73881  0.0  0.1  2312 1136  ??  I     7:08AM   0:00.00 /usr/local/bin/wvalarmd
        +    root     73882  0.0  0.1  2392 1504  ??  Is    7:08AM   0:00.01 /usr/local/bin/wvcwopd
        +    root     73884  0.0  0.1  2304 1136  ??  I     7:08AM   0:00.00 /usr/local/bin/wvcwopd
        +    root     73885  0.0  0.3  4944 3100  ??  Is    7:08AM   0:00.03 /usr/local/bin/wvhttpd
        +    root     73886  0.0  0.2  4788 2216  ??  I     7:08AM   0:00.00 /usr/local/bin/wvhttpd
        +    root@localhost:/root# ps aux | grep htmlgen
        +    root     73876  0.0  0.3  3960 2800  ??  Is    7:08AM   0:05.82 /usr/local/bin/htmlgend
        +    root     73878  0.0  0.2  3860 1964  ??  I     7:08AM   0:00.00 /usr/local/bin/htmlgend
        +    root@localhost:/root#
        +      

        +

        + This indicates I have 6 wview daemons running: wviewd, htmlgend, wviewsqld, + wvalarmd, wvcwopd and wvhttpd. It also displays the radlib message router + process radmrouted.

        +

        + + + + +

        +  Observing wview Operation +

        + + +

        + wview logs quite a bit of information about what is being initialized, + daemons started, etc. in the system log file (/var/log/messages, + /var/log/syslog, etc.). This is where to go to try to investigate a startup + problem or just observe what wview is "doing".
        + + On the other hand, once you get everything set up the way you want, you + can disable informational logs if you are concerned about how quickly your + /var/log/syslog file is rolling over (you are rotating your log files, + right?). Set verbosity off as required on the wviewmgmt Services page to + disable informational log entries. You can always re-enable it if you need + the verbosity.

        +

        +
        +

        + The daily historical charts are generated using the archive records from + the last 24 hours. The granularity depends upon your archive interval + choice. These charts are updated at the same frequency as the archive + interval to include the last archive interval.

        + The weekly and28-day> historical charts are generated using the + hourly average of archive records for the last 28 days. These charts are + updated once per hour to include the last hour's averages.

        + + The 365-day historical charts are generated using the daily average + of archive records for the last 365 days. These charts are updated once per + day (12:01AM) to include the last day's averages.

        +

        + + + +

        +  SYS V IPC (Interprocess Communication) + +

        + + +

        + wview uses many radlib facilities which are based on the system V IPC + concepts of shared memory and semaphores.

        + Here is a listing of IPC objects while wview is running (FreeBSD):
        +

        + + + + + + +
        + root@localhost:/root# ipcs
        +
        +        Message Queues:
        +        T     ID     KEY        MODE       OWNER    GROUP
        +        
        +        Shared Memory:
        +        T     ID     KEY        MODE       OWNER    GROUP
        +        m 4128768     126979 --rw-rw-r--     root    wheel
        +        m 4128769     126977 --rw-rw-r--     root    wheel
        +        m 4128770  267386882 --rw-rw-r--     root    wheel
        +        
        +        Semaphores:
        +        T     ID     KEY        MODE       OWNER    GROUP
        +        s 4128768  267386881 --rw-r--r--     root    wheel
        +        s 4128769     126978 --rw-r--r--     root    wheel
        +            
        + root@localhost:/root#

        + + And, after running /etc/rc.d/wview stop:
        + root@localhost:/root# /etc/rc.d/wview stop
        + Shutting down weather daemons...
        + root@localhost:/root# ipcs
        +
        +        Message Queues:
        +        T     ID     KEY        MODE       OWNER    GROUP
        +        
        +        Shared Memory:
        +        T     ID     KEY        MODE       OWNER    GROUP
        +        
        +        Semaphores:
        +        T     ID     KEY        MODE       OWNER    GROUP
        +            
        + + root@localhost:/root#
        +
        +
        +

        + Linux systems tend to have other IPC objects defined - you do not want to + disturb those. The wview objects are discernable by the "KEY" values:
        + 126977 (0x1F001) - 126979 (0x1F003)    (wview-specific)
        + + 267386881 (0xFF00001) - 267386882 (0xFF00002)    (radlib general) +

        + If, after shutting down wview, any of the IPC objects associated with wview + (or radlib) still remain, you will need to remove them manually using the + "ipcrm" command. This is normally caused by failed wview starts when + initially installing wview with improper configuration or by a faulty wview + start/stop script which is leaving wview processes running.

        +

        + + + +

        +  wview Startup + +

        + + +

        + wview generates a number of log messages, in particular during + initialization. The system log file is normally /var/log/messages. I + typically run "tail -f /var/log/syslog" in a separate console when + starting up wview. You should also when you first install it. Informational + logs can later be disabled using the wviewmgmt web interface (Services page) + then sending a HUP signal to wviewd and htmlgend: + (kill -s HUP `cat $prefix/var/wview/wviewd.pid`) and + (kill -s HUP `cat $prefix/var/wview/htmlgend.pid`) respectively.

        + If you have errors in your startup log, you will need to stop all wview + daemons using the wview start script (/etc/init.d/wview stop). These are + independent processes which will not shut down automatically if there is + an error in one of them. You must use the wview script.

        + Here is what the log file looks like on my server when wview starts up, so + you know what a good start looks like:
        +

        + +
        +    Apr 18 08:48:44 linuxquad radmrouted[11676]: <1240062524756> : radlib: radmrouted started as a daemon ...
        +    Apr 18 08:48:44 linuxquad radmrouted[11676]: <1240062524764> : started on radlib system 1, workdir /usr/local/var/wview
        +    Apr 18 08:48:44 linuxquad radmrouted[11676]: <1240062524764> : running...                                              
        +    Apr 18 08:48:45 linuxquad wviewd[11680]: <1240062525759> : radlib: wviewd started as a daemon ...                      
        +    Apr 18 08:48:45 linuxquad wviewd[11680]: <1240062525759> : wview 5.4.0 starting ...                                    
        +    Apr 18 08:48:45 linuxquad wviewd[11680]: <1240062525777> : station interface: serial ...                               
        +    Apr 18 08:48:45 linuxquad wviewd[11680]: <1240062525777> : Rain Season Start Month set to 1                            
        +    Apr 18 08:48:45 linuxquad wviewd[11680]: <1240062525777> : Rain Storm Start Trigger set to  0.05 in/hr                 
        +    Apr 18 08:48:45 linuxquad wviewd[11680]: <1240062525778> : Rain Storm Stop Time set to 12 hours                        
        +    Apr 18 08:48:45 linuxquad wviewd[11680]: <1240062525778> : Rain YTD preset set to 0.00 inches                          
        +    Apr 18 08:48:45 linuxquad wviewd[11680]: <1240062525778> : ET YTD preset set to 0.000 inches                           
        +    Apr 18 08:48:45 linuxquad wviewd[11680]: <1240062525778> : bad rain/ET YTD Year given, disabling...                                                           
        +    Apr 18 08:48:45 linuxquad wviewd[11680]: <1240062525778> : station polling interval set to 30 seconds                                                         
        +    Apr 18 08:48:45 linuxquad wviewd[11680]: <1240062525837> : HILOW: database OK                                                                                 
        +    Apr 18 08:48:45 linuxquad wviewd[11680]: <1240062525837> : HILOW: beginning normal LOOP operation                                                             
        +    Apr 18 08:48:45 linuxquad wviewd[11680]: <1240062525837> : -- Station Init Start --                                                                           
        +    Apr 18 08:48:45 linuxquad wviewd[11680]: <1240062525838> : station archive interval: 5 minutes                                                                
        +    Apr 18 08:48:45 linuxquad wviewd[11680]: <1240062525838> : Simulator station opened: 720 minute data generation period...                                     
        +    Apr 18 08:48:45 linuxquad wviewd[11680]: <1240062525839> : running...                                                                                         
        +    Apr 18 08:48:45 linuxquad wviewd[11680]: <1240062525839> : station location: elevation: 751 feet                                                              
        +    Apr 18 08:48:45 linuxquad wviewd[11680]: <1240062525839> : station location: latitude: 33.6 N  longitude: 96.9 W                                              
        +    Apr 18 08:48:45 linuxquad wviewd[11680]: <1240062525839> : initializing computed data values...                                                               
        +    Apr 18 08:48:45 linuxquad wviewd[11680]: <1240062525841> : computeDataAllTime: 200904                                                                         
        +    Apr 18 08:48:45 linuxquad wviewd[11680]: <1240062525846> : computeDataYear: 200904                                                                            
        +    Apr 18 08:48:45 linuxquad wviewd[11680]: <1240062525851> : computeDataMonth: 200904                                                                           
        +    Apr 18 08:48:45 linuxquad wviewd[11680]: <1240062525856> : computeDataWeek: 20090414                                                                          
        +    Apr 18 08:48:46 linuxquad wviewd[11680]: <1240062526054> : -- Station Init Complete --                                                                        
        +    Apr 18 08:48:46 linuxquad wviewd[11680]: <1240062526055> : newest archive record: 2009-04-14 22:40                                                            
        +    Apr 18 08:48:46 linuxquad htmlgend[11686]: <1240062526745> : radlib: htmlgend started as a daemon ...                                                         
        +    Apr 18 08:48:46 linuxquad wvalarmd[11691]: <1240062526746> : radlib: wvalarmd started as a daemon ...                                                         
        +    Apr 18 08:48:46 linuxquad htmlgend[11686]: <1240062526746> : generating to /usr/local/var/wview/img                                                           
        +    Apr 18 08:48:46 linuxquad htmlgend[11686]: <1240062526746> : templates at /usr/local/etc/wview/html                                                           
        +    Apr 18 08:48:46 linuxquad htmlgend[11686]: <1240062526747> : !! Dual units will be displayed !!                                                               
        +    Apr 18 08:48:46 linuxquad htmlgend[11686]: <1240062526747> : Tag Search red-black tree: max black node tree height: 7                                         
        +    Apr 18 08:48:46 linuxquad wvalarmd[11691]: <1240062526747> : ALARM daemon not enabled - exiting...                                                            
        +    Apr 18 08:48:46 linuxquad wvcwopd[11696]: <1240062526747> : radlib: wvcwopd started as a daemon ...                                                           
        +    Apr 18 08:48:46 linuxquad wvcwopd[11696]: <1240062526748> : CWOP daemon is NOT enabled - exiting...                                                           
        +    Apr 18 08:48:46 linuxquad wviewftpd[11701]: <1240062526749> : radlib: wviewftpd started as a daemon ...                                                       
        +    Apr 18 08:48:46 linuxquad wviewftpd[11701]: <1240062526750> : ftp daemon disabled - exiting...                                                                
        +    Apr 18 08:48:46 linuxquad wviewsshd[11706]: <1240062526751> : radlib: wviewsshd started as a daemon ...                                                       
        +    Apr 18 08:48:46 linuxquad wviewsshd[11706]: <1240062526752> : ssh daemon disabled - exiting...                                                                
        +    Apr 18 08:48:46 linuxquad wvpmond[11711]: <1240062526753> : radlib: wvpmond started as a daemon ...                                                           
        +    Apr 18 08:48:46 linuxquad wvpmond[11711]: <1240062526829> : PMON: wviewd: 0                                                                                   
        +    Apr 18 08:48:46 linuxquad wvpmond[11711]: <1240062526829> : PMON: wviewd process monitoring is disabled                                                       
        +    Apr 18 08:48:46 linuxquad wvpmond[11711]: <1240062526829> : PMON: htmlgend: 0                                                                                 
        +    Apr 18 08:48:46 linuxquad wvpmond[11711]: <1240062526829> : PMON: htmlgend process monitoring is disabled                                                     
        +    Apr 18 08:48:46 linuxquad wvpmond[11711]: <1240062526829> : PMON: wvalarmd: 120                                                                               
        +    Apr 18 08:48:46 linuxquad wvpmond[11711]: <1240062526829> : PMON: pid file /usr/local/var/wview/wvalarmd.pid not present, disable monitoring...               
        +    Apr 18 08:48:46 linuxquad wvpmond[11711]: <1240062526829> : PMON: wvcwopd: 120                                                                                
        +    Apr 18 08:48:46 linuxquad wvpmond[11711]: <1240062526829> : PMON: pid file /usr/local/var/wview/wvcwopd.pid not present, disable monitoring...                
        +    Apr 18 08:48:46 linuxquad wvpmond[11711]: <1240062526829> : PMON: wvhttpd: 120                                                                                
        +    Apr 18 08:48:46 linuxquad wvpmond[11711]: <1240062526829> : PMON: pid file /usr/local/var/wview/wvhttpd.pid not present, disable monitoring...                
        +    Apr 18 08:48:46 linuxquad wvpmond[11711]: <1240062526829> : PMON: wviewsqld: 600                                                                              
        +    Apr 18 08:48:46 linuxquad wvpmond[11711]: <1240062526829> : PMON: pid file /usr/local/var/wview/wviewsqld.pid not present, disable monitoring...              
        +    Apr 18 08:48:46 linuxquad wvpmond[11711]: <1240062526829> : running...                                                                                        
        +    Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527294> : running...                                                                                       
        +    Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527294> : received station info from wviewd: 20090414 22:40:00                                             
        +    Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527298> : htmlmgrInit: 51 built-in image definitions added                                                 
        +    Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527306> : htmlmgrInit: 0 user image definitions added                                                      
        +    Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527314> : htmlmgrInit: 14 templates added                                                                  
        +    Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527314> : htmlmgrInit: forecast html tags are disabled - /usr/local/etc/wview/forecast.conf not found...   
        +    Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527316> : initializing barometric pressure trend                                                           
        +    Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527316> : initializing historical stores (this may take some time...)                                      
        +    Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527316> : htmlHistoryInit: DAY: samples=0                                                                  
        +    Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527325> : htmlHistoryInit: DAY: samples=50
        +    Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527333> : htmlHistoryInit: DAY: samples=100
        +    Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527341> : htmlHistoryInit: DAY: samples=150
        +    Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527349> : htmlHistoryInit: DAY: samples=200
        +    Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527357> : htmlHistoryInit: DAY: samples=250
        +    Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527364> : htmlHistoryInit: DAY: samples=288
        +    Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527364> : htmlHistoryInit: WEEK: samples=0
        +    Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527372> : htmlHistoryInit: WEEK: samples=50
        +    Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527393> : htmlHistoryInit: WEEK: samples=100
        +    Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527401> : htmlHistoryInit: WEEK: samples=150
        +    Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527404> : htmlHistoryInit: WEEK: samples=168
        +    Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527404> : htmlHistoryInit: MONTH: samples=0
        +    Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527412> : htmlHistoryInit: MONTH: samples=50
        +    Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527421> : htmlHistoryInit: MONTH: samples=100
        +    Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527429> : htmlHistoryInit: MONTH: samples=150
        +    Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527437> : htmlHistoryInit: MONTH: samples=200
        +    Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527445> : htmlHistoryInit: MONTH: samples=250
        +    Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527453> : htmlHistoryInit: MONTH: samples=300
        +    Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527461> : htmlHistoryInit: MONTH: samples=350
        +    Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527470> : htmlHistoryInit: MONTH: samples=400
        +    Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527478> : htmlHistoryInit: MONTH: samples=450
        +    Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527486> : htmlHistoryInit: MONTH: samples=500
        +    Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527494> : htmlHistoryInit: MONTH: samples=550
        +    Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527515> : htmlHistoryInit: MONTH: samples=600
        +    Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527523> : htmlHistoryInit: MONTH: samples=650
        +    Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527527> : htmlHistoryInit: MONTH: samples=672
        +    Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527550> : htmlHistoryInit: YEAR: samples=0
        +    Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527578> : htmlHistoryInit: YEAR: samples=50
        +    Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527607> : htmlHistoryInit: YEAR: samples=100
        +    Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527635> : htmlHistoryInit: YEAR: samples=150
        +    Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527664> : htmlHistoryInit: YEAR: samples=200
        +    Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527692> : htmlHistoryInit: YEAR: samples=250
        +    Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527721> : htmlHistoryInit: YEAR: samples=300
        +    Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527749> : htmlHistoryInit: YEAR: samples=350
        +    Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527769> : htmlHistoryInit: storing day history for Tue Apr 14 00:05:00 2009
        +    Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527783> : htmlHistoryInit: YEAR: samples=365
        +    Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527784> : HILOW: OK
        +    Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527785> : NOAA DB: OK
        +    Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527785> : NOAA: initilizing reports (this may take some time...)
        +    Apr 18 08:48:47 linuxquad htmlgend[11686]: <1240062527785> : NOAA DB: syncing 19700101 => 20090417
        +    Apr 18 08:49:01 linuxquad htmlgend[11686]: <1240062541977> : NOAA DB: done: 4 HILOW records => 1 NOAA records
        +    Apr 18 08:49:02 linuxquad htmlgend[11686]: <1240062542006> : NOAA Generate: creating monthly NOAA reports starting 200904
        +    Apr 18 08:49:02 linuxquad htmlgend[11686]: <1240062542029> : NOAA Generate: creating yearly NOAA reports starting 2009
        +    Apr 18 08:49:02 linuxquad htmlgend[11686]: <1240062542033> : ARCREC: initializing archive browser files (this may take some time...)
        +    Apr 18 08:49:03 linuxquad htmlgend[11686]: <1240062543000> : starting html generation in 1 mins 7 secs
        +    Apr 18 08:49:03 linuxquad htmlgend[11686]: <1240062543000> : doing initial html generation now...
        +    Apr 18 08:49:03 linuxquad htmlgend[11686]: <1240062543113> : Generated: 113 ms: 51 images, 14 template files
        +      
        + +

        + Linux systems tend to have other IPC objects defined - you do not want to + disturb those. The wview objects are discernable by the "KEY" values:
        + 126977 (0x1F001) - 126979 (0x1F003)    (wview-specific) + 267386881 (0xFF00001) - 267386882 (0xFF00002)    (radlib general) +

        + If, after shutting down wview, any of the IPC objects associated with wview + (or radlib) still remain, you will need to remove them manually using the + "ipcrm" command. This is normally caused by failed wview starts when + initially installing wview with improper configuration or by a faulty wview + start/stop script which is leaving wview processes running.

        +

        + + + + +

        +  Log Verbosity +

        + + +

        + Log Verbosity refers to status level logs - "normal" operation informational + messages. These can be quite interesting when first starting wview or after + an upgrade. But when enabled it can generate a bit of log file volume.

        + + While wview is running, you can toggle the log verbosity for a given wview + daemon by sending a HUP signal to that process (this also causes a reload of + image/html configuration for htmlgend). Below are the suitable shell commands + for each daemon:
        +

        +
        +    kill -s HUP `cat $prefix/var/wview/wviewd.pid`
        +    kill -s HUP `cat $prefix/var/wview/htmlgend.pid`
        +    kill -s HUP `cat $prefix/var/wview/wviewftpd.pid`
        +    kill -s HUP `cat $prefix/var/wview/wviewsshd.pid`
        +    kill -s HUP `cat $prefix/var/wview/wvalarmd.pid`
        +    kill -s HUP `cat $prefix/var/wview/wvcwopd.pid`
        +    kill -s HUP `cat $prefix/var/wview/wvhttpd.pid`
        +      
        + + + +

        + +  Vantage Pro +

        + + +

        + Wireless Reception

        + If you are missing archive records, receiving bad archive records, or your + current conditions values don't update as expected, you may have problems + with the wireless reception from the Vantage Pro Integrated Sensor Suite + (ISS). You can access a chart which displays the percentage of successful + packets received at: http://[your_weather_site_url]/rxcheck.png.
        + If the graph displays points equal to zero or at very low values, you should + investigate why your wireless reception is so poor. Possible solutions + include: changing the gain on your VP Console, moving your VP Console to + improve reception or perhaps moving your ISS for better reception.

        + + Serial Interface Problems

        + If you see a log message similar to:
        + daemonStartProcState: lat and long failed

        + or periodic logs similar to:
        + wakeupConsole: Read ERROR!

        + or:
        + + wakeupConsole: Invalid data: FE 13 (data values "FE 13" will vary)

        + you have a serial interface problem. The best test is to stop wview if it + is running and use vpconfig to verify your serial interface.

        + You should be able to run "vpconfig [device] show" repeatedly without any + failures being reported (see section 15.6 for vpconfig usage).

        + Possible culprits are:
        +

        +
          +
        • Bad serial cable
        • + +
        • Bad serial port on your PC
        • +
        • Incorrect terminal server settings (when using a terminal server)
        • +
        • Improper USB driver installation or operation (if using USB and driver)
        • +
        + wview will not work properly without a reliable serial interface.

        + + + + +

        +  WXT510 +

        + + +

        + Comm Problems

        + + If you see error messages like "NMEA: nmea0183Init: XXX failed!" when + starting wview, it is a good indication that the comm settings on the WXT510 + have not been set correctly. Be sure you run wxt510config BEFORE your first + wview start. wxt510config searches through all possible comm setting + combinations until it "fimds" the WXT510, then it sets the comm parameters + properly for wview.

        + Sensor Problems

        + If you see an error similar to: + "NMEA: nmea0183GetReadings: 0x7FC1: not all sensors updated..." + then there is the possibility that the THP sensor connector header under the + very top of the WXT510 is not making good contact. You can remove the long + screws which hold the entire station together then remove the 3 very small + screws which retain the upper PCB cover. Take care to mate the small header + in the middle well then hold the cover in place while screwing the retaining + screws back in. Make sure you do not dislodge the ribbon cable connections.

        +

        + + + +

        + +  Using raddebug +

        + + +

        + raddebug is a general debug utility which is installed when you install + radlib. It can be used to display semaphore usage, buffer usage and + radmrouted statisitics:
        +

        + + + + + + +
        + #> raddebug 1 $prefix/var/wview
        +
        +
        + +

        + It will output something like:
        +

        +
        +    Attached to radlib system 1: UP 0 years, 0 months, 0 days, 5 hours, 17 minutes, 26 seconds
        +    
        +    Buffer Allocation by Size:
        +    Dumping index 0: size 64: Free/Total 64/64
        +    Dumping index 1: size 128: Free/Total 128/128
        +    Dumping index 2: size 256: Free/Total 244/256
        +    Dumping index 3: size 512: Free/Total 256/256
        +    Dumping index 4: size 1024: Free/Total 128/128
        +    Dumping index 5: size 2048: Free/Total 64/64
        +    Dumping index 6: size 4096: Free/Total 32/32
        +    
        +    Buffer Summary:
        +            Total Free: 916
        +            Total Allocated: 12
        +            Total Allocations Since Started: 121628
        +    
        +    Semaphore Info:
        +    INDEX   COUNT  WAITERS  ZCNT   PID
        +      0       0      0        0     0
        +      1       0      0        0     0
        +      2       1      0        0     63615
        +      3       0      0        0     0
        +      4       1      0        0     61597
        +      5       0      0        0     0
        +      6       0      0        0     0
        +      7       0      0        0     0
        +      8       0      0        0     0
        +      9       0      0        0     0
        +     10       0      0        0     0
        +     11       0      0        0     0
        +     12       0      0        0     0
        +     13       0      0        0     0
        +     14       0      0        0     0
        +     15       0      0        0     0
        +    
        +    raddebug[63615]: [3310611880] : radlib: raddebug started ...
        +    Dumping message router stats to the system log file...
        +      

        +

        + Then view the message router (radmrouted) statistics:
        + +

        + + + + + + +
        + #> sudo tail -n 400 -f /var/log/syslog
        +
        +
        +

        + With output like:
        +

        +
        +    raddebug[63615]: [3310611880] : radlib: raddebug started ...
        +    radmrouted[61576]: [3310611917] : ---------- Message Router Totals:  TX:0000001851  RX:0000001662 ----------
        +    radmrouted[61576]: [3310611917] :      Name       MSGS TX         MSGS RX          TXERRS          RXERRS
        +    radmrouted[61576]: [3310611917] : -------------- ----------      ----------      ----------      ----------
        +    radmrouted[61576]: [3310611917] : wviewd               1021             641               0               0
        +    radmrouted[61576]: [3310611917] : wviewsqld               1              64               0               0
        +    radmrouted[61576]: [3310611917] : wvalarmd                1             318               0               0
        +    radmrouted[61576]: [3310611917] : htmlgend              637             700               0               0
        +    radmrouted[61576]: [3310611917] : wvcwopd                 1              64               0               0
        +    radmrouted[61576]: [3310611917] : wvhttpd                 1              64               0               0
        +    radmrouted[61576]: [3310611917] : raddebug                0               0               0               0
        +    radmrouted[61576]: [3310611917] : --------------------------------------------------------------------------
        +      
        +
        + + + +

        + +  Running In the Foreground +

        + + +

        + Typically wview processes run in the background as daemons which among other + properties implies that they have no controlling terminal and stdin/stdout/stderr + have been closed. This makes it difficult to debug using traditional tools + such as gdb since the process that gdb starts is forked of to a daemon and + closes.
        + To allow foreground process debugging for the wview processes, a command line + argument is defined which forces all process to run in the foreground and + not as daemons. "-f" given as an argument to any wview process binary will + cause it to run in the foreground thus enabling use of gdb or other debugging + tools. This should only be done for debugging or analysis purposes and + not for production environments.
        + Note that some wview startup scripts support the argument "start-trace" which + runs the processes in the foreground and traces their system calls using the + strace unix utility. Output of strace is placed in the file + ${prefix}/var/wview/[process_name].trace (i.e., htmlgend.trace).
        +

        + +

        +  Appendix 1: Distribution Contents +

        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        + /stations/common + + Source directory for the station daemon +
        + /stations/VantagePro + + Source directory for the Davis Vantage Pro/Pro2 interface +
        + /stations/Simulator + + Source directory for the station simulator +
        + /stations/WXT510 + + Source directory for the Vaisala WXT510 interface +
        + /stations/WS-2300 + + Source directory for the La Crosse WS-23XX interface +
        + /stations/WMR918 + + Source directory for the Oregon Scientific WMR9X8 interface +
        + /htmlgenerator + + Source directory for the file generator +
        + /common + + Common source and build files +
        + /wviewmgmt + + Source directory for the wview management web site +
        + /examples/conf + + Example config files (see description below) +
        + /examples/html/classic + + Example HTML template files and tag description file parameters.txt + (see description below) +
        + /examples/html/chrome + + Example HTML template files and tag description file parameters.txt + (see description below) +
        + /utilities/archive-be2le + + Source directory for the wview archive directory Big-Endian to + Little-Endian command line convertor +
        + /utilities/archive-le2be + + Source directory for the wview archive directory Little-Endian to + Big-Endian command line convertor +
        + /utilities/wlk2sqlite + + Source directory for the wview WLK to SQLite3 archive convertor +
        + /utilities/sqlite2wlk + + Source directory for the wview SQLite3 to WLK archive convertor +
        + /configure + + build configuration script to be executed before building wview +
        + /cross-compile + + Contains example configure scripts for cross compilation. libz, libpng, + libgd, librad and wview scripts are included (and should be built in that + order). Use these scripts instead of "./configure" to configure these + libraries for cross compilation. These scripts configure for arm-linux + targets but could easily be edited for a different target. +
        + /scripts + + Contains example helper scripts for wview installation and update. +
        + /ChangeLog + + The release notes file +
        + /wview-User-Manual.html + + This file, the User Manual +
        + /wview-Quick-Start.html + + Quick Start Guide +
        + /wview-Quick-Start-MacOSX.html + + MacOSX-specific Quick Start Guide +
        + /wview-Quick-Start-Debian.html + + Debian-specific Quick Start Guide +
        + /UPGRADE + + Notes on upgrading from previous versions

        +
        + + +

        +  Appendix 2: Adding New Weather Stations to wview + +

        +

        + The wview station API is prototyped in ../stations/common/station.h. For + full details of the functional requirements please refer to station.h and + existing station interfaces. +

        + + + +

        +  Required Functions + +

        + + +

        + Init Function

        + Can Be Asynchronous - event indication required.

        + MUST (in this order):
        +

        + +
          +
        1. Set the 'stationGeneratesArchives' flag in WVIEWD_WORK: if the + station generates archive records (TRUE) or they should be + generated automatically by the daemon from the sensor readings (FALSE).
        2. +
        3. Initialize the 'stationData' store for station work area.
        4. +
        5. Initialize the interface medium.
        6. +
        7. Determine the station archive interval - either from the station + itself or from user configuration in wview-conf.sdb - and set the + 'work->archiveInterval' variable (in minutes) in WVIEWD_WORK.
        8. +
        9. VERIFY the archive interval by calling 'stationVerifyArchiveInterval' - + If they don't match, indicate an errant start via the call: + radProcessEventsSend (NULL, STATION_INIT_COMPLETE_EVENT, 1) and + stopping further init activities here!
        10. + +
        11. Do any catch-up on archive records if there is a data logger + (can be asynchronous) - the 'work->runningFlag' can be used for + start up synchronization but should not be modified by the station + interface code.
        12. +
        13. Do initial LOOP acquisition.
        14. +
        15. indicate successful initialization is done via the call: + radProcessEventsSend (NULL, STATION_INIT_COMPLETE_EVENT, 0).
        16. +

        +

        + Optional:
        + Initialize a state machine or any other construct required for the + station interface - these should be stored in the 'stationData' store + (see the VantagePro station interface for an example of this).
        + + 'archiveIndication' - indication callback used to pass back an archive + record generated as a result of 'stationGetArchive' being called; should + receive a NULL pointer for 'newRecord' if no record available; only used + if 'stationGeneratesArchives' flag is set to TRUE by the station interface.

        +

        +
        +    extern int stationInit
        +    (
        +        WVIEWD_WORK     *work,
        +        void            (*archiveIndication)(ARCHIVE_RECORD *newRecord)
        +    );
        +      

        +

        + Exit Function

        + Cleans up any data created during init call.

        + +

        +
        +    extern void stationExit (WVIEWD_WORK *work);
        +      

        +

        + stationGetPosition Function

        + Station-supplied function to retrieve positional info (lat, long, elev) - + should populate WVIEWD_WORK fields: latitude, longitude, elevation. + Synchronous.
        + If station does not store these parameters, they can be retrieved from the + wview-conf.sdb file (see the 'stationGetConfigValue' utilities below) - user + must choose station type "NameOfStation" when running the wviewconfig + script. Returns: OK or ERROR.
        + +

        +
        +    extern int stationGetPosition (WVIEWD_WORK *work);
        +      

        +

        + stationSyncTime Function

        + Station-supplied function to indicate a time sync should be performed + if the station maintains time, otherwise may be safely ignored. Can be + asynchronous. Returns: OK or ERROR.
        +

        + +
        +    extern int stationSyncTime (WVIEWD_WORK *work);
        +      

        +

        + stationGetReadings Function

        + station-supplied function to indicate sensor readings should be performed - + should populate 'work' struct: loopPkt (see datadefs.h for minimum field + requirements). Can Be Asynchronous.
        + Indicate readings are complete by sending the STATION_LOOP_COMPLETE_EVENT + event to this process + (radProcessEventsSend (NULL, STATION_LOOP_COMPLETE_EVENT, 0)).
        +

        + +
        +    extern int stationGetReadings (WVIEWD_WORK *work);
        +      

        +

        + stationGetArchive Function

        + Station-supplied function to indicate an archive record should be + generated - MUST populate an ARCHIVE_RECORD struct and indicate it to + 'archiveIndication' function passed into 'stationInit'. Asynchronous - + callback indication required. Returns: OK or ERROR.
        + Note: 'archiveIndication' should receive a NULL pointer for the newRecord + if no record is available.
        + Note: This function will only be invoked by the wview daemon if the + 'stationInit' function set the 'stationGeneratesArchives' to TRUE.
        + +

        +
        +    extern int stationGetArchive (WVIEWD_WORK *work);
        +      

        +

        + stationDataIndicate Function

        + Station-supplied function to indicate data is available on the station + interface medium (serial or ethernet) - it is the responsibility of the + station interface to read the data from the medium and process + appropriately. The data does not have to be read within the context of + this function, but may be used to stimulate a state machine. Can be + asynchronous.
        +

        + +
        +    extern void stationDataIndicate (WVIEWD_WORK *work);
        +      
        +

        + stationIFTimerExpiry Function

        + Station-supplied function to indicate the interface timer has expired - + It is the responsibility of the station interface to start/stop the + interface timer as needed for the particular station requirements. The + station interface timer is specified by the 'ifTimer' member of the + WVIEWD_WORK structure. No other timers in that structure should be + manipulated in any way by the station interface code. Can be asynchronous.
        +

        +
        +
        +    extern void stationIFTimerExpiry (WVIEWD_WORK *work);
        +      
        + + + +

        +  Notes +

        +
          + +
        • Two events are used for notification to the generic wviewd daemon:
          +
          +        STATION_INIT_COMPLETE_EVENT    used to asynchronously indicate station-
          +        specific initialization is complete
          +        STATION_LOOP_COMPLETE_EVENT    used to asynchronously indicate that a 
          +        current readings acquisition is complete
          +          

        • +
        • If the station does not include a data logger, the generic wviewd + daemon will generate archive records based on the current readings + samples for the archive interval. See the WXT510 implementation for + an example of a straightforward synchronous station interface. It does + not include a data logger and requires the wview daemon to generate + archive records. The Simulator station implementation also demonstrates + a synchronous interface.
        • +
        • If the station does include a data logger, the archiveIndication + function allows an asynchronous way to indicate the archive record to + the wview daemon. See the VantagePro station implementation for an + asynchronous, state-machine driven station interface.
        • +
        • A station-specific work data store pointer is provided in the + WVIEWD_WORK structure to allow the definition and reference of + station-specific data (including state machines, etc.).
        • + +
        • If the station does not configure/store location data, the station + config parameter utility can be used to extract them from + wview-conf.sdb (see stations/Simulator/simulator.c for example usage).
        • +
        + + + + diff --git a/wviewconfig/Makefile.am b/wviewconfig/Makefile.am new file mode 100755 index 0000000..c370d95 --- /dev/null +++ b/wviewconfig/Makefile.am @@ -0,0 +1,34 @@ +# Makefile - wviewconfig/wviewhtmlconfig/wviewcleardata + +EXTRA_DIST = $(srcdir)/wviewconfig.sh $(srcdir)/wviewhtmlconfig.sh $(srcdir)/wviewcleardata.sh + +# define the scripts to be generated +bin_SCRIPTS = wviewconfig wviewhtmlconfig wviewcleardata + +CLEANFILES = $(bin_SCRIPTS) + +wviewconfig: $(srcdir)/wviewconfig.sh + rm -f wviewconfig + echo "#!/bin/sh" > wviewconfig + echo "WVIEW_CONF_DIR=$(sysconfdir)/wview" >> wviewconfig + echo "WVIEW_DATA_DIR=$(localstatedir)/wview" >> wviewconfig + cat $(srcdir)/wviewconfig.sh >> wviewconfig + chmod ugo+x wviewconfig + +wviewhtmlconfig: $(srcdir)/wviewhtmlconfig.sh + rm -f wviewhtmlconfig + echo "#!/bin/sh" > wviewhtmlconfig + echo "WVIEW_CONF_DIR=$(sysconfdir)/wview" >> wviewhtmlconfig + echo "WVIEW_DATA_DIR=$(localstatedir)/wview" >> wviewhtmlconfig + echo "WVIEWCONFIG=$(exec_prefix)/bin/wviewconfig" >> wviewhtmlconfig + cat $(srcdir)/wviewhtmlconfig.sh >> wviewhtmlconfig + chmod ugo+x wviewhtmlconfig + +wviewcleardata: $(srcdir)/wviewcleardata.sh + rm -f wviewcleardata + echo "#!/bin/sh" > wviewcleardata + echo "WVIEW_CONF_DIR=$(sysconfdir)/wview" >> wviewcleardata + echo "WVIEW_DATA_DIR=$(localstatedir)/wview" >> wviewcleardata + cat $(srcdir)/wviewcleardata.sh >> wviewcleardata + chmod ugo+x wviewcleardata + diff --git a/wviewconfig/wviewcleardata.sh b/wviewconfig/wviewcleardata.sh new file mode 100644 index 0000000..27c299e --- /dev/null +++ b/wviewconfig/wviewcleardata.sh @@ -0,0 +1,122 @@ +################################################################################ +# +# File: wviewcleardata.sh +# +# Description: Provide a script to remove all weather data from the databases +# +# Usage: (must be root) +# wviewcleardata +# +# History: +# Engineer Date Ver Comments +# MS Teel 01/24/10 1 Original +# +################################################################################ + + +################################################################################ +################################# M A C R O S ################################ +################################################################################ +WVIEWD_PID=$WVIEW_DATA_DIR/wviewd.pid + +################################################################################ +####################### D E F I N E F U N C T I O N S ####################### +################################################################################ + +show_usage() +{ + echo "" + echo "wviewcleardata" + echo " removes all weather data from the databases" + echo "" +} + + +interactive_intro() +{ + echo "################################################################################" + echo " !!!!!!!!!!!!!!!! READ THIS BEFORE PROCEEDING !!!!!!!!!!!!!!!!" + echo "" + echo "--> wview database purging utility" + echo "" + echo "--> Note: This script will REMOVE all archive records and DELETE the following" + echo " databases:" + echo " wview-hilow.sdb (HILOW data)" + echo " wview-history.sdb (daily history)" + echo " wview-noaa.sdb (NOAA data)" + echo "" + echo "--> Use this script to reset all data - i.e., remove simulator data to begin" + echo " using your real station data." + echo "" + echo "--> It is highly recommended that you BACKUP YOUR ARCHIVE DIRECTORY before doing" + echo " this unless YOU ARE SURE you want to purge all weather data - it will not be" + echo " recoverable after this action!" + echo "" + echo " IF THAT IS NOT WHAT YOU WANT, HIT CTRL-C NOW TO ABORT THIS SCRIPT!" + echo "" + echo "################################################################################" + echo "" + echo -n "Pausing 30 seconds - CTRL-C to ABORT" + for i in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 + do + sleep 1 + echo -n "." + done + echo "" + echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + echo "Are you sure you want to DESTROY all of your collected weather data?" + echo -n "(NO/yes): " + read INVAL + if [ "yes" != "$INVAL" ]; then + echo "Aborting database purge operation." + exit 0 + else + echo "Purging all weather data!" + fi +} + + +purge_archive_data() +{ + echo "delete from archive where dateTime >= '0';" >> $WVIEW_DATA_DIR/commands.sql + echo ".read $WVIEW_DATA_DIR/commands.sql" | sqlite3 $WVIEW_DATA_DIR/archive/wview-archive.sdb + rm -rf $WVIEW_DATA_DIR/commands.sql +} + + +################################################################################ +################## S C R I P T E X E C U T I O N S T A R T ################# +################################################################################ + +# First test to make sure that wview is not running: +if [ -f $WVIEWD_PID ]; then + echo "wviewd is running - stop wview before running this script..." + exit 3 +fi + +# Make sure that the archive DB is there: +if [ ! -f $WVIEW_DATA_DIR/archive/wview-archive.sdb ]; then + echo "wview archive database NOT FOUND" + exit 4 +fi + +## Intro: +interactive_intro + +## Purge all archive records: +echo "Deleting all archive records:" +purge_archive_data + +## Delete dependent databases: +echo "Deleting auto-generated databases:" +rm -rf $WVIEW_DATA_DIR/archive/wview-hilow.sdb +rm -rf $WVIEW_DATA_DIR/archive/wview-history.sdb +rm -rf $WVIEW_DATA_DIR/archive/wview-noaa.sdb + +echo "" +echo "################################################################################" +echo "--> ALL wview weather data purged!" +echo "################################################################################" + +exit 0 + diff --git a/wviewconfig/wviewconfig.sh b/wviewconfig/wviewconfig.sh new file mode 100644 index 0000000..4341c11 --- /dev/null +++ b/wviewconfig/wviewconfig.sh @@ -0,0 +1,309 @@ +################################################################################ +# +# File: wviewconfig.sh +# +# Description: Provide a script to interactively configure a wview +# installation. +# +# Usage: (must be root) +# wviewconfig +# wviewconfig get +# wviewconfig set [new_config_path] +# +# History: +# Engineer Date Ver Comments +# MS Teel 06/20/05 1 Original +# J Barber 02/24/06 2 Partitioned into functions; added get/set +# MS Teel 02/25/06 3 Tweaked arg and function names +# MS Teel 06/21/08 4 Better station type support +# MS Teel 08/23/08 5 Modify to use sqlite config database +# +################################################################################ + + +################################################################################ +################################# M A C R O S ################################ +################################################################################ +WVIEWD_PID=$WVIEW_DATA_DIR/wviewd.pid +CFG_STATION_TYPE=$STATION_TYPE + +STATION_BIN=`cat $WVIEW_CONF_DIR/wview-binary` +if [ "$STATION_BIN" = "wviewd_vpro" ]; then + STATION_TYPE="VantagePro" +else + if [ "$STATION_BIN" = "wviewd_wxt510" ]; then + STATION_TYPE="WXT510" + else + if [ "$STATION_BIN" = "wviewd_twi" ]; then + STATION_TYPE="TWI" + else + if [ "$STATION_BIN" = "wviewd_ws2300" ]; then + STATION_TYPE="WS-2300" + else + if [ "$STATION_BIN" = "wviewd_wmr918" ]; then + STATION_TYPE="WMR918" + else + if [ "$STATION_BIN" = "wviewd_wmrusb" ]; then + STATION_TYPE="WMRUSB" + else + if [ "$STATION_BIN" = "wviewd_wh1080" ]; then + STATION_TYPE="WH1080" + else + if [ "$STATION_BIN" = "wviewd_te923" ]; then + STATION_TYPE="TE923" + else + if [ "$STATION_BIN" = "wviewd_virtual" ]; then + STATION_TYPE="Virtual" + else + STATION_TYPE="Simulator" + fi + fi + fi + fi + fi + fi + fi + fi +fi + +################################################################################ +####################### D E F I N E F U N C T I O N S ####################### +################################################################################ + +show_usage() +{ + echo "" + echo "wviewconfig" + echo " Configures wview interactively" + echo "wviewconfig get" + echo " Prints current settings in text format to stdout" + echo "wviewconfig set [new_config_path]" + echo " Takes text format file of configuration at [new_config_path] and applies" + echo " it to the wview configuration database - it can be a partial or full list" + echo "" +} + + +print_config() +{ + if [ -f $WVIEW_CONF_DIR/wview-conf.sdb ]; then + # get settings from the sqlite database: + echo ".separator =" > $WVIEW_CONF_DIR/commands.sql + echo "select name,value from config;" >> $WVIEW_CONF_DIR/commands.sql + echo ".read $WVIEW_CONF_DIR/commands.sql" | sqlite3 $WVIEW_CONF_DIR/wview-conf.sdb + rm -rf $WVIEW_CONF_DIR/commands.sql + else + echo "wview Configuration database $WVIEW_CONF_DIR/wview-conf.sdb NOT FOUND - ABORTING!" + exit 1 + fi +} + + +interactive_intro() +{ + echo "################################################################################" + echo " !!!!!!!!!!!!!!!! READ THIS BEFORE PROCEEDING !!!!!!!!!!!!!!!!" + echo "" + echo "--> System Configuration for wview" + echo "" + echo "--> Values in parenthesis are your existing values (if they exist) or defaults - " + echo " they will be used if you just hit enter at the prompt..." + echo "" + echo "--> Note: This script will save the existing wview-conf.sdb file to" + echo " $WVIEW_CONF_DIR/wview-conf.old before writing the new file" + echo " based on your answers here - if that is not what you want, hit CTRL-C now to" + echo " abort this script!" + echo "" + echo "################################################################################" + echo "" + echo -n "pausing 3 seconds " + sleep 1 + echo -n "." + sleep 1 + echo -n "." + sleep 1 + echo "." + echo "" +} + + +get_wview_conf_interactive() +{ + if [ -f $WVIEW_CONF_DIR/wview-conf.sdb ]; then + # get settings from the sqlite database: + echo ".separator |" > $WVIEW_CONF_DIR/commands.sql + echo "select name,value,description,dependsOn from config;" >> $WVIEW_CONF_DIR/commands.sql + echo ".read $WVIEW_CONF_DIR/commands.sql" | sqlite3 $WVIEW_CONF_DIR/wview-conf.sdb > $WVIEW_CONF_DIR/parms.out + rm -rf $WVIEW_CONF_DIR/commands.sql + + # Construct the editor script: + echo "#!/bin/sh" > $WVIEW_CONF_DIR/editparm + echo "if [ \"\" != \"\$4\" ]; then" >> $WVIEW_CONF_DIR/editparm + echo " echo \"select value from config where name='\$4';\" > $WVIEW_CONF_DIR/cmnd.sql" >> $WVIEW_CONF_DIR/editparm + echo " echo \".read $WVIEW_CONF_DIR/cmnd.sql\" | sqlite3 $WVIEW_CONF_DIR/wview-conf.sdb > $WVIEW_CONF_DIR/value.out" >> $WVIEW_CONF_DIR/editparm + echo " IS_ENABLED=\`cat $WVIEW_CONF_DIR/value.out\`" >> $WVIEW_CONF_DIR/editparm + echo " rm -rf $WVIEW_CONF_DIR/cmnd.sql $WVIEW_CONF_DIR/value.out" >> $WVIEW_CONF_DIR/editparm + echo "else" >> $WVIEW_CONF_DIR/editparm + echo " IS_ENABLED=yes" >> $WVIEW_CONF_DIR/editparm + echo "fi" >> $WVIEW_CONF_DIR/editparm + echo "if [ \"\$IS_ENABLED\" != \"yes\" ]; then" >> $WVIEW_CONF_DIR/editparm + echo " exit 0" >> $WVIEW_CONF_DIR/editparm + echo "fi" >> $WVIEW_CONF_DIR/editparm + echo "NEWVAL=\$2" >> $WVIEW_CONF_DIR/editparm + echo "echo \"-------------------------------------------------------------\"" >> $WVIEW_CONF_DIR/editparm + echo "echo \"\$3\"" >> $WVIEW_CONF_DIR/editparm + echo "echo \"PARAMETER: \$1\"" >> $WVIEW_CONF_DIR/editparm + echo "echo -n \"(\$2): \"" >> $WVIEW_CONF_DIR/editparm + echo "read INVAL" >> $WVIEW_CONF_DIR/editparm + echo "if [ \"\" != \"\$INVAL\" ]; then" >> $WVIEW_CONF_DIR/editparm + echo " echo \"update config set value='\$INVAL' where name='\$1';\" > $WVIEW_CONF_DIR/cmnd.sql" >> $WVIEW_CONF_DIR/editparm + echo " echo \".read $WVIEW_CONF_DIR/cmnd.sql\" | sqlite3 $WVIEW_CONF_DIR/wview-conf.sdb" >> $WVIEW_CONF_DIR/editparm + echo " rm -rf $WVIEW_CONF_DIR/cmnd.sql" >> $WVIEW_CONF_DIR/editparm + echo " if [ \"\$1\" = \"STATION_TYPE\" ]; then" >> $WVIEW_CONF_DIR/editparm + echo " if [ \"\$INVAL\" = \"VantagePro\" ]; then" >> $WVIEW_CONF_DIR/editparm + echo " echo \"wviewd_vpro\" > $WVIEW_CONF_DIR/wview-binary" >> $WVIEW_CONF_DIR/editparm + echo " else" >> $WVIEW_CONF_DIR/editparm + echo " if [ \"\$INVAL\" = \"WXT510\" ]; then" >> $WVIEW_CONF_DIR/editparm + echo " echo \"wviewd_wxt510\" > $WVIEW_CONF_DIR/wview-binary" >> $WVIEW_CONF_DIR/editparm + echo " else" >> $WVIEW_CONF_DIR/editparm + echo " if [ \"\$INVAL\" = \"WS-2300\" ]; then" >> $WVIEW_CONF_DIR/editparm + echo " echo \"wviewd_ws2300\" > $WVIEW_CONF_DIR/wview-binary" >> $WVIEW_CONF_DIR/editparm + echo " else" >> $WVIEW_CONF_DIR/editparm + echo " if [ \"\$INVAL\" = \"WMR918\" ]; then" >> $WVIEW_CONF_DIR/editparm + echo " echo \"wviewd_wmr918\" > $WVIEW_CONF_DIR/wview-binary" >> $WVIEW_CONF_DIR/editparm + echo " else" >> $WVIEW_CONF_DIR/editparm + echo " if [ \"\$INVAL\" = \"WMRUSB\" ]; then" >> $WVIEW_CONF_DIR/editparm + echo " echo \"wviewd_wmrusb\" > $WVIEW_CONF_DIR/wview-binary" >> $WVIEW_CONF_DIR/editparm + echo " else" >> $WVIEW_CONF_DIR/editparm + echo " if [ \"\$INVAL\" = \"WH1080\" ]; then" >> $WVIEW_CONF_DIR/editparm + echo " echo \"wviewd_wh1080\" > $WVIEW_CONF_DIR/wview-binary" >> $WVIEW_CONF_DIR/editparm + echo " else" >> $WVIEW_CONF_DIR/editparm + echo " if [ \"\$INVAL\" = \"Simulator\" ]; then" >> $WVIEW_CONF_DIR/editparm + echo " echo \"wviewd_sim\" > $WVIEW_CONF_DIR/wview-binary" >> $WVIEW_CONF_DIR/editparm + echo " else" >> $WVIEW_CONF_DIR/editparm + echo " if [ \"\$INVAL\" = \"TWI\" ]; then" >> $WVIEW_CONF_DIR/editparm + echo " echo \"wviewd_twi\" > $WVIEW_CONF_DIR/wview-binary" >> $WVIEW_CONF_DIR/editparm + echo " else" >> $WVIEW_CONF_DIR/editparm + echo " if [ \"\$INVAL\" = \"TE923\" ]; then" >> $WVIEW_CONF_DIR/editparm + echo " echo \"wviewd_te923\" > $WVIEW_CONF_DIR/wview-binary" >> $WVIEW_CONF_DIR/editparm + echo " else" >> $WVIEW_CONF_DIR/editparm + echo " if [ \"\$INVAL\" = \"Virtual\" ]; then" >> $WVIEW_CONF_DIR/editparm + echo " echo \"wviewd_virtual\" > $WVIEW_CONF_DIR/wview-binary" >> $WVIEW_CONF_DIR/editparm + echo " fi" >> $WVIEW_CONF_DIR/editparm + echo " fi" >> $WVIEW_CONF_DIR/editparm + echo " fi" >> $WVIEW_CONF_DIR/editparm + echo " fi" >> $WVIEW_CONF_DIR/editparm + echo " fi" >> $WVIEW_CONF_DIR/editparm + echo " fi" >> $WVIEW_CONF_DIR/editparm + echo " fi" >> $WVIEW_CONF_DIR/editparm + echo " fi" >> $WVIEW_CONF_DIR/editparm + echo " fi" >> $WVIEW_CONF_DIR/editparm + echo " fi" >> $WVIEW_CONF_DIR/editparm + echo " fi" >> $WVIEW_CONF_DIR/editparm + echo "fi" >> $WVIEW_CONF_DIR/editparm + chmod +x $WVIEW_CONF_DIR/editparm + + cd $WVIEW_CONF_DIR + + # Edit parms one at a time: + gawk -F"|" '{ + sysstring=sprintf("./editparm \"%s\" \"%s\" \"%s\" \"%s\"", $1, $2, $3, $4) + system(sysstring) + }' $WVIEW_CONF_DIR/parms.out + + rm -rf $WVIEW_CONF_DIR/editparm $WVIEW_CONF_DIR/parms.out + else + echo "wview Configuration database $WVIEW_CONF_DIR/wview-conf.sdb NOT FOUND - ABORTING!" + exit 1 + fi +} + + +set_config_from_file() +{ + if [ -f $WVIEW_CONF_DIR/wview-conf.sdb ]; then + # Construct the update script: + echo "#!/bin/sh" > $WVIEW_CONF_DIR/updateparm + echo "if [ \"\" != \"\$2\" ]; then" >> $WVIEW_CONF_DIR/updateparm + echo " echo \"update config set value='\$2' where name='\$1';\" > $WVIEW_CONF_DIR/cmnd.sql" >> $WVIEW_CONF_DIR/updateparm + echo " echo \".read $WVIEW_CONF_DIR/cmnd.sql\" | sqlite3 $WVIEW_CONF_DIR/wview-conf.sdb" >> $WVIEW_CONF_DIR/updateparm + echo " rm -rf $WVIEW_CONF_DIR/cmnd.sql" >> $WVIEW_CONF_DIR/updateparm + echo "fi" >> $WVIEW_CONF_DIR/updateparm + chmod +x $WVIEW_CONF_DIR/updateparm + + cd $WVIEW_CONF_DIR + + # Update parms one at a time: + gawk -F"=" '{ + sysstring=sprintf("./updateparm \"%s\" \"%s\"", $1, $2) + system(sysstring) + }' $SET_FILE + + rm -rf $WVIEW_CONF_DIR/updateparm + else + echo "wview Configuration database $WVIEW_CONF_DIR/wview-conf.sdb NOT FOUND - ABORTING!" + exit 1 + fi +} + + +################################################################################ +################## S C R I P T E X E C U T I O N S T A R T ################# +################################################################################ + +# First test to make sure that wview is not running for interactive and set... +if [ -f $WVIEWD_PID -a "$1" != "get" ]; then + echo "wviewd is running - stop wview before running this script..." + exit 3 +fi + +# Make sure that the config DB is there: +if [ ! -f $WVIEW_CONF_DIR/wview-conf.sdb ]; then + echo "wview configuration database NOT FOUND" + exit 4 +fi + + +METHOD=$1 +SET_FILE=$2 + +if [ "$METHOD" = "" ] # run interactively +then + interactive_intro + get_wview_conf_interactive + +echo "" +echo "" +echo "################################################################################" +echo "--> wview Configuration Complete!" +echo "--> Now run wviewhtmlconfig to select your site skin." +echo "################################################################################" +else + case "$METHOD" in + "get" ) + print_config + ;; + "set" ) + if [ "$SET_FILE" = "" ]; then + echo "set requires a source file:" + show_usage + exit 1 + fi + if [ ! -f $SET_FILE ]; then + echo "source path $SET_FILE does not exist" + show_usage + exit 1 + fi + + set_config_from_file + ;; + *) + echo "$METHOD not supported" + show_usage + exit 1 + ;; + esac +fi + +exit 0 + diff --git a/wviewconfig/wviewhtmlconfig.sh b/wviewconfig/wviewhtmlconfig.sh new file mode 100644 index 0000000..757c36e --- /dev/null +++ b/wviewconfig/wviewhtmlconfig.sh @@ -0,0 +1,241 @@ +################################################################################ +# +# File: wviewhtmlconfig.sh +# +# Description: Provide a script to interactively configure wview HTML +# templates. +# +# Usage: (must be root) +# wviewhtmlconfig +# +# History: +# Engineer Date Ver Comments +# MS Teel 02/05/08 1 Original +# MS Teel 09/13/08 2 Modify for SQLite configuration DB +# MS Teel 05/03/09 3 Add non-interactive support +# MS Teel 03/12/12 4 Add exfoliation skin. +# +################################################################################ + + +################################################################################ +################################# M A C R O S ################################ +################################################################################ + + +################################################################################ +####################### D E F I N E F U N C T I O N S ####################### +################################################################################ + +get_wviewconfig() +{ + echo "select value from config where name='HTMLGEN_EXTENDED_DATA';" > $WVIEW_CONF_DIR/cmnd.sql + echo ".read $WVIEW_CONF_DIR/cmnd.sql" | sqlite3 $WVIEW_CONF_DIR/wview-conf.sdb > $WVIEW_CONF_DIR/value.out + DATA_EXTENDED=`cat $WVIEW_CONF_DIR/value.out` + + echo "select value from config where name='HTMLGEN_METRIC_UNITS';" > $WVIEW_CONF_DIR/cmnd.sql + echo ".read $WVIEW_CONF_DIR/cmnd.sql" | sqlite3 $WVIEW_CONF_DIR/wview-conf.sdb > $WVIEW_CONF_DIR/value.out + STATION_METRIC=`cat $WVIEW_CONF_DIR/value.out` + + echo "select value from config where name='HTMLGEN_IMAGE_PATH';" > $WVIEW_CONF_DIR/cmnd.sql + echo ".read $WVIEW_CONF_DIR/cmnd.sql" | sqlite3 $WVIEW_CONF_DIR/wview-conf.sdb > $WVIEW_CONF_DIR/value.out + HTML_DESTINATION=`cat $WVIEW_CONF_DIR/value.out` + + rm -rf $WVIEW_CONF_DIR/cmnd.sql + rm -rf $WVIEW_CONF_DIR/value.out +} + +show_skins() +{ + echo "" + echo "wview site skins currently supported:" + echo " classic - default wview skin" + echo " chrome - classic with a chrome effect" + echo " exfoliation - a clean implementation by Matthew Wall" +} + +interactive_intro() +{ + echo "################################################################################" + echo " !!!!!!!!!!!!!!!! READ THIS BEFORE PROCEEDING !!!!!!!!!!!!!!!!" + echo "" + echo "--> HTML Template Configuration for wview" + echo "" + echo "--> Note: This script will save existing templates to $WVIEW_CONF_DIR/html-YYYYMMDD.HHmmSS" + echo " before writing the new files based on your answers here - if that" + echo " is not what you want, hit CTRL-C now to abort this script!" + echo "" + echo "################################################################################" + echo "" + echo -n "pausing 5 seconds " + sleep 1 + echo -n "." + sleep 1 + echo -n "." + sleep 1 + echo -n "." + sleep 1 + echo -n "." + sleep 1 + echo "." + echo "" +} + +save_old() +{ + OLD_HTML_SAVE=`date '+%Y%m%d.%H%M%S'` + echo "Saving old HTML directory to html-$OLD_HTML_SAVE ..." + cp -rf $WVIEW_CONF_DIR/html $WVIEW_CONF_DIR/html-$OLD_HTML_SAVE + echo "...done." + echo "Saving old config files..." + if [ -f $WVIEW_CONF_DIR/html-templates.conf ]; then + mv $WVIEW_CONF_DIR/html-templates.conf $WVIEW_CONF_DIR/html-templates.conf-$OLD_HTML_SAVE + fi + if [ -f $WVIEW_CONF_DIR/images.conf ]; then + mv $WVIEW_CONF_DIR/images.conf $WVIEW_CONF_DIR/images.conf-$OLD_HTML_SAVE + fi + if [ -f $WVIEW_CONF_DIR/images-user.conf ]; then + mv $WVIEW_CONF_DIR/images-user.conf $WVIEW_CONF_DIR/images-user.conf-$OLD_HTML_SAVE + fi + if [ -f $WVIEW_CONF_DIR/graphics.conf ]; then + mv $WVIEW_CONF_DIR/graphics.conf $WVIEW_CONF_DIR/graphics.conf-$OLD_HTML_SAVE + fi + if [ -f $WVIEW_CONF_DIR/pre-generate.sh ]; then + mv $WVIEW_CONF_DIR/pre-generate.sh $WVIEW_CONF_DIR/pre-generate.sh-$OLD_HTML_SAVE + fi + if [ -f $WVIEW_CONF_DIR/post-generate.sh ]; then + mv $WVIEW_CONF_DIR/post-generate.sh $WVIEW_CONF_DIR/post-generate.sh-$OLD_HTML_SAVE + fi + echo "... done." +} + +remove_all() +{ + rm -f $WVIEW_CONF_DIR/html/*.* + rm -f $WVIEW_CONF_DIR/html-templates.conf + rm -f $WVIEW_CONF_DIR/images.conf + rm -f $WVIEW_CONF_DIR/images-user.conf + rm -f $WVIEW_CONF_DIR/graphics.conf + rm -f $WVIEW_CONF_DIR/pre-generate.sh + rm -f $WVIEW_CONF_DIR/post-generate.sh +} + +get_user_preferences() +{ + echo "" + echo "---------------------------------------------------------------------------" + echo "Which template skin do you want to use for your site?" + show_skins + echo -n "($SITE_SKIN): " + read INVAL + if [ "" != "$INVAL" ]; then + SITE_SKIN=$INVAL + fi + case "$SITE_SKIN" in + "classic" ) + echo "Site skin $SITE_SKIN selected..." + ;; + "chrome" ) + echo "Site skin $SITE_SKIN selected..." + ;; + "exfoliation" ) + echo "Site skin $SITE_SKIN selected..." + ;; + *) + echo "$SITE_SKIN not supported!" + show_skins + echo "Aborting - run wviewhtmlconfig again and select a valid skin..." + exit 1 + ;; + esac + + if [ "yes" = "$DATA_EXTENDED" ]; then + echo "With extended data graphics." + else + echo "With NO extended data graphics." + fi + if [ "yes" = "$STATION_METRIC" ]; then + echo "With metric units." + else + echo "With US/Imperial units." + fi +} + +move_templates() +{ + if [ "yes" = "$DATA_EXTENDED" ]; then + cp -rf $WVIEW_CONF_DIR/html/$SITE_SKIN/plus/*.* $WVIEW_CONF_DIR/html + else + cp -rf $WVIEW_CONF_DIR/html/$SITE_SKIN/standard/*.* $WVIEW_CONF_DIR/html + fi + + if [ -d $WVIEW_CONF_DIR/html/$SITE_SKIN/static ]; then + cp -rf $WVIEW_CONF_DIR/html/$SITE_SKIN/static/*.* $HTML_DESTINATION + fi +} + +setup_conf_files() +{ + cp -f $WVIEW_CONF_DIR/html/html-templates.conf $WVIEW_CONF_DIR + cp -f $WVIEW_CONF_DIR/html/pre-generate.sh $WVIEW_CONF_DIR + chmod +x $WVIEW_CONF_DIR/pre-generate.sh + cp -f $WVIEW_CONF_DIR/html/post-generate.sh $WVIEW_CONF_DIR + chmod +x $WVIEW_CONF_DIR/post-generate.sh + cp -f $WVIEW_CONF_DIR/html/images-user.conf $WVIEW_CONF_DIR + cp -f $WVIEW_CONF_DIR/html/graphics.conf $WVIEW_CONF_DIR + if [ "yes" = "$STATION_METRIC" ]; then + cp -f $WVIEW_CONF_DIR/html/images-metric.conf $WVIEW_CONF_DIR/images.conf + cp -f $WVIEW_CONF_DIR/html/awekas_wl.htx-metric $WVIEW_CONF_DIR/html/awekas_wl.htx + else + cp -f $WVIEW_CONF_DIR/html/images.conf $WVIEW_CONF_DIR/images.conf + cp -f $WVIEW_CONF_DIR/html/awekas_wl.htx-us $WVIEW_CONF_DIR/html/awekas_wl.htx + fi +} + +################################################################################ +################## S C R I P T E X E C U T I O N S T A R T ################# +################################################################################ +export PATH=/bin:/usr/bin:/sbin:/usr/sbin:/usr/local/bin:/usr/local/sbin + +get_wviewconfig + +# Is there an argument? +if [ "" != "$1" ]; then + if [ "$1" = "classic" ]; then + SITE_SKIN=$1 + INTERACTIVE=0 + elif [ "$1" = "chrome" ]; then + SITE_SKIN=$1 + INTERACTIVE=0 + elif [ "$1" = "exfoliation" ]; then + SITE_SKIN=$1 + INTERACTIVE=0 + elif [ "$1" = "remove" ]; then + REMOVE_IT=1 + else + SITE_SKIN="chrome" + INTERACTIVE=1 + fi +else + REMOVE_IT=0 + SITE_SKIN="chrome" + INTERACTIVE=1 +fi + +if [ "$REMOVE_IT" = "1" ]; then + remove_all + echo "HTML setup files removed" + exit 0 +fi + +if [ "$INTERACTIVE" = "1" ]; then + interactive_intro + get_user_preferences + save_old +fi + +move_templates +setup_conf_files +echo "HTML setup complete:" +echo "you may now customize template files in $WVIEW_CONF_DIR/html" + diff --git a/wviewmgmt/Makefile.am b/wviewmgmt/Makefile.am new file mode 100755 index 0000000..d6f25e2 --- /dev/null +++ b/wviewmgmt/Makefile.am @@ -0,0 +1,89 @@ +# Makefile - wviewmgmt + +EXTRA_DIST = \ + $(srcdir)/functions.php.sh \ + $(srcdir)/network_update.php \ + $(srcdir)/preload_station.php \ + $(srcdir)/services.php \ + $(srcdir)/password_protect.php \ + $(srcdir)/preload_system_status.php \ + $(srcdir)/sql_export.php \ + $(srcdir)/alarms.php \ + $(srcdir)/password_update.php \ + $(srcdir)/process_alarms.php \ + $(srcdir)/ssh.php \ + $(srcdir)/calibration.php \ + $(srcdir)/preload_alarms.php \ + $(srcdir)/process_calibration.php \ + $(srcdir)/station.php \ + $(srcdir)/cwop.php \ + $(srcdir)/preload_calibration.php \ + $(srcdir)/process_cwop.php \ + $(srcdir)/style.css \ + $(srcdir)/file_generation.php \ + $(srcdir)/preload_cwop.php \ + $(srcdir)/process_file_generation.php \ + $(srcdir)/system_status.php \ + $(srcdir)/ftp.php \ + $(srcdir)/preload_file_generation.php \ + $(srcdir)/process_ftp.php \ + $(srcdir)/wview-100x100.png \ + $(srcdir)/preload_ftp.php \ + $(srcdir)/process_http_services.php \ + $(srcdir)/wview-40x40.png \ + $(srcdir)/http_services.php \ + $(srcdir)/preload_http_services.php \ + $(srcdir)/process_services.php \ + $(srcdir)/wview.ico \ + $(srcdir)/preload_services.php \ + $(srcdir)/process_sql_export.php \ + $(srcdir)/wview_control.php \ + $(srcdir)/login.php \ + $(srcdir)/preload_sql_export.php \ + $(srcdir)/process_ssh.php \ + $(srcdir)/wview_upgrade.php \ + $(srcdir)/logout.php \ + $(srcdir)/preload_ssh.php \ + $(srcdir)/process_station.php \ + $(srcdir)/imgs/blueline.gif \ + $(srcdir)/imgs/calendar.png \ + $(srcdir)/imgs/email.png \ + $(srcdir)/imgs/header-norm.gif \ + $(srcdir)/imgs/phone.png \ + $(srcdir)/imgs/tip_small.png \ + $(srcdir)/imgs/black.png \ + $(srcdir)/imgs/blue.png \ + $(srcdir)/imgs/yellow.png \ + $(srcdir)/imgs/green.png \ + $(srcdir)/imgs/gray.png \ + $(srcdir)/imgs/red.png + + +# define the scripts to be generated +bin_SCRIPTS = functions.php + +CLEANFILES = $(bin_SCRIPTS) + +functions.php: $(srcdir)/functions.php.sh + echo " functions.php + echo "" >> functions.php + echo "function GetBinaryPrefix()" >> functions.php + echo "{" >> functions.php + echo ' $$WVIEW_BINARY_PREFIX' = \"$(exec_prefix)\"';' >> functions.php + echo ' return $$WVIEW_BINARY_PREFIX;' >> functions.php + echo "}" >> functions.php + echo "" >> functions.php + echo "function GetConfigPrefix()" >> functions.php + echo "{" >> functions.php + echo ' $$WVIEW_CONFIG_PREFIX' = \"$(sysconfdir)\"';' >> functions.php + echo ' return $$WVIEW_CONFIG_PREFIX;' >> functions.php + echo "}" >> functions.php + echo "" >> functions.php + echo "function GetDataPrefix()" >> functions.php + echo "{" >> functions.php + echo ' $$WVIEW_DATA_PREFIX' = \"$(localstatedir)\"';' >> functions.php + echo ' return $$WVIEW_DATA_PREFIX;' >> functions.php + echo "}" >> functions.php + echo "" >> functions.php + cat $(srcdir)/functions.php.sh >> functions.php + diff --git a/wviewmgmt/alarms.php b/wviewmgmt/alarms.php new file mode 100644 index 0000000..695f89d --- /dev/null +++ b/wviewmgmt/alarms.php @@ -0,0 +1,1798 @@ + + + + + + + + wview Management + + + + + + + + + + + + + +
        + +
        + + + + + + + + +
        + +
        + + + + + + + + + + + + + + + + + + +
        +

        Alarms

        +

        + + Weather Server and Site Generator +

        +

        Version +

        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        +

        + System
        Status

        +

        + Services

        +

        + Station

        +

        + File
        Generation

        +

        + Alarms

        +

        + FTP

        +

        + SSH

        +

        + CWOP

        +

        + HTTP
        Services

        +

        + Calibration

        +

        + SQL
        Export

        +
        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        + Settings  +
        +
        +
        + +   + +
        +
        + + + > +
        + + + > +
        + + + '> +
        + Alarm 1  +
        +
        +
        + +   + +
        +
        + + + '> +
        + + + > +
        + + + '> +
        + + + '> +
        + + + + + + ''> +
        + Alarm 2  +
        +
        +
        + +   + +
        +
        + + + '> +
        + + + > +
        + + + '> +
        + + + '> +
        + + + + + + ''> +
        + Alarm 3  +
        +
        +
        + +   + +
        +
        + + + '> +
        + + + > +
        + + + '> +
        + + + '> +
        + + + + + + ''> +
        + Alarm 4  +
        +
        +
        + +   + +
        +
        + + + '> +
        + + + > +
        + + + '> +
        + + + '> +
        + + + + + + ''> +
        + Alarm 5  +
        +
        +
        + +   + +
        +
        + + + '> +
        + + + > +
        + + + '> +
        + + + '> +
        + + + + + + ''> +
        + Alarm 6  +
        +
        +
        + +   + +
        +
        + + + '> +
        + + + > +
        + + + '> +
        + + + '> +
        + + + + + + ''> +
        + Alarm 7  +
        +
        +
        + +   + +
        +
        + + + '> +
        + + + > +
        + + + '> +
        + + + '> +
        + + + + + + ''> +
        + Alarm 8  +
        +
        +
        + +   + +
        +
        + + + '> +
        + + + > +
        + + + '> +
        + + + '> +
        + + + + + + ''> +
        + Alarm 9  +
        +
        +
        + +   + +
        +
        + + + '> +
        + + + > +
        + + + '> +
        + + + '> +
        + + + + + + ''> +
        + Alarm 10  +
        +
        +
        + +   + +
        +
        + + + '> +
        + + + > +
        + + + '> +
        + + + '> +
        + + + + + + ''> +
         
        + + Logout + +    + + + + + + +
        + +   + +
        +
          +
        +
        +
        +
        +
        +
        +
        + + diff --git a/wviewmgmt/calibration.php b/wviewmgmt/calibration.php new file mode 100644 index 0000000..acaa8c5 --- /dev/null +++ b/wviewmgmt/calibration.php @@ -0,0 +1,536 @@ + + + + + + + + wview Management + + + + + + + + + + + + + +
        + +
        + + + + + + + + +
        + +
        + + + + + + + + + + + + + + + + + + +
        +

        Calibration

        +

        + + Weather Server and Site Generator +

        +

        Version +

        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        +

        + System
        Status

        +

        + Services

        +

        + Station

        +

        + File
        Generation

        +

        + Alarms

        +

        + FTP

        +

        + SSH

        +

        + CWOP

        +

        + HTTP
        Services

        +

        + Calibration

        +

        + SQL
        Export

        +
        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        + Settings  +
        +
        +
        + Sensors are calibrated as follows: SensorValue * Multiplier + Constant. +
        +
        +
        +
        + Sensors are calibrated using US (imperial) units. +
        +
        + + + '> +
        + + + '> +
        + + + '> +
        + + + '> +
        + + + '> +
        + + + '> +
        + + + '> +
        + + + '> +
        + + + '> +
        + + + '> +
        + + + '> +
        + + + '> +
        + + + '> +
        + + + '> +
        + + + '> +
        + + + '> +
        + + + '> +
        + + + '> +
         
        + + Logout + +    + + + + + + +
        + +   + +
        +
          +
        +
        +
        +
        +
        +
        +
        + + diff --git a/wviewmgmt/cwop.php b/wviewmgmt/cwop.php new file mode 100644 index 0000000..07a4ad4 --- /dev/null +++ b/wviewmgmt/cwop.php @@ -0,0 +1,440 @@ + + + + + + + + wview Management + + + + + + + + + + + + + +
        + +
        + + + + + + + + +
        + +
        + + + + + + + + + + + + + + + + + + +
        +

        CWOP

        +

        + + Weather Server and Site Generator +

        +

        Version +

        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        +

        + System
        Status

        +

        + Services

        +

        + Station

        +

        + File
        + Generation

        +

        + Alarms

        +

        + FTP

        +

        + SSH

        +

        + CWOP

        +

        + HTTP
        Services

        +

        + Calibration

        +

        + SQL
        Export

        +
        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        + Settings  +
        +
        +
        + +
        +
        + + + '> +
        + + + '> +
        + + + '> +
        + + + '> +
        + + + '> +
        + + + '> +
        + + + '> +
        + Location  +
        +
        +
        + +
        +
        + + + '> +
        + + + '> +
        + Miscellaneous  +
        +
        +
        + +
        +
        + + + > +
         
        + + Logout + +    + + + + + + +
        + +   + +
        +
          +
        +
        +
        +
        +
        +
        +
        + + diff --git a/wviewmgmt/file_generation.php b/wviewmgmt/file_generation.php new file mode 100644 index 0000000..61dfa30 --- /dev/null +++ b/wviewmgmt/file_generation.php @@ -0,0 +1,650 @@ + + + + + + + + wview Management + + + + + + + + + + + + + +
        + +
        + + + + + + + + +
        + +
        + + + + + + + + + + + + + + + + + + +
        +

        File Generation

        +

        + + Weather Server and Site Generator +

        +

        Version +

        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        +

        + System
        Status

        +

        + Services

        +

        + Station

        +

        + File
        Generation

        +

        + Alarms

        +

        + FTP

        +

        + SSH

        +

        + CWOP

        +

        + HTTP
        Services

        +

        + Calibration

        +

        + SQL
        Export

        +
        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        + Name/Location  +
        +
        +
        + + Identify your station for web site display.

        +
        +
        +
        + + + '> +
        + + + '> +
        + + + '> +
        + Settings  +
        +
        +
        + + Configure when and how your files are generated.

        +
        +
        +
        + + + + + + ''> +
        + + + + + + ''> +
        + + + '> +
        + + + '> +
        + + + > +
        + + + > +
        + + + +     + +     + +     + +   +
        + + + > +
        + + + > +
        + + + '> +
        + + + > +
        + Text  +
        +
        +
        + +  

        +
        +
        +
        + + + '> +
        + + + '> +
        + + + '> +
        + + + '> +
        + + + '> +
        + + + '> +
         
        + + Logout + +    + + + + + + +
        + +   + +
        +
          +
        +
        +
        +
        +
        +
        +
        + + diff --git a/wviewmgmt/ftp.php b/wviewmgmt/ftp.php new file mode 100644 index 0000000..fc07366 --- /dev/null +++ b/wviewmgmt/ftp.php @@ -0,0 +1,740 @@ + + + + + + + + wview Management + + + + + + + + + + + + + +
        + +
        + + + + + + + + +
        + +
        + + + + + + + + + + + + + + + + + + +
        +

        FTP

        +

        + + Weather Server and Site Generator +

        +

        Version +

        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        +

        + System
        Status

        +

        + Services

        +

        + Station

        +

        + File
        Generation

        +

        + Alarms

        +

        + FTP

        +

        + SSH

        +

        + CWOP

        +

        + HTTP
        Services

        +

        + Calibration

        +

        + SQL
        Export

        +
        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        + Connection  +
        +
        +
        +
        +
        + + + '> +
        + + + '> +
        + + + '> +
        + + + '> +
        + + + > +
        + + + '> +
            +
        +
        +
        +
        + Rule 1  +
        +
        +
        + + + 0 ) { ?> + + + ''> +
        + Rule 2  +
        +
        +
        + + + 0 ) { ?> + + + ''> +
        + Rule 3  +
        +
        +
        + + + 0 ) { ?> + + + ''> +
        + Rule 4  +
        +
        +
        + + + 0 ) { ?> + + + ''> +
        + Rule 5  +
        +
        +
        + + + 0 ) { ?> + + + ''> +
        + Rule 6  +
        +
        +
        + + + 0 ) { ?> + + + ''> +
        + Rule 7  +
        +
        +
        + + + 0 ) { ?> + + + ''> +
        + Rule 8  +
        +
        +
        + + + 0 ) { ?> + + + ''> +
        + Rule 9  +
        +
        +
        + + + 0 ) { ?> + + + ''> +
        + Rule 10  +
        +
        +
        + + + 0 ) { ?> + + + ''> +
         
        + + Logout + +    + + + + + + +
        + +   + +
        +
          +
        +
        +
        +
        +
        +
        +
        + + diff --git a/wviewmgmt/functions.php.sh b/wviewmgmt/functions.php.sh new file mode 100644 index 0000000..bb09a81 --- /dev/null +++ b/wviewmgmt/functions.php.sh @@ -0,0 +1,563 @@ +$NONE = '"display: none"'; +$EMPTY = '"display: "'; + +// Process status: +function IsStatusAvailable($procname) +{ + $pidstr = "/var/run/wview/" . $procname . ".sts"; + return (file_exists($pidstr)); +} + +function DisplayStatusColor($status) +{ + switch($status) + { + case 0: + return ""; + case 1: + return ""; + case 2: + return ""; + case 3: + return ""; + case 4: + return ""; + case 5: + return ""; + } +} + +function DisplayStatuses() +{ + $procs = array('wview', 'html', 'alarms', 'cwop', 'http', 'ftp', 'ssh', 'pmon'); + $statusLabels = array("Not Started", + "Booting", + "Wait for wviewd", + "Running", + "Shutdown", + "Error"); + + echo ""; + echo " "; + echo " "; + echo " "; + echo " "; + echo " "; + echo " "; + echo " "; + echo " "; + echo " "; + echo " "; + echo " "; + echo " "; + echo " "; + + foreach ($procs as $process) + { + echo ""; + echo ""; + if (IsStatusAvailable($process)) + { + $pidstr = "/var/run/wview/" . $process . ".sts"; + $ini_array = parse_ini_file($pidstr); + echo ""; + echo ""; + if (array_key_exists('message', $ini_array)) + { + echo ""; + } + else + { + echo ""; + } + if (array_key_exists('desc0', $ini_array)) + { + echo ""; + echo ""; + } + else + { + echo ""; + echo ""; + } + if (array_key_exists('desc1', $ini_array)) + { + echo ""; + echo ""; + } + else + { + echo ""; + echo ""; + } + if (array_key_exists('desc2', $ini_array)) + { + echo ""; + echo ""; + } + else + { + echo ""; + echo ""; + } + if (array_key_exists('desc3', $ini_array)) + { + echo ""; + echo ""; + } + else + { + echo ""; + echo ""; + } + } + else + { + echo ""; + echo ""; + echo ""; + echo ""; + echo ""; + echo ""; + echo ""; + echo ""; + echo ""; + echo ""; + echo ""; + } + echo ""; + } + + echo "
        ServiceStatusMessageStat1ValueStat2ValueStat3ValueStat4Value
        $process" . DisplayStatusColor($ini_array['status']) . "" . $statusLabels[$ini_array['status']] . "" . $ini_array['message'] . " " . $ini_array['desc0'] . "" . $ini_array['stat0'] . "  " . $ini_array['desc1'] . "" . $ini_array['stat1'] . "  " . $ini_array['desc2'] . "" . $ini_array['stat2'] . "  " . $ini_array['desc3'] . "" . $ini_array['stat3'] . "  " . DisplayStatusColor(0) . "" . $statusLabels[0] . "         
        "; +} + + +// Network setup: +function NetworkTypeGet() +{ + $lines = file('/etc/network/interfaces', FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); + + for ($i = 0; $i < sizeof($lines); $i++) + { + $pos = stripos($lines[$i], 'iface eth0'); + if ($pos !== false) + { + $type = stripos($lines[$i], 'dhcp'); + if ($type !== false) + { + return 'dhcp'; + } + $type = stripos($lines[$i], 'static'); + if ($type !== false) + { + return 'static'; + } + else + { + return 'none'; + } + } + } + + // If here, we found nothing: + return 'none'; +} + +function NetworkIPGet() +{ + $lines = file('/etc/network/interfaces', FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); + + for ($i = 0; $i < sizeof($lines); $i++) + { + $pos = stristr($lines[$i], 'address'); + if ($pos !== false) + { + $address = substr($pos, 7); + $address = trim($address); + return $address; + } + } + + // If here, we found nothing: + return 'none'; +} + +function NetworkMaskGet() +{ + $lines = file('/etc/network/interfaces', FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); + + for ($i = 0; $i < sizeof($lines); $i++) + { + $pos = stristr($lines[$i], 'netmask'); + if ($pos !== false) + { + $address = substr($pos, 7); + $address = trim($address); + return $address; + } + } + + // If here, we found nothing: + return 'none'; +} + +function NetworkGWGet() +{ + $lines = file('/etc/network/interfaces', FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); + + for ($i = 0; $i < sizeof($lines); $i++) + { + $pos = stristr($lines[$i], 'gateway'); + if ($pos !== false) + { + $address = substr($pos, 7); + $address = trim($address); + return $address; + } + } + + // If here, we found nothing: + return 'none'; +} + +function NetworkDNS1Get() +{ + $lines = file('/etc/resolv.conf', FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); + + for ($i = 0; $i < sizeof($lines); $i++) + { + $pos = stristr($lines[$i], 'nameserver'); + if ($pos !== false) + { + $address = substr($pos, 10); + $address = trim($address); + return $address; + } + } + + // If here, we found nothing: + return 'none'; +} + +function NetworkDNS2Get() +{ + $lines = file('/etc/resolv.conf', FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); + $found = false; + + for ($i = 0; $i < sizeof($lines); $i++) + { + $pos = stristr($lines[$i], 'nameserver'); + if ($pos !== false) + { + if ($found == false) + { + $found = true; + } + else + { + $address = substr($pos, 10); + $address = trim($address); + return $address; + } + } + } + + // If here, we found nothing: + return 'none'; +} + +function SqliteDBOpen() +{ + $connectStr = GetConfigPrefix() . "/wview/wview-conf.sdb"; + try + { + $conn = new PDO('sqlite:'.$connectStr); + } + catch( PDOException $exception ) + { + die("SqliteDBOpen " . $connectStr . " failed: " . $exception->getMessage()); + } + + return $conn; +} + +function SqliteDBClose($dbConn) +{ + $dbConn = NULL; +} + +function SqliteDBCreateValue($dbID, $name, $value, $descr, $dependsOn) +{ + $sql = false; + + if ($dependsOn == 'NULL') + $sql = "INSERT INTO config VALUES('" . $name . "','" . $value . "','" . $descr . "','')"; + else + $sql = "INSERT INTO config VALUES('" . $name . "','" . $value . "','" . $descr . "','" . $dependsOn . "')"; + try + { + $result = $dbID->exec($sql); + } + catch( PDOException $exception ) + { + die("SqliteDBCreateValue (" . $name . "," . $value . ") failed: " . $exception->getMessage()); + } + + if ($result != 1) + die("SqliteDBCreateValue: No rows updated: is the wview-conf.sdb file and containing directory writable by the http user?"); +} + +function SqliteDBSetValue($dbID, $name, $value) +{ + $sql = "update config set value='" .$value ."' where name='" . $name . "'"; + try + { + $result = $dbID->exec($sql); + } + catch( PDOException $exception ) + { + die("SqliteDBSetValue (" . $name . "," . $value . ") failed: " . $exception->getMessage()); + } + + if ($result != 1) + { + $errmsg = "SqliteDBSetValue: No rows updated for parameter $name: "; + $errmsg .= "is the wview-conf.sdb database and containing directory writable by the http user -OR- "; + $errmsg .= "is your configuration database from an older version of wview and missing the $name row?"; + die($errmsg); + } +} + +function SqliteDBGetValue($dbID, $name) +{ + $sql = "SELECT value FROM config WHERE name='" . $name . "'"; + try + { + $result = $dbID->query($sql); + } + catch( PDOException $exception ) + { + die("SqliteDBGetValue (" . $name . ") failed: " . $exception->getMessage()); + } + + if ($result == NULL) + { + return false; + } + + $retVal = $result->fetchColumn(); + $result->closeCursor(); + + return $retVal; +} + +function SqliteDBGetRowCount($dbID) +{ + $sql = "SELECT COUNT(*) FROM config"; + try + { + $result = $dbID->query($sql); + } + catch( PDOException $exception ) + { + die("SqliteDBGetRowCount failed: " . $exception->getMessage()); + } + + $retVal = $result->fetchColumn(); + $result->closeCursor(); + + return $retVal; +} + +function SqliteDBArchiveGetRowCount() +{ + $retVal = 0; + + $connectStr = GetDataPrefix() . "/wview/archive/wview-archive.sdb"; + try + { + $conn = new PDO('sqlite:'.$connectStr); + } + catch( PDOException $exception ) + { + die("SqliteDBOpen " . $connectStr . " failed: " . $exception->getMessage()); + } + + $sql = "SELECT COUNT(*) FROM archive"; + try + { + $result = $conn->query($sql); + } + catch( PDOException $exception ) + { + die("SqliteDBGetRowCount failed: " . $exception->getMessage()); + } + + $retVal = $result->fetchColumn(); + $result->closeCursor(); + $dbConn = NULL; + return $retVal; +} + +function SqliteDBArchiveGetDateRange() +{ + $retVal = 0; + + $connectStr = GetDataPrefix() . "/wview/archive/wview-archive.sdb"; + try + { + $conn = new PDO('sqlite:'.$connectStr); + } + catch( PDOException $exception ) + { + die("SqliteDBOpen " . $connectStr . " failed: " . $exception->getMessage()); + } + + $sql = "SELECT MIN(dateTime) FROM archive"; + try + { + $result = $conn->query($sql); + } + catch( PDOException $exception ) + { + die("SqliteDBGetRowCount failed: " . $exception->getMessage()); + } + $start = $result->fetchColumn(); + $start = getdate($start); + $result->closeCursor(); + + $sql = "SELECT MAX(dateTime) FROM archive"; + try + { + $result = $conn->query($sql); + } + catch( PDOException $exception ) + { + die("SqliteDBGetRowCount failed: " . $exception->getMessage()); + } + $stop = $result->fetchColumn(); + $stop = getdate($stop); + $result->closeCursor(); + $dbConn = NULL; + + $retVal = "$start[month] $start[mday], $start[year] - $stop[month] $stop[mday], $stop[year]"; + return $retVal; +} + +function SqliteDBHiLowGetRowCount() +{ + $retVal = 0; + + $connectStr = GetDataPrefix() . "/wview/archive/wview-hilow.sdb"; + if (file_exists($connectStr)) + { + try + { + $conn = new PDO('sqlite:'.$connectStr); + } + catch( PDOException $exception ) + { + die("SqliteDBOpen " . $connectStr . " failed: " . $exception->getMessage()); + } + + $sql = "SELECT COUNT(*) FROM outTemp"; + try + { + $result = $conn->query($sql); + } + catch( PDOException $exception ) + { + die("SqliteDBGetRowCount failed: " . $exception->getMessage()); + } + + $retVal = $result->fetchColumn(); + $result->closeCursor(); + $dbConn = NULL; + } + + return $retVal; +} + +function SqliteDBNOAAGetRowCount() +{ + $retVal = 0; + + $connectStr = GetDataPrefix() . "/wview/archive/wview-noaa.sdb"; + if (file_exists($connectStr)) + { + try + { + $conn = new PDO('sqlite:'.$connectStr); + } + catch( PDOException $exception ) + { + die("SqliteDBOpen " . $connectStr . " failed: " . $exception->getMessage()); + } + + $sql = "SELECT COUNT(*) FROM noaaHistory"; + try + { + $result = $conn->query($sql); + } + catch( PDOException $exception ) + { + die("SqliteDBGetRowCount failed: " . $exception->getMessage()); + } + + $retVal = $result->fetchColumn(); + $result->closeCursor(); + $dbConn = NULL; + } + + return $retVal; +} + +function IswviewRunning() +{ + $pidstr = GetDataPrefix() . "/wview/wviewd.pid"; + return (file_exists($pidstr)); +} + +function IswviewIndicated() +{ + $dbconn = SqliteDBOpen(); + $htmlEnabled = SqliteDBGetValue($dbconn, 'ENABLE_HTMLGEN'); + SqliteDBClose($dbconn); + + if ($htmlEnabled == "yes") + { + $pidstr = GetDataPrefix() . "/wview/wview-running"; + return (file_exists($pidstr)); + } + else + { + return IswviewRunning(); + } +} + +function wviewVersionGet() +{ + $pidstr = GetConfigPrefix() . "/wview/wview-version"; + return (file_get_contents($pidstr)); +} + +function IsStartupActive() +{ + if (IswviewRunning()) + { + if (IswviewIndicated()) + { + return false; + } + else + { + return true; + } + } + else + { + return false; + } +} + +?> diff --git a/wviewmgmt/http_services.php b/wviewmgmt/http_services.php new file mode 100644 index 0000000..5a5dc2b --- /dev/null +++ b/wviewmgmt/http_services.php @@ -0,0 +1,309 @@ + + + + + + + + wview Management + + + + + + + + + + + + + +
        + +
        + + + + + + + + +
        + +
        + + + + + + + + + + + + + + + + + + +
        +

        HTTP Services

        +

        + + Weather Server and Site Generator +

        +

        Version +

        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        +

        + System
        Status

        +

        + Services

        +

        + Station

        +

        + File
        Generation

        +

        + Alarms

        +

        + FTP

        +

        + SSH

        +

        + CWOP

        +

        + HTTP
        Services

        +

        + Calibration

        +

        + SQL
        Export

        +
        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        + Weather Underground  +
        +
        +
        +
        +
        + + + '> +
        + + + '> +
        + Weather For You  +
        +
        +
        +
        +
        + + + '> +
        + + + '> +
         
        + + Logout + +    + + + + + + +
        + +   + +
        +
          +
        +
        +
        +
        +
        +
        +
        + + diff --git a/wviewmgmt/imgs/black.png b/wviewmgmt/imgs/black.png new file mode 100755 index 0000000000000000000000000000000000000000..4134a091fa780242d78d920bb7c2a01250668b01 GIT binary patch literal 3012 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 NI+ literal 0 HcmV?d00001 diff --git a/wviewmgmt/imgs/phone.png b/wviewmgmt/imgs/phone.png new file mode 100755 index 0000000000000000000000000000000000000000..c39f162f854a7c412fab9b6ff38fffdc61754a58 GIT binary patch literal 488 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 literal 0 HcmV?d00001 diff --git a/wviewmgmt/imgs/yellow.png b/wviewmgmt/imgs/yellow.png new file mode 100755 index 0000000000000000000000000000000000000000..6f0b683dfe436bf667e0d95ecb909c8756ce04ea GIT binary patch literal 3083 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 Z_iquZPz*+4@MQo1002ovPDHLkV1lne_sakP literal 0 HcmV?d00001 diff --git a/wviewmgmt/login.php b/wviewmgmt/login.php new file mode 100755 index 0000000..2961cc0 --- /dev/null +++ b/wviewmgmt/login.php @@ -0,0 +1,10 @@ + \ No newline at end of file diff --git a/wviewmgmt/logout.php b/wviewmgmt/logout.php new file mode 100755 index 0000000..385cc96 --- /dev/null +++ b/wviewmgmt/logout.php @@ -0,0 +1,29 @@ + + + + wview Management Logout + + + + + +
        +
        +
        + You are now logged out of wview Management +
        +
        + Back to wview Management +
        +
        +
        +
        + + + + + diff --git a/wviewmgmt/network_update.php b/wviewmgmt/network_update.php new file mode 100755 index 0000000..e010629 --- /dev/null +++ b/wviewmgmt/network_update.php @@ -0,0 +1,49 @@ + /dev/null"; + system($syscmnd); + + header('Location: ' . 'system_status.php'); + } +?> diff --git a/wviewmgmt/password_protect.php b/wviewmgmt/password_protect.php new file mode 100755 index 0000000..969cf9d --- /dev/null +++ b/wviewmgmt/password_protect.php @@ -0,0 +1,148 @@ +Logout +################################################################################ + +// User will be redirected to this page after logout +define('LOGOUT_URL', 'logout.php'); + +// time out after NN minutes of inactivity. Set to 0 to not timeout +define('TIMEOUT_MINUTES', 30); + +// This parameter is only useful when TIMEOUT_MINUTES is not zero +// true - timeout time from last activity, false - timeout time from login +define('TIMEOUT_CHECK_ACTIVITY', true); + + +function getDBPassword() +{ + // Open the database: + $dbID = SqliteDBOpen(); + + // Get MD5 password: + $retVal = SqliteDBGetValue($dbID, 'ADMIN_PASSWORD'); + if ($retVal == false) + { + // Insert the default: + $passwd = md5("wview"); + SqliteDBCreateValue($dbID, 'ADMIN_PASSWORD', $passwd, 'Admin Password (md5):', 'NULL'); + $retVal = $passwd; + } + + SqliteDBClose($dbID); + return $retVal; +} + +// timeout in seconds +$timeout = (TIMEOUT_MINUTES == 0 ? 0 : time() + TIMEOUT_MINUTES * 60); + +// logout? +if(isset($_GET['logout'])) +{ + setcookie("verify", '', $timeout, '/'); // clear password; + header('Location: ' . LOGOUT_URL); + exit(); +} + +if(!function_exists('showLoginPasswordProtect')) +{ + // show login form + function showLoginPasswordProtect($error_msg) + { +?> + + + Please enter the admin password: + + + + + + +
        +
        + + + + +
        +

        Please enter the admin password:

        + + +

        + +

        +
        +
        +
        +
        + + + diff --git a/wviewmgmt/password_update.php b/wviewmgmt/password_update.php new file mode 100755 index 0000000..822b86a --- /dev/null +++ b/wviewmgmt/password_update.php @@ -0,0 +1,30 @@ + \ No newline at end of file diff --git a/wviewmgmt/preload_alarms.php b/wviewmgmt/preload_alarms.php new file mode 100755 index 0000000..6010cb5 --- /dev/null +++ b/wviewmgmt/preload_alarms.php @@ -0,0 +1,117 @@ + diff --git a/wviewmgmt/preload_calibration.php b/wviewmgmt/preload_calibration.php new file mode 100755 index 0000000..82d3eae --- /dev/null +++ b/wviewmgmt/preload_calibration.php @@ -0,0 +1,110 @@ + diff --git a/wviewmgmt/preload_cwop.php b/wviewmgmt/preload_cwop.php new file mode 100755 index 0000000..f3d4060 --- /dev/null +++ b/wviewmgmt/preload_cwop.php @@ -0,0 +1,73 @@ + diff --git a/wviewmgmt/preload_file_generation.php b/wviewmgmt/preload_file_generation.php new file mode 100755 index 0000000..0e8752d --- /dev/null +++ b/wviewmgmt/preload_file_generation.php @@ -0,0 +1,83 @@ + diff --git a/wviewmgmt/preload_ftp.php b/wviewmgmt/preload_ftp.php new file mode 100755 index 0000000..e855bcc --- /dev/null +++ b/wviewmgmt/preload_ftp.php @@ -0,0 +1,80 @@ + diff --git a/wviewmgmt/preload_http_services.php b/wviewmgmt/preload_http_services.php new file mode 100755 index 0000000..17328ab --- /dev/null +++ b/wviewmgmt/preload_http_services.php @@ -0,0 +1,68 @@ + diff --git a/wviewmgmt/preload_services.php b/wviewmgmt/preload_services.php new file mode 100755 index 0000000..35bfd4e --- /dev/null +++ b/wviewmgmt/preload_services.php @@ -0,0 +1,118 @@ + + diff --git a/wviewmgmt/preload_sql_export.php b/wviewmgmt/preload_sql_export.php new file mode 100755 index 0000000..985f956 --- /dev/null +++ b/wviewmgmt/preload_sql_export.php @@ -0,0 +1,67 @@ + diff --git a/wviewmgmt/preload_ssh.php b/wviewmgmt/preload_ssh.php new file mode 100755 index 0000000..7be91f2 --- /dev/null +++ b/wviewmgmt/preload_ssh.php @@ -0,0 +1,98 @@ + diff --git a/wviewmgmt/preload_station.php b/wviewmgmt/preload_station.php new file mode 100755 index 0000000..a262314 --- /dev/null +++ b/wviewmgmt/preload_station.php @@ -0,0 +1,83 @@ + diff --git a/wviewmgmt/preload_system_status.php b/wviewmgmt/preload_system_status.php new file mode 100755 index 0000000..605c874 --- /dev/null +++ b/wviewmgmt/preload_system_status.php @@ -0,0 +1,106 @@ + diff --git a/wviewmgmt/process_alarms.php b/wviewmgmt/process_alarms.php new file mode 100755 index 0000000..322fe84 --- /dev/null +++ b/wviewmgmt/process_alarms.php @@ -0,0 +1,121 @@ + \ No newline at end of file diff --git a/wviewmgmt/process_calibration.php b/wviewmgmt/process_calibration.php new file mode 100755 index 0000000..e7f5850 --- /dev/null +++ b/wviewmgmt/process_calibration.php @@ -0,0 +1,83 @@ + \ No newline at end of file diff --git a/wviewmgmt/process_cwop.php b/wviewmgmt/process_cwop.php new file mode 100755 index 0000000..f083f7c --- /dev/null +++ b/wviewmgmt/process_cwop.php @@ -0,0 +1,37 @@ + \ No newline at end of file diff --git a/wviewmgmt/process_file_generation.php b/wviewmgmt/process_file_generation.php new file mode 100755 index 0000000..f0f149c --- /dev/null +++ b/wviewmgmt/process_file_generation.php @@ -0,0 +1,57 @@ + \ No newline at end of file diff --git a/wviewmgmt/process_ftp.php b/wviewmgmt/process_ftp.php new file mode 100755 index 0000000..53ef5ea --- /dev/null +++ b/wviewmgmt/process_ftp.php @@ -0,0 +1,41 @@ + \ No newline at end of file diff --git a/wviewmgmt/process_http_services.php b/wviewmgmt/process_http_services.php new file mode 100755 index 0000000..40f8fa6 --- /dev/null +++ b/wviewmgmt/process_http_services.php @@ -0,0 +1,26 @@ + \ No newline at end of file diff --git a/wviewmgmt/process_services.php b/wviewmgmt/process_services.php new file mode 100755 index 0000000..813de2c --- /dev/null +++ b/wviewmgmt/process_services.php @@ -0,0 +1,94 @@ + \ No newline at end of file diff --git a/wviewmgmt/process_sql_export.php b/wviewmgmt/process_sql_export.php new file mode 100755 index 0000000..08749dc --- /dev/null +++ b/wviewmgmt/process_sql_export.php @@ -0,0 +1,25 @@ + \ No newline at end of file diff --git a/wviewmgmt/process_ssh.php b/wviewmgmt/process_ssh.php new file mode 100755 index 0000000..d93ab4d --- /dev/null +++ b/wviewmgmt/process_ssh.php @@ -0,0 +1,55 @@ + diff --git a/wviewmgmt/process_station.php b/wviewmgmt/process_station.php new file mode 100755 index 0000000..3230bf9 --- /dev/null +++ b/wviewmgmt/process_station.php @@ -0,0 +1,121 @@ + " . GetConfigPrefix() . "/wview/wview-binary"; + system($systemstr); + } + else if ($_POST['field_Station_Type'] == "Viasala WXT510") + { + SqliteDBSetValue($dbID, 'STATION_TYPE', 'WXT510'); + $systemstr = "echo \"wviewd_wxt510\" > " . GetConfigPrefix() . "/wview/wview-binary"; + system($systemstr); + } + else if ($_POST['field_Station_Type'] == "Texas Weather Instruments") + { + SqliteDBSetValue($dbID, 'STATION_TYPE', 'TWI'); + $systemstr = "echo \"wviewd_twi\" > " . GetConfigPrefix() . "/wview/wview-binary"; + system($systemstr); + } + else if ($_POST['field_Station_Type'] == "La Crosse WS-23XX") + { + SqliteDBSetValue($dbID, 'STATION_TYPE', 'WS-2300'); + $systemstr = "echo \"wviewd_ws2300\" > " . GetConfigPrefix() . "/wview/wview-binary"; + system($systemstr); + } + else if ($_POST['field_Station_Type'] == "Oregon Scientific WMR9XX") + { + SqliteDBSetValue($dbID, 'STATION_TYPE', 'WMR918'); + $systemstr = "echo \"wviewd_wmr918\" > " . GetConfigPrefix() . "/wview/wview-binary"; + system($systemstr); + } + else if ($_POST['field_Station_Type'] == "Oregon Scientific WMRUSB") + { + SqliteDBSetValue($dbID, 'STATION_TYPE', 'WMRUSB'); + $systemstr = "echo \"wviewd_wmrusb\" > " . GetConfigPrefix() . "/wview/wview-binary"; + system($systemstr); + } + else if ($_POST['field_Station_Type'] == "Fine Offset WH1080") + { + SqliteDBSetValue($dbID, 'STATION_TYPE', 'WH1080'); + $systemstr = "echo \"wviewd_wh1080\" > " . GetConfigPrefix() . "/wview/wview-binary"; + system($systemstr); + } + else if ($_POST['field_Station_Type'] == "Honeywell TE923") + { + SqliteDBSetValue($dbID, 'STATION_TYPE', 'TE923'); + $systemstr = "echo \"wviewd_te923\" > " . GetConfigPrefix() . "/wview/wview-binary"; + system($systemstr); + } + else if ($_POST['field_Station_Type'] == "Simulator") + { + SqliteDBSetValue($dbID, 'STATION_TYPE', 'Simulator'); + $systemstr = "echo \"wviewd_sim\" > " . GetConfigPrefix() . "/wview/wview-binary"; + system($systemstr); + } + else if ($_POST['field_Station_Type'] == "Virtual") + { + SqliteDBSetValue($dbID, 'STATION_TYPE', 'Virtual'); + $systemstr = "echo \"wviewd_virtual\" > " . GetConfigPrefix() . "/wview/wview-binary"; + system($systemstr); + } + else + { + SqliteDBSetValue($dbID, 'STATION_TYPE', 'Simulator'); + $systemstr = "echo \"wviewd_sim\" > " . GetConfigPrefix() . "/wview/wview-binary"; + system($systemstr); + } + + SqliteDBSetValue($dbID, 'STATION_INTERFACE', $_POST['field_Station_Interface']); + SqliteDBSetValue($dbID, 'STATION_DEV', $_POST['field_Station_Device']); + SqliteDBSetValue($dbID, 'STATION_HOST', $_POST['field_Station_Host']); + SqliteDBSetValue($dbID, 'STATION_PORT', $_POST['field_Station_Port']); + if ($_POST['field_Station_WLIP'][0] == "yes") + SqliteDBSetValue($dbID, 'STATION_WLIP', 'yes'); + else + SqliteDBSetValue($dbID, 'STATION_WLIP', 'no'); + if ($_POST['field_Station_Retrieve_Archive'][0] == "yes") + SqliteDBSetValue($dbID, 'STATION_RETRIEVE_ARCHIVE', 'yes'); + else + SqliteDBSetValue($dbID, 'STATION_RETRIEVE_ARCHIVE', 'no'); + if ($_POST['field_Station_Do_DTR'][0] == "yes") + SqliteDBSetValue($dbID, 'STATION_DTR', 'yes'); + else + SqliteDBSetValue($dbID, 'STATION_DTR', 'no'); + SqliteDBSetValue($dbID, 'STATION_RAIN_SEASON_START', $_POST['field_Station_Rain_Season_Start']); + SqliteDBSetValue($dbID, 'STATION_RAIN_STORM_TRIGGER_START', $_POST['field_Station_Storm_Trigger_Start']); + SqliteDBSetValue($dbID, 'STATION_RAIN_STORM_IDLE_STOP', $_POST['field_Station_Storm_Trigger_Stop']); + SqliteDBSetValue($dbID, 'STATION_RAIN_YTD', $_POST['field_Station_Rain_YTD']); + SqliteDBSetValue($dbID, 'STATION_ET_YTD', $_POST['field_Station_ET_YTD']); + SqliteDBSetValue($dbID, 'STATION_RAIN_ET_YTD_YEAR', $_POST['field_Station_YTD_Year']); + SqliteDBSetValue($dbID, 'STATION_ELEVATION', $_POST['field_Station_Elevation']); + SqliteDBSetValue($dbID, 'STATION_LATITUDE', $_POST['field_Station_Latitude']); + SqliteDBSetValue($dbID, 'STATION_LONGITUDE', $_POST['field_Station_Longitude']); + SqliteDBSetValue($dbID, 'STATION_ARCHIVE_INTERVAL', $_POST['field_Station_Archive_Interval']); + SqliteDBSetValue($dbID, 'STATION_POLL_INTERVAL', $_POST['field_Station_Polling_Interval']); + SqliteDBSetValue($dbID, 'STATION_PUSH_INTERVAL', $_POST['field_Station_Push_Interval']); + if ($_POST['field_Station_Do_RX_Check'][0] == "yes") + SqliteDBSetValue($dbID, 'STATION_DO_RCHECK', 'yes'); + else + SqliteDBSetValue($dbID, 'STATION_DO_RCHECK', 'no'); + SqliteDBSetValue($dbID, 'STATION_OUTSIDE_CHANNEL', $_POST['field_Station_Outside_Channel']); + + // Close the database connection: + SqliteDBClose($dbID); + + header('Location: ' . 'station.php'); + +?> diff --git a/wviewmgmt/services.php b/wviewmgmt/services.php new file mode 100644 index 0000000..5a0adf2 --- /dev/null +++ b/wviewmgmt/services.php @@ -0,0 +1,669 @@ + + + + + + + + wview Management + + + + + + + + + + + + + +
        + +
        + + + + + + + + +
        + +
        + + + + + + + + + + + + + + + + + + +
        +

        Services

        +

        + + Weather Server and Site Generator +

        +

        Version +

        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        +

        + System
        Status

        +

        + Services

        +

        + Station

        +

        + File
        Generation

        +

        + Alarms

        +

        + FTP

        +

        + SSH

        +

        + CWOP

        +

        + HTTP
        Services

        +

        + Calibration

        +

        + SQL
        Export

        +
        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        + wview Processes  +
        +
        + You can choose which wview processes are enabled based on the services you require.

        +
        +
        + + + > +
        + + + > +
        + + + > +
        + + + > +
        + + + > +   + > +   + > +   +
        + + + > +
            +
        +
        +
        +
        + Logging  +
        +
        + You can choose which wview processes log verbosely. Error logs will always be enabled.

        +
        +
        + + + > +
        + + + > +
        + + + > +
        + + + > +
        + + + > +
        + + + > +
        + + + > +
            +
        +
        +
        +
        + Email Alerts  +
        +
        + Receive email if significant problems are detected by wview.

        +
        +
        + + + > +
        + + + '> +
        + + + '> +
        + + + > +
            +
        +
        +
        +
        + Process Monitoring  +
        +
        + Seconds to wait before restarting a non-responsive process (0 disables).

        +
        +
        + + + '> +
        + + + '> +
        + + + '> +
        + + + '> +
        + + + '> +
         
        + + Logout + +    + + + + + + +
        + +   + +
        +
          +
        +
        +
        +
        +
        +
        +
        + + diff --git a/wviewmgmt/sql_export.php b/wviewmgmt/sql_export.php new file mode 100644 index 0000000..47ecdbe --- /dev/null +++ b/wviewmgmt/sql_export.php @@ -0,0 +1,294 @@ + + + + + + + + wview Management + + + + + + + + + + + + + +
        + +
        + + + + + + + + +
        + +
        + + + + + + + + + + + + + + + + + + +
        +

        SQL Export

        +

        + + Weather Server and Site Generator +

        +

        Version +

        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        +

        + System
        Status

        +

        + Services

        +

        + Station

        +

        + File
        Generation

        +

        + Alarms

        +

        + FTP

        +

        + SSH

        +

        + CWOP

        +

        + HTTP
        Services

        +

        + Calibration

        +

        + SQL
        Export

        +
        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        + Export Database  +
        +
        +
        +
        +
        + + + '> +
        + + + '> +
        + + + '> +
        + + + '> +
         
        + + Logout + +    + + + + + + +
        + +   + +
        +
          +
        +
        +
        +
        +
        +
        +
        + + diff --git a/wviewmgmt/ssh.php b/wviewmgmt/ssh.php new file mode 100644 index 0000000..315baaa --- /dev/null +++ b/wviewmgmt/ssh.php @@ -0,0 +1,997 @@ + + + + + + + + wview Management + + + + + + + + + + + + + +
        + +
        + + + + + + + + +
        + +
        + + + + + + + + + + + + + + + + + + +
        +

        SSH

        +

        + + Weather Server and Site Generator +

        +

        Version +

        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        +

        + System
        Status

        +

        + Services

        +

        + Station

        +

        + File
        Generation

        +

        + Alarms

        +

        + FTP

        +

        + SSH

        +

        + CWOP

        +

        + HTTP
        Services

        +

        + Calibration

        +

        + SQL
        Export

        +
        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        + Rule 1  +
        +
        +
        +
        +
        + + + + + + ''> +
        + + + '> +
        + + + '> +
        + + + '> +
        + + + '> +
        + + + '> +
        + + + '> +
        + Rule 2  +
        +
        +
        +
        +
        + + + + + + ''> +
        + + + '> +
        + + + '> +
        + + + '> +
        + + + '> +
        + + + '> +
        + + + '> +
        + Rule 3  +
        +
        +
        +
        +
        + + + + + + ''> +
        + + + '> +
        + + + '> +
        + + + '> +
        + + + '> +
        + + + '> +
        + + + '> +
        + Rule 4  +
        +
        +
        +
        +
        + + + + + + ''> +
        + + + '> +
        + + + '> +
        + + + '> +
        + + + '> +
        + + + '> +
        + + + '> +
        + Rule 5  +
        +
        +
        +
        +
        + + + + + + ''> +
        + + + '> +
        + + + '> +
        + + + '> +
        + + + '> +
        + + + '> +
        + + + '> +
         
        + + Logout + +    + + + + + + +
        + +   + +
        +
          +
        +
        +
        +
        +
        +
        +
        + + diff --git a/wviewmgmt/station.php b/wviewmgmt/station.php new file mode 100644 index 0000000..adba53f --- /dev/null +++ b/wviewmgmt/station.php @@ -0,0 +1,1234 @@ + + + + + + + + wview Management + + + + + + + + + + + + + +
        + +
        + + + + + + + + +
        + +
        + + + + + + + + + + + + + + + + + + +
        +

        Station

        +

        + Weather Server and Site Generator +

        +

        Version +

        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        +

        + System
        Status

        +

        + Services

        +

        + Station

        +

        + File
        + Generation

        +

        + Alarms

        +

        + FTP

        +

        + SSH

        +

        + CWOP

        +

        + HTTP
        Services

        +

        + Calibration

        +

        + SQL
        Export

        +
        + + + + > + + + + + + + + + > + + + + + + + + + > + + + + + + + + + > + + + + + + + + + > + + + + + + + + + + + + + + + + + + + + + + + > + + + + + + + + + + > + + + + + + + + + + > + + + + + + + + + + + > + + + + + + + + + + + > + + + + + + + + + + + > + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + > + + + + + + + + + + + > + + + + + + + + + + + > + + + + + + + + + + + > + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + "> + + + + + + + + + "> + + + + + + + + + + + "> + + + + + + + + + + "> + + + + + + + + + + "> + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        + Communication  +
        +
        +
        + The simulator has no station interface. The WMRUSB/WH1080/TE923 weather stations + only support a native USB interface. No interface configuration is required.
        +
        +
        + Communication  +
        +
        +
        + The weather station can be connected using a serial or ethernet interface.
        +
        +
        + Communication  +
        +
        +
        + The weather station can be connected using a serial or ethernet interface.
        +
        +
        + Communication  +
        +
        +
        + The weather station can be connected using a serial or ethernet interface.
        +
        +
        + Communication  +
        +
        +
        + The weather station can be connected using a serial or ethernet interface.
        +
        +
        + + + +
        + + + > +
        + + + > +
        + + + + onclick="hideShowStationObjs(field_Station_Type.value, + field_Station_Interface_option_1.checked, + field_Station_Interface_option_2.checked)"> +     + + onclick="hideShowStationObjs(field_Station_Type.value, + field_Station_Interface_option_1.checked, + field_Station_Interface_option_2.checked)"> +   +
        + + + + + + ''> +
        + + + '> +
        + + + '> +
        + Storms  +
        +
        +
        + Configure storm criteria.
        +
        +
        + + + '> +
        + + + '> +
        + + + '> +
        + Presets  +
        +
        +
        + Preset accumulated values if data was missed
        +
        +
        + + + '> +
        + + + '> +
        + + + '> +
        + Location  +
        +
        +
        +  
        +
        +
        + + + '> +
        + + + '> +
        + + + '> +
        + Timing  +
        +
        +
        +  
        +
        +
        + + + +
        + + + +
        + + + +
        + Miscellaneous  +
        +
        +
        +  
        +
        +
        + Miscellaneous  +
        +
        +
        +  
        +
        +
        + + + > +
        + + + > +
        + + + +
         
        + + Logout + +    + + + + + + +
        + +   + +
        +
          
        +
        +
        + + diff --git a/wviewmgmt/style.css b/wviewmgmt/style.css new file mode 100755 index 0000000..6e8dd72 --- /dev/null +++ b/wviewmgmt/style.css @@ -0,0 +1,224 @@ + + +body +{ + text-align: center; + margin: 10px; /* shift whole page down by 10 pixels */ + background-color: #E8E8F0; +} + +input +{ + background-color: #ffffff; +} + +select +{ + background-color: #ffffff; +} + +textarea +{ + background-color: #ffffff; +} + + +#mainForm +{ + position: relative; + border: 1px; + border-style: solid; + margin: 0 auto; + text-align: left; + width: 1240px; + background-color: #ffffff; +} + +ul.mainForm +{ + list-style-type: none; + font-family: Tahoma, Arial, Verdana, sans-serif; + font-size:14px; + display:inline-block; + width: 100%; +} + +li.mainForm +{ + padding-bottom: 10px; +} + + +#mainFormError +{ + position: relative; + border: 1px; + border-style: solid; + margin: 0 auto; + text-align: left; + width: 90%; +} + + +#formHeader +{ + position: relative; + width: 1240px; + background-color: #060686; + margin:0 0 0 0; + padding-bottom: 10px; +} + +p.formHeader +{ + text-align: right; + margin:0 0 0 0; + font-family: Tahoma, Arial, Verdana, sans-serif; + font-weight:normal; + color: #ffffff; + font-size:25px; + position:relative; + left:-5px; + top:4px; + letter-spacing: 2px; +} + + + +#formInfo +{ + position: relative; + width: 100%; + background-color: #ffffff; + margin:0 0 0 0; +} + +h1.formInfo +{ + text-align: left; + margin:0 0 0 0; + font-family: Tahoma, Arial, Verdana, sans-serif; + font-weight:normal; + font-size:32px; + position:relative; + left:20px; + top:0px; + letter-spacing: 4px; + line-height:175%; + color: #ffffff; +} + + +p.formInfo +{ + text-align: left; + margin:0 0 0 0; + font-family: Tahoma, Arial, Verdana, sans-serif; + font-weight:normal; + color: #000000; + font-size:12px; + position:relative; + left:20px; + top:0px; +} + +#formFields +{ + position: relative; + width: 100%; + background-color: #ffffff; + margin:0 0 0 0; + +} + +label.formFieldQuestion +{ + border:none; + display:inline-block; + font-weight:bold; + background-color: #ffffff; +} + +label.formFieldOption +{ + display:inline-block; + line-height:100%; + margin:0 0 0px 0px; + width:10%; + background-color: #ffffff; +} + + +/* tooltip */ +a.info { + font-family: Tahoma, Arial, Sans-Serif; + text-decoration:none; + position: relative; + width:10%; +} + +a.info span { + position: relative; + display:none; +} + +a.info:hover { + position: relative; + cursor:default; +} + +a.info:hover .infobox { + font-weight: normal; + display:block; + position:absolute; + top:20px;; + left:25px; + width:205px; + height:90px; + border: 1px solid #ccc; + background:#f0f0f0; + color:#000; + text-align:left; + font-size:0.7em; + padding-left:10px; +} + +/* pop-up calendar */ +button.calendarStyle +{ + background-color: transparent; + border: 0; + height:22px; + width:22px; + background-image:url(imgs/calendar.png); + cursor: pointer; + cursor: hand; +} + + +p.footer +{ + text-align: right; + margin:0 0 0 0; + font-family: Tahoma, Arial, Verdana, sans-serif; + font-weight:normal; + color: #000000; + font-size:12px; + position:relative; + top:4px; + left:-140px; + letter-spacing: 2px; +} + +a.footer +{ + text-align: right; + margin:0 0 0 0; + font-family: Tahoma, Arial, Verdana, sans-serif; + font-weight:normal; + color: #000000; + font-size:12px; + position:relative; + top:4px; + letter-spacing: 2px; +} diff --git a/wviewmgmt/system_status-with-network-setup.php b/wviewmgmt/system_status-with-network-setup.php new file mode 100644 index 0000000..d20ef74 --- /dev/null +++ b/wviewmgmt/system_status-with-network-setup.php @@ -0,0 +1,779 @@ + + + + + + + + + wview Management + + + + + + + + + + + + + +
        + + + + + + + + +
        + +
        + + + + + + + + + + + + + + + + + + +
        +

        System Status

        +

        + + Weather Server and Site Generator +

        +

        Version +

        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        +

        + System
        Status

        +

        + Services

        +

        + Station

        +

        + File
        Generation

        +

        + Alarms

        +

        + FTP

        +

        + SSH

        +

        + CWOP

        +

        + HTTP
        Services

        +

        + Calibration

        +

        + SQL
        Export

        +
        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + "> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + "> + + + + + + + + + + "> + + + + + + + + + + + + "> + + + + + + + + + + "> + + + + + + + + + + + "> + + + + + + + + + + + "> + + + + + + + + + + + "> + + + + + + + + + + + "> + + + + + + + + + + + + + + + + + + + + "> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        + System Status  valign="top" bgcolor="#6666CC" align="center"> +
        +
        +
        + +   + +
        +
        + + + + + +
        +
        + +
        +
         
        + + + + + +
        +
        + +
        +
            +
        +
        +
        +
        + + + + + +
        + + + + + +
         
        + + + + + +
        + + + + + +
            +
        +
        +
        +
        + Services  +
        +
        + + wview services status. Statistics are since the last wview start. + +
        +
        + +
         
            +
        +
        +
        +
        + Network  +
        + + Configure network settings. + +
        +
        +
        + Network  +
        + + Configure network settings. + +
        +
        +
        + + + onclick="hideShowNetwork(!this.checked)"> +
        + + + '> +
        + + + '> +
        + + + '> +
        + + + '> +
        + + + '> +
         
        + +
            +
        +
        +
        +
        + Admin Password  +
        + + There is only one admin account. The password is stored as an MD5 hash, so there is no way to get a reminder. + +
        +
        +
            + Passwords did not match!
        "; ?> + New administrator password: +
        + +
            + Repeat password: + + +
         
        + +
        + + Logout + +      
        +
        + + diff --git a/wviewmgmt/system_status.php b/wviewmgmt/system_status.php new file mode 100644 index 0000000..9929111 --- /dev/null +++ b/wviewmgmt/system_status.php @@ -0,0 +1,604 @@ + + + + + + + + + wview Management + + + + + + + + + + + + + + +
        + + + + + + + + + + +
        +

        + +

        +
        + + +

        + +

        +
        + + + + + + + + + + + + + + + + + + +
        +

        System Status

        +

        + + Weather Server and Site Generator +

        +

        Version +

        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        +

        + System
        Status

        +

        + Services

        +

        + Station

        +

        + File
        Generation

        +

        + Alarms

        +

        + FTP

        +

        + SSH

        +

        + CWOP

        +

        + HTTP
        Services

        +

        + Calibration

        +

        + SQL
        Export

        +
        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + "> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        + System Status  valign="top" bgcolor="#6666CC" align="center"> +
        +
        +
        + +   + +
        +
        + + + + + +
        +
        + +
        +
         
        + + + + + +
        +
        + +
        +
            +
        +
        +
        +
        + + + + + +
        + + + + + +
         
        + + + + + +
        + + + + + +
            +
        +
        +
        +
        + Services  +
        +
        + + wview services status. Statistics are since the last wview start. + +
        +
        + +
         
            +
        +
        +
        +
        + Admin Password  +
        + + There is only one admin account. The password is stored as an MD5 hash, so there is no way to get a reminder. + +
        +
        +
            + Passwords did not match!
        "; ?> + New administrator password: +
        + +
            + Repeat password: + + +
         
        + +
        + + Logout + +      
        +
        + + diff --git a/wviewmgmt/wview-100x100.png b/wviewmgmt/wview-100x100.png new file mode 100755 index 0000000000000000000000000000000000000000..115c4d606f800595080e1b2f07217e9f5469fe98 GIT binary patch literal 16062 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 A00000 literal 0 HcmV?d00001 diff --git a/wviewmgmt/wview_control.php b/wviewmgmt/wview_control.php new file mode 100755 index 0000000..8ab4966 --- /dev/null +++ b/wviewmgmt/wview_control.php @@ -0,0 +1,28 @@ + /dev/null"; + system($systemstr); + } + else + { + if (! IswviewRunning()) + { + // Start wview: + $systemstr = "sudo /etc/init.d/wview start > /dev/null"; + system($systemstr); + sleep(1); + } + } + + header('Location: ' . 'system_status.php'); +?> diff --git a/wviewmgmt/wview_upgrade.php b/wviewmgmt/wview_upgrade.php new file mode 100755 index 0000000..748f845 --- /dev/null +++ b/wviewmgmt/wview_upgrade.php @@ -0,0 +1,36 @@ + /dev/null"; + system($systemstr); + while (IswviewRunning() || IswviewIndicated()) + { + sleep(1); + } + } + + $systemstr = "sudo" . $pidstr . "> /dev/null"; + system($systemstr); + $latest_version = file_get_contents('http://www.wviewweather.com/wview-latest.txt'); + $current_version = wviewVersionGet(); + while ($current_version != $latest_version) + { + sleep(1); + $current_version = wviewVersionGet(); + } + + header('Location: ' . 'system_status.php'); + } +?>

Current Weather +Conditions for ,