diff --git a/res/layout/stationview.xml b/res/layout/stationview.xml index da66c8c..0103dfb 100644 --- a/res/layout/stationview.xml +++ b/res/layout/stationview.xml @@ -47,6 +47,15 @@ android:typeface="monospace" /> + None } + } + + def parseWx(comment : String) : String = { + import StationListAdapter._ + // Sample + // 000/000g000t056r000p000P000h75b10164L000eCumulusDsVP + //val comment = "000/000g000t056r000p000P000h75b10164L000eCumulusDsVP" + + // Handle PEET events + if(comment.matches(WX_PEET_RE.toString)) return parseWxPeet(comment) + + val WX_WIND_RE = ".*(\\d{3})/(\\d{3}).*".r + val WX_GUST_RE = ".*g(\\d{3}).*".r + val WX_SPEED_RE = ".*s(\\d{3}).*".r + val WX_DIR_RE = ".*c(\\d{3}).*".r + val WX_TEMP_RE = ".*t(\\d{3}).*".r + val WX_RAIN_HOUR_RE = ".*r(\\d{3}).*".r + val WX_RAIN_24_RE = ".*p(\\d{3}).*".r + val WX_RAIN_TODAY_RE = ".*P(\\d{3}).*".r + val WX_HUMID_RE = ".*h(\\d{2}).*".r + val WX_QNH_RE = ".*b(\\d{5}).*".r + val WX_LUM_LOW_RE = ".*L(\\d{3}).*".r + val WX_LUM_HIGH_RE = ".*l(\\d{4}).*".r + val WX_SNOW = ".*s(\\d{3}).*".r + + var windDir = parseWxElement(comment, WX_WIND_RE, 1) + var windSpeed = parseWxElement(comment, WX_WIND_RE, 2) + var windGust = parseWxElement(comment, WX_GUST_RE, 1) + var snow = parseWxElement(comment, WX_SNOW, 1) + if (windSpeed == None) { + // For some reason, snow and windspeed use the same prefix. If we didn't get wind using the + // XXX/XXX format, then it must be here as sXXX wind speed, + // therefore snow is not possible to get in this situation + snow = None + windSpeed = parseWxElement(comment, WX_SPEED_RE, 1) + } + if (windDir == None) windDir = parseWxElement(comment, WX_DIR_RE, 1) + var ambTemp = parseWxElement(comment, WX_TEMP_RE, 1) + var rainHour = parseWxElement(comment, WX_RAIN_HOUR_RE, 1) + var rainDay = parseWxElement(comment, WX_RAIN_24_RE, 1) + var rainToday = parseWxElement(comment, WX_RAIN_TODAY_RE, 1) + var humidity = parseWxElement(comment, WX_HUMID_RE, 1) + var qnh = parseWxElement(comment, WX_QNH_RE, 1) + var lumLow = parseWxElement(comment, WX_LUM_LOW_RE, 1) + var lumHigh = parseWxElement(comment, WX_LUM_HIGH_RE, 1) + + if(lumHigh != None) + lumHigh = Some(lumHigh.get + 1000.0) + + var lum = if(lumLow != None) lumLow else if(lumHigh != None) lumHigh else None + + // unit conversions + windSpeed = if (windSpeed != None) Some(kt2mps(windSpeed.get)) else None + windGust = if (windGust != None) Some(kt2mps(windGust.get)) else None + ambTemp = if (ambTemp != None) Some(f2c(ambTemp.get)) else None + rainHour = if(rainHour != None) Some(hi2mm(rainHour.get)) else None + rainDay = if(rainDay != None) Some(hi2mm(rainDay.get)) else None + rainToday = if(rainToday != None) Some(hi2mm(rainToday.get)) else None + snow = if(snow != None) Some(hi2mm(snow.get)) else None + var compWind = if (windDir != None) getBearing(windDir.get) else None + + val wxFinal = new scala.collection.mutable.ListBuffer[String] + if(ambTemp != None) wxFinal += "%1.1f°C".format(ambTemp.get) + if(humidity != None) wxFinal += "%1.0f%%".format(humidity.get) + val windBuf = new scala.collection.mutable.ListBuffer[String] + if(windSpeed != None) windBuf += "Wind %1.1fm/s".format(windSpeed.get) + if(windGust != None) windBuf += "Gust %1.1fm/s".format(windGust.get) + if(windDir != None) windBuf += "%s (%1.0f°)".format(compWind, windDir.get) + if(windBuf.length > 0) wxFinal += windBuf.toList.mkString(" ") + if(qnh != None) wxFinal += "%1.0fmbar".format(qnh.get) + val rainBuf = new scala.collection.mutable.ListBuffer[String] + if(rainHour != None) rainBuf += "%1.1f".format(rainHour.get) + if(rainDay != None) rainBuf += "%1.1f".format(rainHour.get) + if(rainToday != None) rainBuf += "%1.1f".format(rainHour.get) + if(rainBuf.length > 0) wxFinal += "Rain " + rainBuf.toList.mkString("/") + "mm" + if(snow != None) wxFinal += "Snow %1.1fmm".format(snow.get) + if(lum != None) wxFinal += "%1.0fW/m2".format(lum.get) + wxFinal.toList.mkString(" ") + } + def parseHostPort(hostport : String, defaultport : Int) : (String, Int) = { val splits = hostport.trim().split(":") try { diff --git a/src/StationListAdapter.scala b/src/StationListAdapter.scala index 72662a6..f74dfbd 100644 --- a/src/StationListAdapter.scala +++ b/src/StationListAdapter.scala @@ -12,12 +12,15 @@ import _root_.android.widget.FilterQueryProvider object StationListAdapter { import StorageDatabase.Station._ - val LIST_FROM = Array(CALL, COMMENT, QRG) - val LIST_TO = Array(R.id.station_call, R.id.listmessage, R.id.station_qrg) + val LIST_FROM = Array(CALL, COMMENT, QRG, WX) + val LIST_TO = Array(R.id.station_call, R.id.listmessage, R.id.station_qrg, R.id.extendedinfo) val SINGLE = 0 val NEIGHBORS = 1 val SSIDS = 2 + // return compass bearing for a given value + private val LETTERS = Array("N", "NE", "E", "SE", "S", "SW", "W", "NW") + def getBearing(b : Double) = LETTERS(((b.toInt + 22 + 720) % 360) / 45) } class StationListAdapter(context : Context, prefs : PrefsWrapper, @@ -52,10 +55,6 @@ class StationListAdapter(context : Context, prefs : PrefsWrapper, mix.reduceLeft(_*256 + _) } - // return compass bearing for a given value - private val LETTERS = Array("N", "NE", "E", "SE", "S", "SW", "W", "NW") - def getBearing(b : Double) = LETTERS(((b.toInt + 22 + 720) % 360) / 45) - override def bindView(view : View, context : Context, cursor : Cursor) { import StorageDatabase.Station._ @@ -67,9 +66,9 @@ class StationListAdapter(context : Context, prefs : PrefsWrapper, val lat = cursor.getInt(COLUMN_LAT) val lon = cursor.getInt(COLUMN_LON) val qrg = cursor.getString(COLUMN_QRG) + var wx = cursor.getString(COLUMN_WX) val symbol = cursor.getString(COLUMN_SYMBOL) val dist = Array[Float](0, 0) - if (call == mycall) { view.setBackgroundColor(0x4020ff20) } else if (call == targetcall) { @@ -85,8 +84,10 @@ class StationListAdapter(context : Context, prefs : PrefsWrapper, val MCD = 1000000. android.location.Location.distanceBetween(my_lat/MCD, my_lon/MCD, lat/MCD, lon/MCD, dist) - distage.setText("%1.1f km %s\n%s".format(dist(0)/1000., getBearing(dist(1)), age)) + distage.setText("%1.1f km %s\n%s".format(dist(0)/1000., StationListAdapter.getBearing(dist(1)), age)) view.findViewById(R.id.station_symbol).asInstanceOf[SymbolView].setSymbol(symbol) + val wx_visible = if(wx != null && wx != "") View.VISIBLE else View.GONE + view.findViewById(R.id.extendedinfo).setVisibility(wx_visible) super.bindView(view, context, cursor) } diff --git a/src/StorageDatabase.scala b/src/StorageDatabase.scala index 5624339..fab6351 100644 --- a/src/StorageDatabase.scala +++ b/src/StorageDatabase.scala @@ -14,7 +14,7 @@ import _root_.scala.math.{cos, Pi} object StorageDatabase { val TAG = "APRSdroid.Storage" - val DB_VERSION = 3 + val DB_VERSION = 4 val DB_NAME = "storage.db" val TSS_COL = "DATETIME(TS/1000, 'unixepoch', 'localtime') as TSS" @@ -57,30 +57,32 @@ object StorageDatabase { val ORIGIN = "origin" // originator call for object/item val QRG = "qrg" // voice frequency val FLAGS = "flags" // bitmask for attributes like "messaging capable" + val WX = "wx" // weather data lazy val TABLE_CREATE = """CREATE TABLE %s (%s INTEGER PRIMARY KEY AUTOINCREMENT, %s LONG, %s TEXT UNIQUE, %s INTEGER, %s INTEGER, %s INTEGER, %s INTEGER, %s INTEGER, - %s TEXT, %s TEXT, %s TEXT, %s TEXT, %s INTEGER)""" + %s TEXT, %s TEXT, %s TEXT, %s TEXT, %s INTEGER, %s TEXT)""" .format(TABLE, _ID, TS, CALL, LAT, LON, SPEED, COURSE, ALT, - SYMBOL, COMMENT, ORIGIN, QRG, FLAGS) + SYMBOL, COMMENT, ORIGIN, QRG, FLAGS, WX) lazy val TABLE_DROP = "DROP TABLE %s".format(TABLE) - lazy val COLUMNS = Array(_ID, TS, CALL, LAT, LON, SYMBOL, COMMENT, SPEED, COURSE, ALT, ORIGIN, QRG) + lazy val COLUMNS = Array(_ID, TS, CALL, LAT, LON, SPEED, COURSE, ALT, SYMBOL, COMMENT, ORIGIN, QRG, FLAGS, WX) lazy val COL_DIST = "((lat - %d)*(lat - %d) + (lon - %d)*(lon - %d)*%d/100) as dist" val COLUMN_TS = 1 val COLUMN_CALL = 2 val COLUMN_LAT = 3 val COLUMN_LON = 4 - val COLUMN_SYMBOL = 5 - val COLUMN_COMMENT = 6 - val COLUMN_SPEED = 7 - val COLUMN_COURSE = 8 - val COLUMN_ALT = 9 + val COLUMN_SPEED = 5 + val COLUMN_COURSE = 6 + val COLUMN_ALT = 7 + val COLUMN_SYMBOL = 8 + val COLUMN_COMMENT = 9 val COLUMN_ORIGIN = 10 val COLUMN_QRG = 11 val COLUMN_FLAGS = 12 + val COLUMN_WX = 13 lazy val COLUMNS_MAP = Array(_ID, CALL, LAT, LON, SYMBOL, ORIGIN) val COLUMN_MAP_CALL = 1 @@ -199,6 +201,9 @@ class StorageDatabase(context : Context) extends db.execSQL(Station.TABLE_CREATE) db.execSQL(Position.TABLE_CREATE) } + if(from <= 3 && to <= 4) { + db.execSQL("ALTER TABLE stations ADD COLUMN wx TEXT") + } } def trimPosts(ts : Long) = Benchmark("trimPosts") { @@ -225,6 +230,8 @@ class StorageDatabase(context : Context) extends val sym = "%s%s".format(pos.getSymbolTable(), pos.getSymbolCode()) val comment = ap.getAprsInformation().getComment() val qrg = AprsPacket.parseQrg(comment) + val wx = if(pos.getSymbolCode() == '_') AprsPacket.parseWx(comment) else if (pos.getSymbolCode() == '$') AprsPacket.parseWxPeet(comment) else "" + cv.put(TS, ts.asInstanceOf[java.lang.Long]) cv.put(CALL, if (objectname != null) objectname else call) cv.put(LAT, lat.asInstanceOf[java.lang.Integer]) @@ -237,13 +244,15 @@ class StorageDatabase(context : Context) extends cv.put(SYMBOL, sym) cv.put(COMMENT, comment) cv.put(QRG, qrg) + cv.put(WX, wx) + if (cse != null) { cv.put(SPEED, cse.getSpeed().asInstanceOf[java.lang.Integer]) cv.put(COURSE, cse.getCourse().asInstanceOf[java.lang.Integer]) } - Log.d(TAG, "got %s(%d, %d)%s -> %s".format(call, lat, lon, sym, comment)) + Log.d(TAG, "got %s(%d, %d)%s -> %s %s".format(call, lat, lon, sym, comment, wx)) // replace the full station info in stations table - getWritableDatabase().replaceOrThrow(TABLE, CALL, cv) + getWritableDatabase().replaceOrThrow(Station.TABLE, CALL, cv) } def isMessageDuplicate(call : String, msgid : String, text : String) : Boolean = {