/*
This file is part of JOP, the Java Optimized Processor
see <http://www.jopdesign.com/>
Copyright (C) 2005-2008, Martin Schoeberl (martin@jopdesign.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 3 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, see <http://www.gnu.org/licenses/>.
*/
/**
*
*/
package oebb;
import util.Dbg;
import util.Timer;
import ejip.Ejip;
import ejip.LinkLayer;
import ejip.Net;
import ejip.Packet;
import ejip.Udp;
/**
* Contains the state of the BG. Will substitute the class Status.
*
* @author martin
*
*/
public class State implements ejip.UdpHandler, Runnable {
// State of the system - exchanged periodically
int bgid;
int date;
int time;
int strnr;
int zugnr;
private volatile int pos; // set in Gps.checkStrMelnr()
int start;
int end;
int startNF;
int ankVerl;
volatile int type; // changed at anmelden, on Verschub
int alarmFlags;
int cmdAck;
int versionStrecke;
// set in Gps.process()
volatile int gpsLat;
volatile int gpsLong;
final static int TYPE_UNKNOWN = 0;
final static int TYPE_ZUG = 1; // fix in menu
final static int TYPE_NF = 2; // fix in menu
final static int TYPE_VERSCH = 3;
final static int TYPE_LERN = 4;
final static int TYPE_ES221 = 5;
final static int TYPE_UPDATE = 6;
final static int TYPE_INFO = 7;
final static int BG_SND_PORT = 2004;
final static int ZLB_RCV_PORT = 2005;
int destIp;
// Alarm and flags
public static final int ALARM_UEBERF = 1;
public static final int ALARM_RICHTUNG = 2;
public static final int ALARM_FAEHRT = 3;
public static final int ALARM_ES221 = 4; // flag not used anymore
public static final int FLAG_ANK = 5;
public static final int FLAG_VERL = 6;
public static final int FLAG_ZIEL = 7;
public static final int ALARM_MLR = 8;
public static final int FLAG_LERN = 9;
private final static int AFLAG_ANK = 1<<(FLAG_ANK-1);
private final static int AFLAG_VERL = 1<<(FLAG_VERL-1);
private final static int AFLAG_ZIEL = 1<<(FLAG_ZIEL-1);
private final static int AFLAG_LERN = 1<<(FLAG_LERN-1);
private final static int ALARM_MSK = (1<<(ALARM_UEBERF-1)) |
(1<<(ALARM_RICHTUNG-1)) | (1<<(ALARM_FAEHRT-1)) | (1<<(ALARM_ES221-1)) | (1<<(ALARM_MLR-1));
// Meldungen von der Zentrale zum BG
final static int CFLAG_ABM = 0x00000001; // Abmelden
final static int CFLAG_FWR = 0x00000002; // Fahrtwiderruf
final static int CFLAG_NOT = 0x00000004; // Nothalt
final static int CFLAG_ANMOK = 0x00000008; // Anmelden OK
final static int CFLAG_ZLB_INT= 0x00000010; // used only internally by ZLB
final static int CFLAG_IGNORE = 0x00000020; // ignore message
final static int CFLAG_DOWNLOAD = 0x00000040; // downloadind SW or Strecke
/**
* The alarm was ack by the TFZ. Set the flags to zero when
* FDL has also acked the alarm.
*/
private boolean alarmQuit;
// local states
/**
* Send the status all 5 seconds.
*/
final static int SEND_PERIOD = 5;
/**
* Send timer
*/
int sendTimer;
/**
* A recieved packet from the ZLB.
*/
Packet zlbMsg;
/**
* Did we have a contact with the ZLB?
*/
boolean contactZLB;
/**
* Timestamp of last ZLB message.
*/
int lastMsgTimestamp;
/**
* Date of last message.
*/
int lastMsgDate;
/**
* Get at least on message all 22 seconds.
*/
final static int ZLB_TIMEOUT = 22;
/**
* Watch ZLB timer
*/
int zlbTimer;
private LinkLayer ipLink;
/**
* FWR was acknowledged by TFZF
*/
boolean fwrQuitPending;
/**
* NOTHALT was acknowledged by TFZF
*/
boolean nothaltQuitPending;
// save some fields and use static
/**
* Ignore flag is set
*/
static boolean ignore;
/**
* Do the reset as we got back a message after ABM
*/
static boolean forceReset;
// reset not yet used - we have a reset function in Logic.
// /**
// * RESET timer
// */
// private static int rtim;
// /**
// * reset pending
// */
// private static boolean scheduleReset;
/**
* some network statistics
*/
private int[] stat;
// TODO: to many fields - we use statics here...
private static Ejip ejip;
private static Net net;
public State(Ejip ejipRef, Net netRef, LinkLayer link) {
ejip = ejipRef;
net = netRef;
ipLink = link;
// setTimestamp(2001, 1, 1, 0, 0, 0, 1);
sendTimer = Timer.getTimeoutSec(SEND_PERIOD);
stat = new int[5];
/*
* start = 1; end = 2; startNF = 3; ankVerl = 4; type = 5; alarmFlags =
* 2; cmdAck = 4; versionStrecke = 789; gpsLat = 6; gpsLong = 7;
*/
}
public void setUDPData(int[] arr, int off) {
arr[off] = bgid;
arr[off + 1] = date;
arr[off + 2] = time;
arr[off + 3] = (strnr << 20) + zugnr;
arr[off + 4] = (getPos() << 16) + start;
arr[off + 5] = (end << 16) + startNF;
int gpsFix = Gps.fix;
if (gpsFix == -1)
gpsFix = 0;
// TODO: BgPpp.getConnType();
arr[off + 6] = (ankVerl << 16) + (type << 13) + (gpsFix << 11);
arr[off + 7] = alarmFlags;
arr[off + 8] = cmdAck;
arr[off + 9] = (((Main.VER_MAJ << 8) + Main.VER_MIN) << 16)
+ versionStrecke;
arr[off + 10] = gpsLat;
arr[off + 11] = gpsLong;
}
static int getDate() {
int d = Gps.getDate();
return (((d%100) + 2000) << 16) + ((d/100%100) << 8) + d/10000%100;
}
static int getTime() {
int t = Gps.getTime();
return (t/10000000%100 << (32 - 6)) + (t/100000%100 << (32 - 12))
+ (t/1000%100 << (32 - 18)) + (t%1000 << 32 - 28);
}
static int cnt;
boolean send() {
// get an IP packet
Packet p = ejip.getFreePacket(ipLink);
if (p == null) { // got no free buffer!
Dbg.wr('!');
Dbg.wr('b');
return false;
}
date = getDate();
time = getTime();
setUDPData(p.buf, Udp.DATA);
p.len = (Udp.DATA + 12) << 2;
Dbg.wr("BG: ");
printMsg(p);
// and send it
net.getUdp().build(p, destIp, BG_SND_PORT);
return true;
}
/**
* Get one UDP status packet from the ZLB.
* Just store it zlbMsg for further handling in run().
*/
public void request(Packet p) {
System.out.println("ZLB pkt");
// just store the received package
synchronized(this) {
if (zlbMsg==null) {
zlbMsg = p;
} else {
// this should not happen
ejip.returnPacket(p);
}
}
}
/**
* Handle a message received from the ZLB.
* zlbMsg point to the received package
*
*/
private void handleMsg() {
Packet p;
synchronized (this) {
p = zlbMsg;
zlbMsg = null;
}
if (!checkPkt(p)) {
ejip.returnPacket(p);
return;
}
int date = p.buf[Udp.DATA + 1];
int time = p.buf[Udp.DATA + 2];
if (!contactZLB) {
lastMsgDate = date;
lastMsgTimestamp = time;
contactZLB = true;
} else {
// check date and time of the message
if (date>lastMsgDate || (date==lastMsgDate && time>lastMsgTimestamp)) {
lastMsgDate = date;
lastMsgTimestamp = time;
} else {
Main.logger.print("Msg. too old");
// it's a too old packet
ejip.returnPacket(p);
return;
}
}
int[] buf = p.buf;
// extract data
// fist set cmd as it is used in isDownloading()
int cmd = buf[Udp.DATA+8];
int strZugnr = buf[Udp.DATA + 3];
// use data from ZLB if not yet set - synchronization if restarted
// but only if we are not in download check!
if (Logic.state!=Logic.DL_CHECK && !isDownloading() && !stickyDl &&
(strZugnr & 0xfffff)!=0) {
if (strnr==0) {
// that one will probably never happen
strnr = strZugnr >> 20;
}
if (zugnr==0) {
zugnr = strZugnr & 0xfffff;
}
if (type==TYPE_UNKNOWN) {
type = (buf[Udp.DATA+6]&0xffff)>>>13;
}
}
// first set ignore flag, then connection state
ignore = (cmd & CFLAG_IGNORE)!=0;
// reset ZLB timeout
zlbTimer = Timer.getTimeoutSec(ZLB_TIMEOUT);
Status.connOk = true;
// ack just this flag, but ignore the rest
// work-around: also Ack the download flag
if (ignore) {
cmdAck |= CFLAG_IGNORE;
// set the download flag
cmdAck &= ~CFLAG_DOWNLOAD;
cmdAck |= cmd & CFLAG_DOWNLOAD;
Dbg.wr("ZLB ignored");
Dbg.lf();
ejip.returnPacket(p);
return;
}
if (cmd!=cmdAck) {
// send the ack
requestSend();
}
// type
int val = (buf[Udp.DATA+6]&0xffff)>>>13;
// all state update synchronized
synchronized (this) {
// update when Verschub
if (val==TYPE_VERSCH) {
type = val;
}
// update ack with cmd as default action
cmdAck = cmd;
// but keep some when not acked from TFZF or Logic
// Abmelden
if ((cmd & CFLAG_ABM)!=0) {
Logic.state = Logic.ABGEMELDET;
}
// Hack for Charlys Abmelden issue:
// don't reset the ABM flag when reset is pending
if ((cmd & CFLAG_ABM)==0 && Logic.state==Logic.ABGEMELDET) {
cmdAck |= CFLAG_ABM;
forceReset = true;
}
// Angemeldet
if ((cmd & CFLAG_ANMOK)!=0) {
Events.anmeldenOk = true;
}
// FWR
if ((cmd & CFLAG_FWR)!=0) {
if (!fwrQuitPending) {
Logic.state = Logic.WIDERRUF;
// we cannot ack it, reset the flag
cmdAck &= ~CFLAG_FWR;
}
} else {
// reset pending when FWR flag was reset by ZLB
fwrQuitPending = false;
}
// NOTHALT
if ((cmd & CFLAG_NOT)!=0) {
if (!nothaltQuitPending) {
Logic.state = Logic.NOTHALT;
// we cannot ack it, reset the flag
cmdAck &= ~CFLAG_NOT;
}
} else {
// reset pending when NOT flag was reset by ZLB
nothaltQuitPending = false;
}
}
// Alarm and flag quits
int alarmAck = buf[Udp.DATA+7];
synchronized (this) {
// we can reset some of the flags here
// check them individual as Charly does not
// always ack the whole flag field - but he should
if ((alarmAck & AFLAG_ANK)!=0) {
alarmFlags &= ~AFLAG_ANK;
ankVerl = 0;
}
if ((alarmAck & AFLAG_VERL)!=0) {
alarmFlags &= ~AFLAG_VERL;
ankVerl = 0;
}
if ((alarmAck & AFLAG_ZIEL)!=0) {
alarmFlags &= ~AFLAG_ZIEL;
}
if ((alarmAck & AFLAG_LERN)!=0) {
alarmFlags &= ~AFLAG_LERN;
Status.lernOk = true;
}
if ((alarmAck&ALARM_MSK)==(alarmFlags&ALARM_MSK)) {
// Alarm has been reset by FDL and seen by ZLB
// we can reset it in the flags
if (alarmQuit) {
alarmFlags &= ~ALARM_MSK;
alarmQuit = false;
}
}
}
boolean ferlChanged = false;
val = buf[Udp.DATA+5]>>>16;
if (val!=end) ferlChanged = true;
val = buf[Udp.DATA+5]&0xffff;
if (val!=startNF) ferlChanged = true;
val = buf[Udp.DATA+4]&0xffff;
if (val!=start) ferlChanged = true;
// logging
if (ferlChanged) {
Main.logger.printSmall("Ferl changed, Logic.state=", Logic.state);
Main.logger.printSmall("From ", buf[Udp.DATA+4]&0xffff);
Main.logger.printSmall("To " , buf[Udp.DATA+5]>>>16);
}
if (val!=0 && ferlChanged && (Logic.state==Logic.ANM_OK || Logic.state==Logic.ZIEL
|| Logic.state==Logic.NOTHALT_OK || Logic.state==Logic.ERLAUBNIS)) {
Main.logger.printSmall("new ferl accepted with state check, from=", buf[Udp.DATA+4]&0xffff);
}
// For a test accept FERL in any Logic.state
if (val!=0 && ferlChanged) {
// this is now a FERL event and we accept the change
synchronized (this) {
start = buf[Udp.DATA+4]&0xffff;
end = buf[Udp.DATA+5]>>>16;
startNF = buf[Udp.DATA+5]&0xffff;
}
Main.logger.printSmall("new ferl accepted, from=", start);
synchronized (Status.dirMutex) {
// let Logik.check() update the direction
Status.direction = Gps.DIR_UNKNOWN;
}
Dbg.wr("set FERL");
Logic.state = Logic.ERLAUBNIS;
}
Dbg.wr("ZLB: ");
printMsg(p);
ejip.returnPacket(p);
}
/**
* Some sanity checks on the received packet.
* @param p
* @return
*/
private boolean checkPkt(Packet p) {
stat[2]++;
stat[3] += p.len;
if (p.len != ((Udp.DATA+9)*4)) { // fix length
// Status.commErr = Logic.COMM_SHORT;
Dbg.wr("wrong length");
Dbg.intVal(p.len);
Dbg.lf();
Main.logger.printHex("wrong length", p.len);
stat[4]++;
return false;
}
if (p.buf[Udp.DATA + 0] != bgid) {
// Status.commErr = Logic.COMM_WRBGID;
Dbg.wr("wrong bgid");
Dbg.intVal(p.buf[Udp.DATA+0]);
Dbg.lf();
Main.logger.printHex("wrong bgid", p.buf[Udp.DATA+0]);
return false;
}
return true;
}
/**
* Debug output of a message.
* @param p
*/
private void printMsg(Packet p) {
int[] buf = p.buf;
int i;
Dbg.wr("date=");
i = p.buf[Udp.DATA+1];
Dbg.intVal(i>>>16);
Dbg.intVal((i>>8) & 0xff);
Dbg.intVal(i & 0xff);
Dbg.wr("time=");
i = p.buf[Udp.DATA+2];
Dbg.intVal((i>>26)&0x3f);
Dbg.intVal((i>>20)&0x3f);
Dbg.intVal((i>>14)&0x3f);
Dbg.intVal(i&0x3ff);
Dbg.wr("strnr=");
Dbg.intVal(buf[Udp.DATA+3]>>>20);
Dbg.wr("zugnr=");
Dbg.intVal(buf[Udp.DATA+3]&0xfffff);
Dbg.wr("pos=");
Dbg.intVal(buf[Udp.DATA+4]>>>16);
Dbg.wr("start=");
Dbg.intVal(buf[Udp.DATA+4]&0xffff);
Dbg.wr("end=");
Dbg.intVal(buf[Udp.DATA+5]>>>16);
Dbg.wr("NFstart=");
Dbg.intVal(buf[Udp.DATA+5]&0xffff);
Dbg.wr("ankVerl=");
Dbg.intVal(buf[Udp.DATA+6]>>>16);
Dbg.wr("type=");
Dbg.intVal((buf[Udp.DATA+6]&0xffff)>>>13);
Dbg.wr("alarm=");
Dbg.hexVal(buf[Udp.DATA+7]);
Dbg.wr("cmd=");
Dbg.hexVal(buf[Udp.DATA+8]);
Dbg.lf();
}
/**
* Periodically invoked to send a status message or handle
* an incoming message. Check if ZLB and
* connection is still alive.
*/
public void run() {
// a hack for a lost bgid in the Flash
if ((bgid==-1 || bgid==0) && Gps.ok()) {
bgid = (getDate()<<16) + (getTime()>>>16);
Dbg.wr("bgid is -1/0 => set a new one");
Dbg.lf();
Main.tftpHandler.programBgid(bgid);
bgid = Flash.getId();
Main.logger.printHex("bgid is -1/0 set to", bgid);
// enough done this round
return;
}
if (Timer.timeout(sendTimer)) {
sendTimer = Timer.getTimeoutSec(SEND_PERIOD);
if (ipLink.getIpAddress()!=0 && destIp!=0) {
send();
}
} else {
if (zlbMsg!=null) {
handleMsg();
}
}
if (!Status.connOk) {
// reset ZLB timeout
zlbTimer = Timer.getTimeoutSec(ZLB_TIMEOUT);
} else if (Timer.timeout(zlbTimer)) {
// trigger a reconnect and display error
// force a reconnect in Ppp-Modem
ipLink.reconnect();
Status.connOk = false;
Status.commErr = Logic.COMM_FDLERR;
Main.logger.print("connection lost");
Dbg.wr("connection lost");
}
// not yet used
// if (scheduleReset) {
// Dbg.wr('$');
// ++rtim;
// if (rtim==20) {
// Main.reset = true;
// }
// }
}
/**
* Request to send a message immediately.
*/
public void requestSend() {
sendTimer = Timer.getTimeoutSec(0);
}
public void sendZiel() {
synchronized (this) {
alarmFlags |= AFLAG_ZIEL;
}
requestSend();
}
public boolean isVerschub() {
return type==TYPE_VERSCH;
}
/**
* Set alarm flag
* @param alarmType
*/
public void setAlarm(int alarmType) {
if (alarmType==0) {
alarmQuit = true;
} else {
alarmFlags |= 1<<(alarmType-1);
}
requestSend();
}
/**
* Quit FWR flag after Enter from TFZF
*/
public void fwrQuit() {
fwrQuitPending = true;
requestSend();
}
/**
* Quit NOT flag after Enter from TFZF
*/
public void nothaltQuit() {
nothaltQuitPending = true;
requestSend();
}
/**
* Flag Ankunft at position melnr
* @param melnr
*/
public void ankunft(int melnr) {
synchronized(this) {
ankVerl = melnr;
alarmFlags |= AFLAG_ANK;
// reset a pending verl
if ((alarmFlags & AFLAG_VERL)!=0) {
alarmFlags &= ~AFLAG_VERL;
}
}
requestSend();
}
/**
* Flag Verlassen at position melnr
* @param melnr
*/
public void verlassen(int melnr) {
synchronized(this) {
ankVerl = melnr;
alarmFlags |= AFLAG_VERL;
// reset a pending verl
if ((alarmFlags & AFLAG_ANK)!=0) {
alarmFlags &= ~AFLAG_ANK;
}
}
requestSend();
}
/**
* Got ack for ANK from ZLB
* @return
*/
public boolean ankuftAck() {
return (alarmFlags & AFLAG_ANK) == 0;
}
/**
* Got ack for VERL from ZLB
* @return
*/
public boolean verlassenAck() {
return (alarmFlags & AFLAG_VERL) == 0;
}
public boolean isDownloading() {
return (cmdAck & CFLAG_DOWNLOAD) != 0;
}
boolean stickyDl;
public boolean isDownloadSticky() {
if ((cmdAck & CFLAG_DOWNLOAD)!=0) {
stickyDl = true;
}
return stickyDl;
}
public void setInfo() {
type = TYPE_INFO;
}
public void resetInfo() {
type = TYPE_UNKNOWN;
}
public void setLern() {
type = TYPE_LERN;
}
public void resetLern() {
type = TYPE_UNKNOWN;
}
public void lern(int melnr, int latAvg, int lonAvg) {
synchronized (this) {
pos = melnr;
gpsLat = latAvg;
gpsLong = lonAvg;
alarmFlags |= AFLAG_LERN;
}
// TODO Auto-generated method stub
}
// flag not used anymore
public void setESAlarm() {
type = TYPE_ES221;
// setAlarm(ALARM_ES221);
}
void setPos(int pos) {
int oldPos = this.pos;
this.pos = pos;
// trigger actions on position change
if (pos != oldPos) {
requestSend();
Main.logic.posChanged();
}
}
int getPos() {
return pos;
}
public void loop() {
// do nothing
}
}