commit d244d406b273905b2ae19e6f1bffc8edd3aff39f Author: steffen Date: Sun Feb 9 13:01:27 2020 +0000 Initial revision diff --git a/Bugs.txt b/Bugs.txt new file mode 100644 index 0000000..a549001 --- /dev/null +++ b/Bugs.txt @@ -0,0 +1,7 @@ + +2020-02-09: +- Züge fahren falsch herrum +- Speed und Fahrtrichtung werden nicht in den Zügen geupdated +- Max Speed ist nicht gleich der Maximalste Step + + diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..6d02a1c --- /dev/null +++ b/ChangeLog @@ -0,0 +1,4 @@ +2020-02-09: +- Initial CVS Import + + diff --git a/License.txt b/License.txt new file mode 100644 index 0000000..d27f8dc --- /dev/null +++ b/License.txt @@ -0,0 +1,9 @@ + +Dieses Programm wird nur von Steffen Pohle, Siegenburg verwendet und ist in einem Experimentellen Status. +Sollte jemand dieses Programm verwenden, so tut er es auf eigene Verantwortung. Ich übernehme keine Haftung +für Schäden durch die Software. + + +Steffen Pohle +9.2.2020, 93354 Siegenburg (Germany) + diff --git a/Readme.txt b/Readme.txt new file mode 100644 index 0000000..1e7a9ce --- /dev/null +++ b/Readme.txt @@ -0,0 +1,11 @@ + +Requirements: +- Webserver with CGI enabled + + +Installation: +- copy the webinterface files to your location of the webserver +- compile the server +- copy the modelbahn-cgi binary to the cgi folder of the webserver +- run the modelbahn-server application. + diff --git a/documentation.odt b/documentation.odt new file mode 100644 index 0000000..9fa447c Binary files /dev/null and b/documentation.odt differ diff --git a/server/Makefile b/server/Makefile new file mode 100644 index 0000000..c673d37 --- /dev/null +++ b/server/Makefile @@ -0,0 +1,79 @@ +# .SILENT: + +WEBUSER=www-data +WEBGROUP=www-data +VERSION=0.1 +PREFIX=/usr/local +ETCPREFIX=/etc + +DISTNAME=modelbahn + +CXX=g++ +CXXFLAGS= -ggdb -fPIC -Wno-write-strings -g -ggdb -std=c++11 +LDFLAGS= -lm -lc -lpthread -L/usr/local/lib -g -ggdb +LDFLAGS_CGI= -lm -lc -lpthread -L/usr/local/lib -g -ggdb + +DEPENDFILE=.depend +TARGET=modelbahn-server + +SERVEROBJ=server.o network.o session.o server-loadsave.o debug.o \ + json.o main.o sensor.o turnout.o railway.o interface.o locomotive.o \ + block.o interface-z21.o + +CURDIR=`pwd` + +all: dep $(TARGET) test-json modelbahn-cgi + +modelbahn-server: $(SERVEROBJ) + $(CXX) -o $@ $^ $(LDFLAGS_CGI) -lUDPTCPNetwork -L./ -I./ -lpthread + +modelbahn-cgi: modelbahn-cgi.o debug.o + $(CXX) -o $@ $^ $(LDFLAGS) -lUDPTCPNetwork -L./ -I./ -lpthread + +test-json: json.o test-json.o debug.o + $(CXX) -o $@ $^ $(LDFLAGS) -L./ -I./ -lpthread + +install: $(TARGET) + cp -f $(TARGET) $(PREFIX)/lib/ + +uninstall: + rm -f $(PREFIX)/lib/$(TARGET) + + +rebuild: clean all + +dep: + $(CXX) -MM `ls *.cc` $(CXXFLAGS) > $(DEPENDFILE) + +clean: + rm $(TARGET) -rf + rm modelbahn-cgi -rf + rm -rf gmon.out + rm *.s -rf + rm *.o -rf + rm *.oo -rf + rm *~ -rf + rm -rf config.h + rm -rf .depend + rm -rf *.so + rm -rf *.a + rm -rf *.so.* + +cleanall: clean + +source: cleanall + +config: + echo "#ifndef _CONFIG_H_" > config.h + echo "#define _CONFIG_H_" >> config.h + echo "" >> config.h + echo "#define VERSION \"$(VERSION)\"" >> config.h + echo "" >> config.h + echo "#define PREFIX \"$(PREFIX)\"" >> config.h + echo "#define ETCPREFIX \"$(ETCPREFIX)\"" >> config.h + echo "" >> config.h + echo "#endif" >> config.h + + +-include $(DEPENDFILE) + diff --git a/server/block.cc b/server/block.cc new file mode 100644 index 0000000..660dbb7 --- /dev/null +++ b/server/block.cc @@ -0,0 +1,152 @@ + + +#include "modelbahn.h" +#include "block.h" + + +Blocks::Blocks () { + changed = 0; + blocks = (Block*) malloc(sizeof(Block)*SENSORS_MAX); + max = BLOCKS_MAX; +}; + +Blocks::~Blocks() { + free (blocks); + blocks = NULL; + max = 0; +}; + + + + +int Blocks::Lock() { + if (pthread_mutex_lock(&mtx) == 0) return 1; + else return 0; +} + + +int Blocks::UnLock() { + if (pthread_mutex_unlock(&mtx) == 0) return 1; + else return 0; +} + + +JSONParse Blocks::_GetJSON(int idx) { + JSONParse json; + JSONElement je; + string s = ""; + + json.Clear(); + + s = blocks[idx].name; json.AddObject("name", s); + json.AddObject("flags", blocks[idx].flags); + + return json; +}; + + +JSONParse Blocks::GetJSON(string name) { + int i; + JSONParse jp; + + jp.Clear(); + + Lock(); + for (i = 0; i < max; i++) if (blocks[i].name[0] != 0) { + if (name.compare(blocks[i].name) == 0) { + jp = _GetJSON(i); + } + } + + UnLock(); + + return jp; +}; + + +void Blocks::GetJSONAll(JSONParse *json) { + int i, cnt; + JSONElement je; + + Lock(); + + // + // write all railway data + // create json object array manualy + je.type = JSON_T_ARRAY; + je.name = "blocks"; + for (cnt = 0, i = 0; i < max; i++) + if (blocks[i].name[0] != 0) { + if (cnt != 0) je.value += ","; // not first element + je.value += _GetJSON(i).ToString(); + cnt++; + } + json->AddObject(je); + + UnLock(); +}; + + +Block Blocks::GetBlockFromJSON(JSONParse *j) { + Block bl; + string s; + + bl.name[0] = 0; + bl.flags = 0; + + j->GetValue("name", &s); + strncpy (bl.name, s.c_str(), REFERENCENAME_LEN); + j->GetValueInt("flags", &bl.flags); + + return bl; +}; + + +int Blocks::Change(Block *bl) { + int i; + int ifree = -1; + + Lock(); + + for (i = 0; i < max; i++) { + if (blocks[i].name[0] != 0) { + // found element + if (strncmp(blocks[i].name, bl->name, REFERENCENAME_LEN) == 0) { + ifree = i; + break; + } + } + else if (ifree == -1) ifree = i; + } + // element not found add new element + if (ifree != -1 && ifree < max) { + blocks[ifree] = *bl; + strncpy (blocks[ifree].name, bl->name, REFERENCENAME_LEN); + } + + changed = 1; + UnLock(); + + return 1; +}; + + +int Blocks::Delete(string name) { + int i; + + Lock(); + for (i = 0; i < max; i++) if (blocks[i].name[0] != 0) { + if (name.compare(blocks[i].name) == 0) { + blocks[i].name[0] = 0; + blocks[i].flags = 0; + changed = 1; + break; + } + } + + UnLock(); + + return 1; +}; + + diff --git a/server/block.h b/server/block.h new file mode 100644 index 0000000..0638783 --- /dev/null +++ b/server/block.h @@ -0,0 +1,42 @@ + +#ifndef _BLOCK_H_ +#define _BLOCK_H_ + +#include "modelbahn.h" +#include "server.h" + +#define BLOCKF_SHORTTRAIN 0x0001 + +struct s_Block { + char name[REFERENCENAME_LEN]; + int flags; +} typedef Block; + +class Blocks { + private: + Block *blocks; + int max; + int changed; + + pthread_mutex_t mtx; + int Lock(); + int UnLock(); + + // not thread safe + JSONParse _GetJSON(int idx); + public: + Blocks(); + ~Blocks(); + + bool IsChanged() { return changed; } + void ClearChanged() { changed = 0; }; + + int Change(Block *se); + int Delete(string name); + + JSONParse GetJSON(string name); + void GetJSONAll(JSONParse *json); + Block GetBlockFromJSON(JSONParse *j); +}; + +#endif diff --git a/server/debug.cc b/server/debug.cc new file mode 100644 index 0000000..2eab6ce --- /dev/null +++ b/server/debug.cc @@ -0,0 +1,56 @@ + +// #define _GNU_SOURCE /* See feature_test_macros(7) */ + +#include +#include +#include +#include +#include + +#include "modelbahn.h" + +#define DEBUG_FILE "/tmp/modelbahn-server.log" +int _debuglevel = 0xff; + +pid_t gettid(); + +void debug (int type, char *fmt,...) { + va_list args; + char text1[DEBUG_TEXT1LEN]; + char text2[DEBUG_TEXT2LEN]; + pid_t pid = gettid(); + + va_start (args, fmt); + vsnprintf (text1, (DEBUG_TEXT1LEN-1), fmt, args); + va_end (args); + text1[DEBUG_TEXT1LEN-1] = 0; + text2[DEBUG_TEXT2LEN-1] = 0; + + if (type > 0) snprintf (text2, DEBUG_TEXT2LEN-1, "(%d)%d: %s", pid, type, text1); + else snprintf (text2, DEBUG_TEXT2LEN-1, "(%d) %s", pid, text1); + + if (type == 0 || (type & _debuglevel) != 0) { + FILE *f; + printf ("%s\n", text2); + f= fopen(DEBUG_FILE, "a"); + if (f) { + fprintf (f, "%s\n", text2); + fclose (f); + } + } +}; + + +/******************************************************************************************* + * helper stuff + *******************************************************************************************/ + + +// +// return the current thread process id +// +pid_t gettid() { + pid_t tid = 0; + tid = syscall(SYS_gettid); + return tid; +}; diff --git a/server/debug.h b/server/debug.h new file mode 100644 index 0000000..67f83b4 --- /dev/null +++ b/server/debug.h @@ -0,0 +1,22 @@ + + +#ifndef _DEBUG_H_ +#define _DEBUG_H_ + +#define DEBUG_INFO 0x0001 +#define DEBUG_ERROR 0x0002 +#define DEBUG_IFACE 0x0004 +#define DEBUG_NET 0x0008 +#define DEBUG_SESSION 0x0010 +#define DEBUG_SERVER 0x0020 +#define DEBUG_RAILWAY 0x0040 +#define DEBUG_LOCO 0x0080 + +#define DEBUG_TEXT1LEN 64000 +#define DEBUG_TEXT2LEN 65000 + +void debug(int type, char *fmt,...); + +#endif // _DEBUG_H_ + + diff --git a/server/interface-z21.cc b/server/interface-z21.cc new file mode 100644 index 0000000..5e81594 --- /dev/null +++ b/server/interface-z21.cc @@ -0,0 +1,447 @@ + + +#include "modelbahn.h" +#include "interface.h" +#include "interface-z21.h" + +// +// command and datasets for communication +static unsigned char TX_X_SET_TRACK_POWER_ON[] { 0x07, 0x00, 0x40, 0x00, 0x21, 0x81, 0xa0 }; +static unsigned char TX_X_SET_TRACK_POWER_OFF[] { 0x07, 0x00, 0x40, 0x00, 0x21, 0x80, 0xa1 }; + +// +// values to comapare for data +static unsigned char RX_SYSTEMSTATE_DATACHANGED[] { 0x14, 0x00, 0x84, 0x00 }; +static unsigned char RX_GET_SERIAL_NUMBER[] { 0x08, 0x00, 0x10, 0x00 }; +static unsigned char RX_LOCONET_Z21_TX[] { 0x00, 0xa1, 0x00 }; // loconet dynamic length +static unsigned char RX_X_GET_TURNOUT_INFO[] { 0x09, 0x00, 0x40, 0x00, 0x43 }; +static unsigned char RX_RMBUS_DATACHANGED[] { 0x0F, 0x00, 0x80, 0x00 }; + +// +// locomotive values +#define Z21_IDX_SETLOCO_DRIVE_SFMT 5 +#define Z21_IDX_SETLOCO_DRIVE_ADRH 6 +#define Z21_IDX_SETLOCO_DRIVE_ADRL 7 +#define Z21_IDX_SETLOCO_DRIVE_STEP 8 +#define Z21_IDX_SETLOCO_DRIVE_CHK 9 + +// +// turnout values +#define Z21_IDX_SETTURNOUT_ADRH 5 +#define Z21_IDX_SETTURNOUT_ADRL 6 +#define Z21_IDX_SETTURNOUT_MODE 7 +#define Z21_IDX_SETTURNOUT_CHK 8 + +InterfaceZ21::InterfaceZ21 () { + status_connected = false; + status_poweron = false; + status_programmingmode = false; + status_shortcircuit = false; + status_emergencystop = false; + + send_logon = false; + + memset (rmsensors, 0x0, INTF_Z21_RMSENSOR_GROUPS * INTF_Z21_RMSENSOR_BYTES); + rmsensorinit = 0; + + serial = ""; + hostname = ""; + timeout = time(NULL); + rmgetdatatimeout = time(NULL) - INTF_Z21_RMGETDATA_TIMEOUT; +}; + +InterfaceZ21::~InterfaceZ21() { + Disconnect(); +}; + + +void InterfaceZ21::Connect (string destination) { + debug (DEBUG_INFO | DEBUG_IFACE, "%s:%d Connect to: %s", __FILE__, __LINE__, destination.c_str()); + + if (status_connected) Disconnect(); + hostname = destination; + + if (udp.Listen(0) == 0) { + debug (DEBUG_ERROR | DEBUG_IFACE, "%s:%d Error could not bind UDP socket (%s)", + __FILE__, __LINE__, strerror(errno)); + return; + } + udp.SetBlocked(1); + timeout = time(NULL); + send_SET_BROADCASTFLAGS(); + send_GET_SERIAL_NUMBER(); + status_connected = true; + + for (int i = 0; i < INTF_Z21_LOCONET_MAXADDR; i++) { + loconet_map[i] = { 0 , 0, 0, 0, 0}; + } +}; + + + +// +// if connected, poweroff track and logoff from Z21 controller +void InterfaceZ21::Disconnect() { + if (status_connected) { + PowerOnOff(0); + send_LOGOFF(); + } + udp.Close(); + + status_connected = false; + status_poweron = false; +}; + +/////////////////////////////////////////////////////////////////////// +// +// +// check if we are connected and for timeouts. +// read and go through incoming data +int InterfaceZ21::Loop(string interfacename) { + time_t curtime = time(NULL); + string source; + int inlen; + int i; + int update = false; + + if (status_connected) { + // + // Z21 timed out? close all connection + if ((timeout + 2 * INTF_Z21_TIMEOUT) < curtime) { + debug (DEBUG_ERROR | DEBUG_IFACE, "%s:%d connection timed out (%d)", __FILE__, __LINE__, curtime-(timeout + 2 * INTF_Z21_TIMEOUT)); + Disconnect(); + update = true; + } + else if (send_logon == false && (timeout + INTF_Z21_TIMEOUT) < curtime) { + send_logon = true; + send_GET_SERIAL_NUMBER(); + send_SET_BROADCASTFLAGS(); + timeout = time(NULL); + update = true; + } + + // + // rmgetdatatimeout? + if (curtime - rmgetdatatimeout > INTF_Z21_RMGETDATA_TIMEOUT) { + rmgetdatatimeout = curtime; + send_RM_GETDATA(0); + send_RM_GETDATA(1); + } + } + + + // + // check for incoming data + if (status_connected) { + if ((inlen = udp.ReadTimeout(&source, inbuffer, INTF_Z21_INBUFFER, 10)) > 0) { + // + // got some data + + // + // check for LAN_SYSTEMSTATE_DATACHANGED + if (memcmp (inbuffer, RX_GET_SERIAL_NUMBER, sizeof(RX_GET_SERIAL_NUMBER)) == 0) { + // got serial number... ignore this for now + send_logon = false; + } + + // + // check for LAN_X_TURNOUT_INFO + if (memcmp (inbuffer, RX_X_GET_TURNOUT_INFO, sizeof(RX_X_GET_TURNOUT_INFO)) == 0) { + // got turnout information + int addr = 0; + + addr = ((unsigned char)inbuffer[Z21_IDX_SETTURNOUT_ADRH] << 8) + (unsigned char)inbuffer[Z21_IDX_SETTURNOUT_ADRL]; + + if (inbuffer[Z21_IDX_SETTURNOUT_MODE] == 2) + server->TurnoutAddrMode(interfacename, addr, 1); + else if (inbuffer[Z21_IDX_SETTURNOUT_MODE] == 1) + server->TurnoutAddrMode(interfacename, addr, 0); + } + + + + // + // check for LAN_SYSTEMSTATE_DATACHANGED + else if (memcmp (inbuffer, RX_SYSTEMSTATE_DATACHANGED, sizeof(RX_SYSTEMSTATE_DATACHANGED)) == 0) { + int cs = (unsigned char) inbuffer[16]; + int csex = (unsigned char) inbuffer[17]; + + bool old_poweron = status_poweron; + bool old_shortcircuit = status_shortcircuit; + bool old_programmingmode = status_programmingmode; + bool old_connected = status_connected; + bool old_emergencystop = status_emergencystop; + + status_emergencystop = (cs & INTF_Z21_CS_EmergencyStop); + status_poweron = (cs & INTF_Z21_CS_TrackVoltageOff); + status_shortcircuit = (cs & INTF_Z21_CS_ShortCircuit); + status_programmingmode = (cs & INTF_Z21_CS_ProgModeActive); + + debug (0, "%s:%d cs:%d csex:%d", __FILE__, __LINE__, cs, csex); + + update = true; + //if ( old_poweron != status_poweron || + // old_shortcircuit != status_shortcircuit || + // old_programmingmode != status_programmingmode || + // old_emergencystop != status_emergencystop) update = true; + } + + // + // LOCONET + //Got some Data:08:00:a1:00:a1:02:00:5c: + //Got some Data:08:00:a1:00:a0:02:55:08: + else if (memcmp (inbuffer+1, RX_LOCONET_Z21_TX, sizeof (RX_LOCONET_Z21_TX)) == 0) { + int loconetopc = (unsigned char)inbuffer[4]; + printf ("%s:%d Got some LOCONET (%x) Data:", __FILE__, __LINE__, loconetopc); + for (i = 0; i < inlen; i++) { + int z = (unsigned char) inbuffer[i]; + printf ("i:%d %02x:", i, z); + } + printf ("\n"); + + // + // + if (loconetopc == 0xa0) { + // + // speed + unsigned int inloc = inbuffer[5]; + unsigned int inspeed = inbuffer[6]; + + if (inloc >= 0 && inloc <= INTF_Z21_LOCONET_MAXADDR) { + if (loconet_last.addr) { + debug (0, "%s:%d loconet_lastaddr:%d", __FILE__, __LINE__, loconet_last.addr); + loconet_map[inloc].addr = loconet_last.addr; + } + + debug (0, "%s:%d Loc:%d Speed:%d\n", __FILE__, __LINE__, loconet_map[inloc].addr, inspeed); + if (loconet_map[inloc].addr > 0) { + int speed = inspeed; + server->LocomotiveAddrSpeed(interfacename, loconet_map[inloc].addr, speed); + } + } + loconet_last.addr = 0; + } + + if (loconetopc == 0xa1) { + // + // speed + unsigned int inloc = inbuffer[5]; + unsigned int infunc = inbuffer[6]; + + if (inloc >= 0 && inloc <= INTF_Z21_LOCONET_MAXADDR) { + if (loconet_last.addr) loconet_map[inloc] = loconet_last; + debug (0, "%s:%d Loc:%d Function:%d\n", __FILE__, __LINE__, loconet_map[inloc].addr, infunc); + } + loconet_last.addr = 0; + } + } + + // + // check for LAM_RMBUS_DATACHANGED + else if (memcmp (inbuffer, RX_RMBUS_DATACHANGED, sizeof(RX_RMBUS_DATACHANGED)) == 0) { + int group = (unsigned char)inbuffer[4]; + int idx, bit; + + printf ("LAN_RMBUS_DATA_CHANGE[%s] ", interfacename.c_str()); + for (idx = 0; idx < INTF_Z21_RMSENSOR_BYTES * INTF_Z21_RMSENSOR_GROUPS; idx++) { + printf ("%x ", (unsigned char) inbuffer[4+idx]); + } + printf ("\n"); + + // groupindex (first or last 10 bytes) + unsigned char rmsold[INTF_Z21_RMSENSOR_BYTES * INTF_Z21_RMSENSOR_GROUPS]; + memcpy (rmsold, rmsensors, INTF_Z21_RMSENSOR_BYTES * INTF_Z21_RMSENSOR_GROUPS); + + if (group < INTF_Z21_RMSENSOR_GROUPS) { + memcpy (rmsensors+(group * INTF_Z21_RMSENSOR_BYTES), inbuffer+5, INTF_Z21_RMSENSOR_BYTES); + + // check for changes + for (idx = 0; idx < INTF_Z21_RMSENSOR_GROUPS*INTF_Z21_RMSENSOR_BYTES; idx++) { + if (rmsold[idx]^rmsensors[idx]) { + for (i = 0; i < 8; i++) if (rmsensorinit || (rmsold[idx] & 1 << i) != (rmsensors[idx] & 1 << i)) { + debug (0, "Sendor Data Changed: %s[%d]", interfacename.c_str(), idx*8+i); + if (rmsensors[idx] & 1 << i) server->SensorAddrChange(interfacename, idx*8+i, 1); + else server->SensorAddrChange(interfacename, idx*8+i, 0); + } + } + } + rmsensorinit = 0; + } + } + + else { + printf ("InterfaceZ21(%s) Got some Data:", interfacename.c_str()); + for (i = 0; i < inlen; i++) { + int z = (unsigned char) inbuffer[i]; + printf ("%02x:", z); + } + printf ("\n"); + } + } + else if (inlen < 0) { + if (errno != EAGAIN && errno != EWOULDBLOCK) { + debug (DEBUG_ERROR | DEBUG_IFACE, "%s:%d error on reading (%s)", + __FILE__, __LINE__, strerror(errno)); + } + } + } + + return update; +}; + + +// +// send_SET_BROADCASTFLAGS(); +void InterfaceZ21::send_SET_BROADCASTFLAGS() { + unsigned char buffer[] = { 0x08, 0x00, 0x50, 0x00, 0x0F, 0x01, 0x00, 0x03 }; + udp.Write(hostname, (char*)buffer, sizeof (buffer)); +} + +// +// send_GET_SERIAL_NUMBER +void InterfaceZ21::send_GET_SERIAL_NUMBER() { + unsigned char buffer[] = { 0x04, 0x00, 0x10, 0x00 }; + udp.Write(hostname, (char*)buffer, sizeof (buffer)); +} + + +// +// send_RM_GETDATA +void InterfaceZ21::send_RM_GETDATA(int group) { + unsigned char buffer[] = { 0x05, 0x00, 0x81, 0x00, 0x00 }; + if (group < INTF_Z21_RMSENSOR_GROUPS) { + buffer[4] = (unsigned char)group; + udp.Write(hostname, (char*)buffer, sizeof (buffer)); + } +} + + +// +// send_LOGOFF +void InterfaceZ21::send_LOGOFF() { + if (status_connected == false) return; + + unsigned char buffer[] = { 0x04, 0x00, 0x30, 0x00 }; + udp.Write(hostname, (char*)buffer, sizeof (buffer)); +} + + +// +// Set Locomotive Speed +void InterfaceZ21::SetLocoSpeed(Locomotive *l, int step) { + unsigned char buffer[] = { 0x0A, 0x00, 0x40, 0x00, 0xE4, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00 }; + + debug (0, "%s:%d InterfaceZ21::SetLocoSpeed step:%d", __FILE__, __LINE__, step); + + // + // reverse? + if (step < 0) { + l->flags |= LOCO_F_REVERSE; + } + else if (step > 0) { + l->flags &= ~LOCO_F_REVERSE; + } + if (l->flags & LOCO_F_REVERSE) buffer[Z21_IDX_SETLOCO_DRIVE_STEP] = 0x80; + else buffer[Z21_IDX_SETLOCO_DRIVE_STEP] = 0x00; + + // + // setup loc addr + if (l->addr >= 128) buffer[Z21_IDX_SETLOCO_DRIVE_ADRH] = 0xC0; + else buffer[Z21_IDX_SETLOCO_DRIVE_ADRH] = 0; + buffer[Z21_IDX_SETLOCO_DRIVE_ADRL] = (unsigned char) (l->addr & 0xFF); + buffer[Z21_IDX_SETLOCO_DRIVE_ADRH] |= (unsigned char) (l->addr >> 8); + + // + // setup steps and selected step + if (l->steps <= 14) { + buffer[Z21_IDX_SETLOCO_DRIVE_SFMT] = 0x10; + if (step == 0) buffer[Z21_IDX_SETLOCO_DRIVE_STEP] |= 0; + else buffer[Z21_IDX_SETLOCO_DRIVE_STEP] |= (abs(step)+1); + } + else if (l->steps <= 28) { + buffer[Z21_IDX_SETLOCO_DRIVE_SFMT] = 0x12; + if (step == 0) buffer[Z21_IDX_SETLOCO_DRIVE_STEP] |= 0; + else { + buffer[Z21_IDX_SETLOCO_DRIVE_STEP] |= ((abs(step)>>1)+1); + if (abs(step) & 0x01) buffer[Z21_IDX_SETLOCO_DRIVE_STEP] |= 0x10; + } + } + else if (l->steps <= 128) { + buffer[Z21_IDX_SETLOCO_DRIVE_SFMT] = 0x13; + if (step == 0) buffer[Z21_IDX_SETLOCO_DRIVE_STEP] |= 0; + else buffer[Z21_IDX_SETLOCO_DRIVE_STEP] |= (abs(step)+1); + } + + // + // XOR Byte + for (int i = 4; i < sizeof (buffer)-1; i++) + buffer[Z21_IDX_SETLOCO_DRIVE_CHK] = (unsigned char)buffer[Z21_IDX_SETLOCO_DRIVE_CHK] xor (unsigned char)buffer[i]; + + printf ("Send Data:"); + for (int i = 0; i < sizeof (buffer); i++) { + int z = (unsigned char) buffer[i]; + printf ("%02x:", z); + } + printf ("\n"); + udp.Write(hostname, (char*)buffer, sizeof (buffer)); + loconet_last.addr = l->addr; + loconet_last.dir = l->flags & LOCO_F_REVERSE; + loconet_last.speed = l->speed; + loconet_last.maxspeed = l->vmax; + loconet_last.steps = l->steps; +}; + + +// +// Set Locomotive Function +void InterfaceZ21::SetLocoFunction(Locomotive *l, int func, int value) { + debug (0, "%s:%d InterfaceZ21::SetLocoFunction", __FILE__, __LINE__); +}; + + +// +// Set Turnout +void InterfaceZ21::SetTurnout(Turnout *t, int activate, int motoractive) { + unsigned char buffer[] = { 0x09, 0x00, 0x40, 0x00, 0x53, 0x00, 0x00, 0x00, 0x00 }; + + debug (0, "%s:%d InterfaceZ21::SetTurnout (a:%d, m:%d)", __FILE__, __LINE__, activate, motoractive); + + // + // setup turnout addr + buffer[Z21_IDX_SETTURNOUT_ADRL] = (unsigned char) (t->addr & 0xFF); + buffer[Z21_IDX_SETTURNOUT_ADRH] |= (unsigned char) (t->addr >> 8); + + // + // setup steps and selected step + buffer[Z21_IDX_SETTURNOUT_MODE] = 0x80; + if (activate) buffer[Z21_IDX_SETTURNOUT_MODE] |= 1; + if (motoractive) buffer[Z21_IDX_SETTURNOUT_MODE] |= 8; + + // + // XOR Byte + for (int i = 4; i < sizeof (buffer)-1; i++) + buffer[Z21_IDX_SETTURNOUT_CHK] = (unsigned char)buffer[Z21_IDX_SETTURNOUT_CHK] xor (unsigned char)buffer[i]; + + printf ("Send Data:"); + for (int i = 0; i < sizeof (buffer); i++) { + int z = (unsigned char) buffer[i]; + printf ("%02x:", z); + } + printf ("\n"); + udp.Write(hostname, (char*)buffer, sizeof (buffer)); +}; + + +// +// power on off +// send_X_SET_TRACK_POWER_ON(); send_X_SET_TRACK_POWER_ON(); +void InterfaceZ21::PowerOnOff(int onoff) { + if (status_connected == false) return; + + if (onoff) { + udp.Write(hostname, (char*)TX_X_SET_TRACK_POWER_ON, sizeof (TX_X_SET_TRACK_POWER_ON)); + } + else { + udp.Write(hostname, (char*)TX_X_SET_TRACK_POWER_OFF, sizeof (TX_X_SET_TRACK_POWER_OFF)); + } +}; diff --git a/server/interface-z21.h b/server/interface-z21.h new file mode 100644 index 0000000..5bb5833 --- /dev/null +++ b/server/interface-z21.h @@ -0,0 +1,81 @@ + +#ifndef _INTERFACE_Z21_H_ +#define _INTERFACE_Z21_H_ + +#include "modelbahn.h" +#include "interface.h" +#include + +#define INTF_Z21_TIMEOUT 10 +#define INTF_Z21_INBUFFER 2048 + +// STATUSCHANGED / CENTRALSTATE +#define INTF_Z21_CS_EmergencyStop 0x01 +#define INTF_Z21_CS_TrackVoltageOff 0x02 +#define INTF_Z21_CS_ShortCircuit 0x04 +#define INTF_Z21_CS_ProgModeActive 0x20 + +#define INTF_Z21_LOCONET_MAXADDR 255 +#define INTF_Z21_RMSENSOR_GROUPS 2 +#define INTF_Z21_RMSENSOR_BYTES 10 +#define INTF_Z21_RMGETDATA_TIMEOUT 30 + +struct s_loconet_map{ + int addr; + int dir; + int steps; + int speed; + int maxspeed; +}; + +class InterfaceZ21 { + private: + string hostname; + string serial; + char inbuffer[INTF_Z21_INBUFFER]; + struct s_loconet_map loconet_map[INTF_Z21_LOCONET_MAXADDR]; + struct s_loconet_map loconet_last; // needed for detecting loco_changes + + UDP udp; + time_t timeout; + time_t rmgetdatatimeout; + bool send_logon; + + bool status_poweron; + bool status_shortcircuit; + bool status_programmingmode; + bool status_connected; + bool status_emergencystop; + + unsigned char rmsensors[INTF_Z21_RMSENSOR_BYTES * INTF_Z21_RMSENSOR_GROUPS]; + int rmsensorinit; + +// void send_X_SET_TRACK_POWER_ON(); +// void send_X_SET_TRACK_POWER_OFF(); + void send_GET_SERIAL_NUMBER(); + void send_SET_BROADCASTFLAGS(); + void send_LOGOFF(); + void send_RM_GETDATA(int group); + + public: + InterfaceZ21(); + ~InterfaceZ21(); + + void Connect(string destination); + void Disconnect(); + + bool IsConnected() { return status_connected; }; + bool IsPoweron() { return status_poweron; }; + bool IsSortCircuit() { return status_shortcircuit; }; + bool IsProgramminnMode() { return status_programmingmode; }; + bool IsEmergencyStop() { return status_emergencystop; }; + + int Loop(string interfacename); + void PowerOnOff(int onoff); + void SetLocoSpeed(Locomotive *l, int step); + void SetLocoFunction(Locomotive *l, int func, int value); + void SetTurnout(Turnout *t, int activate, int motoractive); +}; + + +#endif diff --git a/server/interface.cc b/server/interface.cc new file mode 100644 index 0000000..b7f82b1 --- /dev/null +++ b/server/interface.cc @@ -0,0 +1,392 @@ + + +#include "modelbahn.h" +#include "interface.h" + +// ************************************************************************** +// * +// * I N T E R F A C E (gateway to different devices) +// * +// ************************************************************************** + +Interface::Interface() { + name[0] = 0; + host[0] = 0; + flags = 0; + type = INTF_T_OFF_UNKNOWN; + needs_update = true; +}; + +Interface::~Interface() { +}; + + +void Interface::Connect () { + debug (DEBUG_INFO | DEBUG_IFACE, "* Interface (%s) Connect to %s", name, host); + + switch (type) { + case INTF_T_Z21: intz21.Connect(host); break; + default: break; + } +}; + + +void Interface::Disconnect() { + debug (DEBUG_INFO | DEBUG_IFACE, "* Interface (%s) Disconnect", name); + + switch (type) { + case INTF_T_Z21: intz21.Disconnect(); break; + default: break; + } +}; + + +void Interface::PowerOnOff(int onoff) { + debug (DEBUG_INFO | DEBUG_IFACE, "* Interface (%s) PowerOnOff %d", name, onoff); + + switch (type) { + case INTF_T_Z21: intz21.PowerOnOff(onoff); break; + default: break; + } +}; + +// +// Turnout +// +void Interface::SetTurnout(Turnout *t, int active, int motoractive) { + debug (DEBUG_INFO | DEBUG_IFACE, "* Interface (%s) SetTurnout Addr:%d FinalAcitve:%d Motor:%d", name, + t->addr, active, motoractive); + + switch (type) { + case INTF_T_Z21: intz21.SetTurnout(t, active, motoractive); break; + default: break; + } + + // + // make sure we turn the motor later off + if (motoractive) t->flags |= TURNOUT_F_ACTIVE; + else t->flags &= ~TURNOUT_F_ACTIVE; + gettimeofday (&t->activatetime, NULL); +}; + +// +// Locomotive +// +void Interface::SetLocoSpeed(Locomotive *l, int step) { + debug (DEBUG_INFO | DEBUG_IFACE, "* Interface (%s) SetLocoSpeed Addr:%d Speed:%d ", name, l->addr, step); + + switch (type) { + case INTF_T_Z21: intz21.SetLocoSpeed(l, step); break; + default: break; + } +}; + + +void Interface::SetLocoFunction(Locomotive *l, int func, int value) { + debug (DEBUG_INFO | DEBUG_IFACE, "* Interface (%s) SetLocoFunction Addr:%d Func:%d:%d", name, l->addr, func, value); + + switch (type) { + case INTF_T_Z21: intz21.SetLocoFunction(l, func, value); break; + default: break; + } +}; + + +int Interface::Loop() { + int ret = 0; + + flags &= ~(INTF_F_CONNECTED | INTF_F_POWER | INTF_F_STOP | INTF_F_SHORT_CIRCUIT | INTF_F_PROGRAMMING); + if (needs_update) { + ret = 1; + needs_update = 0; + } + + switch (type) { + case INTF_T_Z21: + ret = intz21.Loop(name); + if (intz21.IsConnected()) flags |= INTF_F_CONNECTED; + if (intz21.IsPoweron()) flags |= INTF_F_POWER; + if (intz21.IsEmergencyStop()) flags |= INTF_F_STOP; + if (intz21.IsSortCircuit()) flags |= INTF_F_SHORT_CIRCUIT; + break; + default: break; + } + +// debug (DEBUG_INFO | DEBUG_IFACE, "%s:%d Interface: name:'%s' , Flags: %d", __FILE__, __LINE__, name, flags); + + return ret; +}; + + +bool Interface::IsConnected() { + bool ret = false; + + switch (type) { + case INTF_T_Z21: ret = intz21.IsConnected(); break; + default: break; + } + + return ret; +} + + +bool Interface::IsPoweron() { + bool ret = false; + + switch (type) { + case 1: ret = intz21.IsPoweron(); break; + default: break; + } + + return ret; +} + + + + +// ************************************************************************** +// * +// * I N T E R F A C E S (gateway to all devices) +// * +// ************************************************************************** + +Interfaces::Interfaces () { + max = INTERFACES_MAX; + changed = 0; +}; + +Interfaces::~Interfaces() { + max = 0; +}; + + +int Interfaces::LockThread() { + if (pthread_mutex_lock(&mtx) == 0) return 1; + else return 0; +} + + +int Interfaces::UnLockThread() { + if (pthread_mutex_unlock(&mtx) == 0) return 1; + else return 0; +} + + +JSONParse Interfaces::_GetJSON(int idx) { + JSONParse json; + JSONElement je; + string s = ""; + + json.Clear(); + + s = interfaces[idx].name; json.AddObject("name", s); + s = interfaces[idx].host; json.AddObject("host", s); + json.AddObject("flags", interfaces[idx].flags); + json.AddObject("type", interfaces[idx].type); + + + return json; +}; + + +JSONParse Interfaces::GetJSON(string name) { + int i; + JSONParse jp; + + jp.Clear(); + + LockThread(); + for (i = 0; i < max; i++) if (interfaces[i].name[0] != 0) { + if (name.compare(interfaces[i].name) == 0) { + jp = _GetJSON(i); + } + } + + UnLockThread(); + + return jp; +}; + + +void Interfaces::GetJSONAll(JSONParse *json) { + int i, cnt; + JSONElement je; + + LockThread(); + + // + // write all railway data + // create json object array manualy + je.type = JSON_T_ARRAY; + je.name = "interfaces"; + for (cnt = 0, i = 0; i < max; i++) + if (interfaces[i].name[0] != 0) { + if (cnt != 0) je.value += ","; // not first element + je.value += _GetJSON(i).ToString(); + cnt++; + } + json->AddObject(je); + + UnLockThread(); +}; + + +Interface Interfaces::GetInterfaceFromJSON(JSONParse *j) { + Interface i; + string s; + + i.name[0] = 0; + i.host[0] = 0; + + j->GetValue("name", &s); + strncpy (i.name, s.c_str(), REFERENCENAME_LEN); + j->GetValue("host", &s); + strncpy (i.host, s.c_str(), REFERENCENAME_LEN); + j->GetValueInt("flags", &i.flags); + j->GetValueInt("type", &i.type); + + return i; +}; + + +int Interfaces::Change(Interface *iface) { + int i; + int ifree = -1; + + LockThread(); + for (i = 0; i < max; i++) { + if (interfaces[i].name[0] != 0) { + // found element + if (strncmp(interfaces[i].name, iface->name, REFERENCENAME_LEN) == 0) { + ifree = i; + break; + } + } + else if (ifree == -1) ifree = i; + } + // element not found add new element + if (ifree != -1 && ifree < max) { + strncpy (interfaces[ifree].name, iface->name, REFERENCENAME_LEN); + strncpy (interfaces[ifree].host, iface->host, REFERENCENAME_LEN); + interfaces[ifree].flags = iface->flags; + interfaces[ifree].type = iface->type; + interfaces[ifree].Connect(); + } + changed = 1; + UnLockThread(); + + return 1; +}; + + +int Interfaces::Delete(string name) { + int i; + + LockThread(); + for (i = 0; i < max; i++) if (interfaces[i].name[0] != 0) { + if (name.compare(interfaces[i].name) == 0) { + interfaces[i].Disconnect(); + interfaces[i].name[0] = 0; + interfaces[i].host[0] = 0; + interfaces[i].flags = 0; + interfaces[i].type = INTF_T_OFF_UNKNOWN; + changed = 1; + break; + } + } + + UnLockThread(); + + return 1; +}; + + +void Interfaces::PowerOnOff(int onoff) { + int i; + + LockThread(); + + for (i = 0; i < max; i++) if (interfaces[i].name[0] != 0) { + if (!interfaces[i].IsConnected()) interfaces[i].Connect(); + interfaces[i].PowerOnOff(onoff); + } + + UnLockThread(); +}; + +// +// Turnouts +// + +void Interfaces::SetTurnout(Turnout *t, int active, int motoractive) { + int i; + + LockThread(); + + for (i = 0; i < max; i++) if (interfaces[i].name[0] != 0) { + if (!interfaces[i].IsConnected()) interfaces[i].Connect(); + if (strncmp(t->ifname, interfaces[i].name, REFERENCENAME_LEN) == 0) + interfaces[i].SetTurnout(t, active, motoractive); + } + + UnLockThread(); +}; + + +// +// Locomotives +// + +void Interfaces::SetLocoSpeed(Locomotive *l, int speed) { + int i; + int step = 0; + + if (abs(speed) < l->vmin) step = 0; + if (abs(speed) >= l->vmin) step = (speed * (l->steps-1)) / l->vmax; + if (abs(speed) > l->vmax) step = l->steps-1; + + l->speed = speed; + + LockThread(); + + for (i = 0; i < max; i++) if (interfaces[i].name[0] != 0) { + if (!interfaces[i].IsConnected()) interfaces[i].Connect(); + if (strncmp(l->ifname, interfaces[i].name, REFERENCENAME_LEN) == 0) + interfaces[i].SetLocoSpeed(l, step); + } + + UnLockThread(); +}; + + +void Interfaces::SetLocoFunction(Locomotive *l, int func, int value) { + int i; + + LockThread(); + + for (i = 0; i < max; i++) if (interfaces[i].name[0] != 0) { + if (!interfaces[i].IsConnected()) interfaces[i].Connect(); + if (strncmp(l->ifname, interfaces[i].name, REFERENCENAME_LEN) == 0) + interfaces[i].SetLocoFunction(l, func, value); + } + + UnLockThread(); +}; + + + +void Interfaces::Loop() { + int i; + JSONParse jout; + + for (i = 0; i < max; i++) if (interfaces[i].name[0] != 0) { + if (interfaces[i].Loop()) { + // + // now we need to send an update + jout.Clear(); + jout.AddObject ("interface", _GetJSON(i)); + if (network) network->ChangeListPushToAll(jout.ToString()); + } + } +}; + diff --git a/server/interface.h b/server/interface.h new file mode 100644 index 0000000..3ded1c6 --- /dev/null +++ b/server/interface.h @@ -0,0 +1,86 @@ + +#ifndef _INTERFACE_H_ +#define _INTERFACE_H_ + +#include "modelbahn.h" +#include "server.h" +#include "UDPTCPNetwork.h" +#include "json.h" +#include "interface-z21.h" + +#define INTF_F_CONNECTED 0x0001 +#define INTF_F_POWER 0x0002 +#define INTF_F_STOP 0x0004 +#define INTF_F_SHORT_CIRCUIT 0x0008 +#define INTF_F_PROGRAMMING 0x0010 +#define INTF_F_NEEDUPDATE 0x8000 // if something changes during the Loop + +#define INTF_T_OFF_UNKNOWN 0 +#define INTF_T_Z21 1 + + +class Interface { +private: + InterfaceZ21 intz21; + bool needs_update; +public: + char name[REFERENCENAME_LEN]; + char host[NET_HOSTLEN]; + int flags; + int type; + + Interface(); + ~Interface(); + + void Connect(); + void Disconnect(); + + void PowerOnOff(int onoff); + void SetLocoSpeed(Locomotive *l, int step); + void SetLocoFunction(Locomotive *l, int func, int value); + void SetTurnout(Turnout *t, int active, int motoractive); + + bool IsConnected(); + bool IsPoweron(); + + int Loop(); +}; + + +class Interfaces { + private: + Interface interfaces[INTERFACES_MAX]; + int max; + int changed; + + pthread_mutex_t mtx; + int LockThread(); + int UnLockThread(); + + // not thread safe + JSONParse _GetJSON(int idx); + public: + Interfaces(); + ~Interfaces(); + + bool IsChanged() { return changed; } + void ClearChanged() { changed = 0; }; + + int Change(Interface *iface); + int Delete(string name); + + JSONParse GetJSON(string name); + void GetJSONAll(JSONParse *json); + Interface GetInterfaceFromJSON(JSONParse *j); + + // + // + void PowerOnOff(int onoff); + void SetLocoSpeed(Locomotive *l, int speed); + void SetLocoFunction(Locomotive *l, int func, int value); + void SetTurnout(Turnout *t, int active, int motoractive); + + void Loop(); +}; + +#endif diff --git a/server/json.cc b/server/json.cc new file mode 100644 index 0000000..bfcf9af --- /dev/null +++ b/server/json.cc @@ -0,0 +1,494 @@ + +#include +#include +#include + +#include "debug.h" +#include "json.h" + +/*********************************************************************** + *********************************************************************** + * + * JSONParse + * + *********************************************************************** + *********************************************************************** + */ + +enum { + STEP_NONE = 0, + STEP_STARTNAME, + STEP_NAME, + STEP_STARTVALUE, + STEP_VALUE, + STEP_END +}; + + +///////////////////////////////////////////////////////// +// +// clear out all data +// +void JSONParse::Clear() { + jsondata = ""; + names.clear(); +} + +///////////////////////////////////////////////////////// +// +// read every element and keep only this in memory. +// +int JSONParse::Set(string json) { + int i; + int step; + int level; + bool ignorenext; + + JSONElement jelement; + + Clear(); + + // find start and read until end + for (step = STEP_NONE, i = 0, ignorenext = false; i < json.length(); i++) { + // need to copy next character +// debug (0, "JSONParse: step:%d i:%d name:'%s' value:'%s'", step, i, jelement.name.c_str(), jelement.value.c_str()); + if (ignorenext) { + ignorenext = false; + if (step == STEP_NAME) jelement.name += json[i]; + if (step == STEP_VALUE) jelement.value += json[i]; + } + + // searching for startname + else if (step == STEP_NONE) { + if (json[i] == '{') { + step = STEP_STARTNAME; + continue; + } + } + + // searching for startname + else if (step == STEP_STARTNAME) { + if (json[i] == '"') { + step = STEP_NAME; + continue; + } + } + + // copy name + else if (step == STEP_NAME) { + if (json[i] == '"') { + step = STEP_STARTVALUE; + continue; + } + else { + jelement.name += json[i]; + continue; + } + } + + // searching for startvalue + else if (step == STEP_STARTVALUE) { + if (json[i] == '"') { + step = STEP_VALUE; + jelement.type = JSON_T_STRING; + continue; + } + if (json[i] == '{') { + step = STEP_VALUE; + level = 0; + jelement.type = JSON_T_OBJECT; + jelement.value = "{"; + continue; + } + if (json[i] == '[') { + step = STEP_VALUE; + level = 0; + jelement.type = JSON_T_ARRAY; + jelement.value = "["; + continue; + } + if ((json[i] >= '0' && json[i] <= '9') || + (json[i] == '+' || json[i] == '-')) { + step = STEP_VALUE; + level = 0; + jelement.type = JSON_T_NUMBER; + jelement.value = json[i]; + continue; + } + } + + // copy value + else if (step == STEP_VALUE) { + if (jelement.type == JSON_T_STRING) { + if (json[i] == '"') step = STEP_END; + else jelement.value += json[i]; + continue; + } + else if (jelement.type == JSON_T_OBJECT) { + if (json[i] == '}' && level == 0) { + jelement.value += json[i]; + step = STEP_END; + } + else { + if (json[i] == '{') level++; // increase level + if (json[i] == '}') level--; // decrease level + jelement.value += json[i]; + } + continue; + } + else if (jelement.type == JSON_T_ARRAY) { + if (json[i] == ']' && level == 0) { + jelement.value += json[i]; + step = STEP_END; + } + else { + if (json[i] == '[') level++; // increase level + if (json[i] == ']') level--; // decrease level + jelement.value += json[i]; + } + continue; + } + else if (jelement.type == JSON_T_NUMBER) { + if ((json[i] < '0' || json[i] > '9') && json[i] != '.' && + json[i] != '+' && json[i] != 'e' && json[i] != 'E') step = STEP_END; + else { + jelement.value += json[i]; + continue; + } + } + } + + // another element? + if (step == STEP_END) { + if (json[i] == ',') { +// debug (0, "* JSON.Set Add name:%s", jelement.name.c_str()); + if (jelement.type != JSON_T_NONE) { +// debug (0, "%s:%d json add element type:%d", __FILE__, __LINE__, jelement.type); + names.push_back (jelement); + } + jelement.Clear(); + step = STEP_STARTNAME; + } + continue; + } + } +// debug (0, "* JSON.Set Add name:%s", jelement.name.c_str()); + if (jelement.type != JSON_T_NONE) { +// debug (0, "%s:%d json add element type:%d", __FILE__, __LINE__, jelement.type); + names.push_back (jelement); + } + + return 0; +}; + + +int JSONParse::GetValue(string varname, string *dest) { + list::iterator iter; + + if (dest == NULL) return 0; + *dest = ""; + + for (iter = names.begin(); iter != names.end(); iter++) { + if (varname.compare(iter->name) == 0) { + *dest = iter->value; + return 1; + } + } + + return 0; +}; + + +int JSONParse::GetValueInt(string varname, int *dest) { + string s; + int res = GetValue(varname, &s); + if (res) { + *dest = atoi (s.c_str()); + return 1; + } + return 0; +}; + + +int JSONParse::GetValueInt64(string varname, int64_t *dest) { + string s; + int res = GetValue(varname, &s); + if (res) { + *dest = atol (s.c_str()); + return 1; + } + return 0; +}; + + +int JSONParse::GetObject(string varname, JSONParse *dest) { + list::iterator iter; + + if (dest == NULL) return 0; + + for (iter = names.begin(); iter != names.end(); iter++) { + if (varname.compare(iter->name) == 0) { + dest->Set(iter->value); + return 1; + } + } + + return 0; +}; + + +#define MAXRECURSIVE 255 +int JSONParse::GetIdx(string src, int idx, string *dest) { + char recursive[MAXRECURSIVE]; + int i = 0, rcnt = 0, cnt = 0; + + (*dest) = ""; +// printf("\n***************************************idx:%d\n", idx); + + for (i = 0; i < MAXRECURSIVE; i++) recursive[i] = 0; + for (i = 0; i < src.length() && rcnt < MAXRECURSIVE && cnt <= idx; i++) { +// printf ("i:%d rcnt:%d['%c'] cnt:%d char:'%c' ous:'%s'\n", +// i, rcnt, recursive[rcnt], cnt, src[i], dest->c_str()); + if (src[i] == '[') { + recursive[rcnt++] = src[i]; + continue; + } + else if (src[i] == '{' && recursive[rcnt] != '"') recursive[++rcnt] = src[i]; + else if (src[i] == '}' && recursive[rcnt] == '{') rcnt--; + else if (src[i] == '"' && recursive[rcnt] == '"') rcnt--; + else if (src[i] == '"') recursive[++rcnt] = src[i]; + else if (src[i] == ',' && rcnt == 1) { + cnt++; + continue; + } + else if (src[i] == ']' && rcnt == 1) { + continue; + } + + if (rcnt > 0 && cnt == idx) { + (*dest) += src[i]; + if (src[i] == '\\') (*dest) += src[i]; + } + else { + if (src[i] == '\\')i++; + } + } + +// printf("\n***************************************idx:%d cnt:%d\n", idx, cnt); +// printf("in:'%s'\n***\nout:'%s'\n\n*****\n", src.c_str(), dest->c_str()); + + // + // final checks + if (cnt == 0 && idx == 0 && // empty source/array? + dest->size() == 0) return 0; // + if (cnt >= idx) return 1; // found the right element + return 0; // element not found +} +#undef MAXRECURSIVE + +int JSONParse::GetValueIdx(string varname, int idx, string *dest) { + list::iterator iter; + + if (dest == NULL) return 0; + + for (iter = names.begin(); iter != names.end(); iter++) { + if (varname.compare(iter->name) == 0) { + return GetIdx(iter->value, idx, dest); + } + } + + return 0; +}; + +int JSONParse::GetObjectIdx(string varname, int idx, JSONParse *dest) { + list::iterator iter; + string deststr; + int ret = 0; + + if (dest == NULL) return 0; + + for (iter = names.begin(); iter != names.end(); iter++) { + if (varname.compare(iter->name) == 0) { + ret = GetIdx(iter->value, idx, &deststr); + if (ret == 1) dest->Set(deststr); + return ret; + } + } + + return 0; +}; + +list JSONParse::GetElements() { + list l; + list::iterator iter; + + l.clear(); + for (iter = names.begin(); iter != names.end(); iter++) { + l.push_back(*iter); + } + + return l; +}; + + +void JSONParse::AddObject (JSONElement element) { + names.push_back (element); +}; + + +void JSONParse::AddObject (string name, JSONParse jp) { + JSONElement je; + je.SetObject(name, jp.ToString()); + names.push_back(je); +}; + + +void JSONParse::AddObject (string name, int val) { + JSONElement je; + je.Set(name, val); + names.push_back(je); +}; + + +void JSONParse::AddObject (string name, int64_t val) { + JSONElement je; + je.Set(name, val); + names.push_back(je); +}; + + +void JSONParse::AddObject (string name, string val) { + JSONElement je; + je.Set(name, val); + names.push_back(je); +}; + + +void JSONParse::AddObject (string name, double val) { + JSONElement je; + je.Set(name, val); + names.push_back(je); +}; + + +string JSONParse::ToString() { + list::iterator iter; + string output; + int level, i; + + output = "{"; + + for (level = 1, iter = names.begin(); iter != names.end(); iter++) { + if (iter != names.begin()) output += ","; + output += "\n"; + for (i = 0; i < 4*level; i++) output += " "; + output += iter->GetString(); + } + + output += "\n}\n"; + + return output; +}; + + +/*********************************************************************** + *********************************************************************** + * + * JSONElement + * + *********************************************************************** + *********************************************************************** + */ + + +void JSONElement::Set (string n, double v) { + name = n; + value = to_string(v); + type = JSON_T_NUMBER; +}; + + +void JSONElement::Set (string n, int v) { + name = n; + value = to_string(v); + type = JSON_T_NUMBER; +}; + + +void JSONElement::Set (string n, int64_t v) { + name = n; + value = to_string(v); + type = JSON_T_NUMBER; +}; + + +void JSONElement::Set (string n, string v) { + name = n; + value = v; + type = JSON_T_STRING; +}; + + +void JSONElement::SetArray (string n, list *l) { + list::iterator iter; + + name = n; + value = "["; + type = JSON_T_STRING; + + for (iter = l->begin(); iter != l->end(); iter++) { + if (iter != l->begin()) value += ","; + value += iter->GetString(); + } + value += "]"; +}; + + +void JSONElement::SetObject (string n, string s) { + name = n; + value = s; + type = JSON_T_OBJECT; +}; + + +string JSONElement::GetString () { + string output = ""; + string filename = __FILE__; + + switch (type) { + case(JSON_T_NUMBER): + output += "\"" + name + "\" : " + value; + break; + case(JSON_T_STRING): + if (value.length()==0) { + output += "\"" + name + "\" : \"\""; + } + // FIXME: we have to define all strings are saved here without "" + // will be set up in the future + // WORKAROUND for empty strings + else if (value[0] != '"') { + output += "\"" + name + "\" : \"" + value + "\""; + } + else output += "\"" + name + "\" : " + value; + break; + case(JSON_T_OBJECT): + output += "\"" + name + "\" : " + value; + break; + case(JSON_T_ARRAY): + if (value.length()==0) { + output += "\"" + name + "\" : []"; + } + // WORKAROUND for arrays without [] + else if (value[0] != '[') { + output += "\"" + name + "\" : [" + value + "]"; + } + else output += "\"" + name + "\" : " + value; + break; + default: + output += "\"error\" : \""+ filename + ":" + to_string(__LINE__) + " JSONElement unknown type error\"("+to_string(type)+")"; + break; + } + + return output; +}; + diff --git a/server/json.h b/server/json.h new file mode 100644 index 0000000..706d7dd --- /dev/null +++ b/server/json.h @@ -0,0 +1,72 @@ + +#ifndef _JSON_H_ +#define _JSON_H_ + +#include +#include +#include + +using namespace std; + +enum { + JSON_T_NONE, + JSON_T_STRING, + JSON_T_NUMBER, + JSON_T_OBJECT, + JSON_T_ARRAY +}; + +class JSONElement { +public: + int type; + string name; + string value; + + JSONElement() { Clear(); }; + ~JSONElement() {}; + + void Clear() { type = JSON_T_NONE; name = ""; value = ""; }; + void Set (string n, double v); + void Set (string n, int v); + void Set (string n, int64_t v); + void Set (string n, string v); + void SetArray (string n, list *l); + void SetObject (string n, string s); + string GetString(); +}; + +class JSONParse { +private: + string jsondata; + list names; + +public: + JSONParse() { Set("{}"); }; + JSONParse(string json) { Set(json); }; + ~JSONParse() {}; + + void Clear(); + int Set(string json); + + int GetValue(string varname, string *dest); + int GetValueInt(string varname, int *dest); + int GetValueInt64(string varname, int64_t *dest); + int GetObject(string varname, JSONParse *dest); + + int GetIdx(string src, int idx, string *dest); + int GetValueIdx(string varname, int idx, string *dest); + int GetObjectIdx(string varname, int idx, JSONParse *dest); + + list GetElements(); + + void AddObject (JSONElement element); + void AddObject (string name, int val); + void AddObject (string name, int64_t val); + void AddObject (string name, string val); + void AddObject (string name, double val); + void AddObject (string name, JSONParse jp); + + string ToString(); +}; + +#endif // _JSON_H_ diff --git a/server/locomotive.cc b/server/locomotive.cc new file mode 100644 index 0000000..93be245 --- /dev/null +++ b/server/locomotive.cc @@ -0,0 +1,269 @@ + + +#include "modelbahn.h" +#include "locomotive.h" + + +Locomotives::Locomotives () { + changed = 0; + locomotives = (Locomotive*) malloc(sizeof(Locomotive)*LOCOMOTIVES_MAX); + memset(locomotives, 0x0, sizeof(Locomotive)*LOCOMOTIVES_MAX); + max = SENSORS_MAX; +}; + +Locomotives::~Locomotives() { + free (locomotives); + locomotives = NULL; + max = 0; +}; + + +int Locomotives::Lock() { + if (pthread_mutex_lock(&mtx) == 0) return 1; + else return 0; +} + + +int Locomotives::UnLock() { + if (pthread_mutex_unlock(&mtx) == 0) return 1; + else return 0; +} + + +JSONParse Locomotives::_GetJSON(int idx) { + JSONParse json; + JSONElement je; + string s = ""; + + json.Clear(); + + s = locomotives[idx].name; json.AddObject("name", s); + s = locomotives[idx].ifname; json.AddObject("ifname", s); + json.AddObject("addr", locomotives[idx].addr); + json.AddObject("steps", locomotives[idx].steps); + json.AddObject("speed", locomotives[idx].speed); + json.AddObject("func", locomotives[idx].func); + json.AddObject("flags", locomotives[idx].flags); + json.AddObject("vmin", locomotives[idx].vmin); + json.AddObject("vslow", locomotives[idx].vslow); + json.AddObject("vmid", locomotives[idx].vmid); + json.AddObject("vfast", locomotives[idx].vfast); + json.AddObject("vmax", locomotives[idx].vmax); + + return json; +}; + + +JSONParse Locomotives::GetJSON(string name) { + int i; + JSONParse jp; + + jp.Clear(); + + Lock(); + for (i = 0; i < max; i++) if (locomotives[i].name[0] != 0) { + if (name.compare(locomotives[i].name) == 0) { + jp = _GetJSON(i); + } + } + + UnLock(); + + return jp; +}; + + +void Locomotives::GetJSONAll(JSONParse *json) { + int i, cnt; + JSONElement je; + + Lock(); + + // + // write all railway data + // create json object array manualy + je.type = JSON_T_ARRAY; + je.name = "locomotives"; + for (cnt = 0, i = 0; i < max; i++) + if (locomotives[i].name[0] != 0) { + if (cnt != 0) je.value += ","; // not first element + je.value += _GetJSON(i).ToString(); + cnt++; + } + json->AddObject(je); + + UnLock(); +}; + + +Locomotive Locomotives::GetLocomotiveFromJSON(JSONParse *j) { + Locomotive l; + string s; + + l.name[0] = 0; + l.ifname[0] = 0; + l.addr = 0; + l.steps = 0; + l.vmin = 0; + l.vslow = 0; + l.vmid = 0; + l.vfast = 0; + l.vmax = 0; + l.flags = 0; + l.speed = 0; + l.func = 0; + + j->GetValue("name", &s); + strncpy (l.name, s.c_str(), REFERENCENAME_LEN); + j->GetValue("ifname", &s); + strncpy (l.ifname, s.c_str(), REFERENCENAME_LEN); + j->GetValueInt("addr", &l.addr); + j->GetValueInt("steps", &l.steps); + j->GetValueInt("speed", &l.speed); + j->GetValueInt64("func", &l.func); + j->GetValueInt("flags", &l.flags); + j->GetValueInt("vmin", &l.vmin); + j->GetValueInt("vslow", &l.vslow); + j->GetValueInt("vmid", &l.vmid); + j->GetValueInt("vfast", &l.vfast); + j->GetValueInt("vmax", &l.vmax); + + return l; +}; + + +int Locomotives::Change(Locomotive *loco) { + int i; + int ifree = -1; + + Lock(); + + for (i = 0; i < max; i++) { + if (locomotives[i].name[0] != 0) { + // found element + if (strncmp(locomotives[i].name, loco->name, REFERENCENAME_LEN) == 0) { + ifree = i; + break; + } + } + else if (ifree == -1) ifree = i; + } + // element not found add new element + if (ifree != -1 && ifree < max) { + locomotives[ifree] = *loco; + strncpy (locomotives[ifree].name, loco->name, REFERENCENAME_LEN); + strncpy (locomotives[ifree].ifname, loco->ifname, REFERENCENAME_LEN); + } + + changed = 1; + UnLock(); + + return 1; +}; + + +int Locomotives::Delete(string name) { + int i; + + Lock(); + for (i = 0; i < max; i++) if (locomotives[i].name[0] != 0) { + if (name.compare(locomotives[i].name) == 0) { + locomotives[i].name[0] = 0; + locomotives[i].ifname[0] = 0; + locomotives[i].addr = 0; + locomotives[i].steps = 0; + locomotives[i].vmin = 0; + locomotives[i].vslow = 0; + locomotives[i].vmid = 0; + locomotives[i].vfast = 0; + locomotives[i].vmax = 0; + locomotives[i].flags = 0; + changed = 1; + break; + } + } + + UnLock(); + + return 1; +}; + + +int Locomotives::SetSpeed(string name, int speed) { + int i; + + Lock(); + for (i = 0; i < max; i++) if (locomotives[i].name[0] != 0) { + if (name.compare(locomotives[i].name) == 0) { + server->interfaces.SetLocoSpeed(&locomotives[i], speed); + break; + } + } + + UnLock(); + + return 1; +}; + + +int Locomotives::SetFunction(string name, int func, int value) { + int i; + + Lock(); + for (i = 0; i < max; i++) if (locomotives[i].name[0] != 0) { + if (name.compare(locomotives[i].name) == 0) { + server->interfaces.SetLocoFunction(&locomotives[i], func, value); + break; + } + } + + UnLock(); + + return 1; +}; + + + +// +// set values from bus... +// +int Locomotives::SetSpeedFromBus(string name, int addr, int speed) { + int i; + JSONParse jp; + + for (i = 0; i < max; i++) if (locomotives[i].name[0] != 0) { + if (name.compare(locomotives[i].name) == 0 && locomotives[i].addr == addr) { + locomotives[i].speed = speed; + + jp.AddObject("locomotive",_GetJSON(i)); + if(network) network->ChangeListPushToAll(jp.ToString()); + + return 1; + } + } + return 0; +}; + + +int Locomotives::SetDirectionFromBus(string name, int addr, int reverse) { + int i; + JSONParse jp; + + for (i = 0; i < max; i++) if (locomotives[i].name[0] != 0) { + if (name.compare(locomotives[i].name) == 0 && locomotives[i].addr == addr) { + if (reverse) locomotives[i].flags |= LOCO_F_REVERSE; + else locomotives[i].flags &= ~LOCO_F_REVERSE; + + jp.AddObject("locomotive",_GetJSON(i)); + if(network) network->ChangeListPushToAll(jp.ToString()); + + return 1; + } + } + return 0; +}; + + + + + diff --git a/server/locomotive.h b/server/locomotive.h new file mode 100644 index 0000000..aa9d436 --- /dev/null +++ b/server/locomotive.h @@ -0,0 +1,59 @@ + +#ifndef _LOCOMOTIVE_H_ +#define _LOCOMOTIVE_H_ + +#include "modelbahn.h" +#include "server.h" + +#define LOCO_F_REVERSE 0x0001 + +struct s_Locomotive { + char name[REFERENCENAME_LEN]; + char ifname[REFERENCENAME_LEN]; + int steps; + int speed; + int64_t func; + int flags; + int addr; + int vmin; + int vslow; + int vmid; + int vfast; + int vmax; +} typedef Locomotive; + +class Locomotives { + private: + Locomotive *locomotives; + int max; + int changed; + + pthread_mutex_t mtx; + int Lock(); + int UnLock(); + + // not thread safe + JSONParse _GetJSON(int idx); + + public: + Locomotives(); + ~Locomotives(); + + bool IsChanged() { return changed; } + void ClearChanged() { changed = 0; }; + + int Change(Locomotive *loco); + int Delete(string name); + int SetSpeed(string name, int speed); + int SetFunction(string name, int func, int value); + + int SetSpeedFromBus (string name, int addr, int speed); + int SetDirectionFromBus (string name, int addr, int speed); + + JSONParse GetJSON(string name); + void GetJSONAll(JSONParse *json); + Locomotive GetLocomotiveFromJSON(JSONParse *j); +}; + + +#endif diff --git a/server/main.cc b/server/main.cc new file mode 100644 index 0000000..945f9f0 --- /dev/null +++ b/server/main.cc @@ -0,0 +1,120 @@ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "modelbahn.h" + +void uprintf (UNIX *u, char *fmt,...); +static void sig_int(int); // signal handler +int running = 1; + +Server *server = NULL; +Network *network = NULL; + +int main (int argc, char **argv) { + int ret; + + // + // setup signals + // + if (signal(SIGINT, sig_int) == SIG_ERR) { + fprintf (stderr, "%s:%d could not set signal for SIGINT\n", __FILE__, __LINE__); + return 0; + } + if (signal(SIGTERM, sig_int) == SIG_ERR) { + fprintf (stderr, "%s:%d could not set signal for SIGTERM\n", __FILE__, __LINE__); + return 0; + } + if (signal(SIGHUP, sig_int) == SIG_ERR) { + fprintf (stderr, "%s:%d could not set signal for SIGHUB\n", __FILE__, __LINE__); + return 0; + } + + + debug (0, "***************************************************"); + debug (0, "* *"); + debug (0, "* Modelbahn Server *"); + debug (0, "* *"); + debug (0, "***************************************************"); + debug (0, ""); + + + ////////////////////////////////////////////////////////////////////// + // + // application startup + // + debug (0, "* application startup"); + server = new Server(); + network = new Network(); + + + ////////////////////////////////////////////////////////////////////// + // + // application loop + // + debug (0, "* application loop"); + server->Start(); + network->Start(); + while (running) { + sleep (1); + } + + ////////////////////////////////////////////////////////////////////// + // + // application shutdown + // + while (server->isRunning() || network->isRunning()); + debug (0, "* application shutdown"); + delete network; + debug (0, "* deleted network"); + delete server; + debug (0, "* deleted server"); + debug (0, "* application exited"); + + return 0; +}; + + +void uprintf (UNIX *u, char *fmt,...) { + va_list args; + char buffer[BUFFERSIZE]; + + va_start (args, fmt); + vsnprintf (buffer, (BUFFERSIZE-1), fmt, args); + va_end (args); + buffer[BUFFERSIZE-1] = 0; + u->Write(buffer, strlen (buffer)); +}; + +static void sig_int(int sig) { + debug (0, "* signal:%d received", sig); + + if (signal (SIGINT, sig_int) == SIG_ERR) { + fprintf (stderr, "%s:%d could not set up signal SIGINT\n", __FILE__, __LINE__); + exit (-1); + } + + running = 0; +} + + +void timer_start(struct timeval *tv) { + gettimeofday(tv, NULL); +}; + + +int timer_end(struct timeval *tv) { + struct timeval tvnow; + + gettimeofday(&tvnow, NULL); + + return ((tvnow.tv_sec - tv->tv_sec)*1000 + (tvnow.tv_usec - tv->tv_usec)/1000); +}; diff --git a/server/modelbahn-cgi.cc b/server/modelbahn-cgi.cc new file mode 100644 index 0000000..0f51855 --- /dev/null +++ b/server/modelbahn-cgi.cc @@ -0,0 +1,64 @@ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#define UNIX_SOCKET_FILE "/tmp/modelbahn.socket" +#define LOG_FILE "/tmp/modelbahn-cgi.log" +#define BUFFERSIZE 64000 + +//extern char **environ; + +int main (int argc, char **argv) { + int i, inlen; + FILE *logf; + char buffer[BUFFERSIZE+1]; + char *ptr = NULL; + + printf ("Content-type: text/html\n"); + printf ("Expires: now\n"); + printf ("Pragma: no-cache\n"); + printf ("\n"); + + // + // open logfile for writing + logf = fopen(LOG_FILE, "a+"); + if (logf == NULL) return 0; + fprintf (logf, "*************************************\n"); + + // + // read data + memset (buffer, 0x0, BUFFERSIZE+1); + // FIXME: fgets is wrong.... + for (ptr = buffer, inlen = 0; inlen < BUFFERSIZE-1 && (fgets (ptr, BUFFERSIZE - inlen, stdin)) > 0;) { + inlen = strlen (buffer); + ptr = buffer+inlen; + } + if (inlen >= BUFFERSIZE-1) fprintf (logf, "read input puffer full.\n"); + fprintf (logf, "read from stdin %d bytes\n", strlen(buffer)); + + // + // send data to server + UNIX u; + if (u.Connect(UNIX_SOCKET_FILE) != 1) return 0; + u.Write(buffer, strlen(buffer)); + + // + // read data and send back to the web server + do { + i = u.ReadTimeout(buffer, BUFFERSIZE-1, 1000); + buffer[i] = 0; + fprintf (logf, "read from server %d bytes\n", i); + printf ("%s", buffer); + } while (i == BUFFERSIZE-1); + u.Close(); + + fclose (logf); + return 0; +}; diff --git a/server/modelbahn.h b/server/modelbahn.h new file mode 100644 index 0000000..2fdbd48 --- /dev/null +++ b/server/modelbahn.h @@ -0,0 +1,40 @@ + +#ifndef _MODELBAHN_H_ +#define _MODELBAHN_H_ + +#define TIMEOUT_REFRESH 1 +#define BUFFERSIZE 64000 +#define MAXWAITTIME 500 +#define UNIX_SOCKET_FILE "/tmp/modelbahn.socket" +#define DEFAULT_DATADIR "/home/steffen/Dokumente/Programmierung/Modelbahn/" +#define SESSIONS_MAX 8 + +#define REFERENCENAME_LEN 128 +#define INTERFACES_MAX 8 +#define TURNOUTS_MAX 255 +#define LOCOMOTIVES_MAX 255 +#define SENSORS_MAX 255 +#define BLOCKS_MAX 128 + +#include "server.h" +#include "turnout.h" +#include "sensor.h" +#include "network.h" +#include "block.h" +#include "debug.h" + +extern int running; + +extern Server *server; +extern Network *network; + + +// +// to measure the time in ms (used for debugging) +// +void timer_start(struct timeval *tv); +int timer_end(struct timeval *tv); + + +#endif // _MODELBAHN_H_ + diff --git a/server/network.cc b/server/network.cc new file mode 100644 index 0000000..dcbcb7e --- /dev/null +++ b/server/network.cc @@ -0,0 +1,279 @@ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "modelbahn.h" +#include "network.h" +#include "json.h" + +Network::Network() { + thread = 0; + sessions.clear(); + thread_running = 0; + mtx = { 0 }; + mtx = PTHREAD_MUTEX_INITIALIZER; +} + +Network::~Network() { + list::iterator iter; + + while ((iter = sessions.begin()) != sessions.end()) { + Session *s = *iter; + sessions.remove(*iter); + delete s; + } + Stop(); +}; + + + +int Network::LockThread() { + if (pthread_mutex_lock(&mtx) == 0) return 1; + else return 0; +} + + +int Network::UnLockThread() { + if (pthread_mutex_unlock(&mtx) == 0) return 1; + else return 0; +} + + + +void Network::ThreadProcess() { + list::iterator iteru; + Session *s; + UNIX *u; + + while (running) { + // + // check network + + // + // server socket + ServerLoop(); + + // + // client socket + for (iteru = clients.begin(); iteru != clients.end(); iteru++) { + if (ClientLoop((UNIX*) *iteru) < 0) { + u = *iteru; + clients.remove(u); + delete u; + iteru = clients.begin(); + } + } + + usleep (100000); + } + thread_running = 0; +}; + + +int Network::Start() { + int err; + pthread_attr_t attr; + mtx = { 0 }; + mtx = PTHREAD_MUTEX_INITIALIZER; + + // + // start socket server + // + if (sockserver.Listen(UNIX_SOCKET_FILE) != 1) { + return 0; + } + chmod(UNIX_SOCKET_FILE, 00666); + + thread_running = 1; + pthread_attr_init (&attr); + pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_JOINABLE); + err = pthread_create (&thread, &attr, ThreadEntry, this); + if (err != 0) { + debug (DEBUG_ERROR, (char*)"%s(%s:%d) pthread_create errror: %s", __FUNCTION__, __FILE__, __LINE__, strerror (errno)); + running = 0; + return 0; + } + return 1; +}; + + +void Network::Stop() { + sockserver.Close(); + unlink(UNIX_SOCKET_FILE); +} + + +void Network::_ChangeListPushToAll(string changes) { + list::iterator iter; + + for (iter = sessions.begin(); iter != sessions.end(); iter++) { + if ((*iter)->GetSessionID() > 0) (*iter)->ChangeListPush(changes); + } +}; + + +void Network::ChangeListPushToAll(string changes) { + LockThread(); + _ChangeListPushToAll(changes); + UnLockThread(); +}; + + + +int Network::ServerLoop() { + UNIX *u = NULL; + + if (sockserver.IsData(10)) { + u = sockserver.Accept(); + clients.push_back(u); + } + + return 1; +}; + + +Session *Network::GetSession(int sid) { + Session *s = NULL; + list::iterator iter; + + for (iter = sessions.begin(); iter != sessions.end(); iter++) { + if ((*iter)->GetSessionID() == sid) { + s = *iter; + break; + } + } + + return s; +}; + + +int Network::ClientLoop(UNIX *client) { + char bufferin[BUFFERSIZE]; + char bufferout[BUFFERSIZE]; + int len, i, rid, sid; + int res; + list::iterator siter; + Session *session = NULL; + string value; + string ssid; + string srid; + string s; + int result = -1; + struct timeval timer; + + timer_start(&timer); + + len = client->ReadTimeout(bufferin, BUFFERSIZE, 20); + if (len > 0 && len < BUFFERSIZE) { + JSONParse json; + JSONParse jsonout; + JSONParse jelement; + + bufferin[len] = 0; // prevent reading behind the data + + json.Set(bufferin); + if (!json.GetValue("sid", &ssid)) { + debug (0, "json.GetValue error --> sid"); + return 0; + } + + if (!json.GetValue("rid", &srid)) { + debug (0, "json.GetValue error --> rid"); + return 0; + } + + // + try { + rid = stoi (srid); + } + catch (...) { + rid = 0; + debug (0, "* rid error"); + } + + try { + sid = stoi (ssid); + } + catch (...) { + sid = 0; + debug (0, "* sid error"); + } + + if (sid <= 0) { + int x, y; + + debug (0, "* sid not set, gettin new SID"); + session = new Session(rid); + sid = session->GetSessionID(); + LockThread(); + sessions.push_back(session); + UnLockThread(); + } + else { + LockThread(); + // + // search for session + session = NULL; + for (siter = sessions.begin(); siter != sessions.end(); siter++) { + if ((*siter)->GetRandomID() == rid && ((Session*)(*siter))->GetSessionID() == sid) { + session = (*siter); + break; + } + } + UnLockThread(); + } + + if (session) { + JSONElement je; + int retval; + + LockThread(); + retval = session->ProcessData(&json, &jsonout); + UnLockThread(); + + len = BUFFERSIZE; + if (retval) { + je.Clear(); + je.Set("success", 1); + jsonout.AddObject(je); + } + else { + je.Clear(); + je.Set("success", 0); + jsonout.AddObject(je); + } + s = jsonout.ToString(); + client->Write((char*)s.c_str(), strlen(s.c_str())); + result = 1; + } + else { + // no session found + result = 0; + } + } + else if (len == 0) { + result = 0; + } + + return result; +}; + diff --git a/server/network.h b/server/network.h new file mode 100644 index 0000000..1881a46 --- /dev/null +++ b/server/network.h @@ -0,0 +1,111 @@ + +#ifndef _NETWORK_H_ +#define _NETWORK_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; + +#include "modelbahn.h" +#include "json.h" + + +extern int next_sessionID; + + +class Session { +private: + int sessionID; + int randomID; + list changes; + + void AddJSONRailway(JSONParse *jp); + void DelJSONRailway(JSONParse *jp); + + void AddJSONTurnout(JSONParse *jp); + void DelJSONTurnout(JSONParse *jp); + void SetJSONTurnout(JSONParse *jp); + + void AddJSONSensor(JSONParse *jp); + void DelJSONSensor(JSONParse *jp); + + void AddJSONInterface(JSONParse *jp); + void DelJSONInterface(JSONParse *jp); + + void AddJSONLocomotive(JSONParse *jp); + void DelJSONLocomotive(JSONParse *jp); + void SetJSONLocomotive(JSONParse *jp); + +public: + Session(int rid); + ~Session(); + + // changed which need to be send to the client // + void ChangeListPush(string chng); // push ChangeList (String must be JSON format) + JSONElement ChangeListGet(); // get ChangeList as JSON and clear list + void ChangesListClear(); // clear out Change list + + int GetSessionID() { return sessionID; }; + int GetRandomID() { return randomID; }; + + int SendData(UNIX *u, string data); + + int ProcessData(JSONParse *jin, JSONParse *jout); +}; + + +class Network { +private: + void ThreadProcess(); + pthread_mutex_t mtx; + pthread_t thread; + int thread_running; + + list sessions; + list clients; + + Session *GetSession(int Sid); + int ServerLoop(); // loop for network connection and call clientloop if needed + int ClientLoop(UNIX *u); + UNIX sockserver; + + void _ChangeListPushToAll (string changes); // not thread save + friend class Session; +public: + Network(); + ~Network(); + + int LockThread(); + int UnLockThread(); + + int Start(); + void Stop(); + void ChangeListPushToAll (string changes); // adds JSON compat. change string too all clients + + int isRunning() { return thread_running; } +protected: + static void *ThreadEntry (void *This) { ((Network*)This)->ThreadProcess(); return NULL;}; +}; + + +#endif // _NETWORK_H_ diff --git a/server/railway.cc b/server/railway.cc new file mode 100644 index 0000000..568004b --- /dev/null +++ b/server/railway.cc @@ -0,0 +1,295 @@ + +#include "modelbahn.h" +#include "server.h" +#include "debug.h" + +Railways::Railways() { + debug (0, "* Railways Constructor"); + height = 0; + width = 0; + railways = NULL; + mtx = { 0 }; + mtx = PTHREAD_MUTEX_INITIALIZER; + changed = 0; +}; + + +Railways::~Railways() { + if (railways) free (railways); + height = 0; + width = 0; +}; + + +// +// thread protection: lock +int Railways::Lock() { + if (pthread_mutex_lock(&mtx) == 0) return 1; + else return 0; +}; + + +// +// thread protection: unlock +int Railways::UnLock() { + if (pthread_mutex_unlock(&mtx) == 0) return 1; + else return 0; +}; + + + +// +// return index inside the array (clip to boundery) +int Railways::GetRIdx(int x, int y) { + int _x, _y; + + if (x < 0) _x = 0; + else if (x >= width) _x = width-1; + else _x = x; + + if (y < 0) _y = 0; + else if (y >= height) _y = height-1; + else _y = y; + + return (_x + _y * width); +}; + + +// +// return JSON strign with the Railway data +// not thread safe !!!! +JSONParse Railways::_GetJSONRailway(int x, int y) { + JSONParse json; + JSONElement je; + int idx = GetRIdx(x,y); + string name = ""; + + json.Clear(); + json.AddObject("x", x); + json.AddObject("y", y); + json.AddObject("dir", railways[idx].dir); + json.AddObject("altdir", railways[idx].altdir); + json.AddObject("type", railways[idx].type); + json.AddObject("maxspeed", railways[idx].maxspeed); + json.AddObject("flags", railways[idx].flags); + name = railways[idx].name; + json.AddObject("name", name); + + return json; +}; + + +// +// return JSON strign with the Railway data +// thread safe. +JSONParse Railways::GetJSONRailway(int x, int y) { + JSONParse json; + + Lock(); + json = _GetJSONRailway(x, y); + UnLock(); + + return json; +}; + + +Railway Railways::GetRailwayFromJSON(JSONParse *j) { + string name; + Railway r = { + type: RAILWAY_NOTHING, + x: 0, + y: 0, + dir: 0, + altdir: 0, + maxspeed: -1, + flags: 0 + }; + r.name[0] = 0; + + j->GetValueInt("x", &r.x); + j->GetValueInt("y", &r.y); + j->GetValueInt("dir", &r.dir); + j->GetValueInt("altdir", &r.altdir); + j->GetValueInt("type", &r.type); + j->GetValueInt("maxspeed", &r.maxspeed); + j->GetValueInt("flags", &r.flags); + j->GetValue("name", &name); + strncpy (r.name, name.c_str(), REFERENCENAME_LEN); + + return r; +}; + + +// +// return some data about the railway/tracks +// +JSONParse Railways::GetJSONTrack() { + JSONParse json; + JSONParse jsondata; + JSONElement je; + + json.Clear(); + json.AddObject("height", height); + json.AddObject("width", width); + + return json; +}; + +// +// change railway +// return 1 on change +int Railways::Change(Railway *rw) { + int x, y; + int result = 1; + JSONParse json; + + if (rw == NULL) return 0; + Lock(); + + if (rw->x < 0 || rw->x >= width) result = 0; + else if (rw->y < 0 || rw->y >= height) result = 0; + else if (rw->dir < 0 || rw->dir > 6) result = 0; + else { + changed = true; + result = 1; + + if (rw->dir == 0) rw->type == RAILWAY_NOTHING; + + // + // need to delete? + if (rw->type == RAILWAY_NOTHING) { + Railway *r = &railways[GetRIdx(rw->x, rw->y)]; + + r->type = RAILWAY_NOTHING; + r->dir = 0; + r->altdir = 0; + r->flags = 0; + r->maxspeed = 0; + r->x = rw->x; + r->y = rw->y; + r->name[0] = 0; + } + + // + // add normal railway + else if (rw->type == RAILWAY_NORMAL) { + railways[GetRIdx(rw->x, rw->y)] = *rw; + } + + // + // add normal railway + else if (rw->type < RAILWAY_MAX) { + railways[GetRIdx(rw->x, rw->y)] = *rw; + } + + else { + debug (DEBUG_INFO, "%s:%d add unknown %d,%d dir(%d) type:%d", __FILE__, __LINE__, rw->x, rw->y, rw->dir, rw->type); + } + } + + UnLock(); + return result; +}; + +// +// redefine the size of the track, keep data +void Railways::SetSize (int neww, int newh) { + Railway *rarray = railways; + int ow = width; + int oh = height; + int x, y; + + debug (0, "* Railways::SetSize (%d, %d)", neww, newh); + + Lock(); + + // + // old values saved during call up of the function, create new array for the railways data + railways = NULL; + _New (neww, newh); + + // + // copy old data + for (x = 0; x < ow && x < width; x++) + for (y = 0; y < oh && x < height; y++) + railways[x + y * width] = rarray[x + y * oh]; + free (rarray); + UnLock(); +}; + + +void Railways::_New (int neww, int newh) { + int x, y; + + debug (0, "* Railways New"); + + // + // clip to minimum and maximum sizes + if (neww < RAILWAYS_MIN_WIDTH) width = RAILWAYS_MIN_WIDTH; + else if (neww > RAILWAYS_MAX_WIDTH) width = RAILWAYS_MAX_WIDTH; + else width = neww; + if (newh < RAILWAYS_MIN_HEIGHT) width = RAILWAYS_MIN_HEIGHT; + else if (newh > RAILWAYS_MAX_HEIGHT) width = RAILWAYS_MAX_HEIGHT; + else height = newh; + + // + // if memory is already allocated free it first + if (railways != NULL) free (railways); + + // + // allocate memory and reset memory + railways = (Railway*) malloc (sizeof (Railway) * width * height); + for (x = 0; x < width; x++) for (y = 0; y < height; y++) { + Railway *r = &railways[GetRIdx(x, y)]; + r->type = RAILWAY_NOTHING; + r->dir = 0; + r->altdir = 0; + r->flags = 0; + r->maxspeed = 0; + r->x = x; + r->y = y; + r->name[0] = 0; + } +}; + +void Railways::New (int neww, int newh) { + Lock(); + _New (neww, newh); + UnLock(); +}; + + +// +// append all data to the givin jasonobject +void Railways::GetJSONAll(JSONParse *json) { + int x, y; + + if (json == NULL) return; + + Lock(); + + json->AddObject("track", GetJSONTrack()); + for (y = 0; y < height; y++) + for (x = 0; x < width; x++) + if (railways[GetRIdx(x, y)].type != RAILWAY_NOTHING) { + json->AddObject("railway", _GetJSONRailway(x, y)); + } + + UnLock(); +} + +// +// get railway element +// thread safe +Railway Railways::Get(int x, int y) { + Railway r; + + Lock(); + r = railways[GetRIdx(x, y)]; + UnLock(); + + return r; +}; + + + diff --git a/server/railway.h b/server/railway.h new file mode 100644 index 0000000..f8ad975 --- /dev/null +++ b/server/railway.h @@ -0,0 +1,95 @@ + +#ifndef _RAILWAY_H_ +#define _RAILWAY_H_ + +#include "modelbahn.h" +#include "server.h" + +enum { + RAILWAY_NOTHING = 0, + RAILWAY_NORMAL, + RAILWAY_CROSSING, + RAILWAY_TURNOUT, + RAILWAY_SENSOR, + RAILWAY_CONNECTOR, + RAILWAY_BUTTON, // just a simple button on the track to press + RAILWAY_TEXT, + RAILWAY_BLOCK, + + RAILWAY_MAX +}; + + +#define RAILWAYS_MIN_WIDTH 40 +#define RAILWAYS_MIN_HEIGHT 25 +#define RAILWAYS_MAX_WIDTH 500 +#define RAILWAYS_MAX_HEIGHT 500 + + +// direktion +// +// +---+ +---+ +---+ +---+ +// | | | | | | | |/ | +// 0| | 1| | | 2|---| 3| | +// | | | | | | | | | +// +---+ +---+ +---+ +---+ +// +// +---+ +---+ +---+ +---+ +// | \| | | | | | | +// 4| | 5| | 6| | 7| | +// | | | /| |\ | | | +// +---+ +---+ +---+ +---+ +// + + +struct s_Railway { + int type; + int x; + int y; + int dir; + int altdir; // turnout or crossing + int maxspeed; + int flags; // not defined yet + char name[REFERENCENAME_LEN]; // reference name +} typedef Railway; + + +class Railways { + private: + Railway *railways; + + int width; + int height; + int changed; + + pthread_mutex_t mtx; + int Lock(); + int UnLock(); + int GetRIdx(int x, int y); + JSONParse _GetJSONRailway(int x, int y); + void _New (int w, int h); + public: + + Railways(); + ~Railways(); + + void New (int w, int h); + void SetSize (int w, int h); + Railway Get(int x, int y); + Railways* GetBlock(int x, int y, int w, int h); + int Set(Railway *rw); + int SetBlock(Railway *rw, int cnt); + int GetHeight () {return height;}; + int GetWidth () {return width;}; + int IsChanged() { return changed; }; + void ClearChanged() { changed = 0; }; + int Change(Railway *rw); + + Railway RailwayGet(int x, int y) {return railways[GetRIdx(x, y)];}; + JSONParse GetJSONRailway(int x, int y); + JSONParse GetJSONTrack(); + void GetJSONAll(JSONParse *json); + Railway GetRailwayFromJSON(JSONParse *j); +}; + +#endif diff --git a/server/sensor.cc b/server/sensor.cc new file mode 100644 index 0000000..184bc88 --- /dev/null +++ b/server/sensor.cc @@ -0,0 +1,190 @@ + + +#include "modelbahn.h" +#include "sensor.h" + + +Sensors::Sensors () { + changed = 0; + sensors = (Sensor*) malloc(sizeof(Sensor)*SENSORS_MAX); + max = SENSORS_MAX; +}; + +Sensors::~Sensors() { + free (sensors); + sensors = NULL; + max = 0; +}; + + + + +int Sensors::Lock() { + if (pthread_mutex_lock(&mtx) == 0) return 1; + else return 0; +} + + +int Sensors::UnLock() { + if (pthread_mutex_unlock(&mtx) == 0) return 1; + else return 0; +} + + +JSONParse Sensors::_GetJSON(int idx) { + JSONParse json; + JSONElement je; + string s = ""; + + json.Clear(); + + s = sensors[idx].name; json.AddObject("name", s); + s = sensors[idx].ifname; json.AddObject("ifname", s); + json.AddObject("addr", sensors[idx].addr); + json.AddObject("flags", sensors[idx].flags); + + return json; +}; + + +JSONParse Sensors::GetJSON(string name) { + int i; + JSONParse jp; + + jp.Clear(); + + Lock(); + for (i = 0; i < max; i++) if (sensors[i].name[0] != 0) { + if (name.compare(sensors[i].name) == 0) { + jp = _GetJSON(i); + } + } + + UnLock(); + + return jp; +}; + + +void Sensors::GetJSONAll(JSONParse *json) { + int i, cnt; + JSONElement je; + + Lock(); + + // + // write all railway data + // create json object array manualy + je.type = JSON_T_ARRAY; + je.name = "sensors"; + for (cnt = 0, i = 0; i < max; i++) + if (sensors[i].name[0] != 0) { + if (cnt != 0) je.value += ","; // not first element + je.value += _GetJSON(i).ToString(); + cnt++; + } + json->AddObject(je); + + UnLock(); +}; + + +Sensor Sensors::GetSensorFromJSON(JSONParse *j) { + Sensor se; + string s; + + se.name[0] = 0; + se.ifname[0] = 0; + se.addr = 0; + se.flags = 0; + + j->GetValue("name", &s); + strncpy (se.name, s.c_str(), REFERENCENAME_LEN); + j->GetValue("ifname", &s); + strncpy (se.ifname, s.c_str(), REFERENCENAME_LEN); + j->GetValueInt("addr", &se.addr); + j->GetValueInt("flags", &se.flags); + + return se; +}; + + +int Sensors::Change(Sensor *se) { + int i; + int ifree = -1; + + Lock(); + + for (i = 0; i < max; i++) { + if (sensors[i].name[0] != 0) { + // found element + if (strncmp(sensors[i].name, se->name, REFERENCENAME_LEN) == 0) { + ifree = i; + break; + } + } + else if (ifree == -1) ifree = i; + } + // element not found add new element + if (ifree != -1 && ifree < max) { + sensors[ifree] = *se; + strncpy (sensors[ifree].name, se->name, REFERENCENAME_LEN); + strncpy (sensors[ifree].ifname, se->ifname, REFERENCENAME_LEN); + } + + changed = 1; + UnLock(); + + return 1; +}; + + +int Sensors::Delete(string name) { + int i; + + Lock(); + for (i = 0; i < max; i++) if (sensors[i].name[0] != 0) { + if (name.compare(sensors[i].name) == 0) { + sensors[i].name[0] = 0; + sensors[i].ifname[0] = 0; + sensors[i].addr = 0; + sensors[i].flags = 0; + changed = 1; + break; + } + } + + UnLock(); + + return 1; +}; + + +// +// got some information from an interface.. +void Sensors::SetFromBus(string name, int addr, int active) { + int i; + JSONParse jp; + + for (i = 0; i < max; i++) if (sensors[i].name[0] != 0) { + if (strncmp (sensors[i].ifname, name.c_str(), REFERENCENAME_LEN) == 0 && sensors[i].addr == addr) { + debug (0, "* Sensor %s changed:%d", sensors[i].name, active); + if (sensors[i].flags & SENSOR_F_INVERSE) { + if (active) sensors[i].flags &= ~SENSOR_F_ACTIVE; + else sensors[i].flags |= SENSOR_F_ACTIVE; + } + else { + if (active) sensors[i].flags |= SENSOR_F_ACTIVE; + else sensors[i].flags &= ~SENSOR_F_ACTIVE; + } + + jp.AddObject("sensor", _GetJSON(i)); + if(network) network->ChangeListPushToAll(jp.ToString()); + } + } + + jp.AddObject("infoline", "Sensor:"+name+"["+ to_string(addr) +"]"); + if(network) network->ChangeListPushToAll(jp.ToString()); + +}; + diff --git a/server/sensor.h b/server/sensor.h new file mode 100644 index 0000000..160e76b --- /dev/null +++ b/server/sensor.h @@ -0,0 +1,48 @@ + +#ifndef _SENSOR_H_ +#define _SENSOR_H_ + +#define SENSOR_F_INVERSE 0x0001 +#define SENSOR_F_ACTIVE 0x0002 + +#include "modelbahn.h" +#include "server.h" + +struct s_Sensor { + char name[REFERENCENAME_LEN]; + char ifname[REFERENCENAME_LEN]; + int addr; + int flags; +} typedef Sensor; + +class Sensors { + private: + Sensor *sensors; + int max; + int changed; + + pthread_mutex_t mtx; + int Lock(); + int UnLock(); + + // not thread safe + JSONParse _GetJSON(int idx); + public: + Sensors(); + ~Sensors(); + + bool IsChanged() { return changed; } + void ClearChanged() { changed = 0; }; + + int Change(Sensor *se); + int Delete(string name); + + JSONParse GetJSON(string name); + void GetJSONAll(JSONParse *json); + Sensor GetSensorFromJSON(JSONParse *j); + + void SetFromBus(string name, int addr, int active); +}; + + +#endif diff --git a/server/server-loadsave.cc b/server/server-loadsave.cc new file mode 100644 index 0000000..bc21f0a --- /dev/null +++ b/server/server-loadsave.cc @@ -0,0 +1,194 @@ + +#include +#include +#include + +#include "modelbahn.h" + +int Server::Load() { + string f = DEFAULT_DATADIR; + f = f + "/track.json"; + + Load (f); + + return 1; +}; + +int Server::Save() { + struct stat sbuf; + string f = DEFAULT_DATADIR; + string nf; + + // + // check if the folder does exists. + if (stat (f.c_str(), &sbuf) != 0) { + debug (DEBUG_ERROR, "* Track::Save could not find directory '%s'. Creating it.", f.c_str()); + if (mkdir (f.c_str(), 0755) != 0) { + debug (DEBUG_ERROR, "* Track::Save could not create directory '%s'. (%s)", f.c_str(), strerror(errno)); + return 0; + } + } + if ((sbuf.st_mode & S_IFMT) != S_IFDIR) { + debug (DEBUG_ERROR, "* Track::Save could not stat directory: '%s'. (%s)", f.c_str(), strerror(errno)); + if (errno != EINTR) return 0; + } + + // + // check if the file exsists + f = DEFAULT_DATADIR; f = f + "/track.json"; + nf = DEFAULT_DATADIR; nf = nf + "/track.json.backup." + to_string(time(NULL)); + if (stat (f.c_str(), &sbuf) == 0) { + debug (DEBUG_INFO, "* Track::Save rename backup file to: %s", nf.c_str()); + if (rename (f.c_str(), nf.c_str())) { + debug (DEBUG_ERROR, "* Track::Save could not rename file '%s' to '%s'. Error:(%s)", + f.c_str(), nf.c_str(), strerror(errno)); + } + } + + return Save(f); +}; + + +//////////////////////////////////////////////////////////////// +// +// loading and saving of the file is done here. We do no checks +// here. +// +int Server::Load(string fn) { + int fd; + size_t len; + struct stat sbuf; + JSONParse json; + JSONParse jtmp; + char *buf = NULL; + string s; + int x, y, i; + + debug (DEBUG_INFO, "* Load File:%s", fn.c_str()); + if (stat (fn.c_str(), &sbuf) == 0) { + buf = (char *) malloc(sbuf.st_size+1); + memset (buf, 0x0, sbuf.st_size+1); + fd = open (fn.c_str(), O_RDONLY); + len = read (fd, buf, sbuf.st_size); + close (fd); + + // everything read? + if (len < sbuf.st_size) { + free (buf); + debug (DEBUG_ERROR, "* Reading Track File Failed. (len < filesize)"); + return 0; + } + + s = buf; + json.Set(s); + } + else { + debug (DEBUG_ERROR, "* cloud not stat file (%s)", strerror(errno)); + return 0; + } + + // + // json holds all the data + + // + // read track values + json.GetObject("track", &jtmp); + jtmp.GetValueInt("width", &x); + jtmp.GetValueInt("height", &y); + railways.New (x, y); + + // + // read railways + Railway rw; + for (i = 0; json.GetObjectIdx("railways", i, &jtmp); i++) { + rw = railways.GetRailwayFromJSON(&jtmp); + railways.Change(&rw); + } + + // + // read interfaces + Interface iface; + for (i = 0; json.GetObjectIdx("interfaces", i, &jtmp); i++) { + iface = interfaces.GetInterfaceFromJSON(&jtmp); + interfaces.Change(&iface); + } + + // + // read locomotives + Locomotive loco; + for (i = 0; json.GetObjectIdx("locomotives", i, &jtmp); i++) { + loco = locomotives.GetLocomotiveFromJSON(&jtmp); + locomotives.Change(&loco); + } + + // + // read sensors + Sensor se; + for (i = 0; json.GetObjectIdx("sensors", i, &jtmp); i++) { + se = sensors.GetSensorFromJSON(&jtmp); + sensors.Change(&se); + } + + // + // read turnouts + Turnout to; + for (i = 0; json.GetObjectIdx("turnouts", i, &jtmp); i++) { + to = turnouts.GetTurnoutFromJSON(&jtmp); + turnouts.Change(&to); + } + + + railways.ClearChanged(); + return 1; +}; + + +int Server::Save(string fn) { + FILE *f; + JSONParse json; + JSONElement je; + int x, y, cnt; + + debug (DEBUG_INFO, "* Save File:%s", fn.c_str()); + json.Clear(); + json.AddObject("track", railways.GetJSONTrack()); + + // + // write all railway data + // create json object array manualy + je.type = JSON_T_ARRAY; + je.name = "railways"; + for (cnt = 0, x = 0; x < railways.GetWidth(); x++) for (y = 0; y < railways.GetHeight(); y++) + if (railways.Get(x, y).type != RAILWAY_NOTHING) { + if (cnt != 0) je.value += ","; // not first element + je.value += railways.GetJSONRailway(x, y).ToString(); + cnt++; + } + json.AddObject(je); + + // + // write all interfaces + interfaces.GetJSONAll(&json); + + // + // write all locomotives + locomotives.GetJSONAll(&json); + + // + // write all sensors + sensors.GetJSONAll(&json); + + // + // write all turnouts + turnouts.GetJSONAll(&json); + + + f = fopen (fn.c_str(), "w"); + fprintf (f, "%s", json.ToString().c_str()); + fclose (f); + + railways.ClearChanged(); + + return 1; +}; + diff --git a/server/server.cc b/server/server.cc new file mode 100644 index 0000000..2447f7e --- /dev/null +++ b/server/server.cc @@ -0,0 +1,105 @@ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#include "modelbahn.h" +#include "server.h" + + +int Server::Start() { + int err; + pthread_attr_t attr; + mtx = { 0 }; + mtx = PTHREAD_MUTEX_INITIALIZER; + thread_running = 1; + pthread_attr_init (&attr); + pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_JOINABLE); + err = pthread_create (&thread, &attr, ThreadEntry, this); + if (err != 0) { + debug (DEBUG_ERROR, (char*)"%s(%s:%d) pthread_create errror: %s", __FUNCTION__, __FILE__, __LINE__, strerror (errno)); + running = 0; + return 0; + } + return 1; +}; + + +Server::Server() { + thread = 0; + thread_running = 0; + railways.SetSize(200, 200); + Load (); +}; + + +Server::~Server() { + if (IsChanged()) Save(); +}; + + +void Server::ThreadProcess() { + int i = 0; + while (running) { + interfaces.Loop(); + turnouts.Loop(); + usleep (25000); + } + debug (0, "Server::ThreadProcess Finished"); + thread_running = 0; +}; + + +void Server::LockThread() { + debug(DEBUG_INFO, "%s:%d Server::LockThread", __FILE__, __LINE__); + pthread_mutex_lock (&mtx); +}; + + +void Server::UnLockThread() { + debug (DEBUG_INFO, "%s:%d Server::UnLockThreads", __FILE__, __LINE__); + pthread_mutex_unlock (&mtx); +}; + + +// +// return JSONObject with all data +void Server::GetJSONAll(JSONParse *json) { + debug (DEBUG_INFO, "* Track::GetJSONAll data"); + if (json == NULL) return; + + railways.GetJSONAll(json); + interfaces.GetJSONAll(json); + sensors.GetJSONAll(json); + locomotives.GetJSONAll(json); + turnouts.GetJSONAll(json); +} + + +bool Server::IsChanged() { + if (railways.IsChanged() || + interfaces.IsChanged() || + locomotives.IsChanged() || + sensors.IsChanged() || + turnouts.IsChanged()) return true; + else return false; +} + + + diff --git a/server/server.h b/server/server.h new file mode 100644 index 0000000..1301d54 --- /dev/null +++ b/server/server.h @@ -0,0 +1,135 @@ + +#ifndef _SERVER_H_ +#define _SERVER_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "json.h" +#include "modelbahn.h" +#include "turnout.h" +#include "railway.h" +#include "locomotive.h" +#include "sensor.h" +#include "interface.h" + +class Server { +private: + pthread_mutex_t mtx; + pthread_t thread; + int thread_running; + Turnouts turnouts; + Sensors sensors; + Railways railways; + Locomotives locomotives; + Interfaces interfaces; + + void ThreadProcess(); + void LoopCheckChanges(); + bool IsChanged(); + + int Load(string fn); + int Save(string fn); + + friend class Interfaces; + friend class InterfacesZ21; + friend class Locomotives; + friend class Sensors; + friend class Turnouts; +public: + ///////////////////////////////////////// + // functions here are required to be thread save + + Server(); + ~Server(); + + int Start(); + int isRunning() { return thread_running; } + + void LockThread(); + void UnLockThread(); + + ////////////////////////////////////////////// + // General Commands, for controlling trains, + // turnouts and sensors + // + void PowerOnOff(int onoff) { interfaces.PowerOnOff(onoff); }; + + int GetHeight() { return railways.GetHeight(); }; + int GetWidth() { return railways.GetWidth(); }; + + ///////////////////////////////////////// + // Railway + int RailwayChange(Railway *rw) { return railways.Change(rw);}; + JSONParse RailwayGetJSONTrack() { return railways.GetJSONTrack(); }; + JSONParse RailwayGetJSONRailway (int x, int y) { return railways.GetJSONRailway(x, y); }; + Railway GetRailwayFromJSON(JSONParse *j) { return railways.GetRailwayFromJSON(j); }; + + ///////////////////////////////////////// + // Turnout + int TurnoutChange(Turnout *t) { return turnouts.Change(t); }; + Turnout TurnoutFromJSON(JSONParse *j) { return turnouts.GetTurnoutFromJSON(j); }; + JSONParse TurnoutGetJSON(string name) { return turnouts.GetJSON(name); }; + int TurnoutDelete(string name) { return turnouts.Delete(name); }; + int TurnoutSet(string name, int active) { return turnouts.Set(name, active); }; + + + ///////////////////////////////////////// + // Interface + int InterfaceChange(Interface *i) { return interfaces.Change(i); }; + Interface InterfaceFromJSON(JSONParse *j) { return interfaces.GetInterfaceFromJSON(j); }; + JSONParse InterfaceGetJSON(string name) { return interfaces.GetJSON(name); }; + int InterfaceDelete(string name) { return interfaces.Delete(name); }; + + ///////////////////////////////////////// + // Locomotive + int LocomotiveChange(Locomotive *l) { return locomotives.Change(l); }; + Locomotive LocomotiveFromJSON(JSONParse *j) { return locomotives.GetLocomotiveFromJSON(j); }; + JSONParse LocomotiveGetJSON(string name) { return locomotives.GetJSON(name); }; + int LocomotiveDelete(string name) { return locomotives.Delete(name); }; + int LocomotiveSetSpeed(string name, int speed) { return locomotives.SetSpeed(name, speed); }; + int LocomotiveSetFunction(string name, int func, int value) { return locomotives.SetFunction(name, func, value); }; + + ///////////////////////////////////////// + // Sensor + int SensorChange(Sensor *s) { return sensors.Change(s); }; + Sensor SensorFromJSON(JSONParse *j) { return sensors.GetSensorFromJSON(j); }; + JSONParse SensorGetJSON(string name) { return sensors.GetJSON(name); }; + int SensorDelete(string name) { return sensors.Delete(name); }; + + void GetJSONAll(JSONParse *json); + + ///////////////////////////////////////// + // reports from interfaces + int LocomotiveAddrSpeed(string name, int addr, int speed) { return locomotives.SetSpeedFromBus(name, addr, speed); }; + int LocomotiveAddrDirection(string name, int addr, int reverse) { return locomotives.SetDirectionFromBus(name, addr, reverse); }; + int TurnoutAddrMode(string name, int addr, int active) { turnouts.SetFromBus(name, addr, active); return 1; }; + int SensorAddrChange(string name, int addr, int active) { sensors.SetFromBus(name, addr, active); return 1; }; + + + // + // Load Save Part + int Load(); + int Save(); + +protected: + static void *ThreadEntry (void *This) { ((Server*)This)->ThreadProcess(); return NULL;}; +}; + +#endif // _SERVER_H_ diff --git a/server/session.cc b/server/session.cc new file mode 100644 index 0000000..0256a89 --- /dev/null +++ b/server/session.cc @@ -0,0 +1,510 @@ + + +#include "modelbahn.h" +#include + +int next_sessionID = 1; + +int Session::SendData(UNIX *u, string data) { + return 0; +}; + +Session::Session(int rid) { + sessionID = random(); + randomID = rid; + changes.clear(); +}; + +Session::~Session() { + changes.clear(); +}; + +//////////////////////////////////////////////////////////////// +// +// process data and send result back +// it will process the command, and reply with all known +// changes. +// +int Session::ProcessData(JSONParse *jin, JSONParse *jout) { + JSONElement je; + JSONParse json; + JSONParse jsondata; + string command; + list elements; + list::iterator iter; + jout->Clear(); + +// debug (0, "* Session sid:%d rid:%d", sessionID, randomID); + + // + // found command + if (jin->GetValue("command", &command) == 1) { + // + // editing elements + // + debug (0, "%s:%d JIN:%s", __FILE__, __LINE__, jin->ToString().c_str()); + if (command.compare("addrailway") == 0) { + debug (0, "* Session Add Railways"); + AddJSONRailway(jin); + } + else if (command.compare("delrailway") == 0) { + debug (0, "* Session Del Railways"); + DelJSONRailway(jin); + } + else if (command.compare("addinterface") == 0) { + debug (0, "* Session Add Interface"); + AddJSONInterface(jin); + } + else if (command.compare("delinterface") == 0) { + debug (0, "* Session Del Interface"); + DelJSONInterface(jin); + } + else if (command.compare("addsensor") == 0) { + debug (0, "* Session Add Sensor"); + AddJSONSensor(jin); + } + else if (command.compare("delsensor") == 0) { + debug (0, "* Session del Sensor"); + DelJSONSensor(jin); + } + else if (command.compare("addturnout") == 0) { + debug (0, "* Session Add Turnout"); + AddJSONTurnout(jin); + } + else if (command.compare("delturnout") == 0) { + debug (0, "* Session del Turnout"); + DelJSONTurnout(jin); + } + else if (command.compare("addlocomotive") == 0) { + debug (0, "* Session Add Locomotive"); + AddJSONLocomotive(jin); + } + else if (command.compare("dellocomotive") == 0) { + debug (0, "* Session Del Locomotive"); + DelJSONLocomotive(jin); + } + + // + // locomotives + // + else if (command.compare("setlocomotive") == 0) { + SetJSONLocomotive(jin); + } + // + // locomotives + // + else if (command.compare("setturnout") == 0) { + SetJSONTurnout(jin); + } + // + // poweron / poweroff + // + else if (command.compare("poweron") == 0) { + debug (0, "* Session Poweron"); + server->PowerOnOff(1); + } + else if (command.compare("poweroff") == 0) { + debug (0, "* Session Poweroff"); + server->PowerOnOff(0); + } + + else if (command.compare("save") == 0) { + debug (0, "* Save All"); + server->Save(); + } + else if (command.compare("getall") == 0) { + json.Clear(); + server->GetJSONAll(&json); + + // debuggin maybe we need to fix this somehow + elements = json.GetElements(); + for (iter = elements.begin(); iter != elements.end(); iter++) + ChangeListPush("{"+(*iter).GetString()+"}"); + } + else { + debug (DEBUG_ERROR | DEBUG_SESSION, "%s:%d Unknown command: '%s' JSON:%s", + __FILE__, __LINE__, command.c_str(), jin->ToString().c_str()); + } + } + + je = ChangeListGet(); + jout->AddObject(je); + jout->AddObject("sid", sessionID); + jout->AddObject("rid", randomID); + + return 1; +} + + +////////////////////////////////////////////////////////////// +// +// add chenges which need to be send to the clients +// +void Session::ChangeListPush(string chng) { + changes.push_back(chng); +}; + + +// +// get the changes as json string and clear list of changes +// +JSONElement Session::ChangeListGet() { + JSONElement je; + list::iterator iter; + +// debug (0, "* Session::ChangeListGet cnt:%d", changes.size()); + + je.type = JSON_T_ARRAY; + je.name = "changes"; + je.value = "["; + + for (iter = changes.begin(); iter != changes.end(); iter++) { + if (iter != changes.begin()) je.value += ",\n "; + else je.value += "\n "; + je.value += (*iter); + } + + changes.clear(); + + je.value += "]"; + +// debug (0, "* Session::ChangeListGet string:'%s'", je.GetString().c_str()); + + return je; +} + + +///////////////////////////////////////////////////////////// +// +// commands send from the client to the server +// +// add a new jsonrailway to the server the JSONParse Object +// will contains the full JSON string from the web page +// +void Session::AddJSONRailway(JSONParse *jp) { + int i; + JSONParse element; + JSONParse pos; + JSONParse jout; + Railway r; + list l; + + if (server == NULL) return; + + server->LockThread(); + for (i = 0; jp->GetObjectIdx("rail", i, &element); i++) { + debug (0, "%s:%d AddJSONRailway Element '%s'", __FILE__, __LINE__, element.ToString().c_str()); + r = server->GetRailwayFromJSON(&element); + server->RailwayChange(&r); + jout.Clear(); + jout.AddObject("railway", server->RailwayGetJSONRailway(r.x, r.y)); + if (network) network->_ChangeListPushToAll(jout.ToString()); + } + server->UnLockThread(); +} + + +// +// delete some jsonrailway from the server the JSONParse Object +// will contains the full JSON string from the web page +// +void Session::DelJSONRailway(JSONParse *jp) { + int i; + JSONParse element; + JSONParse pos; + JSONParse jout; + Railway r; + + if (server == NULL) return; + + server->LockThread(); + for (i = 0; jp->GetObjectIdx("rail", i, &element); i++) { + debug (0, "%s:%d DelJSONRailway Element %s", __FILE__, __LINE__, element.ToString().c_str()); + r = server->GetRailwayFromJSON(&element); + r.type = RAILWAY_NOTHING; + server->RailwayChange(&r); + jout.Clear(); + jout.AddObject("railway", server->RailwayGetJSONRailway(r.x, r.y)); + if (network) network->_ChangeListPushToAll(jout.ToString()); + } + server->UnLockThread(); +} + + +// +// add new interface +// +void Session::AddJSONInterface(JSONParse *jp) { + Interface iface; + JSONParse jiface; + JSONParse jout; + + server->LockThread(); + + jp->GetObject("interface", &jiface); + iface = server->InterfaceFromJSON(&jiface); + if (iface.name[0] != 0) { + debug (0, "%s:%d AddJSONInterface Element %s", __FILE__, __LINE__, iface.name); + // add element + server->InterfaceChange(&iface); + jout.Clear(); + jout.AddObject("interface", server->InterfaceGetJSON(iface.name)); + if (network) network->_ChangeListPushToAll(jout.ToString()); + } + server->UnLockThread(); +}; + + +// +// Delete interface +// +void Session::DelJSONInterface(JSONParse *jp) { + Interface iface; + JSONParse jiface; + JSONParse jout; + string s; + + server->LockThread(); + + jp->GetObject("interface", &jiface); + iface = server->InterfaceFromJSON(&jiface); + if (iface.name[0] != 0) { + debug (0, "%s:%d DelJSONInterface Element %s", __FILE__, __LINE__, iface.name); + // add element + server->InterfaceDelete(iface.name); + jout.Clear(); + s = iface.name; + jout.AddObject("interfacedelete", s); + if (network) network->_ChangeListPushToAll(jout.ToString()); + } + server->UnLockThread(); +}; + + +// +// add new Sensor +// +void Session::AddJSONSensor(JSONParse *jp) { + Sensor se; + JSONParse jtmp; + JSONParse jout; + + server->LockThread(); + + jp->GetObject("sensor", &jtmp); + se = server->SensorFromJSON(&jtmp); + if (se.name[0] != 0) { + debug (0, "%s:%d AddJSONSensor Element %s", __FILE__, __LINE__, se.name); + // add element + server->SensorChange(&se); + jout.Clear(); + jout.AddObject("sensor", server->SensorGetJSON(se.name)); + if (network) network->_ChangeListPushToAll(jout.ToString()); + } + server->UnLockThread(); +}; + + +// +// Delete Sensor +// +void Session::DelJSONSensor(JSONParse *jp) { + Sensor se; + JSONParse jtmp; + JSONParse jout; + string s; + + server->LockThread(); + + jp->GetObject("sensor", &jtmp); + se = server->SensorFromJSON(&jtmp); + if (se.name[0] != 0) { + debug (0, "%s:%d DelJSONTurnout Element %s", __FILE__, __LINE__, se.name); + // add element + server->SensorDelete(se.name); + jout.Clear(); + s = se.name; + jout.AddObject("sensordelete", s); + if (network) network->_ChangeListPushToAll(jout.ToString()); + } + server->UnLockThread(); +}; + + +// +// add new Locomotive +// +void Session::AddJSONLocomotive(JSONParse *jp) { + Locomotive loco; + JSONParse jloco; + JSONParse jout; + + server->LockThread(); + + jp->GetObject("locomotive", &jloco); + loco = server->LocomotiveFromJSON(&jloco); + if (loco.name[0] != 0) { + debug (0, "%s:%d AddJSONLocomotive Element %s", __FILE__, __LINE__, loco.name); + // add element + server->LocomotiveChange(&loco); + jout.Clear(); + jout.AddObject("locomotive", server->LocomotiveGetJSON(loco.name)); + if (network) network->_ChangeListPushToAll(jout.ToString()); + } + server->UnLockThread(); +}; + + +// +// Set Locomotive +// +void Session::SetJSONLocomotive(JSONParse *jp) { + JSONParse jloco; + JSONParse jout; + string loconame; + int speed; + int func; + int value; + + server->LockThread(); + + jp->GetObject("locomotive", &jloco); + jloco.GetValue("name", &loconame); + if (loconame.length() > 0) { + if (jloco.GetValueInt("speed", &speed) == 1) { + // + // set speed + debug (0, "%s:%d SetJSONLocomotive Element %s Speed:%d", __FILE__, __LINE__, loconame.c_str(), speed); + server->LocomotiveSetSpeed(loconame, speed); + } + if (jloco.GetValueInt("function", &func) == 1 && jloco.GetValueInt("value", &value) == 1) { + // + // set function + debug (0, "%s:%d SetJSONLocomotive Element %s Function:%d Value:%d", __FILE__, __LINE__, loconame.c_str(), func, value); + server->LocomotiveSetFunction(loconame, func, speed); + } + jout.Clear(); + jout.AddObject("locomotive", server->LocomotiveGetJSON(loconame)); + if (network) network->_ChangeListPushToAll(jout.ToString()); + } + server->UnLockThread(); +}; + + +// +// Delete Locomotive +// +void Session::DelJSONLocomotive(JSONParse *jp) { + Locomotive loco; + JSONParse jloco; + JSONParse jout; + string s; + + server->LockThread(); + + jp->GetObject("locomotive", &jloco); + loco = server->LocomotiveFromJSON(&jloco); + if (loco.name[0] != 0) { + debug (0, "%s:%d DelJSONLocomotive Element %s", __FILE__, __LINE__, loco.name); + // add element + server->LocomotiveDelete(loco.name); + jout.Clear(); + s = loco.name; + jout.AddObject("locomotivedelete", s); + if (network) network->_ChangeListPushToAll(jout.ToString()); + } + server->UnLockThread(); +}; + + +// +// add new Turnout +// +void Session::AddJSONTurnout(JSONParse *jp) { + Turnout to; + JSONParse jtmp; + JSONParse jout; + + server->LockThread(); + + jp->GetObject("turnout", &jtmp); + to = server->TurnoutFromJSON(&jtmp); + if (to.name[0] != 0) { + debug (0, "%s:%d AddJSONTurnout Element %s", __FILE__, __LINE__, to.name); + // add element + server->TurnoutChange(&to); + jout.Clear(); + jout.AddObject("turnout", server->TurnoutGetJSON(to.name)); + if (network) network->_ChangeListPushToAll(jout.ToString()); + } + server->UnLockThread(); +}; + + +// +// Delete Turnout +// +void Session::DelJSONTurnout(JSONParse *jp) { + Turnout to; + JSONParse jtmp; + JSONParse jout; + string s; + + server->LockThread(); + + jp->GetObject("turnout", &jtmp); + to = server->TurnoutFromJSON(&jtmp); + if (to.name[0] != 0) { + debug (0, "%s:%d DelJSONTurnout Element %s", __FILE__, __LINE__, to.name); + // add element + server->TurnoutDelete(to.name); + jout.Clear(); + s = to.name; + jout.AddObject("turnoutdelete", s); + if (network) network->_ChangeListPushToAll(jout.ToString()); + } + server->UnLockThread(); +}; + + +// +// Set Turnout +// +void Session::SetJSONTurnout(JSONParse *jp) { + JSONParse jout; + string name; + int active; + + server->LockThread(); + + jp->GetValue("name", &name); + jp->GetValueInt("activate", &active); + + if (name.length() > 0) { + // + // activate + debug (0, "%s:%d SetJSONTurnout Element %s active:%d", __FILE__, __LINE__, name.c_str(), active); + server->TurnoutSet(name, active); + jout.Clear(); + jout.AddObject("turnout", server->TurnoutGetJSON(name)); + if (network) network->_ChangeListPushToAll(jout.ToString()); + } + server->UnLockThread(); +}; + + +/* + res = json.GetValue("command", &value); + if (res && value.compare("addelemens") == 0) { + for (i = 0; json.GetObjectIdx("elements", i, &jelement) != 0; i++) { + s = ""; + jelement.GetValue("type", &s); + // printf ("i:%d t:'%s'\n", i, s.c_str()); + } + } + snprintf (bufferout, BUFFERSIZE, "{\"success\":1,\"sid\":123}"); + client->Write(bufferout, strlen(bufferout)); + debug (0, "* write:\n%s\n* %d bytes", bufferout, strlen(bufferout)); + result = 1; + +*/ diff --git a/server/test-json.cc b/server/test-json.cc new file mode 100644 index 0000000..6b06f27 --- /dev/null +++ b/server/test-json.cc @@ -0,0 +1,26 @@ + +#include +#include "json.h" + +using namespace std; + +int main(int argc, char **argv) { + string input; + string json_line; + JSONParse json; + list l; + list::iterator iter; + + for (json_line = "", input = ""; getline(cin, input);) + json_line += input; + + json.Set(json_line); + l = json.GetElements(); + + for (iter = l.begin(); iter != l.end(); iter++) { + cout << iter->name << endl; + } + + return 0; +}; + diff --git a/server/turnout.cc b/server/turnout.cc new file mode 100644 index 0000000..61c2bbc --- /dev/null +++ b/server/turnout.cc @@ -0,0 +1,262 @@ + +#include "modelbahn.h" +#include "turnout.h" + +Turnouts::Turnouts() { + changed = 0; + turnouts = (Turnout*) malloc(sizeof(Turnout)*TURNOUTS_MAX); + max = TURNOUTS_MAX; +}; + +Turnouts::~Turnouts() { + free (turnouts); + max = 0; + turnouts = NULL; +}; + + +int Turnouts::Lock() { + if (pthread_mutex_lock(&mtx) == 0) return 1; + else return 0; +} + + +int Turnouts::UnLock() { + if (pthread_mutex_unlock(&mtx) == 0) return 1; + else return 0; +} + + +JSONParse Turnouts::_GetJSON(int idx) { + JSONParse json; + JSONElement je; + string s = ""; + + json.Clear(); + + s = turnouts[idx].name; json.AddObject("name", s); + s = turnouts[idx].ifname; json.AddObject("ifname", s); + json.AddObject("addr", turnouts[idx].addr); + json.AddObject("activetimeout", turnouts[idx].activetimeout); + json.AddObject("flags", turnouts[idx].flags); + + return json; +}; + + +JSONParse Turnouts::GetJSON(string name) { + int i; + JSONParse jp; + + jp.Clear(); + + Lock(); + for (i = 0; i < max; i++) if (turnouts[i].name[0] != 0) { + if (name.compare(turnouts[i].name) == 0) { + jp = _GetJSON(i); + } + } + + UnLock(); + + return jp; +}; + + +void Turnouts::GetJSONAll(JSONParse *json) { + int i, cnt; + JSONElement je; + + Lock(); + + // + // write all railway data + // create json object array manualy + je.type = JSON_T_ARRAY; + je.name = "turnouts"; + for (cnt = 0, i = 0; i < max; i++) + if (turnouts[i].name[0] != 0) { + if (cnt != 0) je.value += ","; // not first element + je.value += _GetJSON(i).ToString(); + cnt++; + } + json->AddObject(je); + + UnLock(); +}; + + +Turnout Turnouts::GetTurnoutFromJSON(JSONParse *j) { + Turnout to; + string s; + + to.name[0] = 0; + to.ifname[0] = 0; + to.addr = 0; + to.activetimeout = TURNOUT_DEFAULT_ACTIVETIMEOUT; // default active timeout in MS + to.flags = 0; + + j->GetValue("name", &s); + strncpy (to.name, s.c_str(), REFERENCENAME_LEN); + j->GetValue("ifname", &s); + strncpy (to.ifname, s.c_str(), REFERENCENAME_LEN); + j->GetValueInt("addr", &to.addr); + j->GetValueInt("activetimeout", &to.activetimeout); + j->GetValueInt("flags", &to.flags); + + return to; +}; + + +int Turnouts::Change(Turnout *to) { + int i; + int ifree = -1; + + Lock(); + + for (i = 0; i < max; i++) { + if (turnouts[i].name[0] != 0) { + // found element + if (strncmp(turnouts[i].name, to->name, REFERENCENAME_LEN) == 0) { + ifree = i; + break; + } + } + else if (ifree == -1) ifree = i; + } + // element not found add new element + if (ifree != -1 && ifree < max) { + turnouts[ifree] = *to; + strncpy (turnouts[ifree].name, to->name, REFERENCENAME_LEN); + strncpy (turnouts[ifree].ifname, to->ifname, REFERENCENAME_LEN); + turnouts[ifree].activetimeout = to->activetimeout; + } + + changed = 1; + UnLock(); + + return 1; +}; + + +int Turnouts::Delete(string name) { + int i; + + Lock(); + for (i = 0; i < max; i++) if (turnouts[i].name[0] != 0) { + if (name.compare(turnouts[i].name) == 0) { + turnouts[i].name[0] = 0; + turnouts[i].ifname[0] = 0; + turnouts[i].addr = 0; + turnouts[i].flags = 0; + turnouts[i].activetimeout = TURNOUT_DEFAULT_ACTIVETIMEOUT; + changed = 1; + break; + } + } + + UnLock(); + + return 1; +}; + + +int Turnouts::Set(string name, int value) { + int i; + + // + Lock(); + for (i = 0; i < max; i++) if (turnouts[i].name[0] != 0) { + if (name.compare(turnouts[i].name) == 0) { + debug (0, "Turnout::Set: Name:%s Flags:%d[%c%c%c] Value:%d", name.c_str(), turnouts[i].flags, + (turnouts[i].flags & TURNOUT_F_INVERSE) ? 'I' : '-', + (turnouts[i].flags & TURNOUT_F_ACTIVE) ? 'A' : '-', + (turnouts[i].flags & TURNOUT_F_TURNOUT) ? 'T' : '-', + value); + if (turnouts[i].flags & TURNOUT_F_INVERSE) + server->interfaces.SetTurnout(&turnouts[i], !value, 1); // motor on + else + server->interfaces.SetTurnout(&turnouts[i], value, 1); // motor on + gettimeofday (&turnouts[i].activatetime, NULL); + changed = 1; + + break; + } + } + UnLock(); + + return 1; +}; + +// +// got some data from bus +void Turnouts::SetFromBus(string ifname, int addr, int value) { + int i; + JSONParse jp; + + debug (0, "Turnouts::SetFromBus Interface:%s, addr: %d, value:%d", ifname.c_str(), addr, value); + + for (i = 0; i < max; i++) if (turnouts[i].name[0] != 0) { + if (ifname.compare(turnouts[i].ifname) == 0 && turnouts[i].addr == addr) { + debug (0, "Turnout::SetFromBus Name:%s Flags:%d[%c%c%c]", turnouts[i].name, turnouts[i].flags, + (turnouts[i].flags & TURNOUT_F_INVERSE) ? 'I' : '-', + (turnouts[i].flags & TURNOUT_F_ACTIVE) ? 'A' : '-', + (turnouts[i].flags & TURNOUT_F_TURNOUT) ? 'T' : '-'); + +// if (value) turnouts[i].flags |= TURNOUT_F_ACTIVE; +// else turnouts[i].flags &= ~TURNOUT_F_ACTIVE; + + if (turnouts[i].flags & TURNOUT_F_INVERSE) { + if (value) turnouts[i].flags &= ~TURNOUT_F_TURNOUT; + else turnouts[i].flags |= TURNOUT_F_TURNOUT; + } + else { + if (value) turnouts[i].flags |= TURNOUT_F_TURNOUT; + else turnouts[i].flags &= ~TURNOUT_F_TURNOUT; + } + debug (0, "Turnout::SetFromBus Name:%s Flags:%d[%c%c%c]", turnouts[i].name, turnouts[i].flags, + (turnouts[i].flags & TURNOUT_F_INVERSE) ? 'I' : '-', + (turnouts[i].flags & TURNOUT_F_ACTIVE) ? 'A' : '-', + (turnouts[i].flags & TURNOUT_F_TURNOUT) ? 'T' : '-'); + + jp.AddObject("turnout", _GetJSON(i)); + if(network) network->ChangeListPushToAll(jp.ToString()); + } + } +}; + + + +// +// this loop is important: Turnout motors needs to be deactivated after a short time +// +void Turnouts::Loop() { + int i; + struct timeval curtime; + + gettimeofday(&curtime, NULL); + + Lock(); + + for (i = 0; i < max; i++) if (turnouts[i].name[0] != 0) { + if (turnouts[i].flags & TURNOUT_F_ACTIVE) { + // + // motor still active check timeout and deactivate + int timediff = (1000 * (curtime.tv_sec - turnouts[i].activatetime.tv_sec)) + + ((curtime.tv_usec - turnouts[i].activatetime.tv_usec) / 1000); + if (timediff < 0) gettimeofday (&turnouts[i].activatetime, NULL); + +// debug (0, "%s:%d timediff: %d", __FILE__, __LINE__, timediff); + + if (timediff > turnouts[i].activetimeout) { + int active = turnouts[i].flags & TURNOUT_F_TURNOUT; + if (turnouts[i].flags & TURNOUT_F_INVERSE) + server->interfaces.SetTurnout(&turnouts[i], !active, 0); // motor on + else + server->interfaces.SetTurnout(&turnouts[i], active, 0); // motor on + turnouts[i].flags &= ~TURNOUT_F_ACTIVE; + } + } + } + UnLock(); +}; diff --git a/server/turnout.h b/server/turnout.h new file mode 100644 index 0000000..8e7131c --- /dev/null +++ b/server/turnout.h @@ -0,0 +1,56 @@ + +#ifndef _TURNOUT_H_ +#define _TURNOUT_H_ + +#include "modelbahn.h" +#include "server.h" +// +#define TURNOUT_F_INVERSE 0x0001 // inverse output +#define TURNOUT_F_ACTIVE 0x0002 // motor active +#define TURNOUT_F_TURNOUT 0x0004 // turnout active + +#define TURNOUT_DEFAULT_ACTIVETIMEOUT 250 // active timeout default value + +struct s_Turnout { + char name[REFERENCENAME_LEN]; // reference name + char ifname[REFERENCENAME_LEN]; // controllername + int addr; // address on bus + int flags; // setup of some flags; + int activetimeout; // time in ms // 0 will be set to DEFAULT + struct timeval activatetime; // set both to 0 for inactive +} typedef Turnout; + + +class Turnouts { + private: + Turnout *turnouts; + int max; + int changed; + + pthread_mutex_t mtx; + int Lock(); + int UnLock(); + + // not thread safe + JSONParse _GetJSON(int idx); + public: + Turnouts(); + ~Turnouts(); + + bool IsChanged() { return changed; }; + void ClearChanged() { changed = 0; }; + + int Change(Turnout *to); + int Delete(string name); + int Set(string name, int active); + + void Loop(); + + JSONParse GetJSON(string name); + void GetJSONAll(JSONParse *json); + Turnout GetTurnoutFromJSON(JSONParse *j); + + void SetFromBus(string name, int addr, int active); +}; + +#endif diff --git a/webinterface/block.js b/webinterface/block.js new file mode 100644 index 0000000..d738fd6 --- /dev/null +++ b/webinterface/block.js @@ -0,0 +1,213 @@ +// +// +// + +var blocks = []; + +// +// update or add a new element +// +function block_Update(blockdata) { + for (var i = 0; i < blocks.length; i++) { + if (blockdata.name == blocks[i].name) { + blocks[i].name = blockdata.name; + blocks[i].flags = blockdata.flags; + return; + } + } + + // not found add element + //debug ("Add Interface:" + intdata.name + "(" + intdata.host + ")"); + blocks.push ({ + name: blockdata.name, + flags: blockdata.flags + }); +}; + + +// +// delete element from the list +// in arrays we can not delete just an element, so we create a new one +// and replace this one. +// +function block_Delete(name) { + var l = new Array(); + + for (var i = 0; i < blocks.length; i++) { + if (name != blocks[i].name) { + l.push (blocks[i]); + } + } + + // clear list and replace list with new data. + blocks.lenght = 0; + blocks = l; +}; + + +// +// send new element to server +// +function block_server_Add(elm) { + var request = { command: "addblock", block: elm }; + serverinout (request, serverinout_defaultCallback); +}; + + +// +// send delete element to server +// +function block_server_Del(elm) { + var request = { command: "delblock", block: elm }; + serverinout (request, serverinout_defaultCallback); +}; + + + +function blockdetail_show(name) { + var win = document.getElementById("blockdetail"); + + debug ("blockdetail_show"); + + if (!win) { + debug ("blockdetail_show create window"); + win = gWindowCreate("blockdetail", "Block", 400, 300, " \ +
\ + Block Name: \ +
\ + \ + \ +


\ +
\ +
\ +

\ +
\ + \ + \ + \ +
\ + \ + "); + + gAddEventListener("blockdet_CLOSE", 'click', blockdetail_cb_close); + gAddEventListener("blockdet_DELETE", 'click', blockdetail_cb_delete); + gAddEventListener("blockdet_SAVE", 'click', blockdetail_cb_save); + + gAddEventListener("blockdet_NEXT", 'click', blockdetail_cb_next); + gAddEventListener("blockdet_PREV", 'click', blockdetail_cb_prev); + } + + if (name) { + for (var i = 0; i < blocks.length; i++) { + if (name == blocks[i].name) blockdetail_setData(blocks[i]); + } + } +}; + + +function blockdetail_cb_close () { + var win = document.getElementById("blockdetail"); + + if (win) document.body.removeChild(win); +}; + + +// +// Callback: Delete Button +// +function blockdetail_cb_delete () { + var elm = {}; + + elm = blockdetail_getData(); + block_Delete(elm.name); + block_server_Del(elm); +}; + + +// +// Callback: Save Button +// +function blockdetail_cb_save () { + var elm = {}; + + elm = blockdetail_getData(); + block_Update(elm); + block_server_Add(elm); +}; + + +// +// Callback: Next Button +// +function blockdetail_cb_next () { + var cursel = -1; + var name = document.getElementById("blockdet_name"); + + for (var i = 0; i < blocks.length; i++) { + if (name.value == blocks[i].name) cursel = i; + } + + cursel = cursel + 1; + if (cursel >= blocks.length) cursel = 0; + if (cursel < 0) cursel = 0; + + for (var i = 0; i < blocks.length; i++) { + if (i == cursel) blockdetail_setData(blocks[i]); + } + + // debug ("Cursel: " + cursel + " interfaces.lenght:" + interfaces.length); +}; + + +// +// Callback: Prev Button +// +function blockdetail_cb_prev () { + var cursel = -1; + var name = document.getElementById("blockdet_name"); + + + for (var i = 0; i < blocks.length; i++) { + if (name.value == blocks[i].name) cursel = i; + } + + cursel = cursel - 1; + if (cursel < 0 || cursel >= blocks.length) cursel = blocks.length - 1; + + for (var i = 0; i < blocks.length; i++) { + if (i == cursel) blockdetail_setData(blocks[i]); + } + + // debug ("Cursel: " + cursel + " interfaces.lenght:" + interfaces.length); +}; + + +// +// fill out all the elements on the dialogbox +// +function blockdetail_setData(elm) { + var name = document.getElementById("blockdet_name"); + var flags = document.getElementById("blockdet_flags"); + + if (elm) { + if (name) name.value = elm.name; + if (flags) flags.value = elm.flags; + } +}; + + + +// +// return all elements from the dialogbox +// +function blockdetail_getData() { + var res = { name: "", flags:0 }; + var name = document.getElementById("blockdet_name"); + var flags = document.getElementById("blockdet_flags"); + + if (name) res.name = name.value; + if (flags) res.flags = flags.value; + + return res; +}; + diff --git a/webinterface/gui/gui.css b/webinterface/gui/gui.css new file mode 100644 index 0000000..f84cf75 --- /dev/null +++ b/webinterface/gui/gui.css @@ -0,0 +1,30 @@ + + +.GUIwindow { + float:left; + border: 2px solid black; + margin: 1px; + padding: 0px; + position: absolute; +} + +.GUIwindowHead { + border: 1px solid black; + background: blue; + color: white; + margin: 0px; + padding: 5px; + cursor: move; + text-align: center; +} + +.GUIwindowClient { + overflow: auto; + padding: 5px; + background-color: white; +} + + +.GUIbutton { +} + diff --git a/webinterface/gui/gui.js b/webinterface/gui/gui.js new file mode 100644 index 0000000..d70b0b4 --- /dev/null +++ b/webinterface/gui/gui.js @@ -0,0 +1,21 @@ + + +// +// init all variables with the class givin +$(document).ready(function() { +// debug ("init"); + + $(".GUIwindow").each( function (i) { + gWindowDragElement(this); + }); +}); + + +// +// +function gAddEventListener (id, eventname, callback) { + var obj = document.getElementById(id); + + if (obj) obj.addEventListener(eventname, callback); +}; + diff --git a/webinterface/gui/guidebug.js b/webinterface/gui/guidebug.js new file mode 100644 index 0000000..b061b61 --- /dev/null +++ b/webinterface/gui/guidebug.js @@ -0,0 +1,9 @@ + +function debug (t) { + var pre = document.getElementById("debug"); + var div = document.getElementById("debug_div"); + + if (pre) pre.innerHTML = pre.innerHTML + "
" + t; + if (div) div.scrollTop = div.scrollHeight; +}; + diff --git a/webinterface/gui/guiwindow.js b/webinterface/gui/guiwindow.js new file mode 100644 index 0000000..0d4b238 --- /dev/null +++ b/webinterface/gui/guiwindow.js @@ -0,0 +1,137 @@ + + + + +function gWindowGetClient(elmnt) { + var notes = null; + var result = null; + + for (var i = 0; i < elmnt.childNodes.length; i++) { + if (elmnt.childNodes[i].className == "GUIwindowClient") { + result = elmnt.childNodes[i]; + break; + } + } + + return result; +} + + +function gWindowGetHead(elmnt) { + var notes = null; + var result = null; + + for (var i = 0; i < elmnt.childNodes.length; i++) { + if (elmnt.childNodes[i].className == "GUIwindowHead") { + result = elmnt.childNodes[i]; + break; + } + } + + return result; +}; + + +function gWindowDragElement(elmnt) { + var pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0; + + if (gWindowGetHead(elmnt) != null) { + // if present, the header is where you move the DIV from: + gWindowGetHead(elmnt).onmousedown = dragMouseDown; + } else { + // otherwise, move the DIV from anywhere inside the DIV: + elmnt.onmousedown = dragMouseDown; + } + + function dragMouseDown(e) { + e = e || window.event; + e.preventDefault(); + // get the mouse cursor position at startup: + pos3 = e.clientX; + pos4 = e.clientY; + document.onmouseup = closeDragElement; + // call a function whenever the cursor moves: + document.onmousemove = elementDrag; + } + + function elementDrag(e) { + e = e || window.event; + e.preventDefault(); + // calculate the new cursor position: + pos1 = pos3 - e.clientX; + pos2 = pos4 - e.clientY; + pos3 = e.clientX; + pos4 = e.clientY; + // set the element's new position: + elmnt.style.top = (elmnt.offsetTop - pos2 - 1) + "px"; + elmnt.style.left = (elmnt.offsetLeft - pos1 - 1) + "px"; + } + + function closeDragElement() { + // stop moving when mouse button is released: + document.onmouseup = null; + document.onmousemove = null; + } +} + +function gWindowCreate(id, title, sizex, sizey, clientHTML) { + var win = document.getElementById(id); + if (!win) { + debug("create Title:" + title); + + var head = document.createElement("div"); + head.setAttribute("id", id+"Head"); + head.setAttribute("class", "GUIwindowHead"); + head.innerHTML = title; + + var client = document.createElement("div"); + client.setAttribute("id", id+"Client"); + client.setAttribute("class", "GUIwindowClient"); + client.setAttribute("style", "max-height:"+sizey+"px;max-width:"+sizex+"px;"); + client.innerHTML = clientHTML; + + win = document.createElement("div"); + win.setAttribute("id", id); + win.setAttribute("class", "GUIwindow"); + win.appendChild (head); + win.appendChild (client); + + document.body.appendChild(win); + gWindowDragElement(win); + debug ("move to 100px from top"); + win.style.top = "100px"; + } + return win; +} + + +function gWindowCreateSize(id, title, sizex, sizey) { + var win = document.getElementById(id); + if (!win) { + debug("create Title:" + title); + + var head = document.createElement("div"); + head.setAttribute("id", id+"Head"); + head.setAttribute("class", "GUIwindowHead"); + head.innerHTML = title; + + var client = document.createElement("div"); + client.setAttribute("id", id+"Client"); + client.setAttribute("class", "GUIwindowClient"); + client.setAttribute("style", "max-height:"+sizey+"px;max-width:"+sizex+"px;"); + client.style.height = sizey+"px"; + client.style.width = sizex+"px"; + + win = document.createElement("div"); + win.setAttribute("id", id); + win.setAttribute("class", "GUIwindow"); + win.appendChild (head); + win.appendChild (client); + document.body.appendChild(win); + gWindowDragElement(win); + debug ("move to 100px from top"); + win.style.top = "100px"; + } + return win; +} + diff --git a/webinterface/icon.jpg b/webinterface/icon.jpg new file mode 100644 index 0000000..895ebd3 Binary files /dev/null and b/webinterface/icon.jpg differ diff --git a/webinterface/images/btnarrow.png b/webinterface/images/btnarrow.png new file mode 100644 index 0000000..e957a70 Binary files /dev/null and b/webinterface/images/btnarrow.png differ diff --git a/webinterface/images/btndelete.png b/webinterface/images/btndelete.png new file mode 100644 index 0000000..14f9d7c Binary files /dev/null and b/webinterface/images/btndelete.png differ diff --git a/webinterface/images/btnmove.png b/webinterface/images/btnmove.png new file mode 100644 index 0000000..f77f017 Binary files /dev/null and b/webinterface/images/btnmove.png differ diff --git a/webinterface/images/btnonoff.png b/webinterface/images/btnonoff.png new file mode 100644 index 0000000..65b9890 Binary files /dev/null and b/webinterface/images/btnonoff.png differ diff --git a/webinterface/images/btnrail.png b/webinterface/images/btnrail.png new file mode 100644 index 0000000..80c3b0d Binary files /dev/null and b/webinterface/images/btnrail.png differ diff --git a/webinterface/images/btnturnout.png b/webinterface/images/btnturnout.png new file mode 100644 index 0000000..f4e9f82 Binary files /dev/null and b/webinterface/images/btnturnout.png differ diff --git a/webinterface/index.html b/webinterface/index.html new file mode 100644 index 0000000..a8aeb49 --- /dev/null +++ b/webinterface/index.html @@ -0,0 +1,107 @@ + + + Modelbahn + + + + + + + + + + + + + + + + + + + + +
+ +

+ +
+ +
+ +
+

+
+ + + + + + + diff --git a/webinterface/interface.js b/webinterface/interface.js new file mode 100644 index 0000000..c8caf6f --- /dev/null +++ b/webinterface/interface.js @@ -0,0 +1,273 @@ +// +// +// + +var interfaces = []; + +// +// update or add a new element +// +function interface_Update(intdata) { + for (var i = 0; i < interfaces.length; i++) { + if (intdata.name == interfaces[i].name) { + //debug ("Update Interface:" + interfaces[i].name + "(" + interfaces[i].host + ") with Interface:" + intdata.name + "(" + intdata.host + ")"); + + if (!(intdata.flags & 0x0001)) sideBtnOnOffMode (3); // not connected + else if ((intdata.flags & 0x0010)) sideBtnOnOffMode (3); // programming mode + else if ((intdata.flags & 0x0008)) sideBtnOnOffMode (3); // short circuit + else if ((intdata.flags & 0x0004)) sideBtnOnOffMode (2); // stop + else if (!(intdata.flags & 0x0002)) sideBtnOnOffMode (1); // power on + else sideBtnOnOffMode (0); + + interfaces[i].name = intdata.name; + interfaces[i].host = intdata.host; + interfaces[i].flags = intdata.flags; + interfaces[i].type = intdata.type; + return; + } + } + + // not found add element + //debug ("Add Interface:" + intdata.name + "(" + intdata.host + ")"); + interfaces.push ({ + name: intdata.name, + host: intdata.host, + flags: intdata.flags, + type: intdata.type + }); +}; + + +// +// delete element from the list +// in arrays we can not delete just an element, so we create a new one +// and replace this one. +// +function interface_Delete(name) { + var l = new Array(); + + for (var i = 0; i < interfaces.length; i++) { + if (name != interfaces[i].name) { + l.push (interfaces[i]); + } + } + + // clear list and replace list with new data. + interfaces.lenght = 0; + interfaces = l; +}; + + +// +// send new element to server +// +function interface_server_Add(elm) { + var request = { command: "addinterface", interface: elm }; + serverinout (request, serverinout_defaultCallback); +}; + + +// +// send delete element to server +// +function interface_server_Del(elm) { + var request = { command: "delinterface", interface: elm }; + serverinout (request, serverinout_defaultCallback); +}; + + + +function intdetail_show(intname) { + var win = document.getElementById("intdetail"); + + debug ("intdetail_show"); + + if (!win) { + debug ("intdetail_show create window"); + win = gWindowCreate("intdetail", "Interface", 400, 300, " \ +
\ + Interface Name: \ +
\ + \ + \ +


\ +
\ +
\ + \ + \ +
Host:
Flags:
\ +
Type \ + Type:
\ +
\ +
\ +
\ +

\ +
\ + \ + \ + \ +
\ + \ + "); + + gAddEventListener("intdet_type", 'change', intdetail_cb_typechange); + gAddEventListener("intdet_typeunknown", 'click', intdetail_cb_typeselector); + gAddEventListener("intdet_typez21", 'click', intdetail_cb_typeselector); + + + gAddEventListener("intdet_CLOSE", 'click', intdetail_cb_close); + gAddEventListener("intdet_DELETE", 'click', intdetail_cb_delete); + gAddEventListener("intdet_SAVE", 'click', intdetail_cb_save); + + gAddEventListener("intdet_NEXT", 'click', intdetail_cb_next); + gAddEventListener("intdet_PREV", 'click', intdetail_cb_prev); + } + + if (intname) { + for (var i = 0; i < interfaces.length; i++) { + if (intname == interfaces[i].name) intdetail_setData(interfaces[i]); + } + } +}; + + +function intdetail_cb_close () { + var win = document.getElementById("intdetail"); + + if (win) document.body.removeChild(win); +}; + + +// +// Callback: Delete Button +// +function intdetail_cb_delete () { + var elm = {}; + + elm = intdetail_getData(); + interface_Delete(elm.name); + interface_server_Del(elm); +}; + + +// +// Callback: Save Button +// +function intdetail_cb_save () { + var elm = {}; + + elm = intdetail_getData(); + interface_Update(elm); + interface_server_Add(elm); +}; + + +// +// Callback: Next Button +// +function intdetail_cb_next () { + var cursel = -1; + var if_name = document.getElementById("intdet_name"); + + for (var i = 0; i < interfaces.length; i++) { + if (if_name.value == interfaces[i].name) cursel = i; + } + + cursel = cursel + 1; + if (cursel >= interfaces.length) cursel = 0; + if (cursel < 0) cursel = 0; + + for (var i = 0; i < interfaces.length; i++) { + if (i == cursel) intdetail_setData(interfaces[i]); + } + + // debug ("Cursel: " + cursel + " interfaces.lenght:" + interfaces.length); +}; + + +// +// Callback: Prev Button +// +function intdetail_cb_prev () { + var cursel = -1; + var if_name = document.getElementById("intdet_name"); + + + for (var i = 0; i < interfaces.length; i++) { + if (if_name.value == interfaces[i].name) cursel = i; + } + + cursel = cursel - 1; + if (cursel < 0 || cursel >= interfaces.length) cursel = interfaces.length - 1; + + for (var i = 0; i < interfaces.length; i++) { + if (i == cursel) intdetail_setData(interfaces[i]); + } + + // debug ("Cursel: " + cursel + " interfaces.lenght:" + interfaces.length); +}; + + +// +// fill out all the elements on the dialogbox +// +function intdetail_setData(elm) { + var if_name = document.getElementById("intdet_name"); + var if_host = document.getElementById("intdet_host"); + var if_flags = document.getElementById("intdet_flags"); + var if_type = document.getElementById("intdet_type"); + + if (elm) { + if (if_name) if_name.value = elm.name; + if (if_host) if_host.value = elm.host; + if (if_flags) if_flags.value = elm.flags; + if (if_flags) if_type.value = elm.type; + } + + intdetail_cb_typechange(); +}; + + +function intdetail_cb_typechange () { + var type = document.getElementById("intdet_type"); + var typez21 = document.getElementById("intdet_typez21"); + var typeunknown = document.getElementById("intdet_typeunknown"); + + switch(Number(type.value)) { + case 0: typeunknown.checked = true; break; + case 1: typez21.checked = true; break; + default: + type.value = 0; + typeunknown.checked = true; + break; + } +}; + + +function intdetail_cb_typeselector () { + var type = document.getElementById("intdet_type"); + + type.value = this.value; +}; + + + + +// +// return all elements from the dialogbox +// +function intdetail_getData() { + var res = { name: "", host: "", flags:0, type:0 }; + var if_name = document.getElementById("intdet_name"); + var if_host = document.getElementById("intdet_host"); + var if_flags = document.getElementById("intdet_flags"); + var if_type = document.getElementById("intdet_type"); + + if (if_name) res.name = if_name.value; + if (if_host) res.host = if_host.value; + if (if_flags) res.flags = if_flags.value; + if (if_type) res.type = if_type.value; + + return res; +}; + diff --git a/webinterface/jquery-3.1.0.min.js b/webinterface/jquery-3.1.0.min.js new file mode 100644 index 0000000..f6a6a99 --- /dev/null +++ b/webinterface/jquery-3.1.0.min.js @@ -0,0 +1,4 @@ +/*! jQuery v3.1.0 | (c) jQuery Foundation | jquery.org/license */ +!function(a,b){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){"use strict";var c=[],d=a.document,e=Object.getPrototypeOf,f=c.slice,g=c.concat,h=c.push,i=c.indexOf,j={},k=j.toString,l=j.hasOwnProperty,m=l.toString,n=m.call(Object),o={};function p(a,b){b=b||d;var c=b.createElement("script");c.text=a,b.head.appendChild(c).parentNode.removeChild(c)}var q="3.1.0",r=function(a,b){return new r.fn.init(a,b)},s=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,t=/^-ms-/,u=/-([a-z])/g,v=function(a,b){return b.toUpperCase()};r.fn=r.prototype={jquery:q,constructor:r,length:0,toArray:function(){return f.call(this)},get:function(a){return null!=a?a<0?this[a+this.length]:this[a]:f.call(this)},pushStack:function(a){var b=r.merge(this.constructor(),a);return b.prevObject=this,b},each:function(a){return r.each(this,a)},map:function(a){return this.pushStack(r.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(f.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(a<0?b:0);return this.pushStack(c>=0&&c0&&b-1 in a)}var x=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ha(),z=ha(),A=ha(),B=function(a,b){return a===b&&(l=!0),0},C={}.hasOwnProperty,D=[],E=D.pop,F=D.push,G=D.push,H=D.slice,I=function(a,b){for(var c=0,d=a.length;c+~]|"+K+")"+K+"*"),S=new RegExp("="+K+"*([^\\]'\"]*?)"+K+"*\\]","g"),T=new RegExp(N),U=new RegExp("^"+L+"$"),V={ID:new RegExp("^#("+L+")"),CLASS:new RegExp("^\\.("+L+")"),TAG:new RegExp("^("+L+"|[*])"),ATTR:new RegExp("^"+M),PSEUDO:new RegExp("^"+N),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+K+"*(even|odd|(([+-]|)(\\d*)n|)"+K+"*(?:([+-]|)"+K+"*(\\d+)|))"+K+"*\\)|)","i"),bool:new RegExp("^(?:"+J+")$","i"),needsContext:new RegExp("^"+K+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+K+"*((?:-\\d)?\\d*)"+K+"*\\)|)(?=[^-]|$)","i")},W=/^(?:input|select|textarea|button)$/i,X=/^h\d$/i,Y=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,$=/[+~]/,_=new RegExp("\\\\([\\da-f]{1,6}"+K+"?|("+K+")|.)","ig"),aa=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:d<0?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},ba=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\x80-\uFFFF\w-]/g,ca=function(a,b){return b?"\0"===a?"\ufffd":a.slice(0,-1)+"\\"+a.charCodeAt(a.length-1).toString(16)+" ":"\\"+a},da=function(){m()},ea=ta(function(a){return a.disabled===!0},{dir:"parentNode",next:"legend"});try{G.apply(D=H.call(v.childNodes),v.childNodes),D[v.childNodes.length].nodeType}catch(fa){G={apply:D.length?function(a,b){F.apply(a,H.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function ga(a,b,d,e){var f,h,j,k,l,o,r,s=b&&b.ownerDocument,w=b?b.nodeType:9;if(d=d||[],"string"!=typeof a||!a||1!==w&&9!==w&&11!==w)return d;if(!e&&((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,p)){if(11!==w&&(l=Z.exec(a)))if(f=l[1]){if(9===w){if(!(j=b.getElementById(f)))return d;if(j.id===f)return d.push(j),d}else if(s&&(j=s.getElementById(f))&&t(b,j)&&j.id===f)return d.push(j),d}else{if(l[2])return G.apply(d,b.getElementsByTagName(a)),d;if((f=l[3])&&c.getElementsByClassName&&b.getElementsByClassName)return G.apply(d,b.getElementsByClassName(f)),d}if(c.qsa&&!A[a+" "]&&(!q||!q.test(a))){if(1!==w)s=b,r=a;else if("object"!==b.nodeName.toLowerCase()){(k=b.getAttribute("id"))?k=k.replace(ba,ca):b.setAttribute("id",k=u),o=g(a),h=o.length;while(h--)o[h]="#"+k+" "+sa(o[h]);r=o.join(","),s=$.test(a)&&qa(b.parentNode)||b}if(r)try{return G.apply(d,s.querySelectorAll(r)),d}catch(x){}finally{k===u&&b.removeAttribute("id")}}}return i(a.replace(P,"$1"),b,d,e)}function ha(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ia(a){return a[u]=!0,a}function ja(a){var b=n.createElement("fieldset");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ka(a,b){var c=a.split("|"),e=c.length;while(e--)d.attrHandle[c[e]]=b}function la(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&a.sourceIndex-b.sourceIndex;if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function na(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function oa(a){return function(b){return"label"in b&&b.disabled===a||"form"in b&&b.disabled===a||"form"in b&&b.disabled===!1&&(b.isDisabled===a||b.isDisabled!==!a&&("label"in b||!ea(b))!==a)}}function pa(a){return ia(function(b){return b=+b,ia(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function qa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=ga.support={},f=ga.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return!!b&&"HTML"!==b.nodeName},m=ga.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=n.documentElement,p=!f(n),v!==n&&(e=n.defaultView)&&e.top!==e&&(e.addEventListener?e.addEventListener("unload",da,!1):e.attachEvent&&e.attachEvent("onunload",da)),c.attributes=ja(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ja(function(a){return a.appendChild(n.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=Y.test(n.getElementsByClassName),c.getById=ja(function(a){return o.appendChild(a).id=u,!n.getElementsByName||!n.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c?[c]:[]}},d.filter.ID=function(a){var b=a.replace(_,aa);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(_,aa);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){if("undefined"!=typeof b.getElementsByClassName&&p)return b.getElementsByClassName(a)},r=[],q=[],(c.qsa=Y.test(n.querySelectorAll))&&(ja(function(a){o.appendChild(a).innerHTML="",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+K+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+K+"*(?:value|"+J+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ja(function(a){a.innerHTML="";var b=n.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+K+"*[*^$|!~]?="),2!==a.querySelectorAll(":enabled").length&&q.push(":enabled",":disabled"),o.appendChild(a).disabled=!0,2!==a.querySelectorAll(":disabled").length&&q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=Y.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ja(function(a){c.disconnectedMatch=s.call(a,"*"),s.call(a,"[s!='']:x"),r.push("!=",N)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=Y.test(o.compareDocumentPosition),t=b||Y.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===n||a.ownerDocument===v&&t(v,a)?-1:b===n||b.ownerDocument===v&&t(v,b)?1:k?I(k,a)-I(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,g=[a],h=[b];if(!e||!f)return a===n?-1:b===n?1:e?-1:f?1:k?I(k,a)-I(k,b):0;if(e===f)return la(a,b);c=a;while(c=c.parentNode)g.unshift(c);c=b;while(c=c.parentNode)h.unshift(c);while(g[d]===h[d])d++;return d?la(g[d],h[d]):g[d]===v?-1:h[d]===v?1:0},n):n},ga.matches=function(a,b){return ga(a,null,null,b)},ga.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(S,"='$1']"),c.matchesSelector&&p&&!A[b+" "]&&(!r||!r.test(b))&&(!q||!q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return ga(b,n,null,[a]).length>0},ga.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},ga.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&C.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},ga.escape=function(a){return(a+"").replace(ba,ca)},ga.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},ga.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=ga.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=ga.selectors={cacheLength:50,createPseudo:ia,match:V,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(_,aa),a[3]=(a[3]||a[4]||a[5]||"").replace(_,aa),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||ga.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&ga.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return V.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&T.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(_,aa).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+K+")"+a+"("+K+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=ga.attr(d,a);return null==e?"!="===b:!b||(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(O," ")+" ").indexOf(c)>-1:"|="===b&&(e===c||e.slice(0,c.length+1)===c+"-"))}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h,t=!1;if(q){if(f){while(p){m=b;while(m=m[p])if(h?m.nodeName.toLowerCase()===r:1===m.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){m=q,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n&&j[2],m=n&&q.childNodes[n];while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if(1===m.nodeType&&++t&&m===b){k[a]=[w,n,t];break}}else if(s&&(m=b,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n),t===!1)while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if((h?m.nodeName.toLowerCase()===r:1===m.nodeType)&&++t&&(s&&(l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),k[a]=[w,t]),m===b))break;return t-=e,t===d||t%d===0&&t/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||ga.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ia(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=I(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ia(function(a){var b=[],c=[],d=h(a.replace(P,"$1"));return d[u]?ia(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ia(function(a){return function(b){return ga(a,b).length>0}}),contains:ia(function(a){return a=a.replace(_,aa),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ia(function(a){return U.test(a||"")||ga.error("unsupported lang: "+a),a=a.replace(_,aa).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:oa(!1),disabled:oa(!0),checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return X.test(a.nodeName)},input:function(a){return W.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:pa(function(){return[0]}),last:pa(function(a,b){return[b-1]}),eq:pa(function(a,b,c){return[c<0?c+b:c]}),even:pa(function(a,b){for(var c=0;c=0;)a.push(d);return a}),gt:pa(function(a,b,c){for(var d=c<0?c+b:c;++d1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function va(a,b,c){for(var d=0,e=b.length;d-1&&(f[j]=!(g[j]=l))}}else r=wa(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):G.apply(g,r)})}function ya(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=ta(function(a){return a===b},h,!0),l=ta(function(a){return I(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];i1&&ua(m),i>1&&sa(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(P,"$1"),c,i0,e=a.length>0,f=function(f,g,h,i,k){var l,o,q,r=0,s="0",t=f&&[],u=[],v=j,x=f||e&&d.find.TAG("*",k),y=w+=null==v?1:Math.random()||.1,z=x.length;for(k&&(j=g===n||g||k);s!==z&&null!=(l=x[s]);s++){if(e&&l){o=0,g||l.ownerDocument===n||(m(l),h=!p);while(q=a[o++])if(q(l,g||n,h)){i.push(l);break}k&&(w=y)}c&&((l=!q&&l)&&r--,f&&t.push(l))}if(r+=s,c&&s!==r){o=0;while(q=b[o++])q(t,u,g,h);if(f){if(r>0)while(s--)t[s]||u[s]||(u[s]=E.call(i));u=wa(u)}G.apply(i,u),k&&!f&&u.length>0&&r+b.length>1&&ga.uniqueSort(i)}return k&&(w=y,j=v),t};return c?ia(f):f}return h=ga.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=ya(b[c]),f[u]?d.push(f):e.push(f);f=A(a,za(e,d)),f.selector=a}return f},i=ga.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(_,aa),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=V.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(_,aa),$.test(j[0].type)&&qa(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&sa(j),!a)return G.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,!b||$.test(a)&&qa(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ja(function(a){return 1&a.compareDocumentPosition(n.createElement("fieldset"))}),ja(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||ka("type|href|height|width",function(a,b,c){if(!c)return a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ja(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ka("value",function(a,b,c){if(!c&&"input"===a.nodeName.toLowerCase())return a.defaultValue}),ja(function(a){return null==a.getAttribute("disabled")})||ka(J,function(a,b,c){var d;if(!c)return a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),ga}(a);r.find=x,r.expr=x.selectors,r.expr[":"]=r.expr.pseudos,r.uniqueSort=r.unique=x.uniqueSort,r.text=x.getText,r.isXMLDoc=x.isXML,r.contains=x.contains,r.escapeSelector=x.escape;var y=function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&r(a).is(c))break;d.push(a)}return d},z=function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c},A=r.expr.match.needsContext,B=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i,C=/^.[^:#\[\.,]*$/;function D(a,b,c){if(r.isFunction(b))return r.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return r.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(C.test(b))return r.filter(b,a,c);b=r.filter(b,a)}return r.grep(a,function(a){return i.call(b,a)>-1!==c&&1===a.nodeType})}r.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?r.find.matchesSelector(d,a)?[d]:[]:r.find.matches(a,r.grep(b,function(a){return 1===a.nodeType}))},r.fn.extend({find:function(a){var b,c,d=this.length,e=this;if("string"!=typeof a)return this.pushStack(r(a).filter(function(){for(b=0;b1?r.uniqueSort(c):c},filter:function(a){return this.pushStack(D(this,a||[],!1))},not:function(a){return this.pushStack(D(this,a||[],!0))},is:function(a){return!!D(this,"string"==typeof a&&A.test(a)?r(a):a||[],!1).length}});var E,F=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/,G=r.fn.init=function(a,b,c){var e,f;if(!a)return this;if(c=c||E,"string"==typeof a){if(e="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:F.exec(a),!e||!e[1]&&b)return!b||b.jquery?(b||c).find(a):this.constructor(b).find(a);if(e[1]){if(b=b instanceof r?b[0]:b,r.merge(this,r.parseHTML(e[1],b&&b.nodeType?b.ownerDocument||b:d,!0)),B.test(e[1])&&r.isPlainObject(b))for(e in b)r.isFunction(this[e])?this[e](b[e]):this.attr(e,b[e]);return this}return f=d.getElementById(e[2]),f&&(this[0]=f,this.length=1),this}return a.nodeType?(this[0]=a,this.length=1,this):r.isFunction(a)?void 0!==c.ready?c.ready(a):a(r):r.makeArray(a,this)};G.prototype=r.fn,E=r(d);var H=/^(?:parents|prev(?:Until|All))/,I={children:!0,contents:!0,next:!0,prev:!0};r.fn.extend({has:function(a){var b=r(a,this),c=b.length;return this.filter(function(){for(var a=0;a-1:1===c.nodeType&&r.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?r.uniqueSort(f):f)},index:function(a){return a?"string"==typeof a?i.call(r(a),this[0]):i.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(r.uniqueSort(r.merge(this.get(),r(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function J(a,b){while((a=a[b])&&1!==a.nodeType);return a}r.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return y(a,"parentNode")},parentsUntil:function(a,b,c){return y(a,"parentNode",c)},next:function(a){return J(a,"nextSibling")},prev:function(a){return J(a,"previousSibling")},nextAll:function(a){return y(a,"nextSibling")},prevAll:function(a){return y(a,"previousSibling")},nextUntil:function(a,b,c){return y(a,"nextSibling",c)},prevUntil:function(a,b,c){return y(a,"previousSibling",c)},siblings:function(a){return z((a.parentNode||{}).firstChild,a)},children:function(a){return z(a.firstChild)},contents:function(a){return a.contentDocument||r.merge([],a.childNodes)}},function(a,b){r.fn[a]=function(c,d){var e=r.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=r.filter(d,e)),this.length>1&&(I[a]||r.uniqueSort(e),H.test(a)&&e.reverse()),this.pushStack(e)}});var K=/\S+/g;function L(a){var b={};return r.each(a.match(K)||[],function(a,c){b[c]=!0}),b}r.Callbacks=function(a){a="string"==typeof a?L(a):r.extend({},a);var b,c,d,e,f=[],g=[],h=-1,i=function(){for(e=a.once,d=b=!0;g.length;h=-1){c=g.shift();while(++h-1)f.splice(c,1),c<=h&&h--}),this},has:function(a){return a?r.inArray(a,f)>-1:f.length>0},empty:function(){return f&&(f=[]),this},disable:function(){return e=g=[],f=c="",this},disabled:function(){return!f},lock:function(){return e=g=[],c||b||(f=c=""),this},locked:function(){return!!e},fireWith:function(a,c){return e||(c=c||[],c=[a,c.slice?c.slice():c],g.push(c),b||i()),this},fire:function(){return j.fireWith(this,arguments),this},fired:function(){return!!d}};return j};function M(a){return a}function N(a){throw a}function O(a,b,c){var d;try{a&&r.isFunction(d=a.promise)?d.call(a).done(b).fail(c):a&&r.isFunction(d=a.then)?d.call(a,b,c):b.call(void 0,a)}catch(a){c.call(void 0,a)}}r.extend({Deferred:function(b){var c=[["notify","progress",r.Callbacks("memory"),r.Callbacks("memory"),2],["resolve","done",r.Callbacks("once memory"),r.Callbacks("once memory"),0,"resolved"],["reject","fail",r.Callbacks("once memory"),r.Callbacks("once memory"),1,"rejected"]],d="pending",e={state:function(){return d},always:function(){return f.done(arguments).fail(arguments),this},"catch":function(a){return e.then(null,a)},pipe:function(){var a=arguments;return r.Deferred(function(b){r.each(c,function(c,d){var e=r.isFunction(a[d[4]])&&a[d[4]];f[d[1]](function(){var a=e&&e.apply(this,arguments);a&&r.isFunction(a.promise)?a.promise().progress(b.notify).done(b.resolve).fail(b.reject):b[d[0]+"With"](this,e?[a]:arguments)})}),a=null}).promise()},then:function(b,d,e){var f=0;function g(b,c,d,e){return function(){var h=this,i=arguments,j=function(){var a,j;if(!(b=f&&(d!==N&&(h=void 0,i=[a]),c.rejectWith(h,i))}};b?k():(r.Deferred.getStackHook&&(k.stackTrace=r.Deferred.getStackHook()),a.setTimeout(k))}}return r.Deferred(function(a){c[0][3].add(g(0,a,r.isFunction(e)?e:M,a.notifyWith)),c[1][3].add(g(0,a,r.isFunction(b)?b:M)),c[2][3].add(g(0,a,r.isFunction(d)?d:N))}).promise()},promise:function(a){return null!=a?r.extend(a,e):e}},f={};return r.each(c,function(a,b){var g=b[2],h=b[5];e[b[1]]=g.add,h&&g.add(function(){d=h},c[3-a][2].disable,c[0][2].lock),g.add(b[3].fire),f[b[0]]=function(){return f[b[0]+"With"](this===f?void 0:this,arguments),this},f[b[0]+"With"]=g.fireWith}),e.promise(f),b&&b.call(f,f),f},when:function(a){var b=arguments.length,c=b,d=Array(c),e=f.call(arguments),g=r.Deferred(),h=function(a){return function(c){d[a]=this,e[a]=arguments.length>1?f.call(arguments):c,--b||g.resolveWith(d,e)}};if(b<=1&&(O(a,g.done(h(c)).resolve,g.reject),"pending"===g.state()||r.isFunction(e[c]&&e[c].then)))return g.then();while(c--)O(e[c],h(c),g.reject);return g.promise()}});var P=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;r.Deferred.exceptionHook=function(b,c){a.console&&a.console.warn&&b&&P.test(b.name)&&a.console.warn("jQuery.Deferred exception: "+b.message,b.stack,c)},r.readyException=function(b){a.setTimeout(function(){throw b})};var Q=r.Deferred();r.fn.ready=function(a){return Q.then(a)["catch"](function(a){r.readyException(a)}),this},r.extend({isReady:!1,readyWait:1,holdReady:function(a){a?r.readyWait++:r.ready(!0)},ready:function(a){(a===!0?--r.readyWait:r.isReady)||(r.isReady=!0,a!==!0&&--r.readyWait>0||Q.resolveWith(d,[r]))}}),r.ready.then=Q.then;function R(){d.removeEventListener("DOMContentLoaded",R),a.removeEventListener("load",R),r.ready()}"complete"===d.readyState||"loading"!==d.readyState&&!d.documentElement.doScroll?a.setTimeout(r.ready):(d.addEventListener("DOMContentLoaded",R),a.addEventListener("load",R));var S=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===r.type(c)){e=!0;for(h in c)S(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0, +r.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(r(a),c)})),b))for(;h1,null,!0)},removeData:function(a){return this.each(function(){W.remove(this,a)})}}),r.extend({queue:function(a,b,c){var d;if(a)return b=(b||"fx")+"queue",d=V.get(a,b),c&&(!d||r.isArray(c)?d=V.access(a,b,r.makeArray(c)):d.push(c)),d||[]},dequeue:function(a,b){b=b||"fx";var c=r.queue(a,b),d=c.length,e=c.shift(),f=r._queueHooks(a,b),g=function(){r.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return V.get(a,c)||V.access(a,c,{empty:r.Callbacks("once memory").add(function(){V.remove(a,[b+"queue",c])})})}}),r.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.length\x20\t\r\n\f]+)/i,ja=/^$|\/(?:java|ecma)script/i,ka={option:[1,""],thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};ka.optgroup=ka.option,ka.tbody=ka.tfoot=ka.colgroup=ka.caption=ka.thead,ka.th=ka.td;function la(a,b){var c="undefined"!=typeof a.getElementsByTagName?a.getElementsByTagName(b||"*"):"undefined"!=typeof a.querySelectorAll?a.querySelectorAll(b||"*"):[];return void 0===b||b&&r.nodeName(a,b)?r.merge([a],c):c}function ma(a,b){for(var c=0,d=a.length;c-1)e&&e.push(f);else if(j=r.contains(f.ownerDocument,f),g=la(l.appendChild(f),"script"),j&&ma(g),c){k=0;while(f=g[k++])ja.test(f.type||"")&&c.push(f)}return l}!function(){var a=d.createDocumentFragment(),b=a.appendChild(d.createElement("div")),c=d.createElement("input");c.setAttribute("type","radio"),c.setAttribute("checked","checked"),c.setAttribute("name","t"),b.appendChild(c),o.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,b.innerHTML="",o.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var pa=d.documentElement,qa=/^key/,ra=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,sa=/^([^.]*)(?:\.(.+)|)/;function ta(){return!0}function ua(){return!1}function va(){try{return d.activeElement}catch(a){}}function wa(a,b,c,d,e,f){var g,h;if("object"==typeof b){"string"!=typeof c&&(d=d||c,c=void 0);for(h in b)wa(a,h,c,d,b[h],f);return a}if(null==d&&null==e?(e=c,d=c=void 0):null==e&&("string"==typeof c?(e=d,d=void 0):(e=d,d=c,c=void 0)),e===!1)e=ua;else if(!e)return a;return 1===f&&(g=e,e=function(a){return r().off(a),g.apply(this,arguments)},e.guid=g.guid||(g.guid=r.guid++)),a.each(function(){r.event.add(this,b,e,d,c)})}r.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,o,p,q=V.get(a);if(q){c.handler&&(f=c,c=f.handler,e=f.selector),e&&r.find.matchesSelector(pa,e),c.guid||(c.guid=r.guid++),(i=q.events)||(i=q.events={}),(g=q.handle)||(g=q.handle=function(b){return"undefined"!=typeof r&&r.event.triggered!==b.type?r.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(K)||[""],j=b.length;while(j--)h=sa.exec(b[j])||[],n=p=h[1],o=(h[2]||"").split(".").sort(),n&&(l=r.event.special[n]||{},n=(e?l.delegateType:l.bindType)||n,l=r.event.special[n]||{},k=r.extend({type:n,origType:p,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&r.expr.match.needsContext.test(e),namespace:o.join(".")},f),(m=i[n])||(m=i[n]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,o,g)!==!1||a.addEventListener&&a.addEventListener(n,g)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),r.event.global[n]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,o,p,q=V.hasData(a)&&V.get(a);if(q&&(i=q.events)){b=(b||"").match(K)||[""],j=b.length;while(j--)if(h=sa.exec(b[j])||[],n=p=h[1],o=(h[2]||"").split(".").sort(),n){l=r.event.special[n]||{},n=(d?l.delegateType:l.bindType)||n,m=i[n]||[],h=h[2]&&new RegExp("(^|\\.)"+o.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&p!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,o,q.handle)!==!1||r.removeEvent(a,n,q.handle),delete i[n])}else for(n in i)r.event.remove(a,n+b[j],c,d,!0);r.isEmptyObject(i)&&V.remove(a,"handle events")}},dispatch:function(a){var b=r.event.fix(a),c,d,e,f,g,h,i=new Array(arguments.length),j=(V.get(this,"events")||{})[b.type]||[],k=r.event.special[b.type]||{};for(i[0]=b,c=1;c-1:r.find(e,this,null,[i]).length),d[e]&&d.push(f);d.length&&g.push({elem:i,handlers:d})}return h\x20\t\r\n\f]*)[^>]*)\/>/gi,ya=/\s*$/g;function Ca(a,b){return r.nodeName(a,"table")&&r.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a:a}function Da(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function Ea(a){var b=Aa.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function Fa(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(V.hasData(a)&&(f=V.access(a),g=V.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;c1&&"string"==typeof q&&!o.checkClone&&za.test(q))return a.each(function(e){var f=a.eq(e);s&&(b[0]=q.call(this,e,f.html())),Ha(f,b,c,d)});if(m&&(e=oa(b,a[0].ownerDocument,!1,a,d),f=e.firstChild,1===e.childNodes.length&&(e=f),f||d)){for(h=r.map(la(e,"script"),Da),i=h.length;l")},clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=r.contains(a.ownerDocument,a);if(!(o.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||r.isXMLDoc(a)))for(g=la(h),f=la(a),d=0,e=f.length;d0&&ma(g,!i&&la(a,"script")),h},cleanData:function(a){for(var b,c,d,e=r.event.special,f=0;void 0!==(c=a[f]);f++)if(T(c)){if(b=c[V.expando]){if(b.events)for(d in b.events)e[d]?r.event.remove(c,d):r.removeEvent(c,d,b.handle);c[V.expando]=void 0}c[W.expando]&&(c[W.expando]=void 0)}}}),r.fn.extend({detach:function(a){return Ia(this,a,!0)},remove:function(a){return Ia(this,a)},text:function(a){return S(this,function(a){return void 0===a?r.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=a)})},null,a,arguments.length)},append:function(){return Ha(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Ca(this,a);b.appendChild(a)}})},prepend:function(){return Ha(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Ca(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return Ha(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return Ha(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(r.cleanData(la(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null!=a&&a,b=null==b?a:b,this.map(function(){return r.clone(this,a,b)})},html:function(a){return S(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!ya.test(a)&&!ka[(ia.exec(a)||["",""])[1].toLowerCase()]){a=r.htmlPrefilter(a);try{for(;c1)}});function Xa(a,b,c,d,e){return new Xa.prototype.init(a,b,c,d,e)}r.Tween=Xa,Xa.prototype={constructor:Xa,init:function(a,b,c,d,e,f){this.elem=a,this.prop=c,this.easing=e||r.easing._default,this.options=b,this.start=this.now=this.cur(),this.end=d,this.unit=f||(r.cssNumber[c]?"":"px")},cur:function(){var a=Xa.propHooks[this.prop];return a&&a.get?a.get(this):Xa.propHooks._default.get(this)},run:function(a){var b,c=Xa.propHooks[this.prop];return this.options.duration?this.pos=b=r.easing[this.easing](a,this.options.duration*a,0,1,this.options.duration):this.pos=b=a,this.now=(this.end-this.start)*b+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),c&&c.set?c.set(this):Xa.propHooks._default.set(this),this}},Xa.prototype.init.prototype=Xa.prototype,Xa.propHooks={_default:{get:function(a){var b;return 1!==a.elem.nodeType||null!=a.elem[a.prop]&&null==a.elem.style[a.prop]?a.elem[a.prop]:(b=r.css(a.elem,a.prop,""),b&&"auto"!==b?b:0)},set:function(a){r.fx.step[a.prop]?r.fx.step[a.prop](a):1!==a.elem.nodeType||null==a.elem.style[r.cssProps[a.prop]]&&!r.cssHooks[a.prop]?a.elem[a.prop]=a.now:r.style(a.elem,a.prop,a.now+a.unit)}}},Xa.propHooks.scrollTop=Xa.propHooks.scrollLeft={set:function(a){a.elem.nodeType&&a.elem.parentNode&&(a.elem[a.prop]=a.now)}},r.easing={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2},_default:"swing"},r.fx=Xa.prototype.init,r.fx.step={};var Ya,Za,$a=/^(?:toggle|show|hide)$/,_a=/queueHooks$/;function ab(){Za&&(a.requestAnimationFrame(ab),r.fx.tick())}function bb(){return a.setTimeout(function(){Ya=void 0}),Ya=r.now()}function cb(a,b){var c,d=0,e={height:a};for(b=b?1:0;d<4;d+=2-b)c=aa[d],e["margin"+c]=e["padding"+c]=a;return b&&(e.opacity=e.width=a),e}function db(a,b,c){for(var d,e=(gb.tweeners[b]||[]).concat(gb.tweeners["*"]),f=0,g=e.length;f1)},removeAttr:function(a){return this.each(function(){r.removeAttr(this,a)})}}),r.extend({attr:function(a,b,c){var d,e,f=a.nodeType;if(3!==f&&8!==f&&2!==f)return"undefined"==typeof a.getAttribute?r.prop(a,b,c):(1===f&&r.isXMLDoc(a)||(e=r.attrHooks[b.toLowerCase()]||(r.expr.match.bool.test(b)?hb:void 0)),void 0!==c?null===c?void r.removeAttr(a,b):e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:(a.setAttribute(b,c+""),c):e&&"get"in e&&null!==(d=e.get(a,b))?d:(d=r.find.attr(a,b),null==d?void 0:d))},attrHooks:{type:{set:function(a,b){if(!o.radioValue&&"radio"===b&&r.nodeName(a,"input")){var c=a.value;return a.setAttribute("type",b),c&&(a.value=c),b}}}},removeAttr:function(a,b){var c,d=0,e=b&&b.match(K); +if(e&&1===a.nodeType)while(c=e[d++])a.removeAttribute(c)}}),hb={set:function(a,b,c){return b===!1?r.removeAttr(a,c):a.setAttribute(c,c),c}},r.each(r.expr.match.bool.source.match(/\w+/g),function(a,b){var c=ib[b]||r.find.attr;ib[b]=function(a,b,d){var e,f,g=b.toLowerCase();return d||(f=ib[g],ib[g]=e,e=null!=c(a,b,d)?g:null,ib[g]=f),e}});var jb=/^(?:input|select|textarea|button)$/i,kb=/^(?:a|area)$/i;r.fn.extend({prop:function(a,b){return S(this,r.prop,a,b,arguments.length>1)},removeProp:function(a){return this.each(function(){delete this[r.propFix[a]||a]})}}),r.extend({prop:function(a,b,c){var d,e,f=a.nodeType;if(3!==f&&8!==f&&2!==f)return 1===f&&r.isXMLDoc(a)||(b=r.propFix[b]||b,e=r.propHooks[b]),void 0!==c?e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:a[b]=c:e&&"get"in e&&null!==(d=e.get(a,b))?d:a[b]},propHooks:{tabIndex:{get:function(a){var b=r.find.attr(a,"tabindex");return b?parseInt(b,10):jb.test(a.nodeName)||kb.test(a.nodeName)&&a.href?0:-1}}},propFix:{"for":"htmlFor","class":"className"}}),o.optSelected||(r.propHooks.selected={get:function(a){var b=a.parentNode;return b&&b.parentNode&&b.parentNode.selectedIndex,null},set:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex)}}),r.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){r.propFix[this.toLowerCase()]=this});var lb=/[\t\r\n\f]/g;function mb(a){return a.getAttribute&&a.getAttribute("class")||""}r.fn.extend({addClass:function(a){var b,c,d,e,f,g,h,i=0;if(r.isFunction(a))return this.each(function(b){r(this).addClass(a.call(this,b,mb(this)))});if("string"==typeof a&&a){b=a.match(K)||[];while(c=this[i++])if(e=mb(c),d=1===c.nodeType&&(" "+e+" ").replace(lb," ")){g=0;while(f=b[g++])d.indexOf(" "+f+" ")<0&&(d+=f+" ");h=r.trim(d),e!==h&&c.setAttribute("class",h)}}return this},removeClass:function(a){var b,c,d,e,f,g,h,i=0;if(r.isFunction(a))return this.each(function(b){r(this).removeClass(a.call(this,b,mb(this)))});if(!arguments.length)return this.attr("class","");if("string"==typeof a&&a){b=a.match(K)||[];while(c=this[i++])if(e=mb(c),d=1===c.nodeType&&(" "+e+" ").replace(lb," ")){g=0;while(f=b[g++])while(d.indexOf(" "+f+" ")>-1)d=d.replace(" "+f+" "," ");h=r.trim(d),e!==h&&c.setAttribute("class",h)}}return this},toggleClass:function(a,b){var c=typeof a;return"boolean"==typeof b&&"string"===c?b?this.addClass(a):this.removeClass(a):r.isFunction(a)?this.each(function(c){r(this).toggleClass(a.call(this,c,mb(this),b),b)}):this.each(function(){var b,d,e,f;if("string"===c){d=0,e=r(this),f=a.match(K)||[];while(b=f[d++])e.hasClass(b)?e.removeClass(b):e.addClass(b)}else void 0!==a&&"boolean"!==c||(b=mb(this),b&&V.set(this,"__className__",b),this.setAttribute&&this.setAttribute("class",b||a===!1?"":V.get(this,"__className__")||""))})},hasClass:function(a){var b,c,d=0;b=" "+a+" ";while(c=this[d++])if(1===c.nodeType&&(" "+mb(c)+" ").replace(lb," ").indexOf(b)>-1)return!0;return!1}});var nb=/\r/g,ob=/[\x20\t\r\n\f]+/g;r.fn.extend({val:function(a){var b,c,d,e=this[0];{if(arguments.length)return d=r.isFunction(a),this.each(function(c){var e;1===this.nodeType&&(e=d?a.call(this,c,r(this).val()):a,null==e?e="":"number"==typeof e?e+="":r.isArray(e)&&(e=r.map(e,function(a){return null==a?"":a+""})),b=r.valHooks[this.type]||r.valHooks[this.nodeName.toLowerCase()],b&&"set"in b&&void 0!==b.set(this,e,"value")||(this.value=e))});if(e)return b=r.valHooks[e.type]||r.valHooks[e.nodeName.toLowerCase()],b&&"get"in b&&void 0!==(c=b.get(e,"value"))?c:(c=e.value,"string"==typeof c?c.replace(nb,""):null==c?"":c)}}}),r.extend({valHooks:{option:{get:function(a){var b=r.find.attr(a,"value");return null!=b?b:r.trim(r.text(a)).replace(ob," ")}},select:{get:function(a){for(var b,c,d=a.options,e=a.selectedIndex,f="select-one"===a.type,g=f?null:[],h=f?e+1:d.length,i=e<0?h:f?e:0;i-1)&&(c=!0);return c||(a.selectedIndex=-1),f}}}}),r.each(["radio","checkbox"],function(){r.valHooks[this]={set:function(a,b){if(r.isArray(b))return a.checked=r.inArray(r(a).val(),b)>-1}},o.checkOn||(r.valHooks[this].get=function(a){return null===a.getAttribute("value")?"on":a.value})});var pb=/^(?:focusinfocus|focusoutblur)$/;r.extend(r.event,{trigger:function(b,c,e,f){var g,h,i,j,k,m,n,o=[e||d],p=l.call(b,"type")?b.type:b,q=l.call(b,"namespace")?b.namespace.split("."):[];if(h=i=e=e||d,3!==e.nodeType&&8!==e.nodeType&&!pb.test(p+r.event.triggered)&&(p.indexOf(".")>-1&&(q=p.split("."),p=q.shift(),q.sort()),k=p.indexOf(":")<0&&"on"+p,b=b[r.expando]?b:new r.Event(p,"object"==typeof b&&b),b.isTrigger=f?2:3,b.namespace=q.join("."),b.rnamespace=b.namespace?new RegExp("(^|\\.)"+q.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=e),c=null==c?[b]:r.makeArray(c,[b]),n=r.event.special[p]||{},f||!n.trigger||n.trigger.apply(e,c)!==!1)){if(!f&&!n.noBubble&&!r.isWindow(e)){for(j=n.delegateType||p,pb.test(j+p)||(h=h.parentNode);h;h=h.parentNode)o.push(h),i=h;i===(e.ownerDocument||d)&&o.push(i.defaultView||i.parentWindow||a)}g=0;while((h=o[g++])&&!b.isPropagationStopped())b.type=g>1?j:n.bindType||p,m=(V.get(h,"events")||{})[b.type]&&V.get(h,"handle"),m&&m.apply(h,c),m=k&&h[k],m&&m.apply&&T(h)&&(b.result=m.apply(h,c),b.result===!1&&b.preventDefault());return b.type=p,f||b.isDefaultPrevented()||n._default&&n._default.apply(o.pop(),c)!==!1||!T(e)||k&&r.isFunction(e[p])&&!r.isWindow(e)&&(i=e[k],i&&(e[k]=null),r.event.triggered=p,e[p](),r.event.triggered=void 0,i&&(e[k]=i)),b.result}},simulate:function(a,b,c){var d=r.extend(new r.Event,c,{type:a,isSimulated:!0});r.event.trigger(d,null,b)}}),r.fn.extend({trigger:function(a,b){return this.each(function(){r.event.trigger(a,b,this)})},triggerHandler:function(a,b){var c=this[0];if(c)return r.event.trigger(a,b,c,!0)}}),r.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(a,b){r.fn[b]=function(a,c){return arguments.length>0?this.on(b,null,a,c):this.trigger(b)}}),r.fn.extend({hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}}),o.focusin="onfocusin"in a,o.focusin||r.each({focus:"focusin",blur:"focusout"},function(a,b){var c=function(a){r.event.simulate(b,a.target,r.event.fix(a))};r.event.special[b]={setup:function(){var d=this.ownerDocument||this,e=V.access(d,b);e||d.addEventListener(a,c,!0),V.access(d,b,(e||0)+1)},teardown:function(){var d=this.ownerDocument||this,e=V.access(d,b)-1;e?V.access(d,b,e):(d.removeEventListener(a,c,!0),V.remove(d,b))}}});var qb=a.location,rb=r.now(),sb=/\?/;r.parseXML=function(b){var c;if(!b||"string"!=typeof b)return null;try{c=(new a.DOMParser).parseFromString(b,"text/xml")}catch(d){c=void 0}return c&&!c.getElementsByTagName("parsererror").length||r.error("Invalid XML: "+b),c};var tb=/\[\]$/,ub=/\r?\n/g,vb=/^(?:submit|button|image|reset|file)$/i,wb=/^(?:input|select|textarea|keygen)/i;function xb(a,b,c,d){var e;if(r.isArray(b))r.each(b,function(b,e){c||tb.test(a)?d(a,e):xb(a+"["+("object"==typeof e&&null!=e?b:"")+"]",e,c,d)});else if(c||"object"!==r.type(b))d(a,b);else for(e in b)xb(a+"["+e+"]",b[e],c,d)}r.param=function(a,b){var c,d=[],e=function(a,b){var c=r.isFunction(b)?b():b;d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(null==c?"":c)};if(r.isArray(a)||a.jquery&&!r.isPlainObject(a))r.each(a,function(){e(this.name,this.value)});else for(c in a)xb(c,a[c],b,e);return d.join("&")},r.fn.extend({serialize:function(){return r.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var a=r.prop(this,"elements");return a?r.makeArray(a):this}).filter(function(){var a=this.type;return this.name&&!r(this).is(":disabled")&&wb.test(this.nodeName)&&!vb.test(a)&&(this.checked||!ha.test(a))}).map(function(a,b){var c=r(this).val();return null==c?null:r.isArray(c)?r.map(c,function(a){return{name:b.name,value:a.replace(ub,"\r\n")}}):{name:b.name,value:c.replace(ub,"\r\n")}}).get()}});var yb=/%20/g,zb=/#.*$/,Ab=/([?&])_=[^&]*/,Bb=/^(.*?):[ \t]*([^\r\n]*)$/gm,Cb=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Db=/^(?:GET|HEAD)$/,Eb=/^\/\//,Fb={},Gb={},Hb="*/".concat("*"),Ib=d.createElement("a");Ib.href=qb.href;function Jb(a){return function(b,c){"string"!=typeof b&&(c=b,b="*");var d,e=0,f=b.toLowerCase().match(K)||[];if(r.isFunction(c))while(d=f[e++])"+"===d[0]?(d=d.slice(1)||"*",(a[d]=a[d]||[]).unshift(c)):(a[d]=a[d]||[]).push(c)}}function Kb(a,b,c,d){var e={},f=a===Gb;function g(h){var i;return e[h]=!0,r.each(a[h]||[],function(a,h){var j=h(b,c,d);return"string"!=typeof j||f||e[j]?f?!(i=j):void 0:(b.dataTypes.unshift(j),g(j),!1)}),i}return g(b.dataTypes[0])||!e["*"]&&g("*")}function Lb(a,b){var c,d,e=r.ajaxSettings.flatOptions||{};for(c in b)void 0!==b[c]&&((e[c]?a:d||(d={}))[c]=b[c]);return d&&r.extend(!0,a,d),a}function Mb(a,b,c){var d,e,f,g,h=a.contents,i=a.dataTypes;while("*"===i[0])i.shift(),void 0===d&&(d=a.mimeType||b.getResponseHeader("Content-Type"));if(d)for(e in h)if(h[e]&&h[e].test(d)){i.unshift(e);break}if(i[0]in c)f=i[0];else{for(e in c){if(!i[0]||a.converters[e+" "+i[0]]){f=e;break}g||(g=e)}f=f||g}if(f)return f!==i[0]&&i.unshift(f),c[f]}function Nb(a,b,c,d){var e,f,g,h,i,j={},k=a.dataTypes.slice();if(k[1])for(g in a.converters)j[g.toLowerCase()]=a.converters[g];f=k.shift();while(f)if(a.responseFields[f]&&(c[a.responseFields[f]]=b),!i&&d&&a.dataFilter&&(b=a.dataFilter(b,a.dataType)),i=f,f=k.shift())if("*"===f)f=i;else if("*"!==i&&i!==f){if(g=j[i+" "+f]||j["* "+f],!g)for(e in j)if(h=e.split(" "),h[1]===f&&(g=j[i+" "+h[0]]||j["* "+h[0]])){g===!0?g=j[e]:j[e]!==!0&&(f=h[0],k.unshift(h[1]));break}if(g!==!0)if(g&&a["throws"])b=g(b);else try{b=g(b)}catch(l){return{state:"parsererror",error:g?l:"No conversion from "+i+" to "+f}}}return{state:"success",data:b}}r.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:qb.href,type:"GET",isLocal:Cb.test(qb.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Hb,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":JSON.parse,"text xml":r.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(a,b){return b?Lb(Lb(a,r.ajaxSettings),b):Lb(r.ajaxSettings,a)},ajaxPrefilter:Jb(Fb),ajaxTransport:Jb(Gb),ajax:function(b,c){"object"==typeof b&&(c=b,b=void 0),c=c||{};var e,f,g,h,i,j,k,l,m,n,o=r.ajaxSetup({},c),p=o.context||o,q=o.context&&(p.nodeType||p.jquery)?r(p):r.event,s=r.Deferred(),t=r.Callbacks("once memory"),u=o.statusCode||{},v={},w={},x="canceled",y={readyState:0,getResponseHeader:function(a){var b;if(k){if(!h){h={};while(b=Bb.exec(g))h[b[1].toLowerCase()]=b[2]}b=h[a.toLowerCase()]}return null==b?null:b},getAllResponseHeaders:function(){return k?g:null},setRequestHeader:function(a,b){return null==k&&(a=w[a.toLowerCase()]=w[a.toLowerCase()]||a,v[a]=b),this},overrideMimeType:function(a){return null==k&&(o.mimeType=a),this},statusCode:function(a){var b;if(a)if(k)y.always(a[y.status]);else for(b in a)u[b]=[u[b],a[b]];return this},abort:function(a){var b=a||x;return e&&e.abort(b),A(0,b),this}};if(s.promise(y),o.url=((b||o.url||qb.href)+"").replace(Eb,qb.protocol+"//"),o.type=c.method||c.type||o.method||o.type,o.dataTypes=(o.dataType||"*").toLowerCase().match(K)||[""],null==o.crossDomain){j=d.createElement("a");try{j.href=o.url,j.href=j.href,o.crossDomain=Ib.protocol+"//"+Ib.host!=j.protocol+"//"+j.host}catch(z){o.crossDomain=!0}}if(o.data&&o.processData&&"string"!=typeof o.data&&(o.data=r.param(o.data,o.traditional)),Kb(Fb,o,c,y),k)return y;l=r.event&&o.global,l&&0===r.active++&&r.event.trigger("ajaxStart"),o.type=o.type.toUpperCase(),o.hasContent=!Db.test(o.type),f=o.url.replace(zb,""),o.hasContent?o.data&&o.processData&&0===(o.contentType||"").indexOf("application/x-www-form-urlencoded")&&(o.data=o.data.replace(yb,"+")):(n=o.url.slice(f.length),o.data&&(f+=(sb.test(f)?"&":"?")+o.data,delete o.data),o.cache===!1&&(f=f.replace(Ab,""),n=(sb.test(f)?"&":"?")+"_="+rb++ +n),o.url=f+n),o.ifModified&&(r.lastModified[f]&&y.setRequestHeader("If-Modified-Since",r.lastModified[f]),r.etag[f]&&y.setRequestHeader("If-None-Match",r.etag[f])),(o.data&&o.hasContent&&o.contentType!==!1||c.contentType)&&y.setRequestHeader("Content-Type",o.contentType),y.setRequestHeader("Accept",o.dataTypes[0]&&o.accepts[o.dataTypes[0]]?o.accepts[o.dataTypes[0]]+("*"!==o.dataTypes[0]?", "+Hb+"; q=0.01":""):o.accepts["*"]);for(m in o.headers)y.setRequestHeader(m,o.headers[m]);if(o.beforeSend&&(o.beforeSend.call(p,y,o)===!1||k))return y.abort();if(x="abort",t.add(o.complete),y.done(o.success),y.fail(o.error),e=Kb(Gb,o,c,y)){if(y.readyState=1,l&&q.trigger("ajaxSend",[y,o]),k)return y;o.async&&o.timeout>0&&(i=a.setTimeout(function(){y.abort("timeout")},o.timeout));try{k=!1,e.send(v,A)}catch(z){if(k)throw z;A(-1,z)}}else A(-1,"No Transport");function A(b,c,d,h){var j,m,n,v,w,x=c;k||(k=!0,i&&a.clearTimeout(i),e=void 0,g=h||"",y.readyState=b>0?4:0,j=b>=200&&b<300||304===b,d&&(v=Mb(o,y,d)),v=Nb(o,v,y,j),j?(o.ifModified&&(w=y.getResponseHeader("Last-Modified"),w&&(r.lastModified[f]=w),w=y.getResponseHeader("etag"),w&&(r.etag[f]=w)),204===b||"HEAD"===o.type?x="nocontent":304===b?x="notmodified":(x=v.state,m=v.data,n=v.error,j=!n)):(n=x,!b&&x||(x="error",b<0&&(b=0))),y.status=b,y.statusText=(c||x)+"",j?s.resolveWith(p,[m,x,y]):s.rejectWith(p,[y,x,n]),y.statusCode(u),u=void 0,l&&q.trigger(j?"ajaxSuccess":"ajaxError",[y,o,j?m:n]),t.fireWith(p,[y,x]),l&&(q.trigger("ajaxComplete",[y,o]),--r.active||r.event.trigger("ajaxStop")))}return y},getJSON:function(a,b,c){return r.get(a,b,c,"json")},getScript:function(a,b){return r.get(a,void 0,b,"script")}}),r.each(["get","post"],function(a,b){r[b]=function(a,c,d,e){return r.isFunction(c)&&(e=e||d,d=c,c=void 0),r.ajax(r.extend({url:a,type:b,dataType:e,data:c,success:d},r.isPlainObject(a)&&a))}}),r._evalUrl=function(a){return r.ajax({url:a,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,"throws":!0})},r.fn.extend({wrapAll:function(a){var b;return this[0]&&(r.isFunction(a)&&(a=a.call(this[0])),b=r(a,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstElementChild)a=a.firstElementChild;return a}).append(this)),this},wrapInner:function(a){return r.isFunction(a)?this.each(function(b){r(this).wrapInner(a.call(this,b))}):this.each(function(){var b=r(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=r.isFunction(a);return this.each(function(c){r(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(a){return this.parent(a).not("body").each(function(){r(this).replaceWith(this.childNodes)}),this}}),r.expr.pseudos.hidden=function(a){return!r.expr.pseudos.visible(a)},r.expr.pseudos.visible=function(a){return!!(a.offsetWidth||a.offsetHeight||a.getClientRects().length)},r.ajaxSettings.xhr=function(){try{return new a.XMLHttpRequest}catch(b){}};var Ob={0:200,1223:204},Pb=r.ajaxSettings.xhr();o.cors=!!Pb&&"withCredentials"in Pb,o.ajax=Pb=!!Pb,r.ajaxTransport(function(b){var c,d;if(o.cors||Pb&&!b.crossDomain)return{send:function(e,f){var g,h=b.xhr();if(h.open(b.type,b.url,b.async,b.username,b.password),b.xhrFields)for(g in b.xhrFields)h[g]=b.xhrFields[g];b.mimeType&&h.overrideMimeType&&h.overrideMimeType(b.mimeType),b.crossDomain||e["X-Requested-With"]||(e["X-Requested-With"]="XMLHttpRequest");for(g in e)h.setRequestHeader(g,e[g]);c=function(a){return function(){c&&(c=d=h.onload=h.onerror=h.onabort=h.onreadystatechange=null,"abort"===a?h.abort():"error"===a?"number"!=typeof h.status?f(0,"error"):f(h.status,h.statusText):f(Ob[h.status]||h.status,h.statusText,"text"!==(h.responseType||"text")||"string"!=typeof h.responseText?{binary:h.response}:{text:h.responseText},h.getAllResponseHeaders()))}},h.onload=c(),d=h.onerror=c("error"),void 0!==h.onabort?h.onabort=d:h.onreadystatechange=function(){4===h.readyState&&a.setTimeout(function(){c&&d()})},c=c("abort");try{h.send(b.hasContent&&b.data||null)}catch(i){if(c)throw i}},abort:function(){c&&c()}}}),r.ajaxPrefilter(function(a){a.crossDomain&&(a.contents.script=!1)}),r.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(a){return r.globalEval(a),a}}}),r.ajaxPrefilter("script",function(a){void 0===a.cache&&(a.cache=!1),a.crossDomain&&(a.type="GET")}),r.ajaxTransport("script",function(a){if(a.crossDomain){var b,c;return{send:function(e,f){b=r("