diff --git a/pom.xml b/pom.xml index 6803f45..b5e1631 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ weather 2008 - 2.7.3 + 2.9.3 @@ -35,13 +35,13 @@ net.liftweb - lift-util - 1.0 + lift-util_2.9.1 + 2.5.1 net.liftweb - lift-webkit - 1.0 + lift-webkit_2.9.1 + 2.5.1 javax.servlet @@ -69,9 +69,9 @@ test - de.huxhorn.lilith - de.huxhorn.lilith.3rdparty.rrd4j - 2.0.5 + org.rrd4j + rrd4j + 2.2 @@ -123,6 +123,11 @@ + + org.apache.maven.plugins + maven-war-plugin + 2.1.1 + diff --git a/src/main/scala/uk/org/floop/msc/comet/WeatherActor.scala b/src/main/scala/uk/org/floop/msc/comet/WeatherActor.scala index 4ac86dc..fe3b3be 100644 --- a/src/main/scala/uk/org/floop/msc/comet/WeatherActor.scala +++ b/src/main/scala/uk/org/floop/msc/comet/WeatherActor.scala @@ -1,13 +1,12 @@ package uk.org.floop.msc.comet import scala.collection.mutable.HashMap - import net.liftweb.http._ import net.liftweb.http.js.JsCmds._ import net.liftweb.util._ - import uk.org.floop.msc.rrd._ import uk.org.floop.msc.wview.Forecast +import net.liftweb.common.Full class WeatherActor extends CometActor { diff --git a/src/main/scala/uk/org/floop/msc/rest/Dump.scala b/src/main/scala/uk/org/floop/msc/rest/Dump.scala index 50e5fe3..1923576 100644 --- a/src/main/scala/uk/org/floop/msc/rest/Dump.scala +++ b/src/main/scala/uk/org/floop/msc/rest/Dump.scala @@ -2,8 +2,9 @@ import net.liftweb.http._ import net.liftweb.util._ - import uk.org.floop.msc.rrd._ +import net.liftweb.common.Box +import net.liftweb.common.Empty object Dump { diff --git a/src/main/scala/uk/org/floop/msc/rest/Graph.scala b/src/main/scala/uk/org/floop/msc/rest/Graph.scala index e4261a3..8566cd4 100644 --- a/src/main/scala/uk/org/floop/msc/rest/Graph.scala +++ b/src/main/scala/uk/org/floop/msc/rest/Graph.scala @@ -2,9 +2,10 @@ import net.liftweb.http._ import net.liftweb.util._ - - import uk.org.floop.msc.rrd._ +import net.liftweb.common.Box +import net.liftweb.common.Full +import net.liftweb.common.Empty object Graph { diff --git a/src/main/scala/uk/org/floop/msc/rrd/DataStore.scala b/src/main/scala/uk/org/floop/msc/rrd/DataStore.scala index eaf485d..3ec545c 100644 --- a/src/main/scala/uk/org/floop/msc/rrd/DataStore.scala +++ b/src/main/scala/uk/org/floop/msc/rrd/DataStore.scala @@ -3,7 +3,6 @@ import scala.actors.Actor import scala.actors.Actor._ import scala.collection.mutable.ListBuffer - import java.io.{File, IOException, ByteArrayOutputStream, InputStream} import java.util.{Date, Calendar} import java.awt.Color @@ -11,13 +10,14 @@ import javax.imageio.stream.MemoryCacheImageOutputStream import javax.imageio.ImageIO import _root_.org.xml.sax.InputSource - import _root_.org.rrd4j.core.{RrdDef, RrdDb} import _root_.org.rrd4j.{DsType, ConsolFun} import _root_.org.rrd4j.graph.{RrdGraphDef, RrdGraph, RrdGraphDefTemplate} - import net.liftweb.http.LiftRules -import net.liftweb.util.{Full, Empty, Box} +import net.liftweb.common.Full +import net.liftweb.common.Empty +import net.liftweb.http.CometActor + object TimePeriod extends Enumeration { val HOUR, DAY, WEEK, MONTH, YEAR = Value @@ -37,8 +37,8 @@ case class StorePacket(p: List[Pair[String, Any]]) case class GenerateGraph(template: String, p: TimePeriod.Value) case class ImageGenerated(bytes: Array[Byte]) -case class AddWeatherListener(l: Actor) -case class RemoveWeatherListener(l: Actor) +case class AddWeatherListener(l: CometActor) +case class RemoveWeatherListener(l: CometActor) case class CurrentWeather(w: List[Pair[String, Any]]) case class DumpXml() @@ -90,7 +90,7 @@ } var currentWeather: List[Pair[String, Any]] = Nil - val listeners = new ListBuffer[Actor] + val listeners = new ListBuffer[CometActor] def act() { loop { @@ -130,30 +130,18 @@ } updateListeners case GenerateGraph(t, p) => - val gd = LiftRules.getResourceAsStream(t) match { - case Full(inputStream) => - try { - val templ = new RrdGraphDefTemplate(new InputSource(inputStream)) - templ.setVariable("rrdFile", STORE.getPath) - Some(templ.getRrdGraphDef()) - } catch { - case ex: IOException => - println(ex) - None - case ex: IllegalArgumentException => - println(ex) - None - } - case x => - println(x) - None + val gd = LiftRules.doWithResource(t) { inputStream => + val templ = new RrdGraphDefTemplate(new InputSource(inputStream)) + templ.setVariable("rrdFile", STORE.getPath) + templ.getRrdGraphDef() } gd match { - case Some(gd) => + case Full(gd) => gd.setTimeSpan(TimePeriod.last(p)) gd.setFilename("-") // in memory only gd.setImageFormat("PNG") gd.setAntiAliasing(true) + gd.setTextAntiAliasing(true) reply( try { val g = new RrdGraph(gd) @@ -169,7 +157,7 @@ None } ) - case None => + case Empty => reply(None) } } diff --git a/src/main/scala/uk/org/floop/msc/wview/DataCollector.scala b/src/main/scala/uk/org/floop/msc/wview/DataCollector.scala index 0f8ec9a..ca297a3 100644 --- a/src/main/scala/uk/org/floop/msc/wview/DataCollector.scala +++ b/src/main/scala/uk/org/floop/msc/wview/DataCollector.scala @@ -7,9 +7,9 @@ import net.liftweb.http.LiftRules import java.net.{InetSocketAddress, SocketTimeoutException, Socket} -import java.io.{IOException, DataInputStream} +import java.io.{IOException, DataInputStream, InputStream} import java.nio.channels.{SocketChannel, ClosedByInterruptException} -import java.nio.ByteBuffer +import java.nio.{ByteBuffer, ByteOrder} import java.util.Date import uk.org.floop.msc.rrd._ @@ -18,6 +18,7 @@ def act() { var holdoff = 1000 + val buffer = new Array[Byte](LoopPacket.SIZE) //LiftRules.unloadHooks.append{() => continue} while(true) { var sock: Socket = null @@ -29,28 +30,48 @@ sock = new Socket("10.79.0.6", 11011) sock.setSoTimeout(30000) - var dis = new DataInputStream(sock.getInputStream) + var is = sock.getInputStream while (true) { while (startFramePos < 8) { - if (dis.readByte() == LoopPacket.START_FRAME(startFramePos)) { + val res = is.read + if (res == -1) + throw new IOException("Unexpected end of stream.") + else if (res.toByte == LoopPacket.START_FRAME(startFramePos)) { startFramePos = startFramePos + 1 } else { startFramePos = 0 } } - packetPos = 0 - while (packetPos < LoopPacket.fields.length) { - LoopPacket.fields(packetPos) match { - case (VALUE_TYPE.FLOAT, field) => values += (field, dis.readFloat()) - case (VALUE_TYPE.USHORT, field) => values += (field, dis.readShort()) - case (VALUE_TYPE.TIME_T, field) => values += (field, dis.readInt() match { + var bytesRead = 0 + while (bytesRead < LoopPacket.SIZE) { + val res = is.read(buffer, bytesRead, buffer.length - bytesRead) + if (res == -1) + throw new IOException("Unexpected end of stream.") + bytesRead = bytesRead + res + } + val bb = ByteBuffer.wrap(buffer) + bb.order(ByteOrder.BIG_ENDIAN) + for ((valueType, field) <- LoopPacket.fields) { + valueType match { + case VALUE_TYPE.FLOAT => values. += ((field, { + bb.order(ByteOrder.LITTLE_ENDIAN) + val l = bb.getFloat.toLong + bb.order(ByteOrder.BIG_ENDIAN) + val f = ((l >> 16) & 0x7fff) + ((l & 0xffff) / 63365.0f) + if (((l >> 31) & 0x1) == 0x1) { + -f + } else { + f + } + })) + case VALUE_TYPE.USHORT => values += ((field, bb.getShort)) + case VALUE_TYPE.TIME_T => values += ((field, bb.getInt() match { case 0 => None case x => Some(new Date(x.toLong * 1000)) - }) - case (VALUE_TYPE.SHORT, field) => values += (field, dis.readShort()) - case (VALUE_TYPE.UCHAR, field) => values += (field, dis.readByte()) + })) + case VALUE_TYPE.SHORT => values += ((field, bb.getShort())) + case VALUE_TYPE.UCHAR => values += ((field, bb.get())) } - packetPos = packetPos + 1 } startFramePos = 0 DataStore ! StorePacket(values.toList) diff --git a/src/main/scala/uk/org/floop/msc/wview/Forecast.scala b/src/main/scala/uk/org/floop/msc/wview/Forecast.scala index cfa09f2..1aed516 100644 --- a/src/main/scala/uk/org/floop/msc/wview/Forecast.scala +++ b/src/main/scala/uk/org/floop/msc/wview/Forecast.scala @@ -1,11 +1,10 @@ package uk.org.floop.msc.wview import java.io.{BufferedReader, InputStreamReader} - import net.liftweb.http.LiftRules -import net.liftweb.util.{Full, Empty} - import scala.collection.mutable.ArrayBuffer +import net.liftweb.common.Full +import net.liftweb.common.Empty object Forecast { @@ -13,20 +12,18 @@ private val rules = new ArrayBuffer[String]() private def initialize() { - val confReader = LiftRules.getResourceAsStream("/forecast.conf") match { - case Full(inputStream) => - new BufferedReader(new InputStreamReader(inputStream)) - case Empty => null - } - var line = confReader.readLine() - var readingRules = false - while (line != null) { - if (readingRules) { - rules += line - } else if (line == "") { - readingRules = true + LiftRules.doWithResource("/forecast.conf") { inputStream => + val confReader = new BufferedReader(new InputStreamReader(inputStream)) + var line = confReader.readLine() + var readingRules = false + while (line != null) { + if (readingRules) { + rules += line + } else if (line == "") { + readingRules = true + } + line = confReader.readLine() } - line = confReader.readLine() } } @@ -34,7 +31,11 @@ if (rules.length == 0) { initialize() } - rules(i) + if (rules.length > i) { + rules(i) + } else { + "Unknown" + } } } diff --git a/src/main/scala/uk/org/floop/msc/wview/LoopPacket.scala b/src/main/scala/uk/org/floop/msc/wview/LoopPacket.scala index 0721302..c7c6eab 100644 --- a/src/main/scala/uk/org/floop/msc/wview/LoopPacket.scala +++ b/src/main/scala/uk/org/floop/msc/wview/LoopPacket.scala @@ -28,6 +28,7 @@ (VALUE_TYPE.FLOAT, "sampleRain"), // inches (VALUE_TYPE.FLOAT, "sampleET"), // ET (VALUE_TYPE.USHORT, "radiation"), // watts/m^3 + (VALUE_TYPE.USHORT, "padding1"), // ! 2 bytes of padding (VALUE_TYPE.FLOAT, "UV"), // UV index * 10 (VALUE_TYPE.FLOAT, "dewpoint"), // degrees F (VALUE_TYPE.FLOAT, "windchill"), // degrees F @@ -43,7 +44,7 @@ (VALUE_TYPE.FLOAT, "monthET"), // inches (VALUE_TYPE.FLOAT, "yearET"), // inches (VALUE_TYPE.FLOAT, "intervalAvgWCHILL"), // degrees F - (VALUE_TYPE.SHORT, "intervalAvgWSPEED"), // mph + (VALUE_TYPE.USHORT, "intervalAvgWSPEED"), // mph (VALUE_TYPE.USHORT, "yearRainMonth"), // 1-12 Rain Start Month // --- The following may or may not be supported for a given station --- @@ -55,21 +56,22 @@ (VALUE_TYPE.USHORT, "forecastRule"), // VP only (VALUE_TYPE.USHORT, "txBatteryStatus"), // VP only (VALUE_TYPE.USHORT, "consBatteryVoltage"), // VP only - (VALUE_TYPE.SHORT, "extraTemp1"), // degrees F + 90 - (VALUE_TYPE.SHORT, "extraTemp2"), // degrees F + 90 - (VALUE_TYPE.SHORT, "extraTemp3"), // degrees F + 90 - (VALUE_TYPE.SHORT, "soilTemp1"), // degrees F + 90 - (VALUE_TYPE.SHORT, "soilTemp2"), // degrees F + 90 - (VALUE_TYPE.SHORT, "soilTemp3"), // degrees F + 90 - (VALUE_TYPE.SHORT, "soilTemp4"), // degrees F + 90 - (VALUE_TYPE.SHORT, "leafTemp1"), // degrees F + 90 - (VALUE_TYPE.SHORT, "leafTemp2"), // degrees F + 90 + (VALUE_TYPE.FLOAT, "extraTemp1"), // degrees F + 90 + (VALUE_TYPE.FLOAT, "extraTemp2"), // degrees F + 90 + (VALUE_TYPE.FLOAT, "extraTemp3"), // degrees F + 90 + (VALUE_TYPE.FLOAT, "soilTemp1"), // degrees F + 90 + (VALUE_TYPE.FLOAT, "soilTemp2"), // degrees F + 90 + (VALUE_TYPE.FLOAT, "soilTemp3"), // degrees F + 90 + (VALUE_TYPE.FLOAT, "soilTemp4"), // degrees F + 90 + (VALUE_TYPE.FLOAT, "leafTemp1"), // degrees F + 90 + (VALUE_TYPE.FLOAT, "leafTemp2"), // degrees F + 90 (VALUE_TYPE.UCHAR, "extraHumid1"), // percent (VALUE_TYPE.UCHAR, "extraHumid2"), // percent (VALUE_TYPE.UCHAR, "soilMoist1"), (VALUE_TYPE.UCHAR, "soilMoist2"), (VALUE_TYPE.UCHAR, "leafWet1"), (VALUE_TYPE.UCHAR, "leafWet2"), + (VALUE_TYPE.USHORT, "padding2"), // !two bytes of padding // Vaisala WXT-510 (VALUE_TYPE.FLOAT, "wxt510Hail"), // inches @@ -85,8 +87,8 @@ (VALUE_TYPE.FLOAT, "wxt510Rain"), // inches // WMR918/968 - (VALUE_TYPE.UCHAR, "wmr918Humid3"), // percent (VALUE_TYPE.FLOAT, "wmr918Pool"), // degrees F + (VALUE_TYPE.UCHAR, "wmr918Humid3"), // percent (VALUE_TYPE.UCHAR, "wmr918Tendency"), // WMR's Forecast (VALUE_TYPE.UCHAR, "wmr918WindBatteryStatus"), (VALUE_TYPE.UCHAR, "wmr918RainBatteryStatus"), @@ -95,10 +97,69 @@ (VALUE_TYPE.UCHAR, "wmr918poolTempBatteryStatus"), (VALUE_TYPE.UCHAR, "wmr918extra1BatteryStatus"), (VALUE_TYPE.UCHAR, "wmr918extra2BatteryStatus"), - (VALUE_TYPE.UCHAR, "wmr918extra3BatteryStatus") - + (VALUE_TYPE.UCHAR, "wmr918extra3BatteryStatus"), + (VALUE_TYPE.USHORT, "padding3"), // ! 2 bytes of padding + + // Generic extra sensor and status support: + (VALUE_TYPE.FLOAT, "extraTemp0"), + (VALUE_TYPE.FLOAT, "extraTemp1"), + (VALUE_TYPE.FLOAT, "extraTemp2"), + (VALUE_TYPE.FLOAT, "extraTemp3"), + (VALUE_TYPE.FLOAT, "extraTemp4"), + (VALUE_TYPE.FLOAT, "extraTemp5"), + (VALUE_TYPE.FLOAT, "extraTemp6"), + (VALUE_TYPE.FLOAT, "extraTemp7"), + (VALUE_TYPE.FLOAT, "extraTemp8"), + (VALUE_TYPE.FLOAT, "extraTemp9"), + (VALUE_TYPE.FLOAT, "extraTempA"), + (VALUE_TYPE.FLOAT, "extraTempB"), + (VALUE_TYPE.FLOAT, "extraTempC"), + (VALUE_TYPE.FLOAT, "extraTempD"), + (VALUE_TYPE.FLOAT, "extraTempE"), + (VALUE_TYPE.FLOAT, "extraTempF"), + (VALUE_TYPE.USHORT, "extraHumidity0"), + (VALUE_TYPE.USHORT, "extraHumidity1"), + (VALUE_TYPE.USHORT, "extraHumidity2"), + (VALUE_TYPE.USHORT, "extraHumidity3"), + (VALUE_TYPE.USHORT, "extraHumidity4"), + (VALUE_TYPE.USHORT, "extraHumidity5"), + (VALUE_TYPE.USHORT, "extraHumidity6"), + (VALUE_TYPE.USHORT, "extraHumidity7"), + (VALUE_TYPE.USHORT, "extraHumidity8"), + (VALUE_TYPE.USHORT, "extraHumidity9"), + (VALUE_TYPE.USHORT, "extraHumidityA"), + (VALUE_TYPE.USHORT, "extraHumidityB"), + (VALUE_TYPE.USHORT, "extraHumidityC"), + (VALUE_TYPE.USHORT, "extraHumidityD"), + (VALUE_TYPE.USHORT, "extraHumidityE"), + (VALUE_TYPE.USHORT, "extraHumidityF"), + (VALUE_TYPE.UCHAR, "windBatteryStatus"), + (VALUE_TYPE.UCHAR, "rainBatteryStatus"), + (VALUE_TYPE.UCHAR, "outTempBatteryStatus"), + (VALUE_TYPE.UCHAR, "consoleBatteryStatus"), + (VALUE_TYPE.UCHAR, "uvBatteryStatus"), + (VALUE_TYPE.UCHAR, "solarBatteryStatus"), + (VALUE_TYPE.USHORT, "padding4"), // ! 2 bytes of padding + (VALUE_TYPE.UCHAR, "extraTempBatteryStatus0"), + (VALUE_TYPE.UCHAR, "extraTempBatteryStatus1"), + (VALUE_TYPE.UCHAR, "extraTempBatteryStatus2"), + (VALUE_TYPE.UCHAR, "extraTempBatteryStatus3"), + (VALUE_TYPE.UCHAR, "extraTempBatteryStatus4"), + (VALUE_TYPE.UCHAR, "extraTempBatteryStatus5"), + (VALUE_TYPE.UCHAR, "extraTempBatteryStatus6"), + (VALUE_TYPE.UCHAR, "extraTempBatteryStatus7"), + (VALUE_TYPE.UCHAR, "extraTempBatteryStatus8"), + (VALUE_TYPE.UCHAR, "extraTempBatteryStatus9"), + (VALUE_TYPE.UCHAR, "extraTempBatteryStatusA"), + (VALUE_TYPE.UCHAR, "extraTempBatteryStatusB"), + (VALUE_TYPE.UCHAR, "extraTempBatteryStatusC"), + (VALUE_TYPE.UCHAR, "extraTempBatteryStatusD"), + (VALUE_TYPE.UCHAR, "extraTempBatteryStatusE"), + (VALUE_TYPE.UCHAR, "extraTempBatteryStatusF") ) - val START_FRAME = Array(0x88, 0xf3, 0xa2, 0xc6, 0xda, 0xda, 0xcf, 0xe7).map(_.toByte) + val START_FRAME = Array(0x88, 0xf3, 0xa2, 0xc6, 0xda, 0xda, 0x01, 0x00).map(_.toByte) + + val SIZE = LoopPacket.fields.map(pair => VALUE_TYPE.sizeof(pair._1)).foldLeft(0)(_ + _) }