Пример #1
0
        internal static void HandleAbsoluteTimeTriggers(IpiSensorNetConfiguration moduleConfiguration, IReadOnlyDictionary <TriggerSourceTypeEnum, ITriggerSourceHandler> triggerHandlers, IReadOnlyDictionary <int, TriggerDelegate> triggerDelegates, ConcurrentQueue <TriggerSource> absoluteTimeTriggers, Action <string> logger, IReadOnlyDictionary <TriggerDependencyTypeEnum, ITriggerDependencyHandler> triggerDependencyHandlers)
        {
            if (absoluteTimeTriggers.Count == 0)
            {
                return;
            }

            logger("HandleAbsoluteTimeTriggers: Start...");

            using (var context = PiSensorNetDbContext.Connect(moduleConfiguration.ConnectionString))
            {
                var handlerContext = new TriggerSourceHandlerHelperContext(context, triggerHandlers, triggerDelegates, triggerDependencyHandlers, DateTime.Now);

                while (absoluteTimeTriggers.Count > 0)
                {
                    TriggerSource triggerSource;

                    if (absoluteTimeTriggers.TryDequeue(out triggerSource))
                    {
                        continue;
                    }

                    logger($"HandleAbsoluteTimeTriggers: Handling trigger source #{triggerSource.ID}...");

                    TriggerSourceHandlerHelper.Handle(handlerContext, triggerSource);
                }
            }

            logger($"HandleAbsoluteTimeTriggers: Finished handling {absoluteTimeTriggers.Count} trigger source(s)!");
        }
Пример #2
0
        private static void PollMessagesToSend(Queue <Message> messagesToSend, IpiSensorNetConfiguration moduleConfiguration, Action <string> logger)
        {
            logger("PollMessagesToSend: Start...");

            using (var context = PiSensorNetDbContext.Connect(moduleConfiguration.ConnectionString))
            {
                var messages = context.Messages
                               .Include(i => i.Function)
                               .Include(i => i.Module)
                               .AsNoTracking()
                               .Where(i => i.State == MessageStateEnum.Queued)
                               .OrderBy(i => i.Created)
                               .ToList();

                if (messages.Count == 0)
                {
                    return;
                }

                foreach (var message in messages)
                {
                    messagesToSend.Enqueue(message);
                }

                logger($"PollMessagesToSend: Enqueued {messages.Count} messages(s)!"); // ~25ms, 1 message
            }
        }
Пример #3
0
 public FunctionHandlerContext(IpiSensorNetConfiguration moduleConfiguration, PiSensorNetDbContext databaseContext, IReadOnlyDictionary <FunctionTypeEnum, IQueryableFunctionHandler> queryableFunctionHandlers, IReadOnlyMap <FunctionTypeEnum, int> functionTypes, IReadOnlyMap <string, int> functionNames, IReadOnlyDictionary <TriggerSourceTypeEnum, ITriggerSourceHandler> triggerSourceHandlers, IReadOnlyDictionary <int, TriggerDelegate> triggerDelegates, IReadOnlyDictionary <TriggerDependencyTypeEnum, ITriggerDependencyHandler> triggerDependencyHandlers, DateTime triggerDateTime)
     : base(databaseContext, triggerSourceHandlers, triggerDelegates, triggerDependencyHandlers, triggerDateTime)
 {
     ModuleConfiguration       = moduleConfiguration;
     QueryableFunctionHandlers = queryableFunctionHandlers;
     FunctionTypes             = functionTypes;
     FunctionNames             = functionNames;
 }
Пример #4
0
        // ReSharper disable once UnusedMember.Local
        private static void Demo(IpiSensorNetConfiguration moduleConfiguration, IReadOnlyMap <FunctionTypeEnum, int> functionTypes, IReadOnlyMap <string, int> functionNames, IReadOnlyDictionary <FunctionTypeEnum, IFunctionHandler> functionHandlers, IReadOnlyDictionary <FunctionTypeEnum, IQueryableFunctionHandler> queryableFunctionHandlers, IReadOnlyDictionary <TriggerSourceTypeEnum, ITriggerSourceHandler> triggerSourceHandlers, IReadOnlyDictionary <int, TriggerDelegate> triggerDelegates, IReadOnlyDictionary <TriggerDependencyTypeEnum, ITriggerDependencyHandler> triggerDependencyHandlers)
        {
            using (var context = PiSensorNetDbContext.Connect(moduleConfiguration.ConnectionString).WithChangeTracking())
            {
                var functionID   = context.Functions.AsNoTracking().Where(i => i.FunctionType == FunctionTypeEnum.FunctionList).Select(i => i.ID).Single();
                var moduleNumber = context.Modules.AsNoTracking().Count() + 1;
                var address      = $"test{moduleNumber}";

                context.Modules
                .Add(new Module(address)
                {
                    FriendlyName = address,
                    Description  = "Test module",
                }
                     .For(i => { i.ModuleFunctions.Add(context.Functions.Select(f => new ModuleFunction(i, f))); })
                     .For(i =>
                {
                    i.Packets.Add(new[]
                    {
                        new Packet(i, 0, "identify;function_list;voltage;report;ow_list;ow_ds18b20_temperature;ow_ds18b20_temperature_periodical;", DateTime.Now)
                        // "2854280E02000070|25.75;28AC5F2600008030|25.75;"
                        {
                            FunctionID = functionID,
                        }
                    });
                })
                     );

                context.SaveChanges();

                var module = context.Modules.AsNoTracking().Where(i => i.Address == address).Single();
                var packet = context.Packets.Include(i => i.Function).AsNoTracking().Where(i => i.ModuleID == module.ID).OrderByDescending(i => i.Created).First();

                // ReSharper disable once PossibleInvalidOperationException
                var functionHandler = functionHandlers[packet.Function.FunctionType];
                var taskQueue       = new HubMessageQueue();

                functionHandler.Handle(new FunctionHandlerContext(moduleConfiguration, context, queryableFunctionHandlers, functionTypes, functionNames, triggerSourceHandlers, triggerDelegates, triggerDependencyHandlers, DateTime.Now), packet, ref taskQueue);

                context.SaveChanges();
            }
        }
Пример #5
0
        private static int?SendMessage(Queue <Message> messagesToSend, int?lastMessageSentID, IpiSensorNetConfiguration moduleConfiguration, StringBuilder messageBuilder, Action <string> logger)
        {
            if (messagesToSend.Count == 0 || lastMessageSentID.HasValue)
            {
                return(lastMessageSentID);
            }

            logger("SendMessage: Start...");

            var messageToSend = messagesToSend.Dequeue();
            var text          = AssembleMessage(messageBuilder, messageToSend, moduleConfiguration);

            logger($"SendMessage: Putting '{text}'..."); // <0.5ms

            Functionalities.Serial.Put(text);

            using (var context = PiSensorNetDbContext.Connect(moduleConfiguration.ConnectionString))
            {
                context.EnqueueUpdate <Message>(
                    i => i.State == MessageStateEnum.Sent && i.Sent == DateTime.Now,
                    i => i.ID == messageToSend.ID);

                context.ExecuteRaw();
            }

            logger("SendMessage: Message sent!"); // ~22ms

            return(messageToSend.ID);
        }
Пример #6
0
        private static string AssembleMessage(StringBuilder builder, Message messageToSend, IpiSensorNetConfiguration moduleConfiguration)
        {
            var nodeAddress = messageToSend.Module?.Address ?? moduleConfiguration.BroadcastAddress;
            var messageID   = messageToSend.ID.ToBase36();

            builder.Clear();

            builder.Append(nodeAddress);
            builder.Append(moduleConfiguration.AddressDelimiter);
            builder.Append(messageID);
            builder.Append(moduleConfiguration.MessageIDDelimiter);
            builder.Append(messageToSend.Function.Name);

            if (messageToSend.IsQuery)
            {
                builder.Append(moduleConfiguration.FunctionQuery);
            }
            else if (messageToSend.Text != null)
            {
                builder.Append(moduleConfiguration.FunctionDelimiter);
                builder.Append(messageToSend.Text);
            }

            var text = builder.ToString();

            return(text);
        }
Пример #7
0
        internal static void HandleReceivedMessages(int?engineProcessID, ConcurrentQueue <string> receivedMessages, IpiSensorNetConfiguration moduleConfiguration, Action <string> logger)
        {
            if (receivedMessages.Count == 0)
            {
                return;
            }

            logger("HandleReceivedMessages: Start...");

            var arePacketsProcessed = false;

            using (var context = PiSensorNetDbContext.Connect(moduleConfiguration.ConnectionString))
            {
                while (receivedMessages.Count > 0)
                {
                    logger("HandleReceivedMessages: Dequeing...");

                    string text;
                    if (!receivedMessages.TryDequeue(out text))
                    {
                        continue;
                    }

                    logger($"HandleReceivedMessages: Processing '{text}'...");

                    var isFailed = text.StartsWith("FAIL ", StringComparison.InvariantCultureIgnoreCase);
                    var isOk     = text.StartsWith("OK ", StringComparison.InvariantCultureIgnoreCase);

                    if (isFailed || isOk)
                    {
                        if (!_lastMessageSentID.HasValue)
                        {
                            logger($"HandleReceivedMessages: ERROR: Message '{text} was not handled due to lack of {nameof(_lastMessageSentID)}!");
                            continue;
                        }

                        var state = isOk ? MessageStateEnum.Completed : MessageStateEnum.Failed;
                        var error = isFailed ? text.Substring("FAIL ".Length) : null;

                        //context.EnqueueRaw(Message.GenerateUpdate(context,
                        //    new Dictionary<Expression<Func<Message, object>>, string>
                        //    {
                        //        {i => i.State, state.ToSql()},
                        //        {i => i.ResponseReceived, DateTime.Now.ToSql()},
                        //        {i => i.Error, error.ToSql()},
                        //    },
                        //    new Tuple<Expression<Func<Message, object>>, string, string>(i => i.ID, "=", _lastMessageSentID.Value.ToSql())));

                        context.EnqueueUpdate <Message>(
                            i => i.State == state && i.ResponseReceived == DateTime.Now && i.Error == error,
                            i => i.ID == _lastMessageSentID.Value);

                        logger($"HandleReceivedMessages: Updated message #{_lastMessageSentID.Value} to '{state}'!");

                        _lastMessageSentID = null;
                    }
                    else
                    {
                        var partialPacket = new PartialPacket(
                            String.Format(moduleConfiguration.AddressPattern, text.SubstringBetween("@", "#")),
                            Byte.Parse(text.SubstringBetween("#", "(")),
                            Byte.Parse(text.SubstringBetween("(", "/")),
                            Byte.Parse(text.SubstringBetween("/", ")")),
                            text.SubstringAfter("):"),
                            DateTime.Now);

                        context.PartialPackets.Add(partialPacket);

                        arePacketsProcessed = true;

                        logger("HandleReceivedMessages: Processed message to new partial packet!");
                    }
                }

                context.SaveChanges();

                logger("HandleReceivedMessages: Done!"); // ~64ms, 4 messagess
            }

            if (arePacketsProcessed && engineProcessID.HasValue)
            {
                Signal.Send(engineProcessID.Value, SignalTypeEnum.User1);
            }
        }
Пример #8
0
 protected override IReadOnlyCollection <KeyValuePair <string, string> > GetItems(IpiSensorNetConfiguration moduleConfiguration, Packet packet)
 => FunctionHandlerHelper.SplitPairs(packet.Text, moduleConfiguration.FunctionResultDelimiter, moduleConfiguration.FunctionResultValueDelimiter);
Пример #9
0
 protected abstract IReadOnlyCollection <T> GetItems(IpiSensorNetConfiguration moduleConfiguration, Packet packet);
Пример #10
0
 protected override IReadOnlyCollection <string> GetItems(IpiSensorNetConfiguration moduleConfiguration, Packet packet)
 => FunctionHandlerHelper.SplitSingle(packet.Text, moduleConfiguration.FunctionResultDelimiter);
Пример #11
0
        private static void InternalHandleMessage(string clientID, int?moduleID, FunctionTypeEnum functionType, bool isQuery, string text, IReadOnlyMap <string, int> modules, IpiSensorNetConfiguration moduleConfiguration, IReadOnlyMap <FunctionTypeEnum, int> functionTypes, int?serialProcessID, IHubProxy hubProxy, Action <string> logger)
        {
            HandleMessage(clientID, moduleID, functionType, isQuery, text, modules, moduleConfiguration, functionTypes, hubProxy, logger);

            if (serialProcessID.HasValue)
            {
                Signal.Send(serialProcessID.Value, SignalTypeEnum.User1);
            }
        }
Пример #12
0
        internal static void HandleMessage(string clientID, int?moduleID, FunctionTypeEnum functionType, bool isQuery, string text, IReadOnlyMap <string, int> moduleAddresses, IpiSensorNetConfiguration moduleConfiguration, IReadOnlyMap <FunctionTypeEnum, int> functionTypes, IHubProxy hubProxy, Action <string> logger)
        {
            logger($"HandleMessage: Received message from ${clientID} to @{moduleID?.ToString() ?? "ALL"} - {functionType}{(text == null ? (isQuery ? "?" : String.Empty) : ":" + text)}");

            if (moduleID.HasValue && !moduleAddresses.Reverse.ContainsKey(moduleID.Value))
            {
                hubProxy.Invoke("error", clientID, $"Module #{moduleID.Value} does not exist.");

                logger($"HandleMessage: ERROR: Message not handled, module #{moduleID.Value} does not exist!");

                return;
            }

            int messageID;

            using (var context = PiSensorNetDbContext.Connect(moduleConfiguration.ConnectionString))
            {
                var message = new Message(functionTypes.Forward[functionType], isQuery)
                {
                    ModuleID = moduleID,
                };

                if (!isQuery)
                {
                    message.Text = text;
                }

                context.Messages.Add(message);

                context.SaveChanges();

                messageID = message.ID;
            }

            logger($"HandleMessage: Message handled to #{messageID}!"); // ~38ms
        }
Пример #13
0
        internal static bool HandlePackets(PiSensorNetDbContext context, IpiSensorNetConfiguration moduleConfiguration, IReadOnlyMap <FunctionTypeEnum, int> functionTypes, IReadOnlyMap <string, int> functionNames, IReadOnlyDictionary <FunctionTypeEnum, IFunctionHandler> functionHandlers, IReadOnlyDictionary <FunctionTypeEnum, IQueryableFunctionHandler> queryableFunctionHandlers, IReadOnlyDictionary <TriggerSourceTypeEnum, ITriggerSourceHandler> triggerSourceHandlers, IReadOnlyDictionary <int, TriggerDelegate> triggerDelegates, IReadOnlyDictionary <TriggerDependencyTypeEnum, ITriggerDependencyHandler> triggerDependencyHandlers, int?serialProcessID, IHubProxy hubProxy, Action <string> logger)
        {
            logger("HandlePackets: Start...");

            var packets = context.Packets
                          .Include(i => i.Module)
                          .Include(i => i.Function)
                          .Where(i => i.State == PacketStateEnum.New)
                          .Where(i => i.FunctionID.HasValue)
                          .OrderBy(i => i.Received)
                          .ToList();

            logger("HandlePackets: Done selecting..."); // ~21ms

            if (packets.Count == 0)
            {
                return(false);
            }

            var handlerContext = new FunctionHandlerContext(moduleConfiguration, context, queryableFunctionHandlers, functionTypes, functionNames, triggerSourceHandlers, triggerDelegates, triggerDependencyHandlers, DateTime.Now);

            var handleAgain     = false;
            var newMesagesAdded = false;
            var hubTasksQueue   = new HubMessageQueue();

            foreach (var packet in packets)
            {
                // ReSharper disable once PossibleInvalidOperationException
                var handler = functionHandlers.GetValueOrDefault(packet.Function.FunctionType);
                if (handler == null)
                {
                    context.EnqueueUpdate <Packet>(
                        i => i.State == PacketStateEnum.Unhandled,
                        i => i.ID == packet.ID);

                    logger($"HandlePackets: Packet #{packet.ID} (function '{packet.Function.FunctionType}') could not be handled, no handler found!");

                    continue;
                }

                var functionHandlerResult = handler.IsModuleIdentityRequired && packet.Module.State != ModuleStateEnum.Identified
                    ? PacketStateEnum.Skipped
                    : handler.Handle(handlerContext, packet, ref hubTasksQueue);

                handleAgain     = handleAgain || functionHandlerResult.ShouldHandlePacketsAgain;
                newMesagesAdded = newMesagesAdded || functionHandlerResult.NewMessagesAdded;

                context.EnqueueUpdate <Packet>(
                    i => i.State == functionHandlerResult.PacketState && i.Processed == DateTime.Now,
                    i => i.ID == packet.ID);

                if (handler.TriggerSourceType.HasValue && functionHandlerResult.PacketState == PacketStateEnum.Handled)
                {
                    TriggerSources[handler.TriggerSourceType.Value].Each(i =>
                                                                         TriggerSourceHandlerHelper.Handle(handlerContext, i, packet.Module.ID));
                }

                logger($"HandlePackets: Packet #{packet.ID} processed to '{functionHandlerResult.PacketState}'" +
                       $"{(functionHandlerResult.NewMessagesAdded ? ", new messaged were added" : String.Empty)}" +
                       $"{(functionHandlerResult.ShouldHandlePacketsAgain ? ", requested another handling" : String.Empty)}" +
                       "!");
            }

            logger($"HandlePackets: Done handling packets, processed {packets.Count}!"); // ~51ms

            context.ExecuteRaw();

            logger("HandlePackets: Queries executed!"); // ~13ms

            if (newMesagesAdded && serialProcessID.HasValue)
            {
                Signal.Send(serialProcessID.Value, SignalTypeEnum.User1);
            }

            while (hubTasksQueue.Count > 0)
            {
                hubProxy.SafeInvoke(hubTasksQueue.Dequeue());
            }

            logger($"HandlePackets: Hub message(s) sent{(newMesagesAdded ? ", Serial signaled about new message(s)" : String.Empty)}{(handleAgain ? ", packet(s) will be handled again" : String.Empty)}!"); // ~10ms

            return(handleAgain);
        }
Пример #14
0
        internal static bool MergePackets(PiSensorNetDbContext context, IpiSensorNetConfiguration moduleConfiguration, IReadOnlyMap <FunctionTypeEnum, int> functionTypes, IReadOnlyMap <string, int> functionNames, IReadOnlyMap <string, int> moduleAddresses, CacheModuleAddressesDelegate cacheModuleAddresses, Action <string> logger)
        {
            logger("MergePackets: Start...");

            var partialPackets = context.PartialPackets
                                 .AsNoTracking()
                                 .Where(i => i.State == PartialPacketStateEnum.New)
                                 .ToList();

            //logger("MergePackets: Done selecting..."); // ~14ms

            var groupedPackets = partialPackets.GroupBy(i => new
            {
                i.Address,
                i.Number,
                i.Total
            })
                                 .ToList();

            var newModules = new Dictionary <string, Module>();

            foreach (var packetGroup in groupedPackets)
            {
                var address = packetGroup.Key.Address;

                if (moduleAddresses.Forward.ContainsKey(address) || newModules.ContainsKey(address))
                {
                    continue;
                }

                var module = new Module(address);

                context.Modules.Add(module);

                newModules.Add(address, module);
            }

            if (newModules.Count > 0)
            {
                logger("MergePackets: Done finding modules...");

                context.SaveChanges();

                logger("MergePackets: Done saving modules...");

                moduleAddresses = cacheModuleAddresses(context);
            }

            logger($"MergePackets: Done creating new modules, found {newModules.Count}!"); // 700us, no modules

            var packetGroupWithPacket = new List <Tuple <Packet, IEnumerable <PartialPacket> > >(groupedPackets.Count);

            foreach (var packetGroup in groupedPackets)
            {
                if (packetGroup.Count() != packetGroup.Key.Total)
                {
                    context.EnqueueUpdate <PartialPacket>(
                        i => i.State == PartialPacketStateEnum.Fragmented,
                        i => packetGroup.Select(ii => ii.ID).Contains(i.ID));

                    continue;
                }

                var moduleID  = moduleAddresses.Forward[packetGroup.Key.Address];
                int?messageID = null;
                var text      = packetGroup.OrderBy(i => i.Current).Select(i => i.Message).Concat();
                var received  = packetGroup.Max(i => i.Received);

                var messageIDDelimiterIndex = text.IndexOf(moduleConfiguration.MessageIDDelimiter);
                if (messageIDDelimiterIndex > 0)
                {
                    messageID = text.Substring(0, messageIDDelimiterIndex).FromBase36();
                    text      = text.Substring(messageIDDelimiterIndex + 1);
                }

                var functionName = text.SubstringBefore(moduleConfiguration.FunctionResultNameDelimiter).ToLowerInvariant();
                var functionID   = functionNames.Forward.GetValueOrNullable(functionName);

                if (functionID.HasValue)
                {
                    text = text.Substring(functionName.Length + 1);
                }

                var packet = new Packet(moduleID, packetGroup.Key.Number, text, received)
                {
                    MessageID  = messageID,
                    FunctionID = functionID,
                };

                context.Packets.Add(packet);

                packetGroupWithPacket.Add(Tuple.Create(packet, (IEnumerable <PartialPacket>)packetGroup));
            }

            if (packetGroupWithPacket.Count > 0)
            {
                //logger("MergePackets: Done parsing packet groups!"); // ~3ms

                context.SaveChanges();

                logger("MergePackets: Saved changes!"); // ~23ms

                packetGroupWithPacket.Each(p =>
                                           context.EnqueueUpdate <PartialPacket>(
                                               i => i.PacketID == p.Item1.ID && i.State == PartialPacketStateEnum.Merged,
                                               i => p.Item2.Select(ii => ii.ID).Contains(i.ID)));

                context.ExecuteRaw();

                logger("MergePackets: Updated partial packets!"); // ~15ms, 1 packet
            }

            logger($"MergePackets: Done, created {packetGroupWithPacket.Count} packet(s)!");

            return(packetGroupWithPacket.Count > 0);
        }
Пример #15
0
        private static IHubProxy InitializeHubConnection(IReadOnlyConfiguration configuration, IpiSensorNetConfiguration moduleConfiguration, MessageHandler handler, IReadOnlyMap <string, int> moduleAddresses, IReadOnlyMap <FunctionTypeEnum, int> functionTypes, int?serialProcessID, DisposalQueue toDispose, Action <string> logger)
        {
            var hubConnection = new HubConnection(configuration["Settings:WebAddress"],
                                                  new Dictionary <string, string>
            {
                {
                    configuration["Settings:SignalREngineFlagName"], true.ToString().ToLowerInvariant()
                }
            });

            hubConnection.StateChanged += change => logger($"InitializeHubConnection: StateChanged: '{change.OldState}' -> '{change.NewState}'!");

            hubConnection.JsonSerializer.Converters.Add(new NullConverter()); // handle NULLs

            var hubProxy = hubConnection.CreateHubProxy(configuration["Settings:SignalRHubName"]);

            toDispose += hubProxy.On <string, int?, FunctionTypeEnum, string>("sendMessage",
                                                                              (clientID, moduleID, functionType, text)
                                                                              => handler(clientID, moduleID, functionType, false, text, moduleAddresses, moduleConfiguration, functionTypes, serialProcessID, hubProxy, logger));

            toDispose += hubProxy.On <string, int?, FunctionTypeEnum>("sendQuery",
                                                                      (clientID, moduleID, functionType)
                                                                      => handler(clientID, moduleID, functionType, true, null, moduleAddresses, moduleConfiguration, functionTypes, serialProcessID, hubProxy, logger));

            try
            {
                hubConnection.Start().Wait();
            }
            catch (Exception e)
            {
                logger($"InitializeHubConnection: ERROR: Exception occurred while initializing hub connection: {e.Message}.");

                return(null);
            }

            logger($"InitializeHubConnection: Connection to hub started with ID '{hubConnection.ConnectionId}'!");

            toDispose += hubConnection;

            return(hubProxy);
        }