/// <summary> /// Objects of this class spend most of their execution time in this method. It simply waits asynchronously (control /// is returned to the constructor and then returned here on an interrupt) for data. When data is read it is packaged /// and sent to the event dispatcher. /// </summary> /// <returns></returns> /// <exception cref="ReceiverExceptions">Thrown when the EOF is reached on the read stream (contains an /// EndOfStreamException</exception> public async Task run() { while (goState > 0) { var ret = string.Empty; var buffer = new char[1]; // Not the most efficient... while (!ret.Contains("\n") && (!ret.Contains("\r"))) { var charsRead = await textReader.ReadAsync(buffer, 0, 1); if (charsRead == 0) { EndOfStreamException eose = new EndOfStreamException(); ReceiverExceptions re = new ReceiverExceptions(this,"End of stream reached on serial port.",true,eose); dispatcher.enqueueEvent(new RealTimeEvents.ExcepReceiver(re, re.fatal, this, this.portName, this.VEMCO_SerialNumber, this.VEMCO_Model, this.encoder.encoderConfig)); throw re; } ret += buffer[0]; } if (ret.Length > 1) { dispatcher.enqueueEvent(new RealTimeEvents.UnparsedMessage(ret, this, this.portName, this.VEMCO_SerialNumber, this.VEMCO_Model, this.encoder.encoderConfig)); } } goState = -1; }
/// <summary> /// Public constructor for the Receiver class. /// </summary> /// <param name="serialPort">The serial port object to which the receiver is connected</param> /// <param name="portName">The name of the port (i.e. COM1, COM2, etc.)</param> /// <param name="dispatcher">The event queue dispatcher where events generated by this class are sent</param> /// <remarks> /// After opening the port, the constructor calls the init() method which determines whether the hardware connect /// is, in fact, a VEMCO reciever and then proceeds to configure it. After returning, the constructor then instructs /// the receiver to start sending "Real Time" data. Finally the run() method is called and the objects stays in /// the run method until it shutdown() is called. With the exception of whether the class in run()ing or not, no state /// is maintained by the class. /// </remarks> public Receiver(SerialPort serialPort, String portName, Dispatcher dispatcher) { Dictionary<int, string> configFiles = new Dictionary<int, string>(); this.TTL = DEFAULT_TTL; this.serialPort = serialPort; this.portName = portName; this.dispatcher = dispatcher; serialPort.Open(); encoder = null; init(); if (encoder != null) { this.textReader = new StreamReader(serialPort.BaseStream, serialPort.Encoding); Object[] r = {"0"}; write("RTMPROFILE", r); write("START"); Thread.Sleep(500); while (serialPort.BytesToRead > 0) { dispatcher.enqueueEvent(new RealTimeEvents.NoteReceiver("Read: " + serialPort.ReadExisting(), this, this.portName, this.VEMCO_SerialNumber, this.VEMCO_Model, this.encoder.encoderConfig)); } run(); dispatcher.enqueueEvent(new RealTimeEvents.NewReceiver(this, this.portName, this.VEMCO_SerialNumber, this.VEMCO_Model, this.encoder.encoderConfig)); } else { ReceiverExceptions re = new ReceiverExceptions(this, "(FATAL) Failed to configure encoder during init().", true); dispatcher.enqueueEvent(new RealTimeEvents.ExcepReceiver(re,re.fatal, this, this.portName, this.VEMCO_SerialNumber, this.VEMCO_Model, this.encoder.encoderConfig)); serialPort.Close(); throw re; } }
/// <summary> /// Called by the constructor to determine whether a VEMCO receiver is attached (vs another kind of /// serial device), what the firmware is and which configuration file should be used. /// </summary> /// <remarks> /// Because one of the goals of this method is to determine the firmware (and corresponding configuration file), /// for the most part we must try both "default" commands and all those available in the configuration files. /// One assumption here is that future changes to protocol's INFO and discovery methods will not interfere with /// the operation of prior receivers. And although we can take some care to provide as much flexibility for /// changes in the protocol, there are clearly some limits. For example, we cannot anticipate a change to /// a binary format. /// /// The init() method first attempts to "discover" the connected receiver. Generally a receiver will not respond /// to commands that are not prefaced by the serial number, etc. Since we do not have the serial number prior to /// running this method, we must use the VEMCO's broadcast and discover commands. /// /// Next the method issues INFO commands in order to scrape out the firmware version. We assume here that the /// manufacturer's protocol will remain stable within a firmware version. Also note that so long as the protocol /// itself does not change, there is no need for additional configuration files. /// /// Assuming that these two tasks are completed without bailing, then init() command completes and returns /// control to the constructor. /// </remarks> /// <exception cref="ReceiverExceptions">Thrown when either the discovery /// or info phase is not able to acquire the needed information</exception> public void init() { Dictionary<Double, List<dynamic>> discoveryMethods = new Dictionary<Double, List<dynamic>>(); Dictionary<Double, String> infoMethods = new Dictionary<Double, String>(); String discoveryReturns = ""; var jsonParser = new JsonParser() { CamelizeProperties = false }; foreach (string filename in System.IO.Directory.GetFiles(VR2C_COMMAND_FOLDER)) { dynamic config = jsonParser.Parse(System.IO.File.ReadAllText(filename)); var dc = config.discovery_commands; var fwver = config.firmware_version; try { infoMethods.Add(fwver, (string)config.encoder["INFO"]); } catch(Exception e) { ReceiverExceptions re = new ReceiverExceptions(this, "INFO command not found in " + "configuration file: " + filename + ". This is normally not fatal on its own but " + "indicates a serious problem with the format/content of the config file.", false,e); dispatcher.enqueueEvent(new RealTimeEvents.ExcepReceiver(re.fatal, re.ToString(), this, this.portName, null, null, null)); } try { discoveryMethods.Add(((int)fwver), dc); } catch (Exception e) { ReceiverExceptions re = new ReceiverExceptions(this, "Discovery commands not found in " + "configuration file: " + filename + ". This is normally not fatal on its own but " + "indicates a serious problem with the format/content of the config file.", false, e); dispatcher.enqueueEvent(new RealTimeEvents.ExcepReceiver(re.fatal, re.text, this, this.portName, null, null, null)); } } List<dynamic> default_discovery = new List<dynamic>(); default_discovery.Add("*BROADC.A#ST,QUIT"); default_discovery.Add("*BROADC.A#ST,DISCOVERY"); default_discovery.Add("*DISCOV.E#RY,DISCOVERY"); discoveryMethods.Add(-1, default_discovery); int discovery_attempts = 0; serialPort.ReadExisting(); while (serialPort.BytesToRead <= 0 && discovery_attempts < 5) { write_wait = (discovery_attempts + 1) * 100; foreach (List<dynamic> l in discoveryMethods.Values) { foreach(var command in l) { _write(command); } } discovery_attempts++; } if (discovery_attempts >= 5) { ReceiverExceptions re = new ReceiverExceptions(this, "Not able to discover VEMCO receiver attached on this port.", true); dispatcher.enqueueEvent(new RealTimeEvents.ExcepReceiver(re,re.fatal, this, this.portName, null,null, null)); serialPort.Close(); throw re; } while (serialPort.BytesToRead > 0) { discoveryReturns = serialPort.ReadExisting(); } string commandPreamble = discoveryReturns.Substring(0, 12) + ","; this.VEMCO_SerialNumber = discoveryReturns.Substring(1,6); dispatcher.enqueueEvent(new RealTimeEvents.NoteReceiver("command preamble: " + discoveryReturns.Substring(0,12) + ",", this, this.portName, this.VEMCO_SerialNumber, null, null)); dispatcher.enqueueEvent(new RealTimeEvents.NoteReceiver("read: " + discoveryReturns, this, this.portName, this.VEMCO_SerialNumber, null, null)); infoMethods.Add(-1, commandPreamble + "INFO"); int info_attempts = 0; serialPort.ReadExisting(); Boolean gotINFO = false; String infoReturns = ""; int read_attempts = 0; while (!gotINFO && read_attempts < 5) { info_attempts = 0; serialPort.ReadExisting(); while (serialPort.BytesToRead <= 0 && info_attempts < 5) { foreach (String infoc in infoMethods.Values) { dispatcher.enqueueEvent(new RealTimeEvents.NoteReceiver("(receiver note) attempting INFO command with " + infoc, this, this.portName, this.VEMCO_SerialNumber, null, null)); _write(infoc); Thread.Sleep(500); } info_attempts++; } while (serialPort.BytesToRead > 0 && !(infoReturns.Contains(crlf[0]) || infoReturns.Contains(crlf[1]))) { infoReturns = serialPort.ReadExisting(); foreach (string filename in System.IO.Directory.GetFiles(VR2C_COMMAND_FOLDER)) { dynamic config = jsonParser.Parse(System.IO.File.ReadAllText(filename)); if (Regex.IsMatch(infoReturns, config.decoder.sentences["info_response"].format)) { gotINFO = true; } } } read_attempts++; } if (read_attempts >= 5) { ReceiverExceptions re = new ReceiverExceptions(this, "(FATAL) Not able to get INFO from the VEMCO receiver attached on this port.", true); dispatcher.enqueueEvent(new RealTimeEvents.ExcepReceiver(re, re.fatal, this, this.portName, this.VEMCO_SerialNumber, null, null)); serialPort.Close(); throw re; } int RECEIVER_FW_VERSION = -1; if (infoReturns != "") { int fw_start = infoReturns.IndexOf("FW="); int fw_end = infoReturns.IndexOf(",", fw_start); string fw_ver = infoReturns.Substring((fw_start + 3), (fw_end - fw_start - 3)); int fw_ver_firstperiod = fw_ver.IndexOf("."); int fw_ver_secondperiod = fw_ver.IndexOf(".", fw_ver_firstperiod + 1); string major = fw_ver.Substring(0, fw_ver_firstperiod); string minor = fw_ver.Substring(fw_ver_firstperiod + 1, (fw_ver_secondperiod - fw_ver_firstperiod - 1)); string release = fw_ver.Substring(fw_ver_secondperiod + 1, (fw_ver.Length - fw_ver_secondperiod - 1)); RECEIVER_FW_VERSION = (Int32.Parse(major) * 10000) + (Int32.Parse(minor) * 100) + (Int32.Parse(release)); } dispatcher.enqueueEvent(new RealTimeEvents.NoteReceiver("Detected firmware version: " + RECEIVER_FW_VERSION, this, this.portName, this.VEMCO_SerialNumber, this.VEMCO_Model, null)); if (RECEIVER_FW_VERSION >= 0) { int fw_use = -1; foreach (string filename in System.IO.Directory.GetFiles(VR2C_COMMAND_FOLDER)) { string text = System.IO.File.ReadAllText(filename); dynamic config = jsonParser.Parse(System.IO.File.ReadAllText(filename)); if (config.firmware_version >= fw_use && config.firmware_version <= RECEIVER_FW_VERSION) { fw_use = (Int32)config.firmware_version; encoder = new Encoder(commandPreamble, config); } } if (fw_use < 0 || encoder == null) { ReceiverExceptions re = new ReceiverExceptions(this, "(FATAL) Unable to parse out FW version from return from INFO command.", true); dispatcher.enqueueEvent(new RealTimeEvents.ExcepReceiver(re, re.fatal, this, this.portName, this.VEMCO_SerialNumber, null, null)); serialPort.Close(); throw re; } } else { ReceiverExceptions re = new ReceiverExceptions(this, "(FATAL) Unable to parse out FW version from return from INFO command.", true); dispatcher.enqueueEvent(new RealTimeEvents.ExcepReceiver(re,re.fatal, this, this.portName, this.VEMCO_SerialNumber, null, null)); serialPort.Close(); throw re; } Match matches = Regex.Match(infoReturns, encoder.encoderConfig.decoder.words["receiver_model"]); VEMCO_Model = matches.Groups[1].ToString(); dispatcher.enqueueEvent(new RealTimeEvents.NoteReceiver("(receiver note) Successfully configured encoder with fw version = " + encoder.encoderConfig.firmware_version, this, this.portName, this.VEMCO_SerialNumber, this.VEMCO_Model, this.encoder.encoderConfig)); }