Modular Application Creator Use Case Based Documentation
Loading...
Searching...
No Matches
BitLogicNetworks.cs
1using System;
2using System.IO;
3using System.Linq;
4using System.Xml.Linq;
5using Siemens.Automation.ModularApplicationCreator.ControlModules.ModuleEssentials.Objects.Generation.Openness.XML;
6using Siemens.Automation.ModularApplicationCreator.ControlModules.ModuleEssentials.Objects.Generation.Openness.XML.Parts;
7using Siemens.Automation.ModularApplicationCreator.Tia.Helper;
8using Siemens.Automation.ModularApplicationCreator.Tia.Helper.Create_XML_Block;
9using Siemens.Automation.ModularApplicationCreator.Tia.Helper.Create_XML_Block.XmlBlocks.BlockFrames;
10using Siemens.Automation.ModularApplicationCreator.Tia.Openness;
11using MacPLang = Siemens.Automation.ModularApplicationCreator.Tia.Helper.Create_XML_Block.ProgrammingLanguage;
12using MEPlang = Siemens.Automation.ModularApplicationCreator.ControlModules.ModuleEssentials.Objects.Generation.Openness.XML.ProgrammingLanguage;
13
15{
20 public static class BitLogicNetworks
21 {
26 public static SystemBlockCall CreateRSCall(XmlNetwork parent)
27 {
28 var rsCall = parent.AddSystemBlockCall("Rs");
29 rsCall.AddCallParameter("q", "Bool", Section.Output);
30 rsCall.AddCallParameter("r", "Bool", Section.Input);
31 rsCall.AddCallParameter("s1", "Bool", Section.Input);
32 rsCall.AddCallParameter("operand", "Bool", Section.Output);
33
34 return rsCall;
35 }
36
42 public static SystemBlockCall CreateAssignmentCall(XmlNetwork parent)
43 {
44 var coil = parent.AddSystemBlockCall("Coil");
45 coil.CallParameters.Add(new CustomBlockCallParameter(coil, "in", "Bool", Section.Input));
46 coil.CallParameters.Add(new CustomBlockCallParameter(coil, "out", "Bool", Section.Output));
47 coil.CallParameters.Add(new CustomBlockCallParameter(coil, "operand", "Bool", Section.Output));
48 return coil;
49 }
50
62 public static XmlNetwork CreateRSNetwork(
63 string rInputName,
64 string s1InputName,
65 string rsOperandVariableName,
66 MEPlang opnsProgrammingLanguage)
67 {
68 var xmlNw = new XmlNetwork(opnsProgrammingLanguage);
69
70 var rsCall = CreateRSCall(xmlNw);
71
72 var rInputVar = xmlNw.AddVariable(rInputName);
73 xmlNw.AddConnection(rInputVar, rsCall.CallParameters.Single(x => x.Name == "r"));
74
75 var s1InputVar = xmlNw.AddVariable(s1InputName);
76 xmlNw.AddConnection(s1InputVar, rsCall.CallParameters.Single(x => x.Name == "s1"));
77
78 var operandOutputVar = xmlNw.AddVariable(rsOperandVariableName);
79 xmlNw.AddConnection(rsCall.CallParameters.Single(x => x.Name == "operand"), operandOutputVar);
80
81 //var assignmentCall = CreateAssignmentCall(xmlNw);
82 //xmlNw.AddConnection(rsCall.CallParameters.Single(x => x.Name.Contains("q")), assignmentCall.CallParameters.First(x => x.Name.Contains("in")));
83 return xmlNw;
84 }
85
104 public static XmlNetwork CreateRSNetworkLAD(
105 string rInputName,
106 string s1InputName,
107 string rsOperandVariableName)
108 {
109 // XmlNetwork must use FBD here: LAD and FBD share the same underlying graph XML in
110 // TIA Portal. XmlNetwork(MEPlang.LAD) generates a schema TIA Portal rejects on import.
111 var xmlNw = new XmlNetwork(MEPlang.FBD);
112
113 var rsCall = CreateRSCall(xmlNw);
114
115 var rInputVar = xmlNw.AddVariable(rInputName);
116 xmlNw.AddConnection(rInputVar, rsCall.CallParameters.Single(x => x.Name == "r"));
117
118 var s1InputVar = xmlNw.AddVariable(s1InputName);
119 xmlNw.AddConnection(s1InputVar, rsCall.CallParameters.Single(x => x.Name == "s1"));
120
121 var operandOutputVar = xmlNw.AddVariable(rsOperandVariableName);
122 xmlNw.AddConnection(rsCall.CallParameters.Single(x => x.Name == "operand"), operandOutputVar);
123
124 // Q output left unconnected – required for RS in LAD (no downstream Coil element)
125 xmlNw.AddConnection(rsCall.CallParameters.Single(x => x.Name == "q"), null);
126
127 return xmlNw;
128 }
129
156 public static void GenerateFbWithRSNetworkFBD(string blockName, PlcDevice plcDevice, MacPLang macProgrammingLanguage)
157 {
158 var opnsProgrammingLanguage = (MEPlang)Enum.Parse(typeof(MEPlang), macProgrammingLanguage.ToString());
159
160 var block = new XmlFB(blockName);
161 block.BlockAttributes.ProgrammingLanguage = macProgrammingLanguage;
162
163 block.Interface[InterfaceSections.Input].Add(new InterfaceParameter("InputBool_R", "Bool"));
164 block.Interface[InterfaceSections.Input].Add(new InterfaceParameter("InputBool_S1", "Bool"));
165 block.Interface[InterfaceSections.Input].Add(new InterfaceParameter("InputBool_R2", "Bool"));
166
167 var staticItf = block.Interface[InterfaceSections.Static];
168 // FBD RS pair (Networks 1 & 2)
169 staticItf.Add(new InterfaceParameter("RSOperand", "Bool"));
170 staticItf.Add(new InterfaceParameter("RSOperand2", "Bool"));
171 // SCL network temp variable
172 staticItf.Add(new InterfaceParameter("TempVariable", "Bool"));
173
174 // Network 1: first RS flip-flop (FBD)
175 var rsNetwork1 = CreateRSNetwork("#InputBool_R", "#InputBool_S1", "#RSOperand", opnsProgrammingLanguage);
176 block.Networks.Add(rsNetwork1.GenerateFixNetwork());
177
178 // Network 2: second RS flip-flop (FBD) – S1 driven by #OutputBool written in Network 1
179 var rsNetwork2 = CreateRSNetwork("#InputBool_R2", "#RSOperand", "#RSOperand2", opnsProgrammingLanguage);
180 block.Networks.Add(rsNetwork2.GenerateFixNetwork());
181
182 // Network 3: simple SCL assignment – reads the result of Network 1 into TempVariable
183 var sclCode = "#TempVariable := #RSOperand;";
184
185 var sclNetworks = new Parser().ParseSclSnippet(sclCode, block, plcDevice, GroupBlockCalls.NOGROUPING);
186 foreach (var sclNw in sclNetworks)
187 {
188 block.Networks.Add(sclNw);
189 }
190
191 block.GenerateXmlBlock(plcDevice);
192 }
193
218 public static void GenerateFbWithRSNetworkLAD(string blockName, PlcDevice plcDevice)
219 {
220 var block = new XmlFB(blockName);
221 block.BlockAttributes.ProgrammingLanguage = MacPLang.LAD;
222
223 block.Interface[InterfaceSections.Input].Add(new InterfaceParameter("InputBool_R", "Bool"));
224 block.Interface[InterfaceSections.Input].Add(new InterfaceParameter("InputBool_S1", "Bool"));
225 block.Interface[InterfaceSections.Input].Add(new InterfaceParameter("InputBool_R2", "Bool"));
226
227 var staticItf = block.Interface[InterfaceSections.Static];
228 staticItf.Add(new InterfaceParameter("RSOperand", "Bool"));
229 staticItf.Add(new InterfaceParameter("RSOperand2", "Bool"));
230 // SCL network temp variable
231 staticItf.Add(new InterfaceParameter("TempVariable", "Bool"));
232
233 // Network 1: RS flip-flop in LAD
234 var rsNetwork1 = CreateRSNetworkLAD("#InputBool_R", "#InputBool_S1", "#RSOperand");
235 block.Networks.Add(rsNetwork1.GenerateFixNetwork());
236
237 // Network 2: RS flip-flop in LAD – S1 driven by the operand written in Network 1
238 var rsNetwork2 = CreateRSNetworkLAD("#InputBool_R2", "#RSOperand", "#RSOperand2");
239 block.Networks.Add(rsNetwork2.GenerateFixNetwork());
240
241 // Network 3: simple SCL assignment – reads the result of Network 1 into TempVariable
242 var sclCode = "#TempVariable := #RSOperand;";
243 var sclNetworks = new Parser().ParseSclSnippet(sclCode, block, plcDevice, GroupBlockCalls.NOGROUPING);
244 foreach (var sclNw in sclNetworks)
245 {
246 block.Networks.Add(sclNw);
247 }
248
249 // Generate the XML in FBD mode (XmlNetwork only produces valid graph XML in FBD),
250 // patch every CompileUnit language to LAD in memory, then import directly.
251 ImportBlockAsLad(block, plcDevice);
252 }
253
264 public static SystemBlockCall CreateANDCall(XmlNetwork parent, int cardinality)
265 {
266 var andCall = parent.AddSystemBlockCall("A");
267 andCall.AddSystemParameter("Card", "Cardinality", cardinality.ToString());
268 andCall.AddCallParameter("out", "Bool", Section.Output);
269 for (int i = 1; i <= cardinality; i++)
270 {
271 andCall.AddCallParameter("in" + i, "Bool", Section.Input);
272 }
273 // Negate the output and the last input (mirrors reference project behaviour)
274 andCall.CallParameters.First(x => x.Section == Section.Output).Negated = true;
275 andCall.CallParameters.Last(x => x.Section == Section.Input).Negated = true;
276 return andCall;
277 }
278
286 public static XmlNetwork CreateANDCallNetwork(
287 System.Collections.Generic.List<string> inputVariableNames,
288 string outputVariableName,
289 MEPlang opnsProgrammingLanguage)
290 {
291 var xmlNw = new XmlNetwork(opnsProgrammingLanguage);
292 var andCall = CreateANDCall(xmlNw, inputVariableNames.Count);
293
294 var currentIdx = 1;
295 foreach (var varName in inputVariableNames)
296 {
297 var inputVar = xmlNw.AddVariable(varName);
298 xmlNw.AddConnection(inputVar, andCall.CallParameters.Single(x => x.Name == "in" + currentIdx));
299 currentIdx++;
300 }
301
302 var assignmentCall = CreateAssignmentCall(xmlNw);
303 xmlNw.AddConnection(andCall.CallParameters.Single(x => x.Name.Contains("out")),
304 assignmentCall.CallParameters.First(x => x.Name.Contains("in")));
305 var assignmentOperandOutput = xmlNw.AddVariable(outputVariableName);
306 xmlNw.AddConnection(assignmentCall.CallParameters.First(x => x.Name.Contains("operand")),
307 assignmentOperandOutput);
308
309 return xmlNw;
310 }
311
327 public static XmlNetwork CreateNetworkWith2Circuits(
328 System.Collections.Generic.List<string> inputVariableNames,
329 string rInputName,
330 string operandVariableName)
331 {
332 var xmlNw = new XmlNetwork(MEPlang.FBD);
333
334 // ---- Circuit 1 ----
335 var andCall1 = CreateANDCall(xmlNw, 2);
336 var idx = 1;
337 foreach (var varName in inputVariableNames)
338 {
339 var inputVar = xmlNw.AddVariable(varName);
340 xmlNw.AddConnection(inputVar, andCall1.CallParameters.Single(x => x.Name == "in" + idx));
341 idx++;
342 }
343 var rsCall1 = CreateRSCall(xmlNw);
344 xmlNw.AddConnection(andCall1.CallParameters.Single(x => x.Name == "out"),
345 rsCall1.CallParameters.Single(x => x.Name == "s1"));
346 xmlNw.AddConnection(xmlNw.AddVariable(rInputName),
347 rsCall1.CallParameters.Single(x => x.Name == "r"));
348 xmlNw.AddConnection(rsCall1.CallParameters.Single(x => x.Name == "operand"),
349 xmlNw.AddVariable(operandVariableName));
350 // Q left unconnected – must be declared explicitly
351 xmlNw.AddConnection(rsCall1.CallParameters.Single(x => x.Name == "q"), null);
352
353 // ---- Circuit 2 (start only after circuit 1 is complete) ----
354 idx = 1;
355 var andCall2 = CreateANDCall(xmlNw, 2);
356 foreach (var varName in inputVariableNames)
357 {
358 var inputVar = xmlNw.AddVariable(varName);
359 xmlNw.AddConnection(inputVar, andCall2.CallParameters.Single(x => x.Name == "in" + idx));
360 idx++;
361 }
362 var rsCall2 = CreateRSCall(xmlNw);
363 xmlNw.AddConnection(andCall2.CallParameters.Single(x => x.Name == "out"),
364 rsCall2.CallParameters.Single(x => x.Name == "s1"));
365 xmlNw.AddConnection(xmlNw.AddVariable(rInputName),
366 rsCall2.CallParameters.Single(x => x.Name == "r"));
367 xmlNw.AddConnection(rsCall2.CallParameters.Single(x => x.Name == "operand"),
368 xmlNw.AddVariable(operandVariableName));
369 xmlNw.AddConnection(rsCall2.CallParameters.Single(x => x.Name == "q"), null);
370
371 return xmlNw;
372 }
373
379 private static XmlNetwork CreateSingleAndRsCircuitNetwork(
380 string andInput1,
381 string andInput2,
382 string rInputName,
383 string operandVariableName)
384 {
385 var xmlNw = new XmlNetwork(MEPlang.FBD);
386
387 var andCall = CreateANDCall(xmlNw, 2);
388 xmlNw.AddConnection(xmlNw.AddVariable(andInput1), andCall.CallParameters.Single(x => x.Name == "in1"));
389 xmlNw.AddConnection(xmlNw.AddVariable(andInput2), andCall.CallParameters.Single(x => x.Name == "in2"));
390
391 var rsCall = CreateRSCall(xmlNw);
392 xmlNw.AddConnection(andCall.CallParameters.Single(x => x.Name == "out"),
393 rsCall.CallParameters.Single(x => x.Name == "s1"));
394 xmlNw.AddConnection(xmlNw.AddVariable(rInputName),
395 rsCall.CallParameters.Single(x => x.Name == "r"));
396 xmlNw.AddConnection(rsCall.CallParameters.Single(x => x.Name == "operand"),
397 xmlNw.AddVariable(operandVariableName));
398 xmlNw.AddConnection(rsCall.CallParameters.Single(x => x.Name == "q"), null);
399
400 return xmlNw;
401 }
402
403 // ----------------------------------------------------------------
404 // FB generators – AND networks
405 // ----------------------------------------------------------------
406
423 public static void GenerateFbWithANDNetworkFBD(string blockName, PlcDevice plcDevice)
424 {
425 var block = new XmlFB(blockName);
426 block.BlockAttributes.ProgrammingLanguage = MacPLang.FBD;
427
428 block.Interface[InterfaceSections.Input].Add(new InterfaceParameter("InputBool1", "Bool"));
429 block.Interface[InterfaceSections.Input].Add(new InterfaceParameter("InputBool2", "Bool"));
430 block.Interface[InterfaceSections.Input].Add(new InterfaceParameter("InputBool3", "Bool"));
431
432 var staticItf = block.Interface[InterfaceSections.Static];
433 staticItf.Add(new InterfaceParameter("OutputBool", "Bool"));
434 staticItf.Add(new InterfaceParameter("AndOperand", "Bool"));
435
436 // Network 1: AND of all three inputs → OutputBool
437 var inputVars = new System.Collections.Generic.List<string>
438 { "#InputBool1", "#InputBool2", "#InputBool3" };
439 var andNetwork = CreateANDCallNetwork(inputVars, "#OutputBool", MEPlang.FBD);
440 block.Networks.Add(andNetwork.GenerateFixNetwork());
441
442 // Network 2: AndOperand := OutputBool
443 var assignNetwork = new XmlNetwork(MEPlang.FBD);
444 var assignCall = CreateAssignmentCall(assignNetwork);
445 var outputBoolVar = assignNetwork.AddVariable("#OutputBool");
446 assignNetwork.AddConnection(outputBoolVar,
447 assignCall.CallParameters.First(x => x.Name.Contains("in")));
448 var andOperandVar = assignNetwork.AddVariable("#AndOperand");
449 assignNetwork.AddConnection(
450 assignCall.CallParameters.First(x => x.Name.Contains("operand")),
451 andOperandVar);
452 block.Networks.Add(assignNetwork.GenerateFixNetwork());
453
454 block.GenerateXmlBlock(plcDevice);
455 }
456
474 public static void GenerateFbWithANDNetworkLAD(string blockName, PlcDevice plcDevice)
475 {
476 var block = new XmlFB(blockName);
477 block.BlockAttributes.ProgrammingLanguage = MacPLang.LAD;
478
479 block.Interface[InterfaceSections.Input].Add(new InterfaceParameter("InputBool1", "Bool"));
480 block.Interface[InterfaceSections.Input].Add(new InterfaceParameter("InputBool2", "Bool"));
481 block.Interface[InterfaceSections.Input].Add(new InterfaceParameter("InputBool3", "Bool"));
482
483 var staticItf = block.Interface[InterfaceSections.Static];
484 staticItf.Add(new InterfaceParameter("OutputBool", "Bool"));
485 staticItf.Add(new InterfaceParameter("AndOperand", "Bool"));
486
487 // Network 1: AND of all three inputs → OutputBool (FBD graph XML, LAD display)
488 var inputVars = new System.Collections.Generic.List<string>
489 { "#InputBool1", "#InputBool2", "#InputBool3" };
490 var andNetwork = CreateANDCallNetwork(inputVars, "#OutputBool", MEPlang.FBD);
491 block.Networks.Add(andNetwork.GenerateFixNetwork());
492
493 // Network 2: AndOperand := OutputBool
494 var assignNetwork = new XmlNetwork(MEPlang.FBD);
495 var assignCall = CreateAssignmentCall(assignNetwork);
496 var outputBoolVar = assignNetwork.AddVariable("#OutputBool");
497 assignNetwork.AddConnection(outputBoolVar,
498 assignCall.CallParameters.First(x => x.Name.Contains("in")));
499 var andOperandVar = assignNetwork.AddVariable("#AndOperand");
500 assignNetwork.AddConnection(
501 assignCall.CallParameters.First(x => x.Name.Contains("operand")),
502 andOperandVar);
503 block.Networks.Add(assignNetwork.GenerateFixNetwork());
504
505 // Generate the XML in FBD mode (XmlNetwork only produces valid graph XML in FBD),
506 // patch every CompileUnit language to LAD in memory, then import directly.
507 ImportBlockAsLad(block, plcDevice);
508 }
509
510 // ----------------------------------------------------------------
511 // LAD import helper
512 // ----------------------------------------------------------------
513
514 // FlgNet namespace used in every CompileUnit network source
515 private static readonly XNamespace FlgNetNs =
516 "http://www.siemens.com/automation/Openness/SW/NetworkSource/FlgNet/v4";
517
527 private static void ImportBlockAsLad(XmlFB block, PlcDevice plcDevice)
528 {
529 // Generate the XML in FBD mode – the only mode that produces valid Openness graph XML.
530 var xdoc = block.GenerateXmlFile(MacPLang.FBD);
531
532 // Compute a document-wide UId ceiling so new elements added to any FlgNet
533 // cannot collide with UIds in other compile units of the same FB.
534 int nextUId = xdoc.Descendants()
535 .Select(e => e.Attribute("UId")?.Value)
536 .Where(v => v != null)
537 .Select(v => { int n; return int.TryParse(v, out n) ? n : 0; })
538 .DefaultIfEmpty(0)
539 .Max() + 1;
540
541 // Transform each FBD CompileUnit to LAD topology, then patch the language label.
542 foreach (var compileUnit in xdoc.Descendants("SW.Blocks.CompileUnit").ToList())
543 {
544 var langElem = compileUnit.Descendants("ProgrammingLanguage").FirstOrDefault();
545 if (langElem == null || langElem.Value != "FBD")
546 continue; // leave SCL networks untouched
547
548 var flgNet = compileUnit.Descendants(FlgNetNs + "FlgNet").FirstOrDefault();
549 if (flgNet != null)
550 ConvertFlgNetToLad(flgNet, ref nextUId);
551
552 langElem.Value = "LAD";
553 }
554
555 // Also patch block-level ProgrammingLanguage (FB header).
556 foreach (var langElem in xdoc.Descendants("ProgrammingLanguage")
557 .Where(e => e.Value == "FBD").ToList())
558 langElem.Value = "LAD";
559
560 // Import patched XML via a temporary file and clean it up immediately.
561 var tempFile = Path.ChangeExtension(Path.GetTempFileName(), ".xml");
562 try
563 {
564 xdoc.Save(tempFile);
565 OpennessFuncs.ImportBlockToPlc(tempFile, plcDevice);
566 }
567 finally
568 {
569 if (File.Exists(tempFile))
570 File.Delete(tempFile);
571 }
572 }
573
585 private static void ConvertFlgNetToLad(XElement flgNet, ref int nextUId)
586 {
587 var parts = flgNet.Element(FlgNetNs + "Parts");
588 var wires = flgNet.Element(FlgNetNs + "Wires");
589 if (parts == null || wires == null)
590 return;
591
592 // ONE Powerrail Wire for the whole network; all rung-entry Contact "in" pins attach here.
593 var powerrailWire = new XElement(FlgNetNs + "Wire",
594 new XAttribute("UId", nextUId++),
595 new XElement(FlgNetNs + "Powerrail"));
596
597 // Step 1 – expand AND Parts into series Contact chains
598 ExpandAndParts(parts, wires, powerrailWire, ref nextUId);
599
600 // Step 2 – convert remaining direct IdentCon→NameCon(input) wires to single Contacts
601 ConvertDirectInputWires(parts, wires, powerrailWire, ref nextUId);
602
603 if (powerrailWire.Elements(FlgNetNs + "NameCon").Any())
604 wires.AddFirst(powerrailWire);
605
606 // Step 3 – reorder Parts and Wires in strict left-to-right signal-flow order
607 SortFlgNetInFlowOrder(flgNet);
608 }
609
610 // Returns the integer value of an element's UId attribute (0 if absent/non-numeric).
611 private static int GetUId(XElement e) =>
612 int.TryParse(e.Attribute("UId")?.Value, out int v) ? v : 0;
613
619 private static void ExpandAndParts(XElement parts, XElement wires,
620 XElement powerrailWire, ref int nextUId)
621 {
622 var andParts = parts.Elements(FlgNetNs + "Part")
623 .Where(p => p.Attribute("Name")?.Value == "A")
624 .ToList();
625
626 foreach (var andPart in andParts)
627 {
628 int andUId = GetUId(andPart);
629
630 var negatedPins = new System.Collections.Generic.HashSet<string>(
631 andPart.Elements(FlgNetNs + "Negated")
632 .Select(n => n.Attribute("Name")?.Value)
633 .Where(n => n != null));
634
635 // Input wires: IdentCon(var) → NameCon(inN, andUId) – sorted by index
636 var inputWires = wires.Elements(FlgNetNs + "Wire")
637 .Where(w =>
638 {
639 var ch = w.Elements().ToList();
640 return ch.Count == 2
641 && ch[0].Name.LocalName == "IdentCon"
642 && ch[1].Name.LocalName == "NameCon"
643 && GetUId(ch[1]) == andUId;
644 })
645 .OrderBy(w =>
646 {
647 var name = w.Elements(FlgNetNs + "NameCon").First()
648 .Attribute("Name")?.Value ?? "";
649 return int.TryParse(name.Replace("in", ""), out int idx) ? idx : 0;
650 })
651 .ToList();
652
653 // Output wire: NameCon(out, andUId) → NameCon(downstreamPin, downstreamUId)
654 var outputWire = wires.Elements(FlgNetNs + "Wire")
655 .FirstOrDefault(w =>
656 {
657 var ch = w.Elements().ToList();
658 return ch.Count == 2
659 && ch[0].Name.LocalName == "NameCon"
660 && ch[0].Attribute("Name")?.Value == "out"
661 && GetUId(ch[0]) == andUId
662 && ch[1].Name.LocalName == "NameCon";
663 });
664
665 if (inputWires.Count == 0 || outputWire == null)
666 {
667 andPart.Remove();
668 outputWire?.Remove();
669 continue;
670 }
671
672 var downstreamCon = outputWire.Elements(FlgNetNs + "NameCon").Last();
673
674 int prevContactUId = -1;
675 foreach (var inWire in inputWires)
676 {
677 var identCon = inWire.Elements(FlgNetNs + "IdentCon").First();
678 string pinName = inWire.Elements(FlgNetNs + "NameCon").First()
679 .Attribute("Name")?.Value ?? "";
680 bool isNc = negatedPins.Contains(pinName);
681
682 int cUId = nextUId++;
683 var contactPart = new XElement(FlgNetNs + "Part",
684 new XAttribute("UId", cUId),
685 new XAttribute("Name", "Contact"));
686 if (isNc)
687 contactPart.Add(new XElement(FlgNetNs + "Negated",
688 new XAttribute("Name", "operand")));
689 parts.Add(contactPart);
690
691 // Variable → Contact.operand
692 wires.Add(new XElement(FlgNetNs + "Wire",
693 new XAttribute("UId", nextUId++),
694 new XElement(FlgNetNs + "IdentCon",
695 new XAttribute("UId", identCon.Attribute("UId").Value)),
696 new XElement(FlgNetNs + "NameCon",
697 new XAttribute("Name", "operand"),
698 new XAttribute("UId", cUId))));
699
700 if (prevContactUId == -1)
701 {
702 // First in chain: driven from powerrail
703 powerrailWire.Add(new XElement(FlgNetNs + "NameCon",
704 new XAttribute("Name", "in"),
705 new XAttribute("UId", cUId)));
706 }
707 else
708 {
709 // Chain link: prevContact.out → thisContact.in
710 wires.Add(new XElement(FlgNetNs + "Wire",
711 new XAttribute("UId", nextUId++),
712 new XElement(FlgNetNs + "NameCon",
713 new XAttribute("Name", "out"),
714 new XAttribute("UId", prevContactUId)),
715 new XElement(FlgNetNs + "NameCon",
716 new XAttribute("Name", "in"),
717 new XAttribute("UId", cUId))));
718 }
719 prevContactUId = cUId;
720 inWire.Remove();
721 }
722
723 // Last Contact.out → whatever AND.out was connected to
724 wires.Add(new XElement(FlgNetNs + "Wire",
725 new XAttribute("UId", nextUId++),
726 new XElement(FlgNetNs + "NameCon",
727 new XAttribute("Name", "out"),
728 new XAttribute("UId", prevContactUId)),
729 new XElement(FlgNetNs + "NameCon",
730 new XAttribute("Name", downstreamCon.Attribute("Name")?.Value),
731 new XAttribute("UId", downstreamCon.Attribute("UId")?.Value))));
732
733 andPart.Remove();
734 outputWire.Remove();
735 }
736 }
737
747 private static void SortFlgNetInFlowOrder(XElement flgNet)
748 {
749 var partsEl = flgNet.Element(FlgNetNs + "Parts");
750 var wiresEl = flgNet.Element(FlgNetNs + "Wires");
751 if (partsEl == null || wiresEl == null) return;
752
753 // Split Parts into Access/IdentCon nodes (variables) and instruction Part nodes.
754 var accessNodes = partsEl.Elements()
755 .Where(e => e.Name.LocalName == "Access")
756 .ToList();
757 var instrNodes = partsEl.Elements()
758 .Where(e => e.Name.LocalName == "Part")
759 .ToList();
760
761 if (instrNodes.Count > 0)
762 {
763 var instrUIds = new System.Collections.Generic.HashSet<int>(instrNodes.Select(GetUId));
764
765 // Find the Powerrail wire and extract the order of Parts it connects to
766 var powerrailOrder = new System.Collections.Generic.List<int>();
767 var powerrailWire = wiresEl.Elements(FlgNetNs + "Wire")
768 .FirstOrDefault(w => w.Elements(FlgNetNs + "Powerrail").Any());
769 if (powerrailWire != null)
770 {
771 foreach (var nameCon in powerrailWire.Elements(FlgNetNs + "NameCon"))
772 {
773 int uid = GetUId(nameCon);
774 if (instrUIds.Contains(uid))
775 powerrailOrder.Add(uid);
776 }
777 }
778
779 // Build connectivity maps
780 var outgoing = new System.Collections.Generic.Dictionary<int, System.Collections.Generic.List<int>>();
781 var incoming = new System.Collections.Generic.Dictionary<int, System.Collections.Generic.List<int>>();
782
783 foreach (var uid in instrUIds)
784 {
785 outgoing[uid] = new System.Collections.Generic.List<int>();
786 incoming[uid] = new System.Collections.Generic.List<int>();
787 }
788
789 // Analyze wires to build connectivity
790 foreach (var wire in wiresEl.Elements(FlgNetNs + "Wire"))
791 {
792 var ch = wire.Elements().ToList();
793 if (ch.Count < 2) continue;
794
795 // Skip Powerrail wires for connectivity analysis
796 if (ch[0].Name.LocalName == "Powerrail")
797 continue;
798
799 // Find Part output
800 int sourceUId = -1;
801 if (ch[0].Name.LocalName == "NameCon")
802 {
803 var pinName = ch[0].Attribute("Name")?.Value;
804 if (pinName == "out" || pinName == "q")
805 {
806 int uid = GetUId(ch[0]);
807 if (instrUIds.Contains(uid))
808 sourceUId = uid;
809 }
810 }
811
812 // Find Part inputs
813 foreach (var target in ch.Skip(1).Where(c => c.Name.LocalName == "NameCon"))
814 {
815 var pinName = target.Attribute("Name")?.Value ?? "";
816 if (pinName == "out" || pinName == "q" || pinName == "operand")
817 continue;
818
819 int dstUId = GetUId(target);
820 if (instrUIds.Contains(dstUId))
821 {
822 if (sourceUId > 0 && sourceUId != dstUId)
823 {
824 outgoing[sourceUId].Add(dstUId);
825 incoming[dstUId].Add(sourceUId);
826 }
827 }
828 }
829 }
830
831 // Build sorted list: Powerrail-connected Parts first (in their Powerrail order),
832 // then topologically sort remaining Parts
833 var sorted = new System.Collections.Generic.List<int>();
834 var visited = new System.Collections.Generic.HashSet<int>();
835
836 // Add Powerrail-connected Parts in their exact Powerrail order
837 foreach (var uid in powerrailOrder)
838 {
839 sorted.Add(uid);
840 visited.Add(uid);
841 }
842
843 // For each Powerrail-connected Part, add all its downstream dependencies
844 // in topological order
845 foreach (var rootUId in powerrailOrder)
846 {
847 var toVisit = new System.Collections.Generic.Queue<int>();
848 toVisit.Enqueue(rootUId);
849
850 while (toVisit.Count > 0)
851 {
852 int uid = toVisit.Dequeue();
853 foreach (var nextUId in outgoing[uid])
854 {
855 if (!visited.Contains(nextUId))
856 {
857 // Check if all dependencies are visited before adding
858 bool allDepsVisited = incoming[nextUId].All(dep => visited.Contains(dep));
859 if (allDepsVisited)
860 {
861 sorted.Add(nextUId);
862 visited.Add(nextUId);
863 toVisit.Enqueue(nextUId);
864 }
865 }
866 }
867 }
868 }
869
870 // Add any remaining Parts (shouldn't happen, but safeguard)
871 foreach (var uid in instrUIds.Where(u => !visited.Contains(u)))
872 sorted.Add(uid);
873
874 var uidToNode = instrNodes.ToDictionary(GetUId);
875 partsEl.RemoveAll();
876 foreach (var n in accessNodes) partsEl.Add(n);
877 foreach (var uid in sorted)
878 if (uidToNode.ContainsKey(uid)) partsEl.Add(uidToNode[uid]);
879 }
880
881 // Sort Wires: Powerrail first; then other wires in original order
882 var allWires = wiresEl.Elements(FlgNetNs + "Wire").ToList();
883 var powerrailWires = allWires.Where(w => w.Elements(FlgNetNs + "Powerrail").Any()).ToList();
884 var otherWires = allWires.Except(powerrailWires).ToList();
885
886 wiresEl.RemoveAll();
887 foreach (var w in powerrailWires) wiresEl.Add(w);
888 foreach (var w in otherWires) wiresEl.Add(w);
889 }
890
895 private static void ConvertDirectInputWires(XElement parts, XElement wires,
896 XElement powerrailWire, ref int nextUId)
897 {
898 var inputWires = wires.Elements(FlgNetNs + "Wire")
899 .Where(w =>
900 {
901 var ch = w.Elements().ToList();
902 return ch.Count == 2
903 && ch[0].Name.LocalName == "IdentCon"
904 && ch[1].Name.LocalName == "NameCon"
905 && ch[1].Attribute("Name")?.Value != "operand"; // skip wires already created by ExpandAndParts
906 })
907 .ToList();
908
909 foreach (var wire in inputWires)
910 {
911 var identCon = wire.Elements(FlgNetNs + "IdentCon").First();
912 var nameCon = wire.Elements(FlgNetNs + "NameCon").First();
913
914 int cUId = nextUId++;
915 parts.Add(new XElement(FlgNetNs + "Part",
916 new XAttribute("UId", cUId),
917 new XAttribute("Name", "Contact")));
918
919 powerrailWire.Add(new XElement(FlgNetNs + "NameCon",
920 new XAttribute("Name", "in"),
921 new XAttribute("UId", cUId)));
922
923 wires.Add(new XElement(FlgNetNs + "Wire",
924 new XAttribute("UId", nextUId++),
925 new XElement(FlgNetNs + "IdentCon",
926 new XAttribute("UId", identCon.Attribute("UId").Value)),
927 new XElement(FlgNetNs + "NameCon",
928 new XAttribute("Name", "operand"),
929 new XAttribute("UId", cUId))));
930
931 wires.Add(new XElement(FlgNetNs + "Wire",
932 new XAttribute("UId", nextUId++),
933 new XElement(FlgNetNs + "NameCon",
934 new XAttribute("Name", "out"),
935 new XAttribute("UId", cUId)),
936 new XElement(FlgNetNs + "NameCon",
937 new XAttribute("Name", nameCon.Attribute("Name")?.Value),
938 new XAttribute("UId", nameCon.Attribute("UId")?.Value))));
939
940 wire.Remove();
941 }
942 }
943 }
944}
Reusable helper methods for building bit-logic AND and RS networks using the Module Essentials XmlNet...
static void ConvertDirectInputWires(XElement parts, XElement wires, XElement powerrailWire, ref int nextUId)
Converts remaining direct IdentCon→NameCon(input) wires (e.g. variable driving RS....
static void ImportBlockAsLad(XmlFB block, PlcDevice plcDevice)
Generates the block XML using MacPLang.FBD (the only mode in which XmlNetwork produces valid graph XM...
static void GenerateFbWithANDNetworkFBD(string blockName, PlcDevice plcDevice)
Generates an FB in FBD language with two networks and imports it into plcDevice :
static void GenerateFbWithRSNetworkLAD(string blockName, PlcDevice plcDevice)
Generates a Function Block (FB) containing two chained RS flip-flop networks in LAD language and impo...
static void ConvertFlgNetToLad(XElement flgNet, ref int nextUId)
Rewrites the wiring inside a single FlgNet element from FBD topology to LAD topology: AND ("A") Parts...
static SystemBlockCall CreateRSCall(XmlNetwork parent)
Adds an RS flip-flop system block call to the given network and returns it. Pins: Q (output),...
static SystemBlockCall CreateANDCall(XmlNetwork parent, int cardinality)
Adds an AND ("A") system block call to the given network.
static void SortFlgNetInFlowOrder(XElement flgNet)
Re-orders <Parts> and <Wires> inside a single FlgNet element so that both collections are listed in s...
static void GenerateFbWithRSNetworkFBD(string blockName, PlcDevice plcDevice, MacPLang macProgrammingLanguage)
Generates a Function Block (FB) containing two chained RS flip-flop networks in FBD language and a th...
static void ExpandAndParts(XElement parts, XElement wires, XElement powerrailWire, ref int nextUId)
Expands every AND ("A") Part into a series Contact chain: powerrail → C1 → C2 → … → Ck → (what AND....
static XmlNetwork CreateRSNetwork(string rInputName, string s1InputName, string rsOperandVariableName, MEPlang opnsProgrammingLanguage)
Creates an RS flip-flop network for use in an FBD block. The RS Q output is routed through a Coil ele...
static XmlNetwork CreateSingleAndRsCircuitNetwork(string andInput1, string andInput2, string rInputName, string operandVariableName)
Creates one AND→RS circuit in a single network (FBD graph, usable for LAD import)....
static XmlNetwork CreateNetworkWith2Circuits(System.Collections.Generic.List< string > inputVariableNames, string rInputName, string operandVariableName)
Creates a single network that contains two independent parallel AND→RS circuits side by side (FBD onl...
static XmlNetwork CreateANDCallNetwork(System.Collections.Generic.List< string > inputVariableNames, string outputVariableName, MEPlang opnsProgrammingLanguage)
Creates a network that ANDs all inputVariableNames together and writes the result to outputVariableN...
static void GenerateFbWithANDNetworkLAD(string blockName, PlcDevice plcDevice)
Generates an FB in LAD language with two networks and imports it into plcDevice : Network 1: AND of t...
static SystemBlockCall CreateAssignmentCall(XmlNetwork parent)
Adds a Coil (assignment) system block call to the given network and returns it. Used in FBD RS networ...
static XmlNetwork CreateRSNetworkLAD(string rInputName, string s1InputName, string rsOperandVariableName)
Creates a single RS network intended for use inside a LAD-language FB.