#include #include #if BOOST_VERSION < 104400 #if (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ > 4)) #undef BOOST_HAS_RVALUE_REFS #endif #endif #include #include #include #include "FACT.h" #include "Dim.h" #include "Event.h" #include "Shell.h" #include "StateMachineDim.h" #include "Connection.h" #include "Configuration.h" #include "Timers.h" #include "Console.h" #include "Converter.h" #include "tools.h" #include "LocalControl.h" namespace ba = boost::asio; namespace bs = boost::system; namespace dummy = ba::placeholders; using namespace std; // ------------------------------------------------------------------------ namespace Drive { struct DimPointing { } __attribute__((__packed__)); struct DimTracking { } __attribute__((__packed__)); struct DimStarguider { double fMissZd; double fMissAz; double fNominalZd; double fNominalAz; double fCenterX; double fCenterY; double fBrightness; uint16_t fNumCorrelated; uint16_t fNumLeds; uint16_t fNumRings; uint16_t fNumStars; } __attribute__((__packed__)); struct DimTPoint { double fNominalAlt; double fNominalAz; double fCurrentAlt; double fCurrentAz; double fDevZd; double fDevAz; double fRa; double fDec; double fCenterX; double fCenterY; double fCenterMag; double fStarX; double fStarY; double fStarMag; double fBrightness; double fRealMag; uint16_t fNumLeds; uint16_t fNumRings; uint16_t fNumStars; uint16_t fNumCorrelated; } __attribute__((__packed__)); }; // ------------------------------------------------------------------------ class ConnectionDrive : public Connection { int fState; bool fIsVerbose; // --verbose // --hex-out // --dynamic-out // --load-file // --leds // --trigger-interval // --physcis-coincidence // --calib-coincidence // --physcis-window // --physcis-window // --trigger-delay // --time-marker-delay // --dead-time // --clock-conditioner-r0 // --clock-conditioner-r1 // --clock-conditioner-r8 // --clock-conditioner-r9 // --clock-conditioner-r11 // --clock-conditioner-r13 // --clock-conditioner-r14 // --clock-conditioner-r15 // ... virtual void UpdatePointing(const Time &, const boost::array &) { } virtual void UpdateTracking(const Time &, const boost::array &) { } virtual void UpdateStarguider(const Time &, const Drive::DimStarguider &) { } virtual void UpdateTPoint(const Time &, const Drive::DimTPoint &) { } protected: map fCounter; ba::streambuf fBuffer; Time ReadTime(istream &in) { uint16_t y, m, d, hh, mm, ss, ms; in >> y >> m >> d >> hh >> mm >> ss >> ms; return Time(y, m, d, hh, mm, ss, ms*1000); } double ReadAngle(istream &in) { char sgn; uint16_t d, m; float s; in >> sgn >> d >> m >> s; const double ret = ((60.0 * (60.0 * (double)d + (double)m) + s))/3600.; return sgn=='-' ? -ret : ret; } void HandleReceivedReport(const boost::system::error_code& err, size_t bytes_received) { // Do not schedule a new read if the connection failed. if (bytes_received==0 || err) { if (err==ba::error::eof) Warn("Connection closed by remote host (FTM)."); // 107: Transport endpoint is not connected (bs::error_code(107, bs::system_category)) // 125: Operation canceled if (err && err!=ba::error::eof && // Connection closed by remote host err!=ba::error::basic_errors::not_connected && // Connection closed by remote host err!=ba::error::basic_errors::operation_aborted) // Connection closed by us { stringstream str; str << "Reading from " << URL() << ": " << err.message() << " (" << err << ")";// << endl; Error(str); } PostClose(err!=ba::error::basic_errors::operation_aborted); return; } istream is(&fBuffer); string line; getline(is, line); if (fIsVerbose) Out() << line << endl; StartReadReport(); if (line.substr(0, 13)=="STARG-REPORT ") { istringstream stream(line.substr(16)); // 0: Error // 1: Standby // 2: Monitoring uint16_t status1; stream >> status1; const Time t1 = ReadTime(stream); uint16_t status2; stream >> status2; const Time t2 = ReadTime(stream); double misszd, missaz; stream >> misszd >> missaz; const double zd = ReadAngle(stream); const double az = ReadAngle(stream); double cx, cy; stream >> cx >> cy; int ncor; stream >> ncor; double bright, mjd; stream >> bright >> mjd; int nled, nring, nstars; stream >> nled >> nring >> nstars; if (stream.fail()) return; Drive::DimStarguider data; data.fMissZd = misszd; data.fMissAz = missaz; data.fNominalZd = zd; data.fNominalAz = az; data.fCenterX = cx; data.fCenterY = cy; data.fNumCorrelated = ncor; data.fBrightness = bright; data.fNumLeds = nled; data.fNumRings = nring; data.fNumStars = nstars; UpdateStarguider(Time(mjd), data); return; } if (line.substr(0, 14)=="TPOINT-REPORT ") { istringstream stream(line.substr(17)); uint16_t status1; stream >> status1; const Time t1 = ReadTime(stream); uint16_t status2; stream >> status2; const Time t2 = ReadTime(stream); double az1, alt1, az2, alt2, ra, dec, dzd, daz; stream >> az1 >> alt1 >> az2 >> alt2 >> ra >> dec >> dzd >> daz; // c: center, s:start double mjd, cmag, smag, cx, cy, sx, sy; stream >> mjd >> cmag >> smag >> cx >> cy >> sx >> sy; int nled, nring, nstar, ncor; stream >> nled >> nring >> nstar >> ncor; double bright, mag; stream >> bright >> mag; string name; stream >> name; if (stream.fail()) return; Drive::DimTPoint tpoint; tpoint.fNominalAz = az1; tpoint.fNominalAlt = alt1; tpoint.fCurrentAz = az2; tpoint.fCurrentAlt = alt2; tpoint.fDevAz = daz; tpoint.fDevZd = dzd; tpoint.fRa = ra; tpoint.fDec = dec; tpoint.fCenterX = cx; tpoint.fCenterY = cy; tpoint.fCenterMag = cmag; tpoint.fStarX = sx; tpoint.fStarY = sy; tpoint.fStarMag = smag; tpoint.fBrightness = bright; tpoint.fNumCorrelated = ncor; tpoint.fNumLeds = nled; tpoint.fNumRings = nring; tpoint.fNumStars = nstar; tpoint.fRealMag = mag; return; } if (line.substr(0, 13)=="DRIVE-REPORT ") { // DRIVE-REPORT M1 // 01 2011 05 14 11 31 19 038 // 02 1858 11 17 00 00 00 000 // + 000 00 000 + 000 00 000 // + 000 00 000 // 55695.480081 // + 000 00 000 + 000 00 000 // + 000 00 000 + 000 00 000 // 0000.000 0000.000 // 0 2 // status // year month day hour minute seconds millisec // year month day hour minute seconds millisec // ra(+ h m s) dec(+ d m s) ha(+ h m s) // mjd // zd(+ d m s) az(+ d m s) // zd(+ d m s) az(+ d m s) // zd_err az_err // armed(0=unlocked, 1=locked) // stgmd(0=none, 1=starguider, 2=starguider off) istringstream stream(line.substr(16)); uint16_t status1; stream >> status1; const Time t1 = ReadTime(stream); uint16_t status2; stream >> status2; const Time t2 = ReadTime(stream); const double ra = ReadAngle(stream); const double dec = ReadAngle(stream); const double ha = ReadAngle(stream); double mjd; stream >> mjd; const double zd1 = ReadAngle(stream); const double az1 = ReadAngle(stream); const double zd2 = ReadAngle(stream); const double az2 = ReadAngle(stream); double zd_err, az_err; stream >> zd_err; stream >> az_err; uint16_t armed, stgmd; stream >> armed; stream >> stgmd; if (stream.fail()) return; // Status 0: Error // Status 1: Stopped // Status 3: Stopping || Moving // Status 4: Tracking if (status1==0) status1 = 99; fState = status1==1 ? armed+1 : status1; const boost::array point = {{ zd2, az2 }}; UpdatePointing(t1, point); const boost::array track = {{ ra, dec, ha, zd1, az1, zd_err, az_err }}; UpdateTracking(Time(mjd), track); // ---- DIM ----> t1 as event time // status1 // mjd // ra/dec/ha // zd/az (nominal) // zd/az (current) // err(zd/az) // [armed] [stgmd] // Maybe: // POINTING_POSITION --> t1, zd/az (current), [armed, stgmd, status1] // // if (mjd>0) // TRACKING_POSITION --> mjd, zd/az (nominal), err(zd/az) // ra/dec, ha(not well defined), // [Nominal + Error == Current] // MJD is the time which corresponds to the nominal position // t1 is the time which corresponds to the current position/HA return; } } void StartReadReport() { boost::asio::async_read_until(*this, fBuffer, '\n', boost::bind(&ConnectionDrive::HandleReceivedReport, this, dummy::error, dummy::bytes_transferred)); } boost::asio::deadline_timer fKeepAlive; void KeepAlive() { PostMessage(string("KEEP_ALIVE")); fKeepAlive.expires_from_now(boost::posix_time::seconds(10)); fKeepAlive.async_wait(boost::bind(&ConnectionDrive::HandleKeepAlive, this, dummy::error)); } void HandleKeepAlive(const bs::error_code &error) { // 125: Operation canceled (bs::error_code(125, bs::system_category)) if (error && error!=ba::error::basic_errors::operation_aborted) { stringstream str; str << "Write timeout of " << URL() << ": " << error.message() << " (" << error << ")";// << endl; Error(str); PostClose(false); return; } if (!is_open()) { // For example: Here we could schedule a new accept if we // would not want to allow two connections at the same time. return; } // Check whether the deadline has passed. We compare the deadline // against the current time since a new asynchronous operation // may have moved the deadline before this actor had a chance // to run. if (fKeepAlive.expires_at() > ba::deadline_timer::traits_type::now()) return; KeepAlive(); } private: // This is called when a connection was established void ConnectionEstablished() { StartReadReport(); KeepAlive(); } /* void HandleReadTimeout(const bs::error_code &error) { if (error && error!=ba::error::basic_errors::operation_aborted) { stringstream str; str << "Read timeout of " << URL() << ": " << error.message() << " (" << error << ")";// << endl; Error(str); PostClose(); return; } if (!is_open()) { // For example: Here we could schedule a new accept if we // would not want to allow two connections at the same time. return; } // Check whether the deadline has passed. We compare the deadline // against the current time since a new asynchronous operation // may have moved the deadline before this actor had a chance // to run. if (fInTimeout.expires_at() > ba::deadline_timer::traits_type::now()) return; Error("Timeout reading data from "+URL()); PostClose(); }*/ public: static const uint16_t kMaxAddr; public: ConnectionDrive(ba::io_service& ioservice, MessageImp &imp) : Connection(ioservice, imp()), fState(0), fIsVerbose(true), fKeepAlive(ioservice) { SetLogStream(&imp); } void SetVerbose(bool b) { fIsVerbose = b; } int GetState() const { return IsConnected() ? fState+1 : 1; } }; const uint16_t ConnectionDrive::kMaxAddr = 0xfff; // ------------------------------------------------------------------------ #include "DimDescriptionService.h" class ConnectionDimDrive : public ConnectionDrive { private: DimDescribedService fDimPointing; DimDescribedService fDimTracking; template void Update(DimDescribedService &svc, const Time &t, const boost::array &arr) const { svc.setTimestamp(int(t.UnixTime()), t.ms()); svc.setData(const_cast(arr.data()), arr.size()*sizeof(double)); svc.updateService(); } virtual void UpdatePointing(const Time &t, const boost::array &arr) { Update(fDimPointing, t, arr); } virtual void UpdateTracking(const Time &t, const boost::array &arr) { Update(fDimTracking, t, arr); } public: ConnectionDimDrive(ba::io_service& ioservice, MessageImp &imp) : ConnectionDrive(ioservice, imp), fDimPointing("FTM_CONTROL/POINTING_POSITION", "D:2", ""), fDimTracking("FTM_CONTROL/TRACKING_POSITION", "D:7", "") { } // A B [C] [D] E [F] G H [I] J K [L] M N O P Q R [S] T U V W [X] Y Z }; // ------------------------------------------------------------------------ template class StateMachineDrive : public T, public ba::io_service, public ba::io_service::work { int Wrap(boost::function f) { f(); return T::GetCurrentState(); } boost::function Wrapper(boost::function func) { return boost::bind(&StateMachineDrive::Wrap, this, func); } private: S fDrive; enum states_t { kStateDisconnected = 1, kStateConnected, kStateArmed, kStateMoving, kStateTracking, }; // Status 0: Error // Status 1: Unlocked // Status 2: Locked // Status 3: Stopping || Moving // Status 4: Tracking bool CheckEventSize(size_t has, const char *name, size_t size) { if (has==size) return true; stringstream msg; msg << name << " - Received event has " << has << " bytes, but expected " << size << "."; T::Fatal(msg); return false; } enum Coordinates { kPoint, kTrackSlow, kTrackFast }; string AngleToStr(double angle) { /* Handle sign */ const char sgn = angle<0?'-':'+'; /* Round interval and express in smallest units required */ double a = round(3600. * fabs(angle)); // deg to seconds /* Separate into fields */ const double ad = trunc(a/3600.); a -= ad * 3600.; const double am = trunc(a/60.); a -= am * 60.; const double as = trunc(a); /* Return results */ ostringstream str; str << sgn << " " << uint16_t(ad) << " " << uint16_t(am) << " " << as; return str.str(); } int SendCommand(const string &str) { fDrive.PostMessage(str); return T::GetCurrentState(); } int SendCoordinates(const EventImp &evt, const Coordinates type) { if (!CheckEventSize(evt.GetSize(), "SendCoordinates", 16)) return T::kSM_FatalError; const double *dat = evt.Ptr(); string command; switch (type) { case kPoint: command += "ZDAZ "; break; case kTrackSlow: command += "RADEC "; break; case kTrackFast: command += "GRB "; break; } command += AngleToStr(dat[0]) + ' ' + AngleToStr(dat[1]); return SendCommand(command); } int SetVerbosity(const EventImp &evt) { if (!CheckEventSize(evt.GetSize(), "SetVerbosity", 1)) return T::kSM_FatalError; fDrive.SetVerbose(evt.GetBool()); return T::GetCurrentState(); } int Disconnect() { // Close all connections fDrive.PostClose(false); /* // Now wait until all connection have been closed and // all pending handlers have been processed poll(); */ return T::GetCurrentState(); } int Reconnect(const EventImp &evt) { // Close all connections to supress the warning in SetEndpoint fDrive.PostClose(false); // Now wait until all connection have been closed and // all pending handlers have been processed poll(); if (evt.GetBool()) fDrive.SetEndpoint(evt.GetString()); // Now we can reopen the connection fDrive.PostClose(true); return T::GetCurrentState(); } int Execute() { // Dispatch (execute) at most one handler from the queue. In contrary // to run_one(), it doesn't wait until a handler is available // which can be dispatched, so poll_one() might return with 0 // handlers dispatched. The handlers are always dispatched/executed // synchronously, i.e. within the call to poll_one() poll_one(); return fDrive.GetState(); } public: StateMachineDrive(ostream &out=cout) : T(out, "DRIVE_CONTROL"), ba::io_service::work(static_cast(*this)), fDrive(*this, *this) { // ba::io_service::work is a kind of keep_alive for the loop. // It prevents the io_service to go to stopped state, which // would prevent any consecutive calls to run() // or poll() to do nothing. reset() could also revoke to the // previous state but this might introduce some overhead of // deletion and creation of threads and more. // State names AddStateName(kStateDisconnected, "Disconnected", ""); AddStateName(kStateConnected, "Connected", ""); AddStateName(kStateArmed, "Armed", ""); AddStateName(kStateMoving, "Moving", ""); AddStateName(kStateTracking, "Tracking", ""); // kStateIdle // kStateArmed // kStateMoving // kStateTracking // Init // ----------- // "ARM lock" // "STGMD off" /* [ ] WAIT -> WM_WAIT [x] STOP! -> WM_STOP [x] RADEC ra(+ d m s.f) dec(+ d m s.f) [x] GRB ra(+ d m s.f) dec(+ d m s.f) [x] ZDAZ zd(+ d m s.f) az (+ d m s.f) [ ] CELEST id offset angle [ ] MOON wobble offset [ ] PREPS string [ ] TPOIN star mag [ ] ARM lock/unlock [ ] STGMD on/off */ // Drive Commands T::AddEvent("MOVE_TO", "D:2", kStateArmed) // ->ZDAZ (boost::bind(&StateMachineDrive::SendCoordinates, this, _1, kPoint)) ("" "|zd[deg]:" "|az[deg]:"); T::AddEvent("TRACK", "D:2", kStateArmed) // ->RADEC/GRB (boost::bind(&StateMachineDrive::SendCoordinates, this, _1, kTrackSlow)) ("" "|ra[h]:" "|dec[deg]:"); T::AddEvent("MOON", kStateArmed) (boost::bind(&StateMachineDrive::SendCommand, this, "MOON 0 0")) (""); T::AddEvent("VENUS", kStateArmed) (boost::bind(&StateMachineDrive::SendCommand, this, "CELEST 2 0 0")) (""); T::AddEvent("MARS", kStateArmed) (boost::bind(&StateMachineDrive::SendCommand, this, "CELEST 4 0 0")) (""); T::AddEvent("JUPITER", kStateArmed) (boost::bind(&StateMachineDrive::SendCommand, this, "CELEST 5 0 0")) (""); T::AddEvent("SATURN", kStateArmed) (boost::bind(&StateMachineDrive::SendCommand, this, "CELEST 6 0 0")) (""); T::AddEvent("TPOINT") (boost::bind(&StateMachineDrive::SendCommand, this, "TPOIN FACT 0")) (""); T::AddEvent("STOP") (boost::bind(&StateMachineDrive::SendCommand, this, "STOP!")) (""); T::AddEvent("ARM", kStateConnected) (boost::bind(&StateMachineDrive::SendCommand, this, "ARM lock")) (""); // Verbosity commands T::AddEvent("SET_VERBOSE", "B") (boost::bind(&StateMachineDrive::SetVerbosity, this, _1)) ("set verbosity state" "|verbosity[bool]:disable or enable verbosity for received data (yes/no), except dynamic data"); // Conenction commands AddEvent("DISCONNECT", kStateConnected, kStateArmed) (boost::bind(&StateMachineDrive::Disconnect, this)) ("disconnect from ethernet"); AddEvent("RECONNECT", "O", kStateDisconnected, kStateConnected, kStateArmed) (boost::bind(&StateMachineDrive::Reconnect, this, _1)) ("(Re)connect ethernet connection to FTM, a new address can be given" "|[host][string]:new ethernet address in the form "); fDrive.StartConnect(); } void SetEndpoint(const string &url) { fDrive.SetEndpoint(url); } bool SetConfiguration(const Configuration &conf) { SetEndpoint(conf.Get("addr")); fDrive.SetVerbose(!conf.Get("quiet")); return true; } }; // ------------------------------------------------------------------------ void RunThread(StateMachineImp *io_service) { // This is necessary so that the StateMachien Thread can signal the // Readline to exit io_service->Run(); Readline::Stop(); } /* template int RunDim(Configuration &conf) { WindowLog wout; ReadlineColor::PrintBootMsg(wout, conf.GetName(), false); if (conf.Has("log")) if (!wout.OpenLogFile(conf.Get("log"))) wout << kRed << "ERROR - Couldn't open log-file " << conf.Get("log") << ": " << strerror(errno) << endl; // Start io_service.Run to use the StateMachineImp::Run() loop // Start io_service.run to only use the commandHandler command detaching StateMachineDrive io_service(wout); if (!io_service.SetConfiguration(conf)) return -1; io_service.Run(); return 0; } */ template int RunShell(Configuration &conf) { static T shell(conf.GetName().c_str(), conf.Get("console")!=1); WindowLog &win = shell.GetStreamIn(); WindowLog &wout = shell.GetStreamOut(); if (conf.Has("log")) if (!wout.OpenLogFile(conf.Get("log"))) win << kRed << "ERROR - Couldn't open log-file " << conf.Get("log") << ": " << strerror(errno) << endl; StateMachineDrive io_service(wout); if (!io_service.SetConfiguration(conf)) return -1; shell.SetReceiver(io_service); boost::thread t(boost::bind(RunThread, &io_service)); // boost::thread t(boost::bind(&StateMachineDrive::Run, &io_service)); if (conf.Has("exec")) { const vector v = conf.Get>("exec"); for (vector::const_iterator it=v.begin(); it!=v.end(); it++) shell.Execute(*it); } shell.Run(); // Run the shell io_service.Stop(); // Signal Loop-thread to stop // io_service.Close(); // Obsolete, done by the destructor // Wait until the StateMachine has finished its thread // before returning and destroying the dim objects which might // still be in use. t.join(); return 0; } void SetupConfiguration(Configuration &conf) { const string n = conf.GetName()+".log"; po::options_description config("Program options"); config.add_options() ("dns", var("localhost"), "Dim nameserver host name (Overwites DIM_DNS_NODE environment variable)") ("log,l", var(n), "Write log-file") ("no-dim,d", po_switch(), "Disable dim services") ("console,c", var(), "Use console (0=shell, 1=simple buffered, X=simple unbuffered)") ("exec,e", vars(),"Execute one or more scrips at startup") ; po::options_description control("FTM control options"); control.add_options() ("addr,a", var("localhost:7404"), "Network address of FTM") ("quiet,q", po_bool(), "Disable printing contents of all received messages (except dynamic data) in clear text.") ; conf.AddEnv("dns", "DIM_DNS_NODE"); conf.AddOptions(config); conf.AddOptions(control); } /* Extract usage clause(s) [if any] for SYNOPSIS. Translators: "Usage" and "or" here are patterns (regular expressions) which are used to match the usage synopsis in program output. An example from cp (GNU coreutils) which contains both strings: Usage: cp [OPTION]... [-T] SOURCE DEST or: cp [OPTION]... SOURCE... DIRECTORY or: cp [OPTION]... -t DIRECTORY SOURCE... */ void PrintUsage() { cout << "The drivectrl is an interface to cosy.\n" "\n" "The default is that the program is started without user intercation. " "All actions are supposed to arrive as DimCommands. Using the -c " "option, a local shell can be initialized. With h or help a short " "help message about the usuage can be brought to the screen.\n" "\n" "Usage: drivectrl [-c type] [OPTIONS]\n" " or: drivectrl [OPTIONS]\n"; cout << endl; } void PrintHelp() { /* Additional help text which is printed after the configuration options goes here */ /* cout << "bla bla bla" << endl << endl; cout << endl; cout << "Environment:" << endl; cout << "environment" << endl; cout << endl; cout << "Examples:" << endl; cout << "test exam" << endl; cout << endl; cout << "Files:" << endl; cout << "files" << endl; cout << endl; */ } int main(int argc, const char* argv[]) { Configuration conf(argv[0]); conf.SetPrintUsage(PrintUsage); SetupConfiguration(conf); po::variables_map vm; try { vm = conf.Parse(argc, argv); } #if BOOST_VERSION > 104000 catch (po::multiple_occurrences &e) { cerr << "Program options invalid due to: " << e.what() << " of '" << e.get_option_name() << "'." << endl; return -1; } #endif catch (exception& e) { cerr << "Program options invalid due to: " << e.what() << endl; return -1; } if (conf.HasVersion() || conf.HasPrint()) return -1; if (conf.HasHelp()) { PrintHelp(); return -1; } Dim::Setup(conf.Get("dns")); //try { // No console access at all if (!conf.Has("console")) { if (conf.Get("no-dim")) return RunShell(conf); else return RunShell(conf); } // Cosole access w/ and w/o Dim if (conf.Get("no-dim")) { if (conf.Get("console")==0) return RunShell(conf); else return RunShell(conf); } else { if (conf.Get("console")==0) return RunShell(conf); else return RunShell(conf); } } /*catch (std::exception& e) { cerr << "Exception: " << e.what() << endl; return -1; }*/ return 0; }