/// <summary> /// Scans the registered plugins for one that handles the named API string /// </summary> /// <param name="APIname">The API string to handle, excluding any parameters</param> /// <returns>An API-processing delegate, or null if none were found</returns> public APIDelegate GetAPIDelegate(string APIname) { lock (_dataLock) { // Check our handler cache first. if (handlers.ContainsKey(APIname)) { return(handlers[APIname]); } foreach (var plugin in registeredPlugins) { // Does this match the api at all? if (plugin.commands.Contains(APIname) || plugin.regexCommands.Any(x => x.IsMatch(APIname))) { try { var del = plugin.apiHandler(APIname); if (del != null) { // Add to the handling cache and the plugin response list handlers[APIname] = del; plugin.known_handlers.Add(APIname); return(del); } } catch (Exception ex) { PluginLogger.print("Got Exception processing plugin " + plugin.instance.ToString() + ", " + ex.ToString()); } } } } return(null); }
static private void readConfiguration() { config.load(); // Read the port out of the config file int port = config.GetValue <int>("PORT"); if (port != 0 && port.IsPortNumber()) { serverConfig.port = port; } else if (!port.IsPortNumber()) { PluginLogger.print("Port specified in configuration file '" + serverConfig.port + "' must be a value between 1 and 65535 inclusive"); } else { PluginLogger.print("No port in configuration file - using default of " + serverConfig.port.ToString()); } // Read a specific IP address to bind to string ip = config.GetValue <String>("IPADDRESS"); if (ip != null) { IPAddress ipAddress = null; if (IPAddress.TryParse(ip, out ipAddress)) { serverConfig.ipAddress = ipAddress; } else { PluginLogger.print("Invalid IP address in configuration file, falling back to default"); } } else { PluginLogger.print("No IP address in configuration file."); } // Fill the serverconfig list of addresses.... if IPAddress.Any, then enumerate them if (serverConfig.ipAddress == IPAddress.Any) { // Build a list of addresses we will be able to recieve at serverConfig.ValidIpAddresses.Add(IPAddress.Loopback); serverConfig.ValidIpAddresses.AddRange(Dns.GetHostAddresses(Dns.GetHostName())); } else { serverConfig.ValidIpAddresses.Add(serverConfig.ipAddress); } serverConfig.version = Assembly.GetExecutingAssembly().GetName().Version.ToString(); serverConfig.name = "Telemachus"; isPartless = config.GetValue <int>("PARTLESS") == 0 ? false : true; PluginLogger.print("Partless:" + isPartless); }
static private void stopDataLink() { if (webServer != null) { PluginLogger.print("Telemachus data link shutting down."); webServer.Stop(); webServer = null; } }
static private void stopDataLink() { if (server != null) { PluginLogger.print("Telemachus data link shutting down."); server.stopServing(); server = null; webSocketServer.stopServing(); webSocketServer = null; } }
static private void readConfiguration() { config.load(); int port = config.GetValue <int>("PORT"); if (port != 0) { serverConfig.port = port; } else { PluginLogger.print("No port in configuration file."); } String ip = config.GetValue <String>("IPADDRESS"); if (ip != null) { try { serverConfig.addIPAddressAsString(ip); } catch { PluginLogger.print("Invalid IP address in configuration file, falling back to find."); } } else { PluginLogger.print("No IP address in configuration file."); } serverConfig.maxRequestLength = config.GetValue <int>("MAXREQUESTLENGTH"); if (serverConfig.maxRequestLength < 8000) { PluginLogger.print("No max request length specified, setting to 8000."); serverConfig.maxRequestLength = 10000; } else { PluginLogger.print("Max request length set to:" + serverConfig.maxRequestLength); } serverConfig.version = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString(); serverConfig.name = "Telemachus"; serverConfig.backLog = 1000; isPartless = config.GetValue <int>("PARTLESS") == 0 ? false : true; PluginLogger.print("Partless:" + isPartless); }
public void openBrowser() { try { Application.OpenURL("http://" + TelemachusBehaviour.getServerPrimaryIPAddress() + ":" + TelemachusBehaviour.getServerPort() + "/telemachus/information.html"); } catch { PluginLogger.print( "Unable to open the data link. Please try to edit the Telemachus configuration file manually"); } }
/// <summary> /// Deregisters an API plugin handler instance via handler reference /// </summary> /// <param name="handler">The plugin handler for the plugin</param> private void Deregister(PluginHandler handler) { PluginLogger.print("Removing registered plugin " + handler.instance.ToString()); // Remove the handler caches lock (_dataLock) { foreach (var api in handler.known_handlers) { handlers.Remove(api); } registeredPlugins.Remove(handler); } }
static private void startDataLink() { if (webServer == null) { try { PluginLogger.print("Telemachus data link starting"); readConfiguration(); // Data access tools vesselChangeDetector = new VesselChangeDetector(isPartless); apiInstance = new KSPAPI(JSONFormatterProvider.Instance, vesselChangeDetector, serverConfig, pluginManager); // Create the dispatcher and handlers. Handlers added in reverse priority order so that new ones are not ignored. webDispatcher = new KSPWebServerDispatcher(); webDispatcher.AddResponder(new ElseResponsibility()); webDispatcher.AddResponder(new IOPageResponsibility()); var cameraLink = new CameraResponsibility(apiInstance, rateTracker); webDispatcher.AddResponder(cameraLink); var dataLink = new DataLinkResponsibility(apiInstance, rateTracker); webDispatcher.AddResponder(dataLink); // Create the server and associate the dispatcher webServer = new HttpServer(serverConfig.ipAddress, serverConfig.port); webServer.OnGet += webDispatcher.DispatchRequest; webServer.OnPost += webDispatcher.DispatchRequest; // Create the websocket server and attach to the web server webServer.AddWebSocketService("/datalink", () => new KSPWebSocketService(apiInstance, rateTracker)); // Finally, start serving requests! try { webServer.Start(); } catch (Exception ex) { PluginLogger.print("Error starting web server: " + ex.ToString()); throw; } PluginLogger.print("Telemachus data link listening for requests on the following addresses: (" + string.Join(", ", serverConfig.ValidIpAddresses.Select(x => x.ToString() + ":" + serverConfig.port.ToString()).ToArray()) + "). Try putting them into your web browser, some of them might not work."); } catch (Exception e) { PluginLogger.print(e.Message); PluginLogger.print(e.StackTrace); } } }
/// Iterate over all responders to find one that works public void DispatchRequest(object sender, HttpRequestEventArgs request) { foreach (var responder in responderChain.Reverse <IHTTPRequestResponder>()) { try { if (responder.process(request.Request, request.Response)) { return; } } catch (Exception ex) { PluginLogger.print("Caught exception in web handlers: " + ex.ToString()); } } // If here, we had no responder. request.Response.StatusCode = (int)HttpStatusCode.NotFound; }
/// <summary> /// Registers a plugin API with Telemachus /// </summary> /// <param name="toRegister">An instance of a Plugin object, that conforms to the TelemachusPlugin interface. /// NOTE: Does NOT have to be a physical instance of the interface.</param> /// <returns>An Action, calling of which Deregisters the plugin. This is disposeable.</returns> public void Register(object toRegister) { var pluginType = toRegister.GetType(); // Must conform at least to the minimal interface if (!typeof(IMinimalTelemachusPlugin).IsAssignableFrom(pluginType) && !pluginType.DoesMatchInterfaceOf(typeof(IMinimalTelemachusPlugin))) { throw new ArgumentException("Object " + toRegister.GetType().ToString() + " does not conform to the minimal interface"); } var handler = new PluginHandler() { instance = toRegister }; // Get a list of commands that this instance handles var commands = ReadCommandList(toRegister); // Get the plugin handler function var apiMethod = toRegister.GetType().GetMethod("GetAPIHandler", new Type[] { typeof(string) }); handler.apiHandler = (APIHandler)Delegate.CreateDelegate(typeof(APIHandler), toRegister, apiMethod); // Does it match the Deregistration? If so, pass it the deregistration method if (toRegister is IDeregisterableTelemachusPlugin || pluginType.DoesMatchInterfaceOf(typeof(IDeregisterableTelemachusPlugin))) { Action deregistration = () => Deregister(handler); pluginType.GetProperty("Deregister").SetValue(toRegister, deregistration, null); handler.is_deregisterable = true; } var optional_interfaces = new List <string>(); if (handler.is_deregisterable) { optional_interfaces.Add("Deregister"); } PluginLogger.print("Got plugin registration call for " + toRegister.GetType() + ".\n Optional interfaces enabled: " + (optional_interfaces.Count == 0 ? "None" : string.Join(", ", optional_interfaces.ToArray()))); // Make a simple hashset of basic commands handler.commands = new HashSet <string>(commands.Where(x => !x.Contains("*"))); // Now, deal with any wildcard plugin strings by building a regex handler.regexCommands = commands .Where(x => x.Contains("*")) .Select(x => new Regex("^" + x.Replace(".", "\\.").Replace("*", ".*") + "$")).ToList(); lock (_dataLock) registeredPlugins.Add(handler); }
public bool process(HttpListenerRequest request, HttpListenerResponse response) { PluginLogger.print("Falling back on default handler"); // For now, copy the behaviour until we understand it more if (!KSP.IO.FileInfo.CreateForType <TelemachusDataLink>(INDEX_PAGE).Exists) { throw new FileNotFoundException("Unable to find the Telemachus index page. Is it installed in the PluginData folder?"); } else if (request.RawUrl == "/" || request.RawUrl.ToLowerInvariant().StartsWith("/index")) { // Just redirect them var index = new Uri(request.Url, "/" + INDEX_PAGE); response.Redirect(index.ToString()); return(true); } return(false); }
static private void startDataLink() { if (server == null) { try { PluginLogger.print("Telemachus data link starting"); readConfiguration(); server = new Server(serverConfig); server.ServerNotify += HTTPServerNotify; server.addHTTPResponsibility(new ElseResponsibility()); ioPageResponsibility = new IOPageResponsibility(); server.addHTTPResponsibility(ioPageResponsibility); vesselChangeDetector = new VesselChangeDetector(isPartless); dataLinkResponsibility = new DataLinkResponsibility(serverConfig, new KSPAPI(JSONFormatterProvider.Instance, vesselChangeDetector, serverConfig)); server.addHTTPResponsibility(dataLinkResponsibility); Servers.MinimalWebSocketServer.ServerConfiguration webSocketconfig = new Servers.MinimalWebSocketServer.ServerConfiguration(); webSocketconfig.bufferSize = 300; webSocketServer = new Servers.MinimalWebSocketServer.Server(webSocketconfig); webSocketServer.ServerNotify += WebSocketServerNotify; kspWebSocketService = new KSPWebSocketService(new KSPAPI(JSONFormatterProvider.Instance, vesselChangeDetector, serverConfig), kspWebSocketDataStreamer); webSocketServer.addWebSocketService("/datalink", kspWebSocketService); webSocketServer.subscribeToHTTPForStealing(server); server.startServing(); PluginLogger.print("Telemachus data link listening for requests on the following addresses: (" + server.getIPsAsString() + "). Try putting them into your web browser, some of them might not work."); } catch (Exception e) { PluginLogger.print(e.Message); PluginLogger.print(e.StackTrace); } } }
static private void readConfiguration() { config.load(); int port = config.GetValue <int>("PORT"); if (port != 0) { serverConfig.port = port; } else { PluginLogger.print("No port in configuration file."); } String ip = config.GetValue <String>("IPADDRESS"); if (ip != null) { try { serverConfig.addIPAddressAsString(ip); } catch { PluginLogger.print("Invalid IP address in configuration file, falling back to find."); } } else { PluginLogger.print("No IP address in configuration file."); } serverConfig.maxRequestLength = 8000; serverConfig.version = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString(); serverConfig.name = "Telemachus"; serverConfig.backLog = 1000; }
void LookForModsToInject() { string foundMods = "Loading; Looking for compatible mods to inject registration....\nTelemachus compatible modules Found:\n"; int found = 0; foreach (var asm in AssemblyLoader.loadedAssemblies) { foreach (var type in asm.assembly.GetTypes()) { if (type.IsSubclassOf(typeof(MonoBehaviour))) { // Does this have a static property named "Func<string> TelemachusPluginRegister { get; set; }? var prop = type.GetProperty("TelemachusPluginRegister", BindingFlags.Static | BindingFlags.Public); if (prop == null) { continue; } found += 1; foundMods += " - " + type.ToString() + " "; if (prop.PropertyType != typeof(Action <object>)) { foundMods += "(Fail - Invalid property type)\n"; continue; } if (!prop.CanWrite) { foundMods += "(Fail - Property not writeable)\n"; continue; } // Can we read it - if so, only write if it is not null. if (prop.CanRead) { if (prop.GetValue(null, null) != null) { foundMods += "(Fail - Property not null)\n"; continue; } } // Write the value here Action <object> pluginRegister = PluginRegistration.Register; prop.SetValue(null, pluginRegister, null); foundMods += "(Success)\n"; } } } if (found == 0) { foundMods += " None\n"; } foundMods += "Internal plugins loaded:\n"; found = 0; // Look for any mods in THIS assembly that inherit ITelemachusMinimalPlugin... foreach (var typ in Assembly.GetExecutingAssembly().GetTypes()) { try { if (!typeof(IMinimalTelemachusPlugin).IsAssignableFrom(typ)) { continue; } // Make sure we have a default constructor if (typ.GetConstructor(Type.EmptyTypes) == null) { continue; } // We have found a plugin internally. Instantiate it PluginRegistration.Register(Activator.CreateInstance(typ)); foundMods += " - " + typ.ToString() + "\n"; found += 1; } catch (Exception ex) { PluginLogger.print("Exception caught whilst loading internal plugin " + typ.ToString() + "; " + ex.ToString()); } } if (found == 0) { foundMods += " None"; } PluginLogger.print(foundMods); }
/// Process a message recieved from a client protected override void OnMessage(MessageEventArgs e) { // We only care about text messages, for now. if (e.Type != Opcode.Text) { return; } dataRates.RecieveDataFromClient(e.RawData.Length); // deserialize the message as JSON var json = SimpleJson.SimpleJson.DeserializeObject(e.Data) as SimpleJson.JsonObject; lock (dataLock) { // Do any tasks requested here foreach (var entry in json) { // Try converting the item to a list - this is the most common expected. // If we got a string, then add it to the list to allow "one-shot" submission string[] listContents = new string[] { }; if (entry.Value is SimpleJson.JsonArray) { listContents = (entry.Value as SimpleJson.JsonArray).OfType <string>().Select(x => x.Trim()).ToArray(); } else if (entry.Value is string) { listContents = new[] { entry.Value as string }; } // Process the possible API entries if (entry.Key == "+") { PluginLogger.print(string.Format("Client {0} added {1}", ID, string.Join(",", listContents))); subscriptions.UnionWith(listContents); } else if (entry.Key == "-") { PluginLogger.print(string.Format("Client {0} removed {1}", ID, string.Join(",", listContents))); subscriptions.ExceptWith(listContents); } else if (entry.Key == "run") { PluginLogger.print(string.Format("Client {0} running {1}", ID, string.Join(",", listContents))); oneShotRuns.UnionWith(listContents); } else if (entry.Key == "rate") { streamRate = Convert.ToInt32(entry.Value); PluginLogger.print(string.Format("Client {0} setting rate {1}", ID, streamRate)); } else if (entry.Key == "binary") { binarySubscriptions = listContents; PluginLogger.print(string.Format("Client {0} requests binary packets {1}", ID, string.Join(", ", listContents))); } else { PluginLogger.print(String.Format("Client {0} send unrecognised key {1}", ID, entry.Key)); } } } // Lock } // OnMessage
} // OnMessage /// Read all variables and send back the responses for just this client public void SendDataUpdate() { // Don't do anything if we are e.g. still awaiting data to be fully set if (!readyToSend) { return; } lastUpdate = UnityEngine.Time.time; // Grab all of the variables at once string[] allVariables; lock (dataLock) { allVariables = subscriptions.Union(oneShotRuns).Union(binarySubscriptions).ToArray(); oneShotRuns.Clear(); } var vessel = api.getVessel(); // Now, process them all into a data dictionary var apiResults = new Dictionary <string, object>(); var unknowns = new List <string>(); var errors = new Dictionary <string, string>(); foreach (var apiString in allVariables) { try { apiResults[apiString] = api.ProcessAPIString(apiString); } catch (IKSPAPI.UnknownAPIException) { // IF we get this message, we know it was because no variable was found unknowns.Add(apiString); } catch (IKSPAPI.VariableNotEvaluable) { // We can't evaluate this at the moment. Just ignore until we can. } catch (Exception ex) { errors[apiString] = ex.ToString(); } } if (unknowns.Count > 0) { apiResults["unknown"] = unknowns; } if (errors.Count > 0) { apiResults["errors"] = errors; } // Handle sending a binary packet if requested if (binarySubscriptions.Length > 0) { var variableValues = new List <float>(); // Read every binary value foreach (var name in binarySubscriptions) { try { variableValues.Add(Convert.ToSingle(apiResults[name])); } catch (Exception ex) { variableValues.Add(0); if (apiResults.ContainsKey(name)) { errors[name] = "Error streaming to binary " + name + "='" + apiResults[name] + "'; " + ex.ToString(); } else { errors[name] = "Error streaming to binary: value for " + name + " not found; " + ex.ToString(); } } } // Which byte translation? Func <float, IEnumerable <byte> > reverser = x => BitConverter.GetBytes(x).Reverse(); var byteTranslation = BitConverter.IsLittleEndian ? reverser : BitConverter.GetBytes; // Now translate these to binary bytes var byteData = new List <byte>(); byteData.Add(1); byteData.AddRange(variableValues.SelectMany(x => byteTranslation(x))); SendAsync(byteData.ToArray(), x => { }); } //if (allVariables.Contains("binaryNavigation")) //{ // allVariables = allVariables.Where(x => x != "binaryNavigation").ToArray(); // // Build and dispatch the binary information, here and quickly.... // var pitch = Convert.ToSingle(api.ProcessAPIString("n.pitch")); // var roll = Convert.ToSingle(api.ProcessAPIString("n.roll")); // var heading = Convert.ToSingle(api.ProcessAPIString("n.heading")); // var deltaV = Convert.ToSingle(api.ProcessAPIString("v.verticalSpeed")); // var parts = new List<byte[]>(); // parts.Add(new byte[] { 1 }); // parts.Add(BitConverter.GetBytes(heading)); // parts.Add(BitConverter.GetBytes(pitch)); // parts.Add(BitConverter.GetBytes(roll)); // parts.Add(BitConverter.GetBytes(deltaV)); // if (BitConverter.IsLittleEndian) parts = parts.Select(x => x.Reverse().ToArray()).ToList(); // var byteData = parts.SelectMany(x => x).ToArray(); // SendAsync(byteData, x => { }); // PluginLogger.print(string.Format("Send byte data for {0}, {1}, {2}, {3}", heading, pitch, roll, deltaV)); //} var data = SimpleJson.SimpleJson.SerializeObject(apiResults); // Now, if we have data send a message, otherwise send a null message readyToSend = false; try { SendAsync(data, (b) => readyToSend = true); dataRates.SendDataToClient(data.Length); } catch (Exception ex) { PluginLogger.print("Caught " + ex.ToString()); } finally { readyToSend = true; } }