-
Notifications
You must be signed in to change notification settings - Fork 11
Expand file tree
/
Copy pathExtendedEtsSupport.cs
More file actions
446 lines (422 loc) · 24.5 KB
/
ExtendedEtsSupport.cs
File metadata and controls
446 lines (422 loc) · 24.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
using System.Collections;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics;
using System.Globalization;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Xml;
using OpenKNXproducer;
static class ExtendedEtsSupport
{
static readonly Dictionary<DefineContent, Dictionary<string, string>> sParameterInfo = new();
public static readonly StringBuilder GeneratedHeaderAddon = new();
public static string ParameterInfo
{
get
{
string lResult = "\nvar uctChannelParams = {";
foreach (var lEntry in sParameterInfo)
{
Dictionary<string, string> lModuleParams = lEntry.Value;
lResult += $"\n\"{lEntry.Key.prefix}\": {{";
foreach (var lModuleParam in lModuleParams)
{
lResult += $"\n {lModuleParam.Value},";
}
lResult = lResult[..^1] + "},";
}
lResult = lResult[..^1] + "\n};";
return lResult;
}
}
static string mScriptModuleIdPrefix = "\nvar baseModuleIdPrefix = [";
static bool mModulesListGenerated = false;
public static bool ModulesListGenerated { get { return mModulesListGenerated; } }
// generate all parameters in common before heder file generation starts
public static void GenerateModuleListPreprocess(ProcessInclude iInclude, XmlDocument iDocument)
{
int moduleCount = -1;
// each module needs a parameter, we search for the insertion point
XmlNode lParameterInsert = iInclude.SelectSingleNode("//ApplicationProgram/Static/Parameters//Parameter[contains(@Id,'%ProducerModuleId%')]");
if (lParameterInsert != null)
{
// at this point we are in the parameter include of Common, we can generate addisional
Console.Write("Preprocess module list... ");
moduleCount = 0;
Dictionary<int, string> lUsedModuleTypes = [];
foreach (DefineContent lDefine in DefineContent.Defines().Values)
{
if (lDefine.prefix != "LOG")
{
if (lUsedModuleTypes.TryGetValue(lDefine.ModuleType, out string lUsedPrefix))
{
if (lDefine.prefixSubmodule == lDefine.prefix)
{
lUsedModuleTypes.Remove(lDefine.ModuleType);
}
// it is ok, if both belong to the same module, check in both directions
else if (lUsedPrefix != lDefine.prefixSubmodule)
{
Program.Message("3.13.0", "ModuleType has to be unique, you use the same Module type in {0} and {1}", lDefine.prefix, lUsedPrefix);
continue; // module types cannot be reused
} else
continue; // module types can be reused for submodules
}
lUsedModuleTypes.Add(lDefine.ModuleType, lDefine.prefix);
}
else
{
var lEntry = lUsedModuleTypes.FirstOrDefault(x => x.Value == "BASE");
if (lEntry.Key != lDefine.ModuleType)
{
Program.Message("3.13.0", "ModuleType for LOG ({0}) ist not the same as BASE ({1})", lDefine.ModuleType, lEntry.Key);
}
}
if (lDefine.NoConfigTransfer && lDefine.prefix != "UCT")
continue; // skip old modules
if (lDefine.prefixSubmodule != lDefine.prefix)
continue; // skip modules which are only submodules of another module, they will be handled together with their main module
moduleCount++;
GeneratedHeaderAddon.AppendLine($"#define ETS_ModuleId_{lDefine.prefix} {moduleCount}");
// add script extension
mScriptModuleIdPrefix += $"\"{lDefine.prefix}\",";
if (lDefine.prefix == "BASE")
continue; // skip Common
// parameter id
string lId = lParameterInsert.NodeAttr("Id").Replace("%ProducerModuleId%", lDefine.ModuleType.ToString("D2"));
// we create a new parameter for each channel
XmlNode lParameter = lParameterInsert.CloneNode(true);
lParameter.Attributes["Id"].Value = lId;
XmlAttribute lAttr = lParameter.Attributes["Offset"];
if (lAttr != null) lAttr.Value = ((byte)((moduleCount - 1) / 8)).ToString();
lAttr = lParameter.Attributes["BitOffset"];
if (lAttr != null) lAttr.Value = ((byte)((moduleCount - 1) % 8)).ToString();
lParameter.Attributes["Name"].Value = lParameter.Attributes["Name"].Value.Replace("%ProducerModuleName%", "ModuleEnabled_" + lDefine.prefix);
lParameter.Attributes["Text"].Value = lParameter.Attributes["Text"].Value.Replace("%ProducerModuleText%", lDefine.prefix);
lParameterInsert.ParentNode.AppendChild(lParameter);
}
lParameterInsert.ParentNode.RemoveChild(lParameterInsert);
// lParameterInsert.Attributes["Id"].Value = lParameterInsert.Attributes["Id"].Value.Replace("%ProducerModuleId%", "%OpenKNXProducerModuleId%");
if (moduleCount <= 0)
Console.WriteLine("OK (skipped, Common too old)");
else
{
Console.WriteLine("OK");
GeneratedHeaderAddon.Insert(0, $"#define ETS_ModuleId_NONE 0\n");
mScriptModuleIdPrefix = mScriptModuleIdPrefix[..^1] + "];\n";
mModulesListGenerated = true;
}
}
}
// finish generation of module list after all modules are processed
private static void GenerateModuleListPostprocess(ProcessInclude iInclude)
{
// we generate a list of modules, which is used in ETS to enable/disable module visibility
Console.Write("Generating module list... ");
int moduleCount = -1;
// each module needs a parameter, we search for the insertion point
XmlNode lParameterInsert = iInclude.SelectSingleNode("//ApplicationProgram/Static/Parameters//Parameter[contains(@Name, 'BASE_ModuleEnabled_')]");
if (lParameterInsert != null)
{
// extract Id-Prefix
string lIdPrefix = lParameterInsert.NodeAttr("Id")[..^2];
XmlNode lParameterParent = lParameterInsert.ParentNode;
// each parameter needs a parameterRef, we search for the insertion point
XmlNode lParameterRefInsert = iInclude.SelectSingleNode($"//ApplicationProgram/Static/ParameterRefs/ParameterRef[@RefId='{lIdPrefix}%ProducerModuleId%']");
// find parameter block for module selection
XmlNode lParameterBlockReplace = iInclude.SelectSingleNode("//ParameterBlock[@Name='BASE_OpenKNXproducerModules']");
if (lParameterRefInsert != null && lParameterBlockReplace != null)
{
// find the row which has to be repeated
XmlNode lRowInsert = lParameterBlockReplace.SelectSingleNode(@"//Row[starts-with(@Name,'BASE_Row%ModuleCount')]");
// all Parameters will be checkboxes within a ParameterBlock, we search for the insertion point
XmlNode lParameterRefRefInsert = lParameterBlockReplace.SelectSingleNode($"//ParameterRefRef[@RefId='{lIdPrefix}%ProducerModuleRefId%']");
// Replacement line for module names
XmlNode lParameterSeparatorNameInsert = lParameterBlockReplace.SelectSingleNode("//ParameterSeparator[@Text='%ProducerModuleName%']");
// Replacement line for module versions
XmlNode lParameterSeparatorVersionInsert = lParameterBlockReplace.SelectSingleNode("//ParameterSeparator[@Text='%ProducerModuleVersion%']");
// finally, we search all Channels, which are used to generate the module list
XmlNodeList lChannels = iInclude.SelectNodes("//ApplicationProgram/Dynamic/Channel");
moduleCount = 0;
// here we also reuse part parameter parsing code
ParamEntry lModuleParam = new("%ModuleCount%");
foreach (XmlNode lChannel in lChannels)
{
string lModulePrefix = lChannel.NodeAttr("Number");
if (lModulePrefix == "BASE") continue; // skip Common
moduleCount++;
// Verify that module prefix exists
DefineContent lDefine = DefineContent.GetDefineContent(lModulePrefix);
if (lDefine.header == null) continue; // skip unknown modules
// get the right parameter for this module
XmlNode lParameter = lParameterParent.SelectSingleNode($"Parameter[contains(@Name, '{lModulePrefix}')]");
if (lParameter == null) continue;
lModuleParam.Next();
string lId = lParameter.NodeAttr("Id");
lParameter.Attributes["Text"].Value = lParameter.Attributes["Text"].Value.Replace(lModulePrefix, lChannel.NodeAttr("Text"));
// we create a new parameterRef for each channel
XmlNode lParameterRef = lParameterRefInsert.CloneNode(true);
string lRefId = lId + "_R-" + lId.Split('-')[1] + "01";
lParameterRef.Attributes["Id"].Value = lRefId;
lParameterRef.Attributes["RefId"].Value = lId;
lParameterRefInsert.ParentNode.InsertBefore(lParameterRef, lParameterRefInsert);
// we add a new row in the table
XmlNode lRow = lRowInsert.CloneNode(true);
ProcessPart.ReplaceAttribute(lRow.Attributes["Id"], lModuleParam);
ProcessPart.ReplaceAttribute(lRow.Attributes["Name"], lModuleParam);
lRowInsert.ParentNode.InsertBefore(lRow, lRowInsert);
// and the correct entry as a ParameterRefRef
XmlNode lParameterRefRef = lParameterRefRefInsert.CloneNode(true);
lParameterRefRef.Attributes["RefId"].Value = lRefId;
ProcessPart.ReplaceAttribute(lParameterRefRef.Attributes["Cell"], lModuleParam);
lParameterRefRefInsert.ParentNode.InsertBefore(lParameterRefRef, lParameterRefRefInsert);
// followed by the according module text
XmlNode lParameterSeparatorName = lParameterSeparatorNameInsert.CloneNode(true);
lParameterSeparatorName.Attributes["Text"].Value = lParameterSeparatorName.Attributes["Text"].Value.Replace("%ProducerModuleName%", lChannel.NodeAttr("Text"));
ProcessPart.ReplaceAttribute(lParameterSeparatorName.Attributes["Cell"], lModuleParam);
lParameterSeparatorNameInsert.ParentNode.InsertBefore(lParameterSeparatorName, lParameterSeparatorNameInsert);
// and the module version
string lModuleVersion = "(nicht gefunden)";
if (lDefine.header != null)
lModuleVersion = lDefine.VerifyVersionString;
XmlNode lParameterSeparatorVersion = lParameterSeparatorVersionInsert.CloneNode(true);
lParameterSeparatorVersion.Attributes["Text"].Value = lParameterSeparatorVersion.Attributes["Text"].Value.Replace("%ProducerModuleVersion%", lModuleVersion);
ProcessPart.ReplaceAttribute(lParameterSeparatorVersion.Attributes["Cell"], lModuleParam);
lParameterSeparatorVersionInsert.ParentNode.InsertBefore(lParameterSeparatorVersion, lParameterSeparatorVersionInsert);
// around each channel, we have to genarate a choose
XmlNode lChoose = iInclude.CreateElement("choose", "ParamRefId", lRefId);
XmlNode lWhen = iInclude.CreateElement("when", "test", "1");
lChoose.AppendChild(lWhen);
lChannel.ParentNode.ReplaceChild(lChoose, lChannel);
lWhen.AppendChild(lChannel);
}
// we delete all template nodes
lParameterRefInsert.ParentNode.RemoveChild(lParameterRefInsert);
lParameterRefRefInsert.ParentNode.RemoveChild(lParameterRefRefInsert);
lParameterSeparatorNameInsert.ParentNode.RemoveChild(lParameterSeparatorNameInsert);
lParameterSeparatorVersionInsert.ParentNode.RemoveChild(lParameterSeparatorVersionInsert);
lRowInsert.ParentNode.RemoveChild(lRowInsert);
}
}
if (moduleCount <= 0)
Console.WriteLine("OK (skipped, Common too old)");
else
{
Console.WriteLine("OK");
XmlNode lNode = iInclude.SelectSingleNode("//ApplicationProgram/Static/Script");
if (lNode != null)
lNode.InnerText = mScriptModuleIdPrefix + lNode.InnerText;
}
}
private static bool GenerateModuleSelector(ProcessInclude iInclude, int iApplicationVersion, int iApplicationNumber)
{
Console.Write("Generating ConfigTransfer extensions... ");
XmlNode lModuleSelector = iInclude.CreateElement("ParameterType", "Id", "%AID%_PT-ModuleSelector", "Name", "ModuleSelector");
XmlNode lTypeRestriction = iInclude.CreateElement("TypeRestriction", "Base", "Value", "SizeInBit", "8", "UIHint", "DropDown");
lTypeRestriction.AppendChild(iInclude.CreateElement("Enumeration", "Text", "Bitte wählen...", "Value", "255", "Id", "%ENID%"));
XmlNode lModuleSelectorCopy = iInclude.CreateElement("ParameterType", "Id", "%AID%_PT-ModuleSelectorWithChannels", "Name", "ModuleSelectorWithChannels");
lModuleSelector.AppendChild(lTypeRestriction);
XmlNode lTypeRestrictionCopy = iInclude.CreateElement("TypeRestriction", "Base", "Value", "SizeInBit", "8", "UIHint", "DropDown");
lTypeRestrictionCopy.AppendChild(iInclude.CreateElement("Enumeration", "Text", "Bitte wählen...", "Value", "255", "Id", "%ENID%"));
lModuleSelectorCopy.AppendChild(lTypeRestrictionCopy);
int lCount = 0;
string lVersionInformation = $"\nvar uctVersionInformation = [0x{iApplicationNumber:X}, 0x{iApplicationVersion:X}];";
string lModuleOrder = "\nvar uctModuleOrder = [";
XmlNodeList lChannels = iInclude.SelectNodes("//ApplicationProgram/Dynamic/Channel");
IEnumerator lIterator = lChannels.GetEnumerator();
lIterator.Reset();
var lModuleSelectorCopyCounter = 0;
var lModuleSelectorCopyValue = 255;
foreach (var lEntry in sParameterInfo)
{
string lText = lEntry.Key.prefix;
if (lEntry.Key.ConfigTransferName != "")
lText = lEntry.Key.ConfigTransferName;
// if (lText == "BASE")
// lText = "Allgemein";
// else
for (int lIndex = 0; lIndex < lChannels.Count; lIndex++)
{
if (!lIterator.MoveNext())
{
lIterator.Reset();
lIterator.MoveNext();
}
XmlNode lChannel = (XmlNode)lIterator.Current;
if (lChannel.NodeAttr("Name").Contains(lText))
{
lText = lChannel.NodeAttr("Text");
break;
}
}
if (lEntry.Key.NumChannels > 0)
{
lTypeRestrictionCopy.AppendChild(iInclude.CreateElement("Enumeration", "Text", lText, "Value", lCount.ToString(), "Id", "%ENID%"));
lModuleSelectorCopyCounter++;
lModuleSelectorCopyValue = lCount;
}
lTypeRestriction.AppendChild(iInclude.CreateElement("Enumeration", "Text", lText, "Value", lCount++.ToString(), "Id", "%ENID%"));
lModuleOrder += "\"" + lEntry.Key.prefix + "\",";
}
// special handling if there is just on entry in ModuleSelectorCopy dropdown
if (lModuleSelectorCopyCounter == 1)
{
// we remove the "please choose" entry from dropdown
lTypeRestrictionCopy.RemoveChild(lTypeRestrictionCopy.FirstChild);
// and we replace the default values of according parameters to the new entry
XmlNodeList lParameters = iInclude.SelectNodes("//ApplicationProgram/Static/Parameters//Parameter[@ParameterType='%AID%_PT-ModuleSelectorWithChannels']");
foreach (XmlNode lParameter in lParameters)
{
if (lParameter.NodeAttr("Value") == "255")
{
lParameter.Attributes["Value"].Value = lModuleSelectorCopyValue.ToString();
}
}
}
lModuleOrder = lModuleOrder[..^1] + "];\n";
XmlNode lNode = iInclude.SelectSingleNode("//ApplicationProgram/Static/ParameterTypes");
lNode?.InsertAfter(lModuleSelector, null);
lNode?.InsertAfter(lModuleSelectorCopy, lModuleSelector);
if (lNode != null)
{
lNode = iInclude.SelectSingleNode("//ApplicationProgram/Static/Script");
if (lNode != null)
lNode.InnerText = lVersionInformation + lModuleOrder + lNode.InnerText;
}
if (lNode == null)
Console.WriteLine("Not possible, ConfigTrasfer not supported");
else
Console.WriteLine("OK");
return lNode != null;
}
public static bool AddEtsExtensions(ProcessInclude iInclude, int iApplicationVersion, int iApplicationNumber)
{
if (!DefineContent.WithConfigTransfer)
return false;
XmlNode lScript = iInclude.SelectSingleNode("//ApplicationProgram/Static/Script");
if (lScript == null)
return false;
lScript.InnerText = ParameterInfo + "\n\n" + lScript.InnerText;
// return true;
GenerateModuleSelector(iInclude, iApplicationVersion, iApplicationNumber);
GenerateModuleListPostprocess(iInclude);
return true;
}
/// <summary>
/// Generate an array of field names for ETS extensions like channel copy
/// </summary>
public static void GenerateScriptContent(ProcessInclude iInclude, DefineContent iDefine)
{
if (iDefine.NoConfigTransfer)
return;
if (string.IsNullOrEmpty(iDefine.template) && string.IsNullOrEmpty(iDefine.share))
return; // pre-v1
Dictionary<string, string> lDict;
XmlNodeList lParameters = iInclude.SelectNodes("//ApplicationProgram/Static/Parameters//Parameter");
string lSuffix = "share";
string lDelimiter = "";
StringBuilder lParameterNames = new();
StringBuilder lParameterDefaults = new();
if (sParameterInfo.ContainsKey(iDefine))
lDict = sParameterInfo[iDefine];
else
{
lDict = new();
sParameterInfo.Add(iDefine, lDict);
// add version information, if available
if (iDefine.NumChannels > 0)
lDict.Add("channels", $"\"channels\": {iDefine.NumChannels}");
if (iDefine.VerifyVersion > 0)
lDict.Add("version", $"\"version\": 0x{iDefine.VerifyVersion:X}");
}
if (iDefine.template.EndsWith(Path.GetFileName(iInclude.XmlFileName)))
lSuffix = "templ";
lParameterNames.Append('[');
lParameterDefaults.Append('[');
foreach (XmlNode lNode in lParameters)
{
string lAccess = lNode.NodeAttr("Access");
// we evaluate config transfer specific attributes
string lConfigTransfer = lNode.NodeAttr("op:configTransfer");
if (lConfigTransfer != "")
lNode.Attributes.RemoveNamedItem("op:configTransfer");
// following cases transfer/suppress parameters
// 1. implicit suppress for parameters with access None or Read
// 2. explicit suppress for parameters with configTransfer="never"
// 3. explicit transfer for parameters with configTransfer="transfer" (overrides access None or Read)
// 4. explicit transfer for parameters with configTransfer="always" (even if the parameter has default value)
if (lConfigTransfer == "transfer" || lConfigTransfer == "always" || (lAccess != "None" && lAccess != "Read" && lConfigTransfer != "never"))
{
XmlNode lType = ProcessInclude.ParameterType(lNode.NodeAttr("ParameterType"), false);
string lTypeName = "";
if (lType != null) lTypeName = lType.Name;
// determine name
XmlNodeList lParameterRefs = iInclude.SelectNodes($"//ApplicationProgram/Static/ParameterRefs/ParameterRef[@RefId='{lNode.NodeAttr("Id")}']");
foreach (XmlNode lParameterRef in lParameterRefs)
{
string lName = lNode.NodeAttr("Name");
if (lName.Contains("%CC%") || lName.Contains("%CCC%"))
{
Program.Message(true, "Name {0} has wrong format, just %C% allowed", lName);
lName = "";
}
if (lName.Contains('~'))
{
Program.Message(true, "Name {0} contains not allowed character '~'", lName);
lName = "";
}
if (lName.Contains("%C%") && !iDefine.IsTemplate)
{
Program.Message("3.13.6", "Name {0} with %C% not allowed in share", lName);
lName = "";
}
lName = lName.Replace("%C%", "~");
string lDefault = lNode.NodeAttr("Value");
lDefault = lParameterRef.NodeAttr("Value", lDefault); // ParameterRef default has priority
_ = int.TryParse(lParameterRef.NodeAttr("Id")[^2..], out int lRefSuffix);
if (lParameterRefs.Count > 1 || (lParameterRefs.Count == 1 && lRefSuffix != 1))
lName = $"{lName}:{lRefSuffix}";
// determine default
if ((lDefault.StartsWith('%') && lDefault.EndsWith('%')) || lConfigTransfer == "always")
lDefault = null;
else if ("TypeNumber,TypeRestriction".Contains(lTypeName))
{
_ = Int64.TryParse(lDefault, out long lDefaultInt);
lDefault = lDefaultInt.ToString();
}
else if (lTypeName == "TypeFloat")
{
_ = double.TryParse(lDefault, NumberStyles.Float, CultureInfo.InvariantCulture, out double lDefaultFloat);
lDefault = lDefaultFloat.ToString(CultureInfo.InvariantCulture);
}
else if (lTypeName == "TypeColor")
{
if (lDefault.Length == 7 && lDefault.StartsWith('#')) lDefault = lDefault[1..];
if (lDefault.Length == 6) lDefault = "FF" + lDefault;
_ = int.TryParse(lDefault, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out int lDefaultHex);
lDefault = lDefaultHex.ToString();
}
else if (lTypeName == "TypePicture")
lDefault = "";
else
lDefault = $"\"{lDefault}\"";
if (lName != "" && lDefault != "")
{
lParameterNames.Append($"{lDelimiter}\"{lName}\"");
lParameterDefaults.Append($"{lDelimiter}{lDefault}");
lDelimiter = ",";
}
}
}
}
lParameterNames.Append(']');
lParameterDefaults.Append(']');
string lOutput = $"\"{lSuffix}\": {{\n \"names\": {lParameterNames.ToString()},\n \"defaults\": {lParameterDefaults.ToString()}\n }}";
if (lDict.ContainsKey(lSuffix))
Program.Message(true, "Duplicate suffix {0} for module {1}, usually this means you included module {1} twice (copy/paste without changing prefix?)", lSuffix, iDefine.prefix);
else
lDict.Add(lSuffix, lOutput);
// Console.WriteLine("{2}: {0}, {1} Bytes", lOutput, lOutput.Length, iDefine.prefix);
}
}