123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577 |
- using DSLink;
- using DSLink.Nodes;
- using DSLink.Nodes.Actions;
- using DSLink.Request;
- using Newtonsoft.Json.Linq;
- using System;
- using System.Collections.Generic;
- using System.Threading;
- using System.Threading.Tasks;
- using System.Net.NetworkInformation;
- using System.Net;
- using System.Text.RegularExpressions;
- using System.Diagnostics;
- using Iot.Device.Pigpio;
- using Serilog;
- using CommandLine;
- using System.IO;
- using RTSP_DSLink;
- using System.Device.Gpio;
-
- namespace RTSP_DSLink
- {
- public class RSTP_DSLink : DSLinkContainer
- {
- private readonly Dictionary<string, System.Diagnostics.Process> _processes;
- private readonly Dictionary<string, CancellationTokenSource> _alarmCTs;
- public static bool PortInUse(int port)
- {
- bool inUse = false;
-
- IPGlobalProperties ipProperties = IPGlobalProperties.GetIPGlobalProperties();
- IPEndPoint[] ipEndPoints = ipProperties.GetActiveTcpListeners();
- foreach (IPEndPoint endPoint in ipEndPoints)
- {
- if (endPoint.Port == port)
- {
- inUse = true;
- break;
- }
- }
- return inUse;
- }
- private static void Main(string[] args)
- {
- Parser.Default.ParseArguments<CommandLineArguments>(args)
- .WithParsed(cmdLineOptions =>
- {
- cmdLineOptions = ProcessDSLinkJson(cmdLineOptions);
-
- //Init the logging engine
- InitializeLogging(cmdLineOptions);
-
- //Construct a link Configuration
- var config = new Configuration(cmdLineOptions.LinkName, true, true);
-
- //Construct our custom link
- var dslink = new RSTP_DSLink(config, cmdLineOptions);
-
- InitializeLink(dslink).Wait();
- })
- .WithNotParsed(errors => { Environment.Exit(-1); });
-
- while (true)
- {
- Thread.Sleep(1000);
- }
- }
-
- public static async Task InitializeLink(RSTP_DSLink dsLink)
- {
- await dsLink.Connect();
- //dsLink.ClearNodes();
- }
-
- public RSTP_DSLink(Configuration config, CommandLineArguments cmdLineOptions) : base(config)
- {
- _processes = new Dictionary<string, Process>();
- _alarmCTs = new Dictionary<string, CancellationTokenSource>();
- //Perform any configuration overrides from command line options
- if (cmdLineOptions.BrokerUrl != null)
- {
- config.BrokerUrl = cmdLineOptions.BrokerUrl;
- }
-
- if (cmdLineOptions.Token != null)
- {
- config.Token = cmdLineOptions.Token;
- }
-
- if (cmdLineOptions.NodesFileName != null)
- {
- config.NodesFilename = cmdLineOptions.NodesFileName;
- }
-
- if (cmdLineOptions.KeysFolder != null)
- {
- config.KeysFolder = cmdLineOptions.KeysFolder;
- }
-
- Responder.AddNodeClass("streamAdd", delegate(Node node)
- {
- node.Configs.Set(ConfigType.DisplayName, new Value("Add Stream"));
- node.AddParameter(new Parameter
- {
- Name = "RSTP URL",
- ValueType = DSLink.Nodes.ValueType.String
- }
- );
- node.AddParameter(new Parameter
- {
- Name = "Stream name",
- ValueType = DSLink.Nodes.ValueType.String
- }
- );
- node.SetAction(new ActionHandler(Permission.Config, _createStreamAction));
- });
-
- Responder.AddNodeClass("alarmAdd", delegate (Node node)
- {
- node.Configs.Set(ConfigType.DisplayName, new Value("Add Alarm"));
- node.AddParameter(new Parameter
- {
- Name = "Alarm name",
- ValueType = DSLink.Nodes.ValueType.String
- }
- );
- /*node.AddParameter(new Parameter
- {
- Name = "Camera name",
- ValueType = DSLink.Nodes.ValueType.String
- }
- ); */
- node.AddParameter(new Parameter
- {
- Name = "Raspberry Pi IP Address",
- ValueType = DSLink.Nodes.ValueType.String
- }
- );
- node.AddParameter(new Parameter
- {
- Name = "Port Number",
- ValueType = DSLink.Nodes.ValueType.Number
- }
- );
- node.AddParameter(new Parameter
- {
- Name = "GPIO Pin",
- ValueType = DSLink.Nodes.ValueType.Number
- }
- );
-
- node.SetAction(new ActionHandler(Permission.Config, _createAlarmAction));
- });
-
- Responder.AddNodeClass("removeNode", delegate (Node node)
- {
- node.Configs.Set(ConfigType.DisplayName, new Value("Remove"));
- node.SetAction(new ActionHandler(Permission.Config, removeNodeAction));
- });
-
- Responder.AddNodeClass("stream", delegate (Node node)
- {
- node.Configs.Set(ConfigType.Writable, new Value(Permission.Read.Permit));
- node.Configs.Set(ConfigType.ValueType, DSLink.Nodes.ValueType.Number.TypeValue);
-
- node.Value.Set(-1);
- });
-
- Responder.AddNodeClass("alarm", delegate (Node node)
- {
- node.Configs.Set(ConfigType.Writable, new Value(Permission.Read.Permit));
- node.Configs.Set(ConfigType.ValueType, DSLink.Nodes.ValueType.Boolean.TypeValue);
- node.Value.Set(false);
- });
-
- Responder.AddNodeClass("onoffNode", delegate (Node node)
- {
- node.Configs.Set(ConfigType.Writable, new Value(Permission.Read.Permit));
- node.Configs.Set(ConfigType.ValueType, DSLink.Nodes.ValueType.Boolean.TypeValue);
- node.Value.Set(true);
- });
-
- /*Responder.AddNodeClass("rng", delegate(Node node)
- {
- node.Configs.Set(ConfigType.Writable, new Value(Permission.Read.Permit));
- node.Configs.Set(ConfigType.ValueType, DSLink.Nodes.ValueType.Number.TypeValue);
- node.Value.Set(0);
-
- });*/
- }
- public void ClearNodes()
- {
- foreach (var key in Responder.SuperRoot.Children.Keys)
- {
- Responder.SuperRoot.Children.TryGetValue(key, out Node oldStream);
- if (oldStream.ClassName == "stream")
- {
- //Console.WriteLine(key);
- Responder.SuperRoot.RemoveChild(key);
- }
- }
- }
-
- public override void InitializeDefaultNodes()
- {
- Responder.SuperRoot.CreateChild("createStream", "streamAdd").BuildNode();
- Responder.SuperRoot.CreateChild("createAlarm", "alarmAdd").BuildNode();
- Responder.SuperRoot.CreateChild("alarms").BuildNode();
- Responder.SuperRoot.CreateChild("streams").BuildNode();
- SaveNodes();
- //Responder.SuperRoot.CreateChild("removeStream", "remove").BuildNode();
- }
-
- private async void _createStreamAction(InvokeRequest request)
- {
- try
- {
- var address = request.Parameters["RSTP URL"].Value<string>();
- var streamName = request.Parameters["Stream name"].Value<string>();
- int port = 8081;
- Responder.SuperRoot.Children.TryGetValue("streams", out Node streamRootNode);
-
- if (string.IsNullOrEmpty(address)) return;
- if (string.IsNullOrEmpty(streamName)) return;
- if (streamRootNode == null) return;
- if (streamRootNode.Children.ContainsKey(streamName)) return;
-
- //on part du port 8081 et on scanne les ports jusqu'à en trouver un inutilisé
- while (PortInUse(port))
- port++;
-
- /*foreach (var oldStreamName in Responder.SuperRoot.Children.Keys)
- {
-
- Responder.SuperRoot.Children.TryGetValue(oldStreamName, out Node oldStream);
- if (oldStream.ClassName == "stream")
- {
- //Console.WriteLine(oldStreamName);
- if (oldStream.Value.Int == port)
- {
- Responder.SuperRoot.RemoveChild(oldStreamName);
- }
- }
- }*/
-
- var stream = streamRootNode.CreateChild(streamName, "stream").BuildNode();
-
- stream.Value.Set(port);
- stream.CreateChild("remove", "removeNode").BuildNode();
-
- var proc = new Process {
- StartInfo = new ProcessStartInfo
- {
- FileName = "C:\\Program Files\\VideoLAN\\VLC\\VLC.exe",
- Arguments = " -vvv -Idummy " + address + " --sout #transcode{vcodec=MJPG,venc=ffmpeg{strict=1}}" +
- ":standard{access=http{mime=multipart/x-mixed-replace;boundary=--7b3cc56e5f51db803f790dad720ed50a}," +
- "mux=mpjpeg,dst=:" + port + "/} vlc://quit;",
- UseShellExecute = false,
- RedirectStandardOutput = false,
- CreateNoWindow = true
- }};
- proc.Start();//on lance la conversion du stream avec VLC
-
- _processes.Add(streamName, proc);
-
- await request.Close();
- await SaveNodes();
- }
- catch(Exception e)
- {
- Console.WriteLine(e.GetType() + ":" + e.Message);
- }
- }
-
- private async void _createAlarmAction(InvokeRequest request)
- {
- try
- {
- var alarmName = request.Parameters["Alarm name"].Value<string>();
- var address = request.Parameters["Raspberry Pi IP Address"].Value<string>();
- var port = request.Parameters["Port Number"].Value<int>();
- var pin = request.Parameters["GPIO Pin"].Value<int>();
- Responder.SuperRoot.Children.TryGetValue("alarms", out Node alarmRootNode);
-
- if (string.IsNullOrEmpty(alarmName)) return;
- if (alarmRootNode.Children.ContainsKey(alarmName)) return;
-
- var alarm = alarmRootNode.CreateChild(alarmName, "alarm").BuildNode();
-
- alarm.CreateChild("remove", "removeNode").BuildNode();
- alarm.CreateChild("armed", "onoffNode").BuildNode();
- var ts = new CancellationTokenSource();
- CancellationToken ct = ts.Token;
- //on lance une tâche en parallèle, qui observe la pin et met à jour la valeur du noeud
- Task.Run(() => _updateAlarm(alarm, address, port, pin, ct));
- _alarmCTs.Add(alarmName, ts);
-
- await request.Close();
- await SaveNodes();
- }
- catch (Exception e)
- {
- Console.WriteLine(e.GetType() + ":" + e.Message);
- }
- }
- private async void removeNodeAction(InvokeRequest request)
- {
- try
- {
- //Console.WriteLine(request.Path);
-
- //on récupère le nom et le type du noeud ayant envoyé la requête
- //dans le chemin de la requête en utilisant une regex (le chemin se terminant par /type/noeud/remove)
- var parentName = Regex.Replace(request.Path, "^.*\\/([^\\/]*)\\/([^\\/]*)\\/remove$", "$1");
- var nodeName = Regex.Replace(request.Path, "^.*\\/([^\\/]*)\\/([^\\/]*)\\/remove$", "$2");
- /*Console.WriteLine(nodeName);
- Console.WriteLine(parentName);*/
-
- Responder.SuperRoot.Children.TryGetValue(parentName, out Node parent);
- if(parent != null)
- {
- parent.Children.TryGetValue(nodeName, out Node node);
- if (node != null)
- {
- if (node.ClassName == "stream")
- {
- //si le noeud est un stream, on tue le processus VLC associé
- var port = node.Value.Int;
- _processes.TryGetValue(nodeName, out Process proc);
- if (proc != null)
- proc.Kill();
- _processes.Remove(nodeName);
-
- /*System.Diagnostics.Process.Start("CMD.exe", "/C FOR /F \"tokens=5 delims= \" %P IN ('netstat -a -n -o ^|" +
- "findstr :" + stream.Value.Int + "') DO @ECHO taskkill /F /PID %P");*/
-
- //FOR /F "tokens=5 delims= " %P IN('netstat -a -n -o | findstr :8081') DO @ECHO TaskKill.exe /PID %P
- }
- else if (node.ClassName == "alarm")
- {
- //si le noeud est une alarme, on arrête la tâche associée
- _alarmCTs.TryGetValue(nodeName, out CancellationTokenSource ts);
- if (ts != null)
- ts.Cancel();
- }
- parent.RemoveChild(nodeName);
- }
- }
-
- await request.Close();
- await SaveNodes();
- }
- catch (Exception e)
- {
- Console.WriteLine(e.GetType() + ":" + e.Message);
- }
- }
-
- private async void _updateAlarm(Node alarm, string addr, int port, int pin, CancellationToken ct)
- {
- try
- {
- using (var driver = new Driver(new IPEndPoint(IPAddress.Parse(addr), port)))
- {
- await driver.ConnectAsync();
- await Task.Delay(TimeSpan.FromSeconds(1)); //Give the socket time to get connected
-
- Console.WriteLine("Connected");
-
- using (var controller = new GpioController(PinNumberingScheme.Logical, driver))
- {
- controller.OpenPin(pin);
- controller.SetPinMode(pin, PinMode.InputPullUp);
-
- while (!ct.IsCancellationRequested)
- //boucle infinie, qui s'arrête uniquement lorsque l'on annule la tâche
- {
- controller.WaitForEvent(pin, PinEventTypes.Falling, ct);
- if (!ct.IsCancellationRequested)
- {
- // Console.WriteLine("Beep boop");
- alarm.Children.TryGetValue("armed", out Node ArmedNode);
- if(ArmedNode.Value.Boolean == true)
- {
- alarm.Value.Set(true);
- }
- }
- }
- //Console.WriteLine("Task cancelled");
- controller.ClosePin(pin);
- }
- }
- }
- catch (Exception e)
- {
- Console.WriteLine(e.GetType() + ":" + e.Message);
- }
- }
-
- #region Initialize Logging
-
- /// <summary>
- /// This method initializes the logging engine. In this case Serilog is used, but you
- /// may use a variety of logging engines so long as they are compatible with
- /// Liblog (the interface used by the DSLink SDK)
- /// </summary>
- /// <param name="cmdLineOptions"></param>
- private static void InitializeLogging(CommandLineArguments cmdLineOptions)
- {
- if (cmdLineOptions.LogFileFolder != null &&
- !cmdLineOptions.LogFileFolder.EndsWith(Path.DirectorySeparatorChar))
- {
- throw new ArgumentException($"Specified LogFileFolder must end with '{Path.DirectorySeparatorChar}'");
- }
-
- var logConfig = new LoggerConfiguration();
- switch (cmdLineOptions.LogLevel)
- {
- case LogLevel.Debug:
- logConfig.MinimumLevel.Debug();
- break;
-
- case LogLevel.Unspecified:
- case LogLevel.Info:
- logConfig.MinimumLevel.Information();
- break;
-
- case LogLevel.Warning:
- logConfig.MinimumLevel.Warning();
- break;
-
- case LogLevel.Error:
- logConfig.MinimumLevel.Error();
- break;
- }
-
- logConfig.WriteTo.Console(
- outputTemplate:
- "{Timestamp:MM/dd/yyyy HH:mm:ss} {SourceContext} [{Level}] {Message}{NewLine}{Exception}");
- logConfig.WriteTo.Logger(lc =>
- {
- lc.WriteTo.RollingFile(cmdLineOptions.LogFileFolder + "log-{Date}.txt", retainedFileCountLimit: 3);
- });
- Log.Logger = logConfig.CreateLogger();
- }
-
- #endregion
-
- #region dslink-json file processing
-
- /// <summary>
- /// This method will return an instance of CommandLineArguments build with the following logic rules.
- /// The file dslink.json can be utilized to specifiy command line arguments. These live within the config block
- /// of the file. Here is an example:
- /// ...
- /// "configs" : {
- /// "broker" : {
- /// "type": "url",
- /// "value": "mybroker",
- /// "default": "http:localhost:8080\conn"
- /// },
- /// }
- ///
- /// The code in this method considers only the attribute's name ("broker") and value ("mybroker") in this example).
- /// "type" and "default" are not used.
- ///
- /// The receives an instance of CommandLineArguments previously built from the parser. If the dslink-json paramater
- /// is not null the code will use the value specified rather than the default value of "dslink.json" for the file
- /// to read containing the information.
- ///
- /// Options specified on the command line wins out over those specified in the file.
- ///
- /// </summary>
- /// <param name="cmdLineOptions"></param>
- /// <returns></returns>
- private static CommandLineArguments ProcessDSLinkJson(CommandLineArguments cmdLineOptions)
- {
- bool errorIfNotFound = false;
- string fileName = "dslink.json";
-
- //If filename is specified then error if it is not found
- if (!String.IsNullOrEmpty(cmdLineOptions.DSLinkJsonFilename))
- {
- errorIfNotFound = true;
- fileName = cmdLineOptions.DSLinkJsonFilename;
- }
-
- string fileData = "";
- if (File.Exists(fileName))
- {
- fileData = File.ReadAllText(fileName);
- Console.WriteLine(
- $"Will use a combination of options specified from the command line and those specified in {fileName}");
- }
- else
- {
- if (errorIfNotFound == true)
- {
- throw new ArgumentException($"Specified dslink-json file <{fileName}> was not found");
- }
- else
- {
- return cmdLineOptions;
- }
- }
-
- JObject dslinkJson = JObject.Parse(fileData);
- var dsLinkJsonConfig = dslinkJson["configs"];
-
- var cmdLineOptionsDslinkJson = new CommandLineArguments();
-
- cmdLineOptionsDslinkJson.BrokerUrl =
- GetDsLinkStringValueForAttributeName(dsLinkJsonConfig, "broker", cmdLineOptions.BrokerUrl);
- cmdLineOptionsDslinkJson.LinkName =
- GetDsLinkStringValueForAttributeName(dsLinkJsonConfig, "name", cmdLineOptions.LinkName);
- cmdLineOptionsDslinkJson.LogFileFolder =
- GetDsLinkStringValueForAttributeName(dsLinkJsonConfig, "log-file", cmdLineOptions.LogFileFolder);
- cmdLineOptionsDslinkJson.KeysFolder =
- GetDsLinkStringValueForAttributeName(dsLinkJsonConfig, "key", cmdLineOptions.KeysFolder);
- cmdLineOptionsDslinkJson.NodesFileName =
- GetDsLinkStringValueForAttributeName(dsLinkJsonConfig, "nodes", cmdLineOptions.NodesFileName);
- cmdLineOptionsDslinkJson.Token =
- GetDsLinkStringValueForAttributeName(dsLinkJsonConfig, "token", cmdLineOptions.Token);
- cmdLineOptionsDslinkJson.LogLevel = GetDsLinkLogLevel(dsLinkJsonConfig, cmdLineOptions.LogLevel);
-
- return cmdLineOptionsDslinkJson;
- }
-
- private static LogLevel GetDsLinkLogLevel(JToken configObj, LogLevel logLevel)
- {
- if (logLevel != LogLevel.Unspecified)
- {
- return logLevel;
- }
-
- string testString = "";
- try
- {
- testString = configObj["log"]["value"].ToString();
- }
- catch
- {
- }
-
- ;
-
- LogLevel useLogLevel = LogLevel.Info;
- if (!Enum.TryParse(testString, out useLogLevel))
- {
- throw new ArgumentException("Invalid 'value' specified for 'log' value in dslink-json file");
- }
-
- return useLogLevel;
- }
-
- private static string GetDsLinkStringValueForAttributeName(JToken configObj, string attributeName,
- string cmdLineValue)
- {
- //use cmdLineValue if specified else attempt to use the one from the dslink-json
- if (cmdLineValue != null)
- {
- return cmdLineValue;
- }
-
- try
- {
- return configObj[attributeName]["value"].ToString();
- }
- catch
- {
- return null;
- }
- }
-
- #endregion processingq
- }
- }
|