{"id":104,"date":"2010-01-10T13:48:13","date_gmt":"2010-01-10T13:48:13","guid":{"rendered":"http:\/\/blog.stratus.org.uk\/?page_id=104"},"modified":"2011-06-26T11:41:23","modified_gmt":"2011-06-26T10:41:23","slug":"php-ultimeter-800-weather-station-cwop","status":"publish","type":"page","link":"https:\/\/blog.stratus.org.uk\/?page_id=104","title":{"rendered":"PHP: Ultimeter 800 Weather Station (CWOP)"},"content":{"rendered":"<h2>Overview<\/h2>\n<p><a href=\"http:\/\/www.findu.com\/citizenweather\/signup.html\"><img loading=\"lazy\" decoding=\"async\" class=\"alignright\" title=\"Citizen Weather Observer Program (CWOP)\" src=\"http:\/\/wxqa.com\/archive\/cwp_logo.gif\" alt=\"Citizen Weather Observer Program (CWOP)\" width=\"170\" height=\"170\" \/><\/a>This article discusses how to build a weather station that uses a Peet Brothers\u2019 <em><strong>Ultimeter 800 <\/strong><\/em>Weather Station and enable it to send to the Citizen Weather Observation Programme (CWOP).<\/p>\n<h2>Why Do This?<\/h2>\n<p>The reasons for doing this are as follows:<\/p>\n<ol>\n<li>The unit only has a serial output and is not \u201cInternet\u201d ready<\/li>\n<li>Most systems rely on using a PC and this is not very ecological in terms of power usage<\/li>\n<li>There are almost no working scripts published on the Internet for Ultimeter units<\/li>\n<\/ol>\n<p>With this in mind, it is hoped this article will give you a script you can use to allow your Ultimeter unit to send data to CWOP.<\/p>\n<h2>Physical Architecture<\/h2>\n<p>The physical architecture of how this works is shown below.<\/p>\n<div style=\"width: 510px\" class=\"wp-caption aligncenter\"><img loading=\"lazy\" decoding=\"async\" title=\"Ultimeter 800 and NSLU2 Unit\" src=\"http:\/\/static.flickr.com\/3391\/3426373046_d08528b20f.jpg\" alt=\"Ultimeter 800 and NSLU2 Unit\" width=\"500\" height=\"212\" \/><p class=\"wp-caption-text\">Ultimeter 800 and NSLU2 Unit<\/p><\/div>\n<p>To keep this artical shorter, the subject of linux-enabling the NSLU2 unit won\u2019t be discussed (this unit runs <a href=\"http:\/\/www.nslu2-linux.org\/wiki\/SlugOS\/InstallandTurnupABasicSlugOSSystem\" target=\"_blank\">SLUGOS<\/a> and all the instructions for doing this are covered <a href=\"http:\/\/www.nslu2-linux.org\/wiki\/SlugOS\/InstallandTurnupABasicSlugOSSystem\" target=\"_blank\">elsewhere<\/a>).<\/p>\n<p>The following list the items shown:<\/p>\n<ul>\n<li>NSLU2 Unit (Lower Left)<\/li>\n<li>USB hub adaptor (Upper Left)<\/li>\n<li>Serial to USB adaptor (Mid centre)<\/li>\n<li>Temperature sensor (coiled up Lower Centre)<\/li>\n<li>Ultimeter \u201chub\u201d (Right of tempertature sensor)<\/li>\n<li>Ultimeter 800 unit (Lower Right)<\/li>\n<li>CAT5 cable (Yellow coiled cable)<\/li>\n<\/ul>\n<h2>System Pre-requisities<\/h2>\n<h3>General<\/h3>\n<p>You will need to get some things in place fisrt before you can connect your weather station to the NSLU2.<\/p>\n<ul>\n<li>USB to Serial Adaptor<\/li>\n<li>USB stick for extra memory (see the NSLU2 build, but put simply there is not enough core memory to run the linux build on the unit)<\/li>\n<li>A fully working NSLU2 with SlugOs and using the external USB flash\/pen drive<\/li>\n<li>A working Peet Unit (i.e. it can be powered up and see its peripherals)<\/li>\n<\/ul>\n<h3>Notes To The Above<\/h3>\n<p>While there is a USB hub shown, it is not mandatory as there are two USB ports on the NSLU2. One will connect to the USB to serial adaptor, the other to the extra file system space that linux build requires. It was found that if there was a USB hub, other units could be connected at the same time (e.g. a WS-2350 weather station).<\/p>\n<p>Before adding any scripts, make sure you have a working NSLU2 with linux, with <em>PHP<\/em> and correctly working <em>cron <\/em>daemon. How to do this is all covered on the SlugOS web site.<\/p>\n<p>With a working NSLU2 and linux, make sure you can see <em>\/dev\/ttyS0<\/em> (AKA com1 on a PC). Use <em>miniterm<\/em> from the command line to determine that the Ultimeter unit is sending data regularly in \u201c<em>Complete Record Mode<\/em>\u201d (every few seconds). Once you can see it in \u201c<em>Complete Record Mode<\/em>\u201c, reset it back to \u201c<em>Modem Mode<\/em>\u201d (data on demand). How to do this is described in the handbook for the unit or on the link at the bottom of this article.<\/p>\n<p>Assuming the pre-requisites are in place, we can look at scripts and software.<\/p>\n<h2>Software Architecture<\/h2>\n<p>The software\/script elements are based on the use of <em>PHP script<\/em>, a <em>shell script <\/em>and a <em>cron <\/em>job. The <em>cron job <\/em>runs the <em>shell script <\/em>every ten minutes which in turn runs the <em>PHP script<\/em>.<\/p>\n<h3>Cron Job<\/h3>\n<p>The cron job to run the shell script is very simple and is as follows:<\/p>\n<pre style=\"padding-left: 30px;\">* 0 * * *\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/usr\/bin\/ntpdate -s -u pool.ntp.org\r\n0,10,20,30,40,50 * * * * \/opt\/bin\/run_u800.sh cwop<\/pre>\n<p>We need to ensure that the NSLU2 unit is time synchronised, hence the use of <em>ntpdate<\/em> once per day (midnight). We also send the data to the Ultimeter unit (because it seems to be fairly inaccurate), so again to keep things right, the unit must have the right time.<\/p>\n<h3>Shell Script<\/h3>\n<p>The shell script is used for a few reasons, but importantly to not litter the crontab table. The other reasons for using a shell script are:<\/p>\n<ul>\n<li>To allow some pre- and post-amble processing (which we need to do)<\/li>\n<li>To apply and system-type checks (e.g. permissions and error checking)<\/li>\n<\/ul>\n<p style=\"padding-left: 30px;\"><strong>Main <\/strong>run_u800.sh <strong>script<\/strong><\/p>\n<pre style=\"padding-left: 30px;\">#!\/bin\/sh\r\n\r\n# set the serial ports to the correct rate (just in case)\r\n\/opt\/bin\/setserial.sh\r\n\r\n# Carry out specific functions when asked to by the system\r\ncase \"$1\" in\r\n cwop)\r\n echo \"Running ultimeter 800 to cwop\"\r\n \/opt\/bin\/php \/opt\/bin\/wx-silent.php cwop >> \/opt\/share\/www\/wx.log\r\n ;;\r\n local)\r\n echo \"Running ultimeter 800 to 10 minute local file\"\r\n #Beep so we know it is now running\r\n beep -f 1300 -l 30\r\n \/opt\/bin\/php \/opt\/bin\/wx-silent.php >> \/opt\/share\/www\/wx-10.log\r\n beep -f 1300 -l 90 -d 30\r\n beep -f 1300 -l 30 -d 30\r\n beep -f 1300 -l 90 -d 30\r\n ;;\r\n *)\r\n echo \"Running ultimeter 800 locally with APRS data\"\r\n \/opt\/bin\/php \/opt\/bin\/wx-silent.php >> \/opt\/share\/www\/wx.log\r\n ;;\r\nesac<\/pre>\n<p>The above script initially calls a second script to set the\u00a0 serial port before anything else, which is discussed below. The above script then checks whether an extra parameter \u201ccwop\u201d or \u201clocal\u201d is passed. The reason for this check is that it allows testing without send live data to the Internet.<\/p>\n<p>If the script does have \u201ccwop\u201d as a parameter, it then firstly outputs to the standard output, then calls the php script \/opt\/bin\/wx-silent.php before sending the script output to \/opt\/share\/www\/wx.log<\/p>\n<p>If the script does have \u201clocal\u201d as a parameter, it firstly sends a message to the standard output. Then it sounds a beep at 1.3kHz for 30 msec. It then calls the php script, outputs to the standard output, then calls the php script \/opt\/bin\/wx-silent.php before sending the script output to \/opt\/share\/www\/wx-10.log. After that it calls three beeps in the sound of a morse code \u201cK\u201d.<\/p>\n<p>If no parameter is passed then same script is called a bit like before to a local file only \u2013 you get the idea. Make sure you set the UNIX file permissions to 755 (and for the serial script below) or <em>cron <\/em>will have trouble.<\/p>\n<p>The above script also calls setserial.sh, which is as follows:<\/p>\n<pre style=\"padding-left: 30px;\">#!\/bin\/sh\r\nstty -F \/dev\/ttyUSB0 ispeed 2400\r\nstty -F \/dev\/ttyUSB0 ospeed 2400\r\nstty -F \/dev\/ttyUSB0\r\n<strong>\r\n<\/strong><\/pre>\n<p>The sets the serial device to 2400 baud. The reason for this is to make sure that each time the weather station is communicated to, it is not at any other serial baud rate.<\/p>\n<p>Ok, lets take a look at the PHP script that the shell script calls.<\/p>\n<h3>PHP Script<\/h3>\n<p style=\"padding-left: 30px;\">The entire PHP script is shown below (we\u2019ll decompose it at the bottom). But if you just cut and paste it in, make sure you:<\/p>\n<ol>\n<li>Put the CWOP ID in or your callsign if you are a radio ham callsign in the \u201cFormatAPRSData ()\u201d function<\/li>\n<li>Put a correct latitude and longitude in the\u00a0\u201cFormatAPRSData ()\u201d function<\/li>\n<\/ol>\n<p style=\"padding-left: 30px;\">Or the data sent to \u00a0CWOP will be thrown away.<\/p>\n<pre style=\"padding-left: 30px;\"><?php\r\n\r\n\/* -----------------------------------------------------------------------\r\n\r\nSee: http:\/\/www.peetbros.com\/shop\/custom.aspx?recid=7\r\n... for a full definition of the record output as it's too long to include here\r\nGENERAL\r\n\r\n * 2400 baud, 8 data bits, 1 stop bit, no parity.\r\n * ASCII hex digits (0 - F).\r\n * All fields are 4 bytes except 2 bytes where indicated\r\n in the complete record mode (that's only fields 111,112,113,114).\r\n So just chop up the record up into 110 4 byte fields and you're fine.\r\n * Most significant digit first.\r\n\r\nNOTES:\r\n1. IGNORE THE FIRST TWO DIGITS OF WIND DIRECTION. THESE DIGITS ARE\r\nNORMALLY 00, BUT IF A DIRECTION CALIBRATION NUMBER HAS BEEN ENTERED,\r\nTHEY MAY BE FF.\r\n2. RAIN GAUGES THAT MEASURE IN INCREMENTS OF 0.1 MM ARE FULLY SUPPORTED\r\nON THE ULTIMETER LIQUID CRYSTAL DISPLAY. ANY PROGRAM OR DEVICE USING THE\r\nSERIAL DATA OUTPUT MUST UNDERSTAND THAT THE DATA IS IN 0.1 MM INCREMENTS.\r\nSERIAL OUTPUT FOR ALL OTHER RAIN GAUGE SELECTIONS IS REPORTED IN INCREMENTS\r\nOF 0.01 INCHES.\r\n\r\n ----------------------------------------------------------------------\r\n\/\/ Don't forget to make sure your Unix serial line\r\n\/\/\u00a0 has been set using \"stty\" to the right speed and so on for example:\r\n\r\nstty -F \/dev\/ttyUSB0 ispeed 2400\r\nstty -F \/dev\/ttyUSB0 ospeed 2400\r\nstty -F \/dev\/ttyUSB0\r\n\r\n... if you've a USB0 device, but it could be any serial line on your device.\r\n ----------------------------------------------------------------------- *\/\r\n\r\n\/\/ The array of data is as follows\r\n$gWxData = array(1 => \"\" );\r\n\r\n\/\/ Don't forget to make sure your Unix serial line\r\n\/\/\u00a0 has been set using \"stty\" to the right speed and so on\r\n\r\n$handle = fopen(\"\/dev\/ttyUSB0\", \"r+\");\r\n\r\nif ($handle) {\r\n \/\/ The U800 used for development had a very poor clock ... losing\r\n \/\/ 10-15 mins per day. Hence it needs setting on each read to keep in step\r\n\r\n \/\/ If you want to send the current time use:\r\n \/\/ $command = \">A\" follwed by\r\n \/\/\u00a0\u00a0\u00a0 4 bytes for day of year in dec and 4 bytes time in minutes today in de\r\nc\r\n \/\/\u00a0\u00a0\u00a0 e.g. March 28th at 0922z is \">A00560562\"\r\n $tm = localtime(); \/\/ this assumes we're in GMT - amend accordingly\r\n $tm_h = gmdate(\"H\");\r\n $tm_m = gmdate(\"i\");\r\n $dt = sprintf( \"%04d%04d\",$tm[7], ($tm_h*60)+$tm_m ); \/\/ days since Jan 1st\r\nand then minutes since 00:00\r\n $command = \">A$dt\\n\";\r\n fwrite($handle,$command);\r\n\r\n \/\/assumes were in ultimeter modem mode\r\n \/\/ send a get command to the ultimeter\r\n $command = \">H\\n\";\r\n fwrite($handle,$command);\r\n\r\n \/\/now get any results\r\n $data = stream_get_line ($handle, 452, \"\\n\");\r\n fclose($handle);\r\n\r\n \/\/ lets split this out ...\r\n SplitDataOut ($data);\r\n\r\n \/\/ Now as an APRS string\r\n $APRS = FormatAPRSData();\r\n echo \"\\n\".$APRS;\r\n\r\n \/\/ Send APRS data to outside world\r\n if ($argc == 2){\r\n $option = $argv[1];\r\n if ($option == \"cwop\"){\r\n echo \"\\n\\nSending to CWOP\\n\";\r\n SendAPRSData( $APRS );\r\n }\r\n }\r\n}\r\n\r\n\/\/----------------------------------------------------------------------\r\nfunction OutputData( )\r\n{\r\n global $gWxData;\r\n echo \"\\n\";\r\n print_r($gWxData);\r\n}\r\n\r\n\/\/----------------------------------------------------------------------\r\nfunction SplitDataOut ($inputdata )\r\n{\r\n global $gWxData;\r\n \/\/ lets split this out ... Note we ignore the &CR&t the start\r\n $iPtr = 4;\r\n for ($i = 1; $i <= 110; $i++) {\r\n $gWxData[$i] = substr( $inputdata, $iPtr, 4 );\r\n $iPtr = $iPtr+4;\r\n }\r\n for ($i = 111; $i <= 114; $i++) {\r\n $gWxData[$i] = substr( $inputdata, $iPtr, 2 );\r\n $iPtr = $iPtr+2;\r\n }\r\n $gWxData[115] = substr( $inputdata, $iPtr, 40 );\r\n\r\n}\r\n\/\/----------------------------------------------------------------------\r\n\r\n\/\/ This function will telenet to APRS-land and send a report.\r\n\r\n\/* The following applies:\r\n\r\nOnce you have formed this record, open a connection to port 14580\r\non 'cwop.aprs.net' and then send the following:\r\n\r\nuser CW0003 pass -1 vers linux-1wire 1.00\r\n\r\n(substitute your CW number), followed by CR,LF (i.e. two characters),\r\nfollowed by your data record. Then disconnect. Also, it works best\r\nwith a three second delay between the USER command and the data packet.\r\nAlso wait three seconds after sending the data to close the connection.\r\n\r\n*\/\r\nfunction SendAPRSData( $data )\r\n{\r\n    global $gWxData;\r\n\r\n    $fp = fsockopen(\"cwop.aprs.net\", 14580, $errno, $errstr, 30);\r\n    if (!$fp) {\r\n       echo \"$errstr ($errno)\\n\";\r\n    } else {\r\n       $out = \"user CW0003 pass -1 vers linux-1wire 1.00\\r\\n\";\r\n       fwrite($fp, $out);\r\n       sleep(3);\r\n       $out = \"$data\\r\\n`\";\r\n       \/\/ echo \"\\n\".$data;\r\n       fwrite($fp, $out);\r\n       sleep(3);\r\n       fclose($fp);\r\n    }\r\n}\r\n\/\/----------------------------------------------------------------------\r\n\r\n\/* This function will build a string up from the data collected\r\n Note that there are rules on the format of this here:\r\n\r\n http:\/\/pond1.gladstonefamily.net:8080\/aprswxnet.html\r\n\r\n An example of a good one is:\r\n\r\n CW0003>APRS,TCPXX*:\/241505z4220.45N\/07128.59W_032\/005g008t054r001p078P048h50b10245e1w\r\n\r\n Which is built up as follows:\r\n\r\nField\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Meaning\r\n----------------------- -----------------------\r\nCW0003\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Your CW number\r\n>APRS,TCPXX*:\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Boilerplate\r\n\/241505z\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 The ddhhmm in UTC of the time that you generate the report. However, the timestamp is pretty much ignored by everybody as it is assumed that your clock is not set correctly! If you want to omit this field, then just send an exclamation mark '!' instead.\r\n4220.45N\/07128.59W\u00a0\u00a0\u00a0\u00a0\u00a0 Your location. This is ddmm.hh -- i.e. degrees, minutes and hundreths of minutes. The Longitude has three digits of degrees and leading zero digits cannot be omitted.\r\n_032\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 The direction of the wind from true north (in degrees).\r\n\/005\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 The average windspeed in mph\r\ng008\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 The maximum gust windspeed in mph (over the last five minutes)\r\nt054\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 The temperature in degrees Farenheit -- if not available, then use '...' Temperatures below zero are expressed as -01 to -99.\r\nr001\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 The rain in the last 1 hour (in hundreths of an inch) -- this can be omitted\r\np078\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Rain in the last 24 hours (in hundreths of an inch) -- this can be omitted\r\nP044\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 The rain since the local midnight (in hundreths of an inch) -- this can be omitted\r\nh50\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 The humidity in percent. '00' => 100%. -- this can be omitted.\r\nb10245\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 The barometric pressure in tenths of millbars -- this can be omitted. This is a corrected pressure and not the actual (station) pressure as measured at your weatherstation. The pressure is adjusted according to altimeter rules -- i.e. the adjustment is purely based on station elevation and does not include temperature compensation.\r\ne1w\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 The equipment you are using. This is the string that I use to identify my software. Please use something different. In particular, please include the version number of your software and the type of hardware sensors attached. For example: eMyWx123DVP -- MyWx version 1.2.3 with Davis Vantage Pro hardware. Being explicit here allows other software to automatically determine the type of station software in use. It can also be used to encourage people to upgrade to the current version! Weather Display has a table of codes that cover most weather station types and it would be helpful to use the same codes.\r\n\r\nNote that most fields are fixed width. This constraint means that use\r\nof C-like formatting strings such as 'h%02d' is not really appropriate\r\nas they do not deal with out of range values. In this particular case,\r\nit does not deal with the special case of 100% humidity either!\r\n\r\nThe letters are all case sensitive. The fields should be sent in the\r\norder that they appear in the table above.\r\n\r\n*\/\r\nfunction FormatAPRSData ()\r\n{\r\n global $gWxData;\r\n\r\n $APRSdata =\"\";\r\n\r\n $APRSdata = \"<strong>NOCALL<\/strong>\"; \/\/ CWOP ID or callsign -> change this to yours!!!!\r\n $APRSdata = $APRSdata . \">APRS,TCPXX*:\"; \/\/ network type\r\n $time = DateFromWxStn();\r\n $APRSdata = $APRSdata . \"@\".$time; \/\/ time of report\r\n $APRSdata = $APRSdata . \"<strong>0000.00N\/00000.00E<\/strong>\"; \/\/\u00a0 lat long -> change this to yours!!!!\r\n $APRSdata = $APRSdata . \"_\".Wind5MinAveDirectionFromWxStn();\r\n $APRSdata = $APRSdata . \"\/\".WindCurrentSpeedFromWxStn();\r\n $APRSdata = $APRSdata . \"g\".Wind5MinPeakSpeedFromWxStn();\r\n $APRSdata = $APRSdata . \"t\".CurrentOutTempFromWxStn();\r\n $APRSdata = $APRSdata . \"r...\"; \/\/ The U800 doesn't have last rain in hour\r\n $APRSdata = $APRSdata . \"p...\"; \/\/ The U800 doesn't have last rain in 24\r\n $APRSdata = $APRSdata . \"P...\"; \/\/\/ Add this if you have a rain sensor -> .RainSinceMidnightInches();\r\n $APRSdata = $APRSdata . \"h\".CurrentOutHumidityFromWxStn();\r\n $APRSdata = $APRSdata . \"b\".CurrentOutsidePressure();\r\n $APRSdata = $APRSdata . \"ehomebrew\"; \r\n\r\n return $APRSdata;\r\n}\r\n\/\/----------------------------------------------------------------------\r\nfunction DateFromWxStn()\r\n{\r\n \/\/ The U800 puts the date (day of year) in field 16\r\n \/\/\u00a0 and the time in field 17 (minute of day)\r\n \/\/\u00a0 And for this application we don't report the year anyway\r\n \/\/\u00a0 we only need the day of the month\r\n\r\n global $gWxData;\r\n\r\n $day = hexdec($gWxData[16]) + 1; \/\/ Day zero is Jan 1st!\r\n $time = hexdec($gWxData[17]);\r\n\r\n \/\/ ok this will depend on your locale as to where your day is\r\n $date = jdtojulian($day);\r\n $day_no = explode(\"\/\",$date); \/\/ split the bits out\r\n\r\n \/\/ now the time\r\n $hours = explode(\".\",($time\/60) );\u00a0\u00a0 \/\/ we want the bit before the decimal\r\n $minute = sprintf(\"%02s\",bcmod($time,\"60\"));\r\n\r\n $result = sprintf(\"%02d\",$day_no[1]).gmdate('Hi').\"z\";\r\n return $result;\r\n}\r\n\/\/----------------------------------------------------------------------\r\nfunction WindCurrentSpeedFromWxStn()\r\n{\r\n \/\/ The U800 puts the current wind speed in field 1\r\n\r\n global $gWxData;\r\n\r\n $wind = round(hexdec($gWxData[1])*0.1); \/\/Note this is in steps of 0.1\r\n\r\n $result = sprintf(\"%03s\", $wind );\r\n\r\n return $result;\r\n}\r\n\/\/----------------------------------------------------------------------\r\nfunction Wind5MinAveDirectionFromWxStn()\r\n{\r\n \/\/ The U800 puts the average wind in field 4 for last 5 mins\r\n\r\n global $gWxData;\r\n\r\n $wind = hexdec($gWxData[4]); \/\/ This is 0 to 255 and we need in degrees\r\n\r\n $result = sprintf(\"%03s\",round( (360\/255) * $wind ));\r\n\r\n return $result;\r\n}\r\n\/\/----------------------------------------------------------------------\r\nfunction CurrentOutTempFromWxStn()\r\n{\r\n \/\/ The U800 puts the outdoor temperature in field 6 in F x 10\r\n\r\n global $gWxData;\r\n\r\n $temp = hexdec($gWxData[6]);\r\n\r\n if ( $temp == 0 ) {\r\n $result=\"...\";\r\n } else {\r\n $result = sprintf(\"%03s\", round($temp\/10) );\r\n }\r\n\r\n return $result;\r\n}\r\n\/\/----------------------------------------------------------------------\r\nfunction Wind5MinPeakSpeedFromWxStn()\r\n{\r\n \/\/ The U800 puts the ave 5 min wind speed in field 3\r\n\r\n global $gWxData;\r\n\r\n $wind = round(hexdec($gWxData[3]) * 0.1); \/\/note this is in lumps of 0.1\r\n\r\n $result = sprintf(\"%03s\", $wind );\r\n\r\n return $result;\r\n}\r\n\/\/----------------------------------------------------------------------\r\nfunction RainSinceMidnightInches()\r\n{\r\n \/\/ The U800 puts the rain today in field 7\r\n\r\n global $gWxData;\r\n\r\n $rain = hexdec($gWxData[7]);\r\n\r\n $result = sprintf(\"%03s\", $rain );\r\n\r\n return $result;\r\n}\r\n\/\/----------------------------------------------------------------------\r\nfunction CurrentOutHumidityFromWxStn()\r\n{\r\n \/\/ The U800 puts the outdoor humidity\u00a0 in field 13\r\n\r\n global $gWxData;\r\n\r\n if (strcmp($gWxData[13], \"----\") ==0) { \/\/ then there's no data\r\n return \"..\";\r\n }\r\n $hum = hexdec($gWxData[13]);\r\n\r\n if ($hum == 100) {\r\n $hum = 0; \/\/ this is because APRS can only do two digits\r\n echo \"\\nIs 100% - setting to zero\";\r\n }\r\n $result = sprintf(\"%02s\", $hum );\r\n\r\n return $result;\r\n}\r\n\/\/----------------------------------------------------------------------\r\nfunction CurrentOutsidePressure()\r\n{\r\n \/\/ The U800 puts the rain today in field 8\r\n\r\n global $gWxData;\r\n\r\n if (strcmp($gWxData[8], \"----\") ==0) { \/\/ then there's no data\r\n return \".....\";\r\n }\r\n $pressure = hexdec($gWxData[8]);\r\n\r\n $result = sprintf(\"%05s\", $pressure );\r\n\r\n return $result;\r\n}\r\n\/\/----------------------------------------------------------------------\r\n?><\/pre>\n<p>Right lets take a look at the script.<\/p>\n<ul>\n<li>The first few lines are a big comment explaining a bit about the data received from the weather unit and also reminding to set the serial port first.<\/li>\n<li>After that we set a global called $gWxData. We use this to send the information to CWOP \u2013 more on that function later.<\/li>\n<li>Then we open a file (handle) for the serial port in us. Select the right one you use here if it isn\u2019t \u201c\/dev\/ttyS0\u201d. If the file open fails, we simply exit the PHP script \u2013 you may want to add something here to alert you.<\/li>\n<li>The comments in the script tell you what\u2019s going on, but the first job is to build the date\/time based on what the NSLU2 has and then send it to the weather unit. As long as you have set the unit properly, the time should be sent to the weather unit. If you have problems here, use miniterm and try setting the unit manually from the command line.<\/li>\n<li>Ok, having set the time, we now go into \u201cmodem mode\u201d and then ask the unit to send us all the data back.<\/li>\n<li>Providing all has gone well, we get all the data back with the PHP \u201c$data = stream_get_line ($handle, 452, \u201c\\n\u201d);\u201d<\/li>\n<li>That grabs 452 bytes or unless a \u201c\\n\u201d is read in. That should be all the data we need.<\/li>\n<li>We then close the file handle to the weather unit so we can then process it and finally send it to CWOP.<\/li>\n<li>The first thing after getting the data from the weather station is to carve it up into useful bits. This is done in \u201cSplitDataOut ($data);\u201d<\/li>\n<li>Providing we have split it up ok (and we assume it\u2019s worked for now as there is no error checking), we build an APRS string (discussed in a bit), and then send it to the standard output.<\/li>\n<li>We then check to see if we had a parameter passed to the PHP script. If a string was passed and was \u201ccwop\u201d, then we will try to send the weather data to the Internet. If not, then we\u2019ll do nothing \u2013 obviously you can add something here if you want to send it somewhere else.<\/li>\n<li>If we were passed \u201ccwop\u201d, send a message to the standard output followed by a call to the function \u201cSendAPRSData( $APRS );\u201d This last function we\u2019ll also cover in a bit.<\/li>\n<li>Now providing at all worked, by now, the weather station data should be with CWOP on the Internet.<\/li>\n<\/ul>\n<h3>Overview of Functions<\/h3>\n<p>The following describe a few of the important functions.<\/p>\n<h4 style=\"padding-left: 30px;\">function SendAPRSData( $data )<\/h4>\n<p style=\"padding-left: 30px;\">This is the key PHP function that sends the formatted data to CWOP. Very simply, it opens a socket to the cwop main server, logs in (and make sure your ID is in upper case \u2013 took ages to work that one out), writes the data we\u2019ve built up in $gWxData, and then closes the socket, after waiting for three seconds (for the transmission to settle down).<\/p>\n<h4 style=\"padding-left: 30px;\">function SplitDataOut ($inputdata )<\/h4>\n<p style=\"padding-left: 30px;\">This function splits the large input string into 114 pieces into a global array which we use elsewhere.<\/p>\n<h4 style=\"padding-left: 30px;\">function FormatAPRSData ()<\/h4>\n<p style=\"padding-left: 30px;\">This function builds a string that it returns to the caller based on the APRS format rules. The function gives an overview as a comment at the start so you get an idea what it should look like. Use the embedded URL if you want to know more.<\/p>\n<h4 style=\"padding-left: 30px;\">function DateFromWxStn()<\/h4>\n<p style=\"padding-left: 30px;\">This function builds an APRS date based on the APRS format rules.<\/p>\n<h4 style=\"padding-left: 30px;\">APRS Data Formatting Functions<\/h4>\n<p style=\"padding-left: 30px;\">The following functions build a single data item and return it. If you know a bit of C or PHP, you can work it out. These are all used in FormatAPRSData().<\/p>\n<ul style=\"padding-left: 30px;\">\n<li>WindCurrentSpeedFromWxStn()<\/li>\n<li>Wind5MinAveDirectionFromWxStn()<\/li>\n<li>CurrentOutTempFromWxStn()<\/li>\n<li>Wind5MinPeakSpeedFromWxStn()<\/li>\n<li>RainSinceMidnightInches()<\/li>\n<li>CurrentOutHumidityFromWxStn() \u2013 Note this does a decimal \/ hexadecimal conversion (using hexdec() )<\/li>\n<li>CurrentOutsidePressure()<\/li>\n<\/ul>\n<p style=\"padding-left: 30px;\">There is a lot of data still available from the weather station that isn\u2019t processed. If you want all that, use some of the above functions as a guide and go from there. Note that for APRS purposes, the ones you need have already been done.<\/p>\n<p style=\"padding-left: 30px;\">And that\u2019s it. The script should just run as shown above, but note there isn\u2019t a lot of error handling as the script has been found to be reliable month after month without change.<\/p>\n<h2>Suggestions for Generic Linux Usage<\/h2>\n<p>It is possible to use the above on any linux system that has <em>PHP<\/em>, a <em>cron <\/em>daemon and both a serial port to the weather station and online access to the Internet. Try the script out and see how you go (but make sure you set the CWOP ID and the latitude\/longitude).<\/p>\n<h2>Links and Useful Pointers<\/h2>\n<p>The following gives locations of useful infromation, used during the development of this application.<\/p>\n<ul style=\"padding-left: 30px;\">\n<li><a title=\"CWOP Programme\" href=\"http:\/\/www.wxqa.com\/\" target=\"_blank\">CWOP Programme<\/a><\/li>\n<li><a href=\"http:\/\/home.comcast.net\/~dshelms\/cwop.html\" target=\"_blank\">Other CWOP softwares for weather stations<\/a><\/li>\n<li><a href=\"http:\/\/www.peetbros.com\/shop\/custom.aspx?recid=7\" target=\"_blank\">Peet Brothers Ultimeter Resources<\/a> \u2013 A copy of which is here:\u00a0<a href=\"http:\/\/blog.stratus.org.uk\/wp-content\/uploads\/2010\/01\/ULTIMETER-MODELS-2100-800-100-SERIAL-DATA-SPEC.txt\">ULTIMETER MODELS 2100 800 100 SERIAL DATA SPEC<\/a><\/li>\n<li><a href=\"http:\/\/www.linksysbycisco.com\/US\/en\/products\/NSLU2\" target=\"_blank\">Linksys NSLU2<\/a> and the <a href=\"http:\/\/www.nslu2-linux.org\/\" target=\"_blank\">Linux Build <\/a>for it<\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>Overview This article discusses how to build a weather station that uses a Peet Brothers\u2019 Ultimeter 800 Weather Station and enable it to send to the Citizen Weather Observation Programme (CWOP). Why Do This? The reasons for doing this are &hellip; <a href=\"https:\/\/blog.stratus.org.uk\/?page_id=104\">Continue reading <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"parent":84,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-104","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/blog.stratus.org.uk\/index.php?rest_route=\/wp\/v2\/pages\/104","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.stratus.org.uk\/index.php?rest_route=\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/blog.stratus.org.uk\/index.php?rest_route=\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/blog.stratus.org.uk\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.stratus.org.uk\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=104"}],"version-history":[{"count":40,"href":"https:\/\/blog.stratus.org.uk\/index.php?rest_route=\/wp\/v2\/pages\/104\/revisions"}],"predecessor-version":[{"id":423,"href":"https:\/\/blog.stratus.org.uk\/index.php?rest_route=\/wp\/v2\/pages\/104\/revisions\/423"}],"up":[{"embeddable":true,"href":"https:\/\/blog.stratus.org.uk\/index.php?rest_route=\/wp\/v2\/pages\/84"}],"wp:attachment":[{"href":"https:\/\/blog.stratus.org.uk\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=104"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}