All Downloads are FREE. Search and download functionalities are using the official Maven repository.

lrgs.archive.MsgValidator Maven / Gradle / Ivy

Go to download

A collection of software for aggregatting and processing environmental data such as from NOAA GOES satellites.

The newest version!
/**
 * $Id$
 */
package lrgs.archive;

import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import java.util.LinkedList;
import java.util.Date;
import java.util.Iterator;
import java.util.TimeZone;

import decodes.util.ChannelMap;
import decodes.util.Pdt;
import decodes.util.PdtEntry;
import ilex.util.IDateFormat;
import ilex.util.Logger;
import lrgs.common.DcpAddress;
import lrgs.common.DcpMsgFlag;
import lrgs.common.DcpMsg;
import lrgs.drgsrecv.DrgsRecv;
import lrgs.lrgsmain.LrgsInputInterface;


/**
 * Validate Messages.
 * This class encapsulates message validation functions. It is used
 * by the DRGS interface and the DCP Monitor.
 */
public class MsgValidator
{
	private MsgValidatee caller;
	private Pdt pdt;
	private ChannelMap channelMap;
	
	public static final int SEC_PER_DAY = 86400;
	public static final double msecPerBit100 = 1000./100.;
	public static final double msecPerBit300 = 1000./300.;
	public static final double msecPerBit1200 = 1000./1200.;
	private SimpleDateFormat domsatDateFmt;
	private SimpleDateFormat errmsgDateFmt;
	private NumberFormat lenFormat;
	private CheckMissingThread checkMissingThread = null;
	private XmitWindow lastXmitWindow = null;
	
	private boolean _extraChecks = false;
	
	private long maxCarrierMS = 1500L;
	
	/** Anything < this will generate a warning */
	private int minSignalStrength = 35;
	
	/** Maximum allowable frequency offset (either + or -) in 50*Hz. */
	private int maxFreqOffset = 2;
	
	/** Minimum allowable battery voltage */
	private double minBattVolt = 11.0;
	
	/**
	An array of these objects is used to keep track of expected messages.
	*/
	class ExpectedMsg
	{
		/** Message expected by this (second-of-day) time */
		int endSod; 

		/** The PDT Schedule entry with dcp addr, channel, etc. */
		PdtEntry pdtEntry;
		
		ExpectedMsg(int endSod, PdtEntry pdtEntry)
		{
			this.endSod = endSod;
			this.pdtEntry = pdtEntry;
		}
	}

	/** An array of messages expected in the next minute. */
	private LinkedList expectedMsgList = new LinkedList();

	
	public MsgValidator(MsgValidatee caller, Pdt pdt, ChannelMap channelMap)
	{
		setCaller(caller);
		this.pdt = pdt;
		this.channelMap = channelMap;
		domsatDateFmt = new SimpleDateFormat("yyDDDHHmmss");
		domsatDateFmt.setTimeZone(TimeZone.getTimeZone("UTC"));
		errmsgDateFmt = new SimpleDateFormat("DDD/yyyy-HH:mm:ss");
		errmsgDateFmt.setTimeZone(TimeZone.getTimeZone("UTC"));
		lenFormat = NumberFormat.getIntegerInstance();
		lenFormat.setMinimumIntegerDigits(5);
		lenFormat.setGroupingUsed(false);
	}
	
	public synchronized void setCaller(MsgValidatee caller)
	{
		this.caller = caller;
	}
	
	/**
	 * Validate a message, issuing callbacks for any problems found.
	 * @param msg The message to validate
	 * @param origAddr From a DRGS this is original address, null for other apps.
	 * @param localRecvTime message local receive-time
	 */
	public synchronized void validateMsg(DcpMsg msg, 
		LrgsInputInterface src, Date localRecvTime)
	{
		if (!pdt.isLoaded())
			return;
		
		lastXmitWindow = null;
		DcpAddress dcpAddress = msg.getDcpAddress();
		PdtEntry pdtEntry = pdt.find(dcpAddress);
		if (pdtEntry == null)
		{
			caller.useValidationResults('I', 
				"Invalid DCP Address " + dcpAddress.toString(), 
				msg, src, localRecvTime, null);
			return;
		}
			
		int chan = msg.getGoesChannel();
		int expChan = channelMap.isRandom(chan) ? 
			pdtEntry.rd_channel : pdtEntry.st_channel;
		if (chan != expChan)
		{
			caller.useValidationResults('W',
				"Wrong channel -- expected " + expChan, msg, src, 
				localRecvTime, pdtEntry);
		}

		boolean isRandom = channelMap.isRandom(chan);
		
		// Check this entry off of the expected list, if it's there.
		if (!isRandom)
			for(Iterator expit = expectedMsgList.iterator();
				expit.hasNext(); )
			{
				ExpectedMsg expected = expit.next();
				if (expected.pdtEntry.dcpAddress.equals(dcpAddress))
				{
					expit.remove();
					break;
				}
			}

		if ((msg.flagbits & DcpMsgFlag.ADDR_CORRECTED) != 0 
		 && msg.getOrigAddress() != null)
			caller.useValidationResults('A', 
				"Address corrected, original=" + msg.getOrigAddress(), 
				msg, src, localRecvTime, pdtEntry);

		int xi = pdtEntry.st_xmit_interval;
		if (!isRandom && xi != 0)
		{
			// Compute expected start & end time for this message.
			Date xmitTime = msg.getXmitTime();
			Date cstart = msg.getCarrierStart();
			if (cstart != null)
				xmitTime = cstart;
			long xmitmsec = xmitTime.getTime();
			long xmit_timet = xmitmsec/1000L;
			long base_timet = (xmit_timet/SEC_PER_DAY - 1) * SEC_PER_DAY;
			long windows_since_base =
				((xmit_timet - base_timet - pdtEntry.st_first_xmit_sod) + xi/2)
				/ xi;
			long expected_start_tt = base_timet + pdtEntry.st_first_xmit_sod
				+ windows_since_base * xi;
			long expected_end_tt = expected_start_tt + pdtEntry.st_xmit_window;
			lastXmitWindow = new XmitWindow(pdtEntry.st_first_xmit_sod,
				pdtEntry.st_xmit_window, pdtEntry.st_xmit_interval, 
				(int)(expected_start_tt % SEC_PER_DAY));
			msg.setXmitWindow(lastXmitWindow);

			// Determine msg end time to nearest msec.
			Date cstop = msg.getCarrierStop();
			long end_msec;
			if (cstop != null)
				end_msec = cstop.getTime();
			else
			{
				int baud = msg.getBaud();
				double bitlen = msg.getData().length * 8;
				long durmsec = (long)(bitlen * 
					(baud == 100 ? msecPerBit100 :
					 baud == 1200 ? msecPerBit1200: msecPerBit300));
				end_msec = xmitmsec + durmsec;
			}
			
			if (xmitmsec < expected_start_tt*1000L) // Early
			{
				if (end_msec < expected_start_tt*1000L) // U=way early
				{
					String errmsg = 
						"Very Early: nearest window for ("
						+ (cstart==null?"DAPS":"CARRIER") + ") " 
						+ errmsgDateFmt.format(xmitTime)
						+ " is " 
						+ IDateFormat.printSecondOfDay(expected_start_tt, true)
						+ "-" 
						+ IDateFormat.printSecondOfDay(expected_end_tt, true);
					caller.useValidationResults('U', errmsg,
						msg, src, localRecvTime, pdtEntry);
				}
				else
				{
					caller.useValidationResults('T',
						"Early: Expected window is " 
						+ IDateFormat.printSecondOfDay(expected_start_tt, true) 
						+ "-" 
						+ IDateFormat.printSecondOfDay(expected_end_tt,	true),
						msg, src, localRecvTime, pdtEntry);
				}
			}
			else if (xmitmsec <= expected_end_tt*1000L)
			{
				if (end_msec > expected_end_tt*1000L) // Ended late
				{
					caller.useValidationResults('T',
						"Late: Expected window is " 
						+ IDateFormat.printSecondOfDay(expected_start_tt, true) 
						+ "-" 
						+ IDateFormat.printSecondOfDay(expected_end_tt, true),
						msg, src, localRecvTime, pdtEntry);
				}
			}
			else // start was after expected end.
			{
				String errmsg = 
					"Very Late: nearest window for ("
					+ (cstart==null?"DAPS":"CARRIER") + ") "
					+ errmsgDateFmt.format(xmitTime)
					+ " is " 
					+ IDateFormat.printSecondOfDay(expected_start_tt, true)
					+ "-" 
					+ IDateFormat.printSecondOfDay(expected_end_tt, true);
				caller.useValidationResults('U', errmsg,
					msg, src, localRecvTime, pdtEntry);
			}
	 	}
		if (!_extraChecks)
			return;
		
		Date xmitTime = msg.getXmitTime();
		Date cstart = msg.getCarrierStart();
		if (xmitTime != null && cstart != null)
		{
			long carrierMsec = xmitTime.getTime() - cstart.getTime();
			if (carrierMsec > maxCarrierMS)
				caller.useValidationResults('C', 
					"Excessive carrier: " + carrierMsec + " ms."
					+ ", xmitTime=" + xmitTime + ", carrierStart=" + cstart,
					msg, src, localRecvTime, pdtEntry);
		}
		
		int ss = msg.getSignalStrength();
		if (ss < minSignalStrength)
			caller.useValidationResults('S', 
				"Low signal strength: " + ss + " dB.",
				msg, src, localRecvTime, pdtEntry);
	
		int fo = msg.getFrequencyOffset();
		if (fo > maxFreqOffset || fo < -maxFreqOffset)
			caller.useValidationResults('F', 
				"Excessive Frequency Offset: " + fo + " ("
				+ (fo*50) + " Hz.)",
				msg, src, localRecvTime, pdtEntry);
		
		char mi = msg.getModulationIndex();
		if (mi != 'N')
			caller.useValidationResults('X', 
				"Bad modulation index code: " + mi,
				msg, src, localRecvTime, pdtEntry);
		
		double bv = msg.getBattVolt();
		if (bv < -.1 || (bv > .1 && bv < minBattVolt))
			caller.useValidationResults('V', 
				"Low battery voltage: " + bv,
				msg, src, localRecvTime, pdtEntry);
	}
	
	

	/**
	 * Add to the expected array all messages expected by the minute that
	 * ends with the specified time.
	 * @param sod second-of-day of the minute-end 
	 */
	public synchronized void findExpectedBy(int sod)
	{
		if (sod == 0)
			sod = (3600*24); 	//  for midnight use 86400.

		synchronized(pdt)
		{
			for(PdtEntry pe : pdt.getEntries())
			{
				int xi = pe.st_xmit_interval;
				int stChan = pe.st_channel;

				if (stChan <= 0 || xi <= 0)
					continue;
				if (pe.active_flag == 'N')
					continue;

				int offset = pe.st_xmit_window + pe.st_first_xmit_sod;
				int mnum = (sod - offset) / xi;
				int expectBy = offset + mnum * xi;
				if (expectBy > sod-60 && expectBy <= sod)
					expectedMsgList.add(new ExpectedMsg(expectBy, pe));
			}
		}
	}

	/**
	 * Generate MISSING status messages for anything in the expected-array
	 * that was expected by the specified second-of-day.
	 * Finally the expected array is cleared.
	 * @param sod second of day (midnight = 86400)
	 * @param daynum since the epoch.
	 */
	public synchronized void genMissingFor(int sod, int daynum)
	{
		if (!pdt.isLoaded())
			return;
		
		for(Iterator expit = expectedMsgList.iterator();
			expit.hasNext(); )
		{
			ExpectedMsg expected = expit.next();
			Date msgTime = new Date(
				(daynum * SEC_PER_DAY + expected.endSod)*1000L);
			if (expected.endSod < sod)
			{
				String body = "Missing message from "
					+ expected.pdtEntry.dcpAddress.toString()
					+ " expected by "
					+ IDateFormat.printSecondOfDay(expected.endSod, true);
				String msgData = expected.pdtEntry.dcpAddress.toString() 
					+ formatDomsatDate(msgTime)
					+ "M00+0NN"
					+ fmtChan(expected.pdtEntry.st_channel)
					+ "--"
					+ formatLength(body.length())
					+ body;
				byte[] md = msgData.getBytes();
				DcpMsg msg = new DcpMsg(md, md.length, 0);
				msg.setBaud(expected.pdtEntry.baud);
				
				caller.useValidationResults('M', body, msg, 
					(LrgsInputInterface)null, msgTime, expected.pdtEntry);
				expit.remove();
			}
		}
	}

	public void startCheckMissingThread()
	{
		checkMissingThread = new CheckMissingThread(this);
		checkMissingThread.start();
	}
	
	/**
	 * @return String in the format cccS, where ccc is 3-digit channel number
	 * and S is the spacecraft designator.
	 */
	public String fmtChan(int chan)
	{
		byte b[] = new byte[4];
		b[0] = (byte)((int)'0' + chan / 100);
		chan %= 100;
		b[1] = (byte)((int)'0' + chan / 10);
		b[2] = (byte)((int)'0' + chan % 10);
		b[3] = (chan%2 == 1 ? (byte)'E' : (byte)'W');
		return new String(b);
	}
	
	public String formatLength(int len)
	{
		synchronized(lenFormat)
		{
			return lenFormat.format(len);
		}
	}

	public String formatDomsatDate(Date d)
	{
		synchronized(domsatDateFmt)
		{
			return domsatDateFmt.format(d);
		}
	}

	/**
	 * Call before application exit to release resources & stop the missing-
	 * message check thread.
	 */
	public synchronized void shutdown()
	{
		if (checkMissingThread != null)
			checkMissingThread.shutdown = true;
		expectedMsgList.clear();
	}

	/**
     * @return the lastXmitWindow
     */
    public XmitWindow getLastXmitWindow()
    {
	    return lastXmitWindow;
    }

    /**
     * Call with true to have validator perform the additional checks for
     * C=excessive carrier, V=low battery, S=low signal strength,
     * F=excessive Frequency Offset, X=bad modulation index.
     * @param tf
     */
    public void setDoExtraChecks(boolean tf) { _extraChecks = tf; }

	/**
     * @param maxCarrierMS Maximum allowable carrier in msec
     */
    public void setMaxCarrierMS(long maxCarrierMS)
    {
	    this.maxCarrierMS = maxCarrierMS;
    }

	/**
     * @return Maximum allowable carrier in msec
     */
    public long getMaxCarrierMS()
    {
	    return maxCarrierMS;
    }

	/**
     * @param minSignalStrength Minimum allowable signal strength in dB
     */
    public void setMinSignalStrength(int minSignalStrength)
    {
	    this.minSignalStrength = minSignalStrength;
    }

	/**
     * @return Minimum allowable signal strength in dB
     */
    public int getMinSignalStrength()
    {
	    return minSignalStrength;
    }

	/**
     * @param maxFreqOffset maximum allowable freq offset in units of 50Hz
     */
    public void setMaxFreqOffset(int maxFreqOffset)
    {
	    this.maxFreqOffset = maxFreqOffset;
    }

	/**
     * @return maximum allowable freq offset in units of 50Hz
     */
    public int getMaxFreqOffset()
    {
	    return maxFreqOffset;
    }

	/**
     * @param minBattVolt the minBattVolt to set
     */
    public void setMinBattVolt(double minBattVolt)
    {
	    this.minBattVolt = minBattVolt;
    }

	/**
     * @return the minBattVolt
     */
    public double getMinBattVolt()
    {
	    return minBattVolt;
    }
}


class CheckMissingThread
	extends Thread
{
	MsgValidator validator;
	boolean shutdown = false;
	public CheckMissingThread(MsgValidator validator)
	{
		this.validator = validator;
	}
	public void run()
	{
		int lastMin = -1;
		long lastPoll = System.currentTimeMillis();
		while(!shutdown)
		{
			long now = System.currentTimeMillis();

			/*
			  At each new minute, generate MISSING stat messages for
			  anything expected by one minute ago. Then add expectations
			  for one minute from now.
			*/
			int min = (int)((now/60000L) % (60*24));
			if (min != lastMin)
			{
				lastMin = min;

				int daynum = (int)(now / DrgsRecv.MS_PER_DAY);
				int prevMin = min - 1;
				if (prevMin <= 0) 
				{
					prevMin += (60*24);
					daynum--;
				}
				validator.genMissingFor(prevMin*60, daynum);

				int nextMin = (min+1) % (60*24);
				validator.findExpectedBy(nextMin*60);
			}
			try { sleep(1000L); }
			catch(InterruptedException ex) {}
		}
	}


}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy