public void StartPropertyUpdateLoop() { testThing = new TheThing() { FriendlyName = "MyTestSensor" }; TheThingRegistry.RegisterThing(testThing); Random rand = new Random(); // Used for generating "fake" sensor values cdeP[] props = new cdeP[propertyCount]; // Run the loop on another thread TheCommonUtils.cdeRunAsync("PropertyUpdateLoop", true, (o) => { while (TheBaseAssets.MasterSwitch) { // Set the properties and log props[0] = testThing.SetProperty(properties[0], rand.Next(25, 51)); props[1] = testThing.SetProperty(properties[1], rand.NextDouble()); props[2] = testThing.SetProperty(properties[2], rand.Next(10000, 50000)); LogChanges(false, props); // Wait timeout of 1 second between property updates TheCommonUtils.SleepOneEye(1000, 500); } }); }
public override string ProcessEventData(TheThing thing, object evnt, DateTimeOffset defaultTimestamp) { if (!(evnt is EventData)) { return "Invalid Payload"; } if (thing == null) { return "AMQP Converter does not support thing mapping"; } var eventData = evnt as EventData; DateTimeOffset time; if (eventData.Properties.ContainsKey("time")) { time = TheCommonUtils.CDate(eventData.Properties["time"]); } else if (defaultTimestamp == DateTimeOffset.MinValue) { time = eventData.SystemProperties.EnqueuedTimeUtc; } else { time = defaultTimestamp; } foreach (var prop in eventData.Properties) { var tProp = thing.SetProperty(prop.Key, prop.Value, time); //var tProp = thing.GetProperty(prop.Key, true); //tProp.cdeCTIM = time; //tProp.Value = prop.Value; //tProp.cdeE = 0x40; // NMI visible } return null; }
public void GetAllPropertiesTest() { var tThing = new TheThing(); tThing.SetProperty("Prop1", "v1"); var prop2 = tThing.SetProperty("Prop2", "v2"); var prop2_1 = prop2.SetProperty("Prop2_1", "v2_1"); prop2_1.SetProperty("Prop2_1_1", "v2_1_1"); prop2.SetProperty("Prop2_2", "v2_2"); var allProps = tThing.GetAllProperties(10); var expectedProps = new List <string> { "Prop1", "Prop2", "[Prop2].[Prop2_1]", "[Prop2].[Prop2_1].[Prop2_1_1]", "[Prop2].[Prop2_2]" }.OrderBy(n => n); Assert.IsTrue(allProps.Select(p => cdeP.GetPropertyPath(p)).OrderBy(n => n).SequenceEqual(expectedProps)); }
public override string ProcessEventData(TheThing thing, object evnt, DateTimeOffset defaultTimestamp) { if (thing == null) { return("P08 Event Converter does not support thing mapping"); } List <JSonOpcArrayElement> payload = null; string error = null; string json = DecodeEventAsString(evnt); if (json == null) { error = "Invalid payload"; } else { try { payload = TheCommonUtils.DeserializeJSONStringToObject <List <JSonOpcArrayElement> >(json); //payload = TheCommonUtils.DeserializeJSONStringToObject<List<JSonArrayElement>>(json); if (payload == null || payload.Count == 0) { error = "No data in payload"; } else { foreach (var prop in payload) { if (prop != null) { if (prop.identifier != null) { var tProp = thing.SetProperty(prop.identifier, prop.value, new DateTimeOffset(prop.sourcetimestamp, DateTimeOffset.Now.Offset)); // TODO Capture servertimestamp } else { } } else { } } } } catch (Exception e) { error = e.ToString(); } } return(error); }
public override bool DoCreateUX() { MyBaseThing.RegisterProperty("ClickState"); var tFlds = TheNMIEngine.AddStandardForm(MyBaseThing, null, 24, null, "Value"); MyStatusForm = tFlds["Form"] as TheFormInfo; //MyStatusForm.PropertyBag = new ThePropertyBag { "TileWidth=6" }; MyStatusForm.RegisterEvent2(eUXEvents.OnShow, (pmse, sen) => { UpdateUx(); }); SummaryForm = tFlds["DashIcon"] as TheDashPanelInfo; SummaryForm.PropertyBag = new nmiDashboardTile() { Format = $"{MyBaseThing.FriendlyName}<br>{{0}}", Caption = MyBaseThing.FriendlyName }; CountBar = TheNMIEngine.AddSmartControl(MyBaseThing, MyStatusForm, eFieldType.SingleEnded, 5, 2, MyBaseThing.cdeA, "CurrentValue", "Value", null); ChangeTimestampField = TheNMIEngine.AddSmartControl(MyBaseThing, MyStatusForm, eFieldType.DateTime, 6, 0, MyBaseThing.cdeA, "Last Change", nameof(ValueChangeTimestamp), new nmiCtrlDateTime { ParentFld = 1, Visibility = ShowChangeTimestamp }); TimestampField = TheNMIEngine.AddSmartControl(MyBaseThing, MyStatusForm, eFieldType.DateTime, 7, 0, MyBaseThing.cdeA, "Timestamp", nameof(ValueTimestamp), new nmiCtrlDateTime { ParentFld = 1, Visibility = ShowChangeTimestamp }); TheNMIEngine.AddSmartControl(MyBaseThing, MyStatusForm, eFieldType.CollapsibleGroup, 800, 2, 0x80, "Configuration...", null, ThePropertyBag.Create(new nmiCtrlCollapsibleGroup() { ParentFld = 1, DoClose = true, IsSmall = true })); var ts = TheNMIEngine.AddStatusBlock(MyBaseThing, MyStatusForm, 810); ts["Group"].SetParent(800); ts["Group"].PropertyBag = new nmiCtrlCollapsibleGroup { DoClose = true }; TheNMIEngine.AddSmartControl(MyBaseThing, MyStatusForm, eFieldType.CollapsibleGroup, 1000, 2, 0x80, "NMI Settings...", null, ThePropertyBag.Create(new nmiCtrlCollapsibleGroup() { ParentFld = 800, DoClose = true, IsSmall = true, TileWidth = 6 })); TheNMIEngine.AddSmartControl(MyBaseThing, MyStatusForm, eFieldType.CollapsibleGroup, 3000, 2, 0x80, "Advanced NMI Settings...", null, ThePropertyBag.Create(new nmiCtrlCollapsibleGroup() { ParentFld = 1000, DoClose = true, IsSmall = true, TileWidth = 6 })); TheNMIEngine.AddSmartControl(MyBaseThing, MyStatusForm, eFieldType.ComboOption, 3010, 2, 0x80, "NMI Screen", "FormName", new ThePropertyBag() { "Options=%GetLiveScreens%", "TileWidth=6", "TileHeight=1", "ParentFld=3000" }); TheNMIEngine.AddSmartControl(MyBaseThing, MyStatusForm, eFieldType.CheckField, 3020, 2, 0x80, "Flags", "Flags", new ThePropertyBag() { "Bits=6", "TileHeight=6", "TileFactorY=2", "ImageList=<span class='fa-stack'><i class='fa fa-stack-1x'></i><i class='fa fa-stack-2x'> </i></span>," + "<span class='fa-stack'><i class='fa fa-stack-1x'></i><i class='fa fa-stack-2x'> </i></span>," + "<span class='fa-stack'><i class='fa fa-stack-1x'></i><i class='fa fa-stack-2x text-danger' style='opacity:0.5'></i></span>," + "<span class='fa-stack'><i class='fa fa-stack-1x'></i><i class='fa fa-stack-2x text-danger' style='opacity:0.5'></i></span>," + "<span class='fa-stack'><i class='fa fa-stack-1x'></i><i class='fa fa-stack-2x text-danger' style='opacity:0.5'></i></span>," + "<span class='fa-stack'><i class='fa fa-stack-1x'></i><i class='fa fa-stack-2x text-danger' style='opacity:0.5'></i></span>", "ParentFld=3000" }).FldWidth = 1; TheNMIEngine.AddSmartControl(MyBaseThing, MyStatusForm, eFieldType.ComboBox, 1500, 2, 0x80, "Control Type", "ControlType", new ThePropertyBag() { "Options=%RegisteredControlTypes%", "ParentFld=1000" }); TheNMIEngine.AddSmartControl(MyBaseThing, MyStatusForm, eFieldType.CollapsibleGroup, 2000, 2, 0x80, "All Properties...", null, ThePropertyBag.Create(new nmiCtrlCollapsibleGroup() { NoTE = true, ParentFld = 1000, DoClose = true, IsSmall = true, TileWidth = 6 })); TheNMIEngine.AddSmartControl(MyBaseThing, MyStatusForm, eFieldType.Table, 2010, 0xA2, 0x80, "All Properties", "mypropertybag;1", new ThePropertyBag() { "NoTE=true", "TileHeight=4", "TileLeft=9", "TileTop=3", "TileWidth=6", "FldWidth=6", "ParentFld=2000" }); TheNMIEngine.AddFields(MyStatusForm, new List <TheFieldInfo> { { new TheFieldInfo() { FldOrder = 2020, DataItem = "MyPropertyBag.ScratchName.Value", Flags = 0x0A, Type = eFieldType.SingleEnded, Header = "New Property Name", TileWidth = 6, TileHeight = 1, PropertyBag = new ThePropertyBag() { "ParentFld=2000" } } }, }); TheFieldInfo tBut = TheNMIEngine.AddSmartControl(MyBaseThing, MyStatusForm, eFieldType.TileButton, 2040, 0x0A, 0, "Add Property", false, null, null, new nmiCtrlTileButton() { ParentFld = 2000, NoTE = true, ClassName = "cdeGoodActionButton" }); tBut.RegisterUXEvent(MyBaseThing, eUXEvents.OnClick, "AddProp", (pThing, pObj) => { TheProcessMessage pMsg = pObj as TheProcessMessage; if (pMsg?.Message == null) { return; } string[] parts = pMsg.Message.PLS.Split(':'); TheThing tOrg = TheThingRegistry.GetThingByMID(MyBaseEngine.GetEngineName(), TheCommonUtils.CGuid(parts[2])); if (tOrg == null) { return; } string tNewPropName = TheThing.GetSafePropertyString(tOrg, "ScratchName"); if (string.IsNullOrEmpty(tNewPropName)) { TheCommCore.PublishToOriginator(pMsg.Message, new TSM(eEngineName.NMIService, "NMI_TOAST", "Please specify a new property name")); } else { if (tOrg.GetProperty(tNewPropName) != null) { TheCommCore.PublishToOriginator(pMsg.Message, new TSM(eEngineName.NMIService, "NMI_TOAST", "Property already exists")); tOrg.SetProperty("ScratchName", ""); } else { tOrg.DeclareNMIProperty(tNewPropName, ePropertyTypes.TString); TheCommCore.PublishToOriginator(pMsg.Message, new TSM(eEngineName.NMIService, "NMI_TOAST", "Property Added")); tOrg.SetProperty("ScratchName", ""); MyStatusForm.Reload(pMsg, false); } } }); TheFieldInfo mSendbutton = TheNMIEngine.AddSmartControl(MyBaseThing, MyStatusForm, eFieldType.TileButton, 1550, 2, 0x80, "Reload", false, "", null, new nmiCtrlTileButton() { TileWidth = 6, NoTE = true, ClassName = "cdeGoodActionButton", ParentFld = 1000 }); mSendbutton.RegisterUXEvent(MyBaseThing, eUXEvents.OnClick, "", (pThing, pPara) => { TheProcessMessage pMsg = pPara as TheProcessMessage; if (pMsg?.Message == null) { return; } UpdateUx(); MyStatusForm.Reload(pMsg, true); //TheNMIEngine.GetEngineDashBoardByThing(MyBaseEngine.GetBaseThing()).Reload(pMsg, true); }); TheNMIEngine.AddSmartControl(MyBaseThing, MyStatusForm, eFieldType.CollapsibleGroup, 5000, 2, 0x80, "Source...", null, ThePropertyBag.Create(new nmiCtrlCollapsibleGroup() { DoClose = true, IsSmall = true, ParentFld = 800, TileWidth = 6 })); TheNMIEngine.AddSmartControl(MyBaseThing, MyStatusForm, eFieldType.ThingPicker, 5010, 2, 0x80, "Source Thing", "Address", new nmiCtrlThingPicker() { ParentFld = 5000, IncludeEngines = true, IncludeRemotes = true }); TheNMIEngine.AddSmartControl(MyBaseThing, MyStatusForm, eFieldType.PropertyPicker, 5020, 2, 0x80, "Source Property", "SourceProp", new nmiCtrlPropertyPicker() { ParentFld = 5000, ThingFld = 5010 }); TheNMIEngine.AddSmartControl(MyBaseThing, MyStatusForm, eFieldType.SingleCheck, 5030, 2, 0x80, "Show Timestamp", nameof(ShowTimestamp), new nmiCtrlSingleCheck { ParentFld = 5000, TileWidth = 3 }); TheNMIEngine.AddSmartControl(MyBaseThing, MyStatusForm, eFieldType.SingleCheck, 5040, 2, 0x80, "Show Change Time", nameof(ShowChangeTimestamp), new nmiCtrlSingleCheck { ParentFld = 5000, TileWidth = 3 }); TheFieldInfo mSendbutton2 = TheNMIEngine.AddSmartControl(MyBaseThing, MyStatusForm, eFieldType.TileButton, 5050, 2, 0x80, "Engage", false, "", null, new nmiCtrlTileButton() { NoTE = true, TileWidth = 6, ClassName = "cdeGoodActionButton", ParentFld = 5000 }); mSendbutton2.RegisterUXEvent(MyBaseThing, eUXEvents.OnClick, "", (pThing, pPara) => { TheProcessMessage pMsg = pPara as TheProcessMessage; if (pMsg?.Message == null) { return; } UpdateUx(); }); UpdateUx(); return(true); }
public cdeP SetProperty(string pName, object pValue) { return(MyBaseThing?.SetProperty(pName, pValue)); }
public virtual bool DoCreateUX() { MyEditorForm = new TheFormInfo(MyBaseThing) { DefaultView = eDefaultView.Form, PropertyBag = new ThePropertyBag { "MaxTileWidth=6", "HideCaption=true", "AllowDrag=true" } }; MyEditorForm.ModelID = "NMIEditor"; TheDashboardInfo tDash = TheNMIEngine.GetDashboardById(TheNMIHtml5RT.eNMIDashboard); MyEditorDashIcon = TheNMIEngine.AddFormToThingUX(tDash, MyBaseThing, MyEditorForm, "CMyForm", "NMI Control Editor", 1, 0x89, 0x80, "NMI", null, new ThePropertyBag() { "RenderTarget=cdeInSideBarRight", "NeverHide=true" }); //"mAllowDrag=true", "nVisibility=false", MySampleControl = TheNMIEngine.AddSmartControl(MyBaseThing, MyEditorForm, eFieldType.SingleEnded, 3, 2, MyBaseThing.cdeA, "CurrentValue", "Value", null); TheNMIEngine.AddSmartControl(MyBaseThing, MyEditorForm, eFieldType.TileGroup, 9, 0, 0, null, null, new nmiCtrlTileGroup { TileWidth = 7, TileHeight = 1, TileFactorY = 2 }); TheNMIEngine.AddSmartControl(MyBaseThing, MyEditorForm, eFieldType.TileButton, 10, 2, 0, "Basic", null, new nmiCtrlTileButton { ParentFld = 9, OnClick = "GRP:NMIP:Basic", TileWidth = 1, TileHeight = 1, TileFactorY = 2, NoTE = true, ClassName = "cdeTransitButton" }); TheNMIEngine.AddSmartControl(MyBaseThing, MyEditorForm, eFieldType.TileButton, 20, 2, 0, "Screen", null, new nmiCtrlTileButton { ParentFld = 9, OnClick = "GRP:NMIP:Screen", TileWidth = 1, TileHeight = 1, TileFactorY = 2, NoTE = true, ClassName = "cdeTransitButton" }); TheNMIEngine.AddSmartControl(MyBaseThing, MyEditorForm, eFieldType.TileButton, 30, 2, 0, "All", null, new nmiCtrlTileButton { ParentFld = 9, OnClick = "GRP:NMIP:All", TileWidth = 1, TileHeight = 1, TileFactorY = 2, NoTE = true, ClassName = "cdeTransitButton" }); TheNMIEngine.AddSmartControl(MyBaseThing, MyEditorForm, eFieldType.TileButton, 40, 2, 0, "Source", null, new nmiCtrlTileButton { ParentFld = 9, OnClick = "GRP:NMIP:Source", TileWidth = 1, TileHeight = 1, TileFactorY = 2, NoTE = true, ClassName = "cdeTransitButton" }); //TheNMIEngine.AddSmartControl(MyBaseThing, MyStatusForm, eFieldType.TileButton, 50, 2, 0, "Compounds", null, new nmiCtrlTileButton { OnClick = "GRP:Cate:5", TileWidth = 2, TileHeight = 1, TileFactorY = 2, NoTE = true, ClassName = "cdeTransitButton" }); //TheNMIEngine.AddSmartControl(MyBaseThing, MyStatusForm, eFieldType.TileButton, 60, 2, 0, "Gauges", null, new nmiCtrlTileButton { OnClick = "GRP:Cate:6", TileWidth = 2, TileHeight = 1, TileFactorY = 2, NoTE = true, ClassName = "cdeTransitButton" }); TheFieldInfo mSendbutton = TheNMIEngine.AddSmartControl(MyBaseThing, MyEditorForm, eFieldType.TileButton, 70, 2, 0x80, "Reload", false, "", null, new nmiCtrlTileButton() { ParentFld = 9, TileWidth = 2, NoTE = true, TileFactorY = 2, ClassName = "cdeGoodActionButton" }); mSendbutton.RegisterUXEvent(MyBaseThing, eUXEvents.OnClick, "", (pThing, pPara) => { TheProcessMessage pMsg = pPara as TheProcessMessage; if (pMsg?.Message == null) { return; } UpdateUx(pThing.GetBaseThing()); MyEditorForm.Reload(pMsg, tDash.cdeMID, true); }); TheNMIEngine.AddSmartControl(MyBaseThing, MyEditorForm, eFieldType.TileGroup, 1000, 2, 0x80, null, null, new nmiCtrlTileGroup() { Group = "NMIP:Basic" }); TheNMIEngine.AddSmartControl(MyBaseThing, MyEditorForm, eFieldType.ComboBox, 1010, 2, 0x80, "Control Type", "ControlType", new ThePropertyBag() { "Options=%RegisteredControlTypes%", "ParentFld=1000" }); GetProperty("ControlType", true).RegisterEvent(eThingEvents.PropertyChanged, (p) => { UpdateUx(MyBaseThing); }); TheNMIEngine.AddSmartControl(MyBaseThing, MyEditorForm, eFieldType.Number, 1020, 2, 0, "Tile Width", "TileWidth", new nmiCtrlNumber() { ParentFld = 1000, TileHeight = 1, TileFactorY = 1, DefaultValue = "6", TileWidth = 3 }); GetProperty("TileWidth", true).RegisterEvent(eThingEvents.PropertyChanged, (p) => { MySampleControl.SetUXProperty(Guid.Empty, "TileWidth= " + p.ToString()); }); TheNMIEngine.AddSmartControl(MyBaseThing, MyEditorForm, eFieldType.Number, 1030, 2, 0, "Tile Height", "TileHeight", new nmiCtrlNumber() { ParentFld = 1000, TileHeight = 1, TileFactorY = 1, DefaultValue = "1", TileWidth = 3 }); GetProperty("TileHeight", true).RegisterEvent(eThingEvents.PropertyChanged, (p) => { MySampleControl.SetUXProperty(Guid.Empty, "TileHeight= " + p.ToString()); }); TheNMIEngine.AddSmartControl(MyBaseThing, MyEditorForm, eFieldType.ComboBox, 1040, 2, 0, "Horizontal Alignment", "HorizontalAlignment", new nmiCtrlComboBox() { ParentFld = 1000, Options = ";left;center;right" }); GetProperty("HorizontalAlignment", true).RegisterEvent(eThingEvents.PropertyChanged, (p) => { MySampleControl.SetUXProperty(Guid.Empty, "HorizontalAlignment= " + p.ToString()); }); TheNMIEngine.AddSmartControl(MyBaseThing, MyEditorForm, eFieldType.ComboBox, 1050, 2, 0, "Class Name", "ClassName", new nmiCtrlComboBox() { ParentFld = 1000, Options = ";BlueWhite;GreenYellow;RedWhite" }); GetProperty("ClassName", true).RegisterEvent(eThingEvents.PropertyChanged, (p) => { MySampleControl.SetUXProperty(Guid.Empty, "ClassName= " + p.ToString()); }); TheNMIEngine.AddSmartControl(MyBaseThing, MyEditorForm, eFieldType.SingleEnded, 1060, 2, 0, "HelpText", "HelpText", new nmiCtrlSingleEnded() { ParentFld = 1000 }); GetProperty("HelpText", true).RegisterEvent(eThingEvents.PropertyChanged, (p) => { MySampleControl.SetUXProperty(Guid.Empty, "HelpText= " + p.ToString()); }); TheNMIEngine.AddSmartControl(MyBaseThing, MyEditorForm, eFieldType.ComboBox, 1070, 2, 0, "Opacity", "Opacity", new nmiCtrlComboBox() { ParentFld = 1000, Options = ";0.1;0.3;0.5;0.7;0.9;1.0" }); GetProperty("Opacity", true).RegisterEvent(eThingEvents.PropertyChanged, (p) => { MySampleControl.SetUXProperty(Guid.Empty, "Opacity= " + p.ToString()); }); TheNMIEngine.AddSmartControl(MyBaseThing, MyEditorForm, eFieldType.SingleCheck, 1080, 2, 0, "No TE", "NoTE", new nmiCtrlSingleCheck() { ParentFld = 1000 }); GetProperty("NoTE", true).RegisterEvent(eThingEvents.PropertyChanged, (p) => { MySampleControl.SetUXProperty(Guid.Empty, "NoTE= " + p.ToString()); }); //Screen Properties TheNMIEngine.AddSmartControl(MyBaseThing, MyEditorForm, eFieldType.TileGroup, 3000, 2, 0x80, null, null, new nmiCtrlTileGroup() { Group = "NMIP:Screen", Visibility = false }); TheNMIEngine.AddSmartControl(MyBaseThing, MyEditorForm, eFieldType.ComboOption, 3010, 2, 0x80, "NMI Screen", "FormName", new ThePropertyBag() { "Options=%GetLiveScreens%", "TileWidth=6", "TileHeight=1", "ParentFld=3000" }); TheNMIEngine.AddSmartControl(MyBaseThing, MyEditorForm, eFieldType.CheckField, 3020, 2, 0x80, "Flags", "Flags", new ThePropertyBag() { "Bits=6", "TileHeight=6", "TileFactorY=2", "ImageList=<span class='fa-stack'><i class='fa fa-stack-1x'></i><i class='fa fa-stack-2x'> </i></span>," + "<span class='fa-stack'><i class='fa fa-stack-1x'></i><i class='fa fa-stack-2x'> </i></span>," + "<span class='fa-stack'><i class='fa fa-stack-1x'></i><i class='fa fa-stack-2x text-danger' style='opacity:0.5'></i></span>," + "<span class='fa-stack'><i class='fa fa-stack-1x'></i><i class='fa fa-stack-2x text-danger' style='opacity:0.5'></i></span>," + "<span class='fa-stack'><i class='fa fa-stack-1x'></i><i class='fa fa-stack-2x text-danger' style='opacity:0.5'></i></span>," + "<span class='fa-stack'><i class='fa fa-stack-1x'></i><i class='fa fa-stack-2x text-danger' style='opacity:0.5'></i></span>", "ParentFld=3000" }).FldWidth = 1; //ALL Properties TheNMIEngine.AddSmartControl(MyBaseThing, MyEditorForm, eFieldType.TileGroup, 2000, 2, 0x80, null, null, new nmiCtrlTileGroup() { TileWidth = 6, Group = "NMIP:All", Visibility = false }); TheNMIEngine.AddSmartControl(MyBaseThing, MyEditorForm, eFieldType.Table, 2010, 0xA2, 0x80, "All Properties", "mypropertybag;1", new ThePropertyBag() { "NoTE=true", "TileHeight=4", "TileLeft=9", "TileTop=3", "TileWidth=6", "FldWidth=6", "ParentFld=2000" }); TheNMIEngine.AddSmartControl(MyBaseThing, MyEditorForm, eFieldType.SingleEnded, 2020, 0x0A, 0, "New Property Name", "ScratchName", new nmiCtrlSingleEnded() { ParentFld = 2000 }); TheFieldInfo tBut = TheNMIEngine.AddSmartControl(MyBaseThing, MyEditorForm, eFieldType.TileButton, 2040, 0x0A, 0, "Add Property", false, null, null, new nmiCtrlTileButton() { ParentFld = 2000, NoTE = true, ClassName = "cdeGoodActionButton" }); tBut.RegisterUXEvent(MyBaseThing, eUXEvents.OnClick, "AddProp", (pThing, pObj) => { TheProcessMessage pMsg = pObj as TheProcessMessage; if (pMsg?.Message == null) { return; } string[] parts = pMsg.Message.PLS.Split(':'); TheThing tOrg = pThing.GetBaseThing(); // TheThingRegistry.GetThingByMID(MyBaseEngine.GetEngineName(), TheCommonUtils.CGuid(parts[2])); //if (tOrg == null) return; string tNewPropName = TheThing.GetSafePropertyString(tOrg, "ScratchName"); if (string.IsNullOrEmpty(tNewPropName)) { TheCommCore.PublishToOriginator(pMsg.Message, new TSM(eEngineName.NMIService, "NMI_TOAST", "Please specify a new property name")); } else { if (tOrg.GetProperty(tNewPropName) != null) { TheCommCore.PublishToOriginator(pMsg.Message, new TSM(eEngineName.NMIService, "NMI_TOAST", "Property already exists")); } else { tOrg.DeclareNMIProperty(tNewPropName, ePropertyTypes.TString); TheCommCore.PublishToOriginator(pMsg.Message, new TSM(eEngineName.NMIService, "NMI_TOAST", "Property Added")); MyEditorForm.Reload(pMsg, false); } tOrg.SetProperty("ScratchName", ""); } }); //THING Connector TheNMIEngine.AddSmartControl(MyBaseThing, MyEditorForm, eFieldType.TileGroup, 5000, 2, 0x80, null, null, new nmiCtrlTileGroup() { TileWidth = 6, Group = "NMIP:Source", Visibility = false }); TheNMIEngine.AddSmartControl(MyBaseThing, MyEditorForm, eFieldType.ThingPicker, 5010, 2, 0x80, "Source Thing", "Address", new nmiCtrlThingPicker() { ParentFld = 5000, IncludeEngines = true }); TheNMIEngine.AddSmartControl(MyBaseThing, MyEditorForm, eFieldType.PropertyPicker, 5020, 2, 0x80, "Source Property", "SourceProp", new nmiCtrlPropertyPicker() { ParentFld = 5000, ThingFld = 5010 }); TheFieldInfo mSendbutton2 = TheNMIEngine.AddSmartControl(MyBaseThing, MyEditorForm, eFieldType.TileButton, 5050, 2, 0x80, "Engage", false, "", null, new nmiCtrlTileButton() { NoTE = true, TileWidth = 6, ClassName = "cdeGoodActionButton", ParentFld = 5000 }); mSendbutton2.RegisterUXEvent(MyBaseThing, eUXEvents.OnClick, "", (pThing, pPara) => { TheProcessMessage pMsg = pPara as TheProcessMessage; if (pMsg?.Message == null) { return; } UpdateUx(pThing.GetBaseThing()); }); UpdateUx(MyBaseThing); return(true); }
static protected TheThing StartOPCServer(bool disableSecurity) { #if OPCUASERVER // TODO Actually use our own OPC Server for the unit test var opcServerThing = TheThingRegistry.GetThingsOfEngine("CDMyOPCUAServer.cdeMyOPCServerService").FirstOrDefault(); Assert.IsNotNull(opcServerThing, $"Unable to obtain OPC Server thing: error loading plug-in or server not yet initialized?"); lock (opcServerStartupLock) { if (!TheThing.GetSafePropertyBool(opcServerThing, "IsRunning")) { TheThing.SetSafePropertyBool(opcServerThing, "DisableSecurity", disableSecurity); TheThing.SetSafePropertyBool(opcServerThing, "NoServerCertificate", disableSecurity); var theOpcThing = new TheThing(); theOpcThing.EngineName = "OPCTestEng"; theOpcThing.DeviceType = "OPCTestDT"; theOpcThing.Address = "OPCTestAddress"; theOpcThing.SetProperty("OpcProp01", "0001"); theOpcThing.SetProperty("OpcProp02", "0002"); theOpcThing.SetProperty("OpcProp03", "0003"); theOpcThing.SetProperty("OpcProp04", "0004"); theOpcThing.SetProperty("OpcProp05", "0005"); theOpcThing.SetProperty("OpcProp06", "0006"); theOpcThing.SetProperty("OpcProp07", "0007"); theOpcThing.SetProperty("OpcProp08", "0008"); theOpcThing.SetProperty("OpcProp09", "0009"); theOpcThing.SetProperty("OpcProp10", "0010"); var tThing = TheThingRegistry.RegisterThing(theOpcThing); Assert.IsNotNull(tThing); var addThingResponse = TheCommRequestResponse.PublishRequestJSonAsync <MsgAddThingsToServer, MsgAddThingsToServerResponse>(myContentService, opcServerThing, new MsgAddThingsToServer ( new TheThingToAddToServer { cdeMID = Guid.NewGuid(), ReplaceExistingThing = false, ThingMID = TheCommonUtils.cdeGuidToString(theOpcThing.cdeMID), } ), new TimeSpan(0, 0, 30)).Result; Assert.IsNotNull(addThingResponse, "No reply to OPC Server MsgAddThingToServer"); Assert.IsTrue(string.IsNullOrEmpty(addThingResponse.Error), $"Error adding thing to OPC Server: '{addThingResponse.Error}'."); Assert.AreEqual(1, addThingResponse.ThingStatus.Count, $"Error adding thing to OPC Server."); Assert.IsTrue(string.IsNullOrEmpty(addThingResponse.ThingStatus[0].Error), $"Error adding thing to OPC Server: '{addThingResponse.ThingStatus[0].Error}'."); MsgStartStopServerResponse responseStart; int retryCount = 1; do { responseStart = TheCommRequestResponse.PublishRequestJSonAsync <MsgStartStopServer, MsgStartStopServerResponse>(myContentService, opcServerThing, new MsgStartStopServer { Restart = true, }, new TimeSpan(0, 0, 30)).Result; retryCount--; } while (retryCount >= 0 && responseStart == null); Assert.IsNotNull(responseStart, "Failed to send MsgStartStopServer message to restart OPC UA Server"); Assert.IsTrue(string.IsNullOrEmpty(responseStart.Error), $"Error restarting OPC Server: '{addThingResponse.Error}'."); Assert.IsTrue(responseStart.Running, $"OPC Server not running after MsgStartStopServer Restart message"); } } return(opcServerThing); #else return(null); #endif }
internal static void ToThingProperties(TheThing pThing, bool bReset) { lock (thingHarvestLock) { if (pThing == null) { return; } if (KPIIndexes == null) { CreateKPIIndexes(); } if (KPIs == null) { KPIs = new long[KPIIndexes.Count]; } if (KPIs != null && KPIIndexes != null) { TimeSpan timeSinceLastReset = DateTimeOffset.Now.Subtract(LastReset); bool resetReady = timeSinceLastReset.TotalMilliseconds >= 1000; foreach (var keyVal in KPIIndexes.GetDynamicEnumerable()) { bool donres = doNotReset.Contains(keyVal.Key); long kpiValue; if (bReset && !donres && resetReady) { kpiValue = Interlocked.Exchange(ref KPIs[keyVal.Value], 0); } else { kpiValue = Interlocked.Read(ref KPIs[keyVal.Value]); } // LastReset not set yet - shouldn't happen since it is set in first call to Reset (TheBaseAssets.InitAssets) if (LastReset == DateTimeOffset.MinValue || timeSinceLastReset.TotalSeconds <= 1 || donres) { pThing.SetProperty(keyVal.Key, kpiValue); } else { pThing.SetProperty(keyVal.Key, kpiValue / timeSinceLastReset.TotalSeconds); // Normalize value to "per second" } if (!doNotComputeTotals.Contains(keyVal.Key)) { pThing.SetProperty(keyVal.Key + "Total", TheThing.GetSafePropertyNumber(pThing, keyVal.Key + "Total") + kpiValue); } } if (bReset && resetReady) { LastReset = DateTimeOffset.Now; } // Grab some KPIs from sources - Workaround, this should be computed in the source instead SetKPI(eKPINames.QSenders, TheQueuedSenderRegistry.GetSenderListNodes().Count); SetKPI(eKPINames.QSenderInRegistry, TheQueuedSenderRegistry.Count()); SetKPI(eKPINames.SessionCount, TheBaseAssets.MySession.GetSessionCount()); SetKPI(eKPINames.UniqueMeshes, TheQueuedSenderRegistry.GetUniqueMeshCount()); SetKPI(eKPINames.UnsignedNodes, TheQueuedSenderRegistry.GetUnsignedNodeCount()); SetKPI(eKPINames.KnownNMINodes, Engines.NMIService.TheFormsGenerator.GetNMINodeCount()); SetKPI(eKPINames.StreamsNotFound, Communication.HttpService.TheHttpService.IsStreaming.Count); SetKPI(eKPINames.BlobsNotFound, Engines.ContentService.TheContentServiceEngine.BlobsNotHere.Count); } } }
internal void FireAction(bool FireNow) { if (TheCDEngines.MyThingEngine == null || !TheBaseAssets.MasterSwitch) { return; } if (!FireNow) { int tDelay = ActionDelay; if (tDelay > 0) { Timer tTimer = GetDelayTimer(); if (tTimer != null) { tTimer.Dispose(); tTimer = null; } tTimer = new Timer(sinkFireOnTimer, this, tDelay * 1000, Timeout.Infinite); SetDelayTimer(tTimer); return; } } switch (ActionObjectType) { case "CDE_PUBLISHCENTRAL": SendRuleTSM(false); break; case "CDE_PUBLISH2SERVICE": SendRuleTSM(true); break; default: //case "CDE_THING": TheThing tActionThing = TheThingRegistry.GetThingByMID("*", TheCommonUtils.CGuid(ActionObject)); if (tActionThing != null) { string tActionValue = ActionValue; if (!string.IsNullOrEmpty(tActionValue)) { ICDEThing triggerThing = TheThingRegistry.GetThingByMID("*", TheCommonUtils.CGuid(TriggerObject)) as ICDEThing; tActionValue = TheCommonUtils.GenerateFinalStr(tActionValue, triggerThing); tActionValue = tActionValue.Replace("%OldValue%", TriggerOldValue); tActionValue = TheCommonUtils.GenerateFinalStr(tActionValue, MyBaseThing); } ICDEThing tObject = tActionThing.GetObject() as ICDEThing; if (tObject != null) { tObject.SetProperty(ActionProperty, tActionValue); } else { tActionThing.SetProperty(ActionProperty, tActionValue); } if (IsRuleLogged) { LogEvent(tActionValue); } if (TheThing.GetSafePropertyBool(MyBaseThing, "IsEVTLogged")) { TheLoggerFactory.LogEvent(eLoggerCategory.RuleEvent, TheCommonUtils.GenerateFinalStr(MyBaseThing.FriendlyName, MyBaseThing), eMsgLevel.l4_Message, TheBaseAssets.MyServiceHostInfo.GetPrimaryStationURL(false), TriggerObject, tActionValue); } } break; } TheThing.SetSafePropertyDate(MyBaseThing, "LastAction", DateTimeOffset.Now); FireEvent("RuleFired", this, this, true); }
void OnThingUpdate(ICDEThing thing, object param) { try { if (IsDisabled) { StopVerifier(); return; } { var property = param as cdeP; if (property == null) { return; } string genName = property.Name; var indexGen = genName.IndexOf("Gen_"); if (indexGen >= 0) { genName = property.Name.Substring(indexGen + "Gen_".Length); } switch (genName) { case "Stats_PropertyCounter": long generatorPropertyCounter = TheCommonUtils.CLng(property.Value); if (generatorPropertyCounter + this.Config_NumberOfActiveProperties < lastGeneratorPropertyCounter) { this.Stats_PropertyCounter = generatorPropertyCounter; thingStats = new ConcurrentDictionary <string, Stats>(); TheBaseAssets.MySYSLOG.WriteToLog(700, TSM.L(eDEBUG_LEVELS.FULLVERBOSE) ? null : new TSM("Data Verifier", "Verifier", eMsgLevel.l6_Debug, "Resetting property counter")); } lastGeneratorPropertyCounter = generatorPropertyCounter; break; case "Config_NumberOfActiveProperties": case "Stats_PropertiesPerSecond": case "Config_PropertyUpdateInterval": case "Stats_UpdateTime": case "Config_StatsUpdateInterval": break; case "Value": MyBaseThing.SetProperty("Value", property.Value); System.Threading.Interlocked.Increment(ref count); break; default: if (!genName.StartsWith("Prop")) { TheBaseAssets.MySYSLOG.WriteToLog(700, TSM.L(eDEBUG_LEVELS.FULLVERBOSE) ? null : new TSM("Data Verifier", "Verifier", eMsgLevel.l6_Debug, String.Format("Unexpected property: {0}", property.Name))); } System.Threading.Interlocked.Increment(ref count); break; } thingStats.AddOrUpdate(property.Name, new Stats { count = 1 }, (name, currentStats) => { currentStats.count++; return(currentStats); } ); var lastUpdate = property.cdeCTIM; var now = DateTime.UtcNow; var latency = (now.Ticks - lastUpdate.Ticks); if (latency >= 0) { if (latency > _maxLatency) { _maxLatency = latency; } if (latency < _minLatency) { _minLatency = latency; } } else { TheBaseAssets.MySYSLOG.WriteToLog(700, TSM.L(eDEBUG_LEVELS.FULLVERBOSE) ? null : new TSM("Data Verifier", "Verifier", eMsgLevel.l6_Debug, "Negative latency!!??")); } } if (g_sw.ElapsedMilliseconds > 5000) { long elapsed = 0; long newCount = -1; lock (g_sw) { elapsed = g_sw.ElapsedMilliseconds; if (elapsed > 5000) { newCount = System.Threading.Interlocked.Exchange(ref count, 0); g_sw.Restart(); } } if (elapsed > 5000) { this.Stats_PropertyCounter += newCount; this.Stats_PropertiesPerSecond = newCount / (elapsed / 1000.0); // this.MinLatency = _minLatency / 1000000; // this.MaxLatency = _maxLatency / 1000000; this.MinLatency = _minLatency / 100000000; this.MaxLatency = _maxLatency / 100000000; _maxLatency = 0; _minLatency = long.MaxValue; TheBaseAssets.MySYSLOG.WriteToLog(700, TSM.L(eDEBUG_LEVELS.FULLVERBOSE) ? null : new TSM("Data Verifier", "Verifier", eMsgLevel.l6_Debug, String.Format("Verify Rate: {0,11:N2} properties/s", this.Stats_PropertiesPerSecond))); } } } catch (Exception) { } }
private async Task RunScriptAsync(TheScript script, TheThing variables, int stepNumber = 1, bool replay = false) { TheThing variablesSnapshot; try { for (; stepNumber <= script.Steps.Length; stepNumber++) { //Clone thing before step occurs variablesSnapshot = new TheThing(); variables.CloneThingAndPropertyMetaData(variablesSnapshot, true); var step = script.Steps[stepNumber - 1]; var existingSnapshot = MyScriptTableStorage.MyMirrorCache.GetEntryByFunc(snapshot => snapshot.ScriptName == script.Name && snapshot.ScriptStep == stepNumber); if (existingSnapshot?.Disabled == true) { TheBaseAssets.MySYSLOG.WriteToLog(175002, TSM.L(eDEBUG_LEVELS.OFF) ? null : new TSM(MyBaseThing.EngineName, "Finished script step: skipped step because it was disabled", eMsgLevel.l3_ImportantMessage, TheCommonUtils.SerializeObjectToJSONString(new Dictionary <String, object> { { "Script", script.Name }, { "Step", stepNumber }, { "Message", step.Message.MessageName }, { "Target", step.Message.Target }, }))); UpdateStorageList(script.Name, "Disabled", stepNumber, script, variablesSnapshot, replay); continue; } if (step.Condition != null) { var condition = TheCommonUtils.GenerateFinalStr(step.Condition, variables); if ( (condition == "" || condition.ToLowerInvariant() == "false" || condition.Trim() == "0") || (condition.StartsWith("!") && condition.Length >= 1 && (condition.Substring(1).ToLowerInvariant() == "true") || condition.Substring(1).Trim() == "1")) { TheBaseAssets.MySYSLOG.WriteToLog(175002, TSM.L(eDEBUG_LEVELS.OFF) ? null : new TSM(MyBaseThing.EngineName, "Finished script step: skipped step due to condition not met", eMsgLevel.l3_ImportantMessage, TheCommonUtils.SerializeObjectToJSONString(new Dictionary <String, object> { { "Script", script.Name }, { "Step", stepNumber }, { "Message", step.Message.MessageName }, { "Target", step.Message.Target }, { "Condition", step.Condition }, { "ConditionEvaluated", condition }, }))); UpdateStorageList(script.Name, "Condition Not Met", stepNumber, script, variablesSnapshot, replay); continue; } } var messageType = TheCommonUtils.GenerateFinalStr(step.Message.MessageName, variables); var txtPayload = TheCommonUtils.GenerateFinalStr(step.Message.Parameters?.ToString(), variables); { var txtPayload2 = txtPayload?.Replace("\"\\\"", ""); var txtPayload3 = txtPayload2?.Replace("\\\"\"", ""); txtPayload = txtPayload3; } // TODO Need a simpler and more flexible way to specify thing address in the script JSON var target = step.Message.Target; if (target == null) { if (txtPayload.Contains("EngineName")) { var payloadDict = TheCommonUtils.DeserializeJSONStringToObject <Dictionary <string, object> >(txtPayload); object engineNameInferred = null; if (payloadDict?.TryGetValue("EngineName", out engineNameInferred) == true && !string.IsNullOrEmpty(engineNameInferred?.ToString())) { target = new TheMessageAddress { EngineName = engineNameInferred.ToString() }; } } } if (target.EngineName.StartsWith("%") || target.EngineName.StartsWith("{")) { target.EngineName = TheCommonUtils.GenerateFinalStr(target.EngineName, variables); // TODO Clean this up: support a serialized TheMessageAddress in the engine name, so that an output variable can be fed into a method invocation try { var newTarget = TheCommonUtils.DeserializeJSONStringToObject <TheMessageAddress>(target.EngineName); if (newTarget != null) { target = newTarget; } } catch { // parsing error: ignore, will result in other errors downstream } } await TheThingRegistry.WaitForInitializeAsync(target); bool bDoRetry; int remainingRetryCount = step.RetryCount ?? 0; do { existingSnapshot = MyScriptTableStorage.MyMirrorCache.GetEntryByFunc(snapshot => snapshot.ScriptName == script.Name && snapshot.ScriptStep == stepNumber); if (existingSnapshot?.Disabled == true) { TheBaseAssets.MySYSLOG.WriteToLog(175002, TSM.L(eDEBUG_LEVELS.OFF) ? null : new TSM(MyBaseThing.EngineName, "Finished script step: skipped step because it was disabled", eMsgLevel.l3_ImportantMessage, TheCommonUtils.SerializeObjectToJSONString(new Dictionary <String, object> { { "Script", script.Name }, { "Step", stepNumber }, { "Message", step.Message.MessageName }, { "Target", step.Message.Target }, }))); UpdateStorageList(script.Name, "Disabled", stepNumber, script, variablesSnapshot, replay); break; } bDoRetry = false; var response = await TheCommRequestResponse.PublishRequestAsync(MyBaseThing, target, messageType, new TimeSpan(0, 0, 0, 0, step.Message.timeout), null, txtPayload, null); if (!string.IsNullOrEmpty(response?.PLS)) { var outputs = TheCommonUtils.DeserializeJSONStringToObject <Dictionary <string, object> >(response.PLS); if (outputs != null) { if (step.Message.outputs != null) { foreach (var output in step.Message.outputs) { if (output.Key == "*") { variables.SetProperty(output.Value, response.PLS); } else if (outputs.TryGetValue(output.Key, out var outputValue)) { variables.SetProperty(output.Value, outputValue); if (output.Value.Contains("Error") && !string.IsNullOrEmpty(TheCommonUtils.CStr(outputValue))) { TheBaseAssets.MySYSLOG.WriteToLog(175004, TSM.L(eDEBUG_LEVELS.OFF) ? null : new TSM(MyBaseThing.EngineName, "Error in script step: output reported error", eMsgLevel.l1_Error, TheCommonUtils.SerializeObjectToJSONString(new Dictionary <String, object> { { "Script", script.Name }, { "Step", stepNumber }, { "Message", messageType }, { "Target", target }, { "PLS", txtPayload }, { "Response", response }, { "ResponsePLS", response?.PLS }, }))); UpdateStorageList(script.Name, $"Error {outputValue} in output", stepNumber, script, variablesSnapshot, replay); if (remainingRetryCount < 0 || remainingRetryCount > 0) { remainingRetryCount--; bDoRetry = true; } string retriesRemaining = bDoRetry ? (remainingRetryCount >= 0 ? $"{remainingRetryCount + 1}" : "infinite") : "none"; MyBaseThing.SetStatus(3, $"Error in script '{script?.Name}', step {stepNumber}: output '{output.Value}' reported error {outputValue}. Retries remaining: {retriesRemaining}"); } } else { // TODO provide access to sub-elements in the JSON //var outputParts = output.Key.Split('/'); //dynamic currentNode = outputs; //foreach (var outputPart in outputParts) //{ // if (currentNode.TryGetValue(outputPart, out var nextNode)) // { // currentNode = nextNode; // } //} } } } TheBaseAssets.MySYSLOG.WriteToLog(175003, TSM.L(eDEBUG_LEVELS.OFF) ? null : new TSM(MyBaseThing.EngineName, "Finished script step", eMsgLevel.l3_ImportantMessage, TheCommonUtils.SerializeObjectToJSONString(new Dictionary <String, object> { { "Script", script.Name }, { "Step", stepNumber }, { "Message", messageType }, { "Target", target }, { "PLS", txtPayload }, { "ResponsePLS", response.PLS }, }))); UpdateStorageList(script.Name, "Finished", stepNumber, script, variablesSnapshot, replay); } else { TheBaseAssets.MySYSLOG.WriteToLog(175004, TSM.L(eDEBUG_LEVELS.OFF) ? null : new TSM(MyBaseThing.EngineName, "Error in script step: no outputs found in response", eMsgLevel.l1_Error, TheCommonUtils.SerializeObjectToJSONString(new Dictionary <String, object> { { "Script", script.Name }, { "Step", stepNumber }, { "Message", messageType }, { "Target", target }, { "PLS", txtPayload }, { "Response", response }, { "ResponsePLS", response?.PLS }, }))); UpdateStorageList(script.Name, "Error: No Output", stepNumber, script, variablesSnapshot, replay); if (step.DontRetryOnEmptyResponse != true && (remainingRetryCount < 0 || remainingRetryCount > 0)) { remainingRetryCount--; bDoRetry = true; } string retriesRemaining = bDoRetry ? (remainingRetryCount >= 0 ? $"{remainingRetryCount + 1}" : "infinite") : "none"; MyBaseThing.SetStatus(3, $"Error in script '{script?.Name}', step {stepNumber}: no outputs found in response. Retries remaining: {retriesRemaining}"); } } else { TheBaseAssets.MySYSLOG.WriteToLog(175005, TSM.L(eDEBUG_LEVELS.OFF) ? null : new TSM(MyBaseThing.EngineName, "Error Script step: timeout", eMsgLevel.l1_Error, TheCommonUtils.SerializeObjectToJSONString(new Dictionary <String, object> { { "Script", script.Name }, { "Step", stepNumber }, { "Message", messageType }, { "Target", target }, { "PLS", txtPayload }, { "Response", response }, }))); UpdateStorageList(script.Name, "Error: Timeout", stepNumber, script, variablesSnapshot, replay); //Retries infinitely unless count is specified if (remainingRetryCount < 0 || remainingRetryCount > 0) { remainingRetryCount--; bDoRetry = true; } string retriesRemaining = bDoRetry ? (remainingRetryCount >= 0 ? $"{remainingRetryCount + 1}" : "infinite") : "none"; MyBaseThing.SetStatus(3, $"Error in script '{script?.Name}', step {stepNumber}: timeout. Retries remaining: {retriesRemaining}"); } if (bDoRetry) { await TheCommonUtils.TaskDelayOneEye(30000, 100).ConfigureAwait(false); } } while (bDoRetry && TheBaseAssets.MasterSwitch); } } catch (Exception e) { TheBaseAssets.MySYSLOG.WriteToLog(175006, TSM.L(eDEBUG_LEVELS.OFF) ? null : new TSM(MyBaseThing.EngineName, "Error in script step", eMsgLevel.l1_Error, TheCommonUtils.SerializeObjectToJSONString(new Dictionary <String, object> { { "Script", script.Name }, { "Exception", e.Message }, }))); MyBaseThing.SetStatus(3, $"Error in script '{script?.Name}': {e.Message}"); //Save variables instead of snapshot in case of error UpdateStorageList(script.Name, $"Error: {e.Message}", stepNumber, script, variables, replay); } }
public void SetPropertyCloningTest() { var testThing = new TheThing(); { // This test illustrates some of the corner cases with property values: // 1) object values are not cloned on set property; with strings this is mostly irrelevant string x = "123"; testThing.SetProperty("Prop1", x); var prop1Value = testThing.GetProperty("Prop1").Value; Assert.AreEqual(prop1Value, x); Assert.AreSame(prop1Value, x); // Strings can't be (and don't need to be) cloned because they are immutable Assert.AreSame(testThing.GetProperty("Prop1").Value, x); x = "456"; Assert.AreEqual(prop1Value, "123"); Assert.AreNotSame(prop1Value, x); testThing.SetProperty("Prop1", "789"); Assert.IsTrue(String.Equals(x, "456")); Assert.AreEqual(prop1Value, "123"); var prop1Value_2 = testThing.GetProperty("Prop1").Value; Assert.AreEqual(prop1Value_2, "789"); Assert.AreEqual(prop1Value, "123"); Assert.AreNotSame(prop1Value_2, prop1Value); } // 2) value types { // This test illustrates some of the corner cases with property values: // 1) object values are not cloned on set property; with strings this is mostly irrelevant double x = 1.23; testThing.SetProperty("Prop1", x); var prop1Value = testThing.GetProperty("Prop1").Value; Assert.AreEqual(prop1Value, x); Assert.AreNotSame(prop1Value, x); // Value types are automatically cloned Assert.AreNotSame(testThing.GetProperty("Prop1").Value, x); // Value types are automatically cloned x = 4.56; Assert.AreEqual(prop1Value, 1.23); Assert.AreNotSame(prop1Value, x); testThing.SetProperty("Prop1", 7.89); Assert.IsTrue(double.Equals(x, 4.56)); Assert.AreEqual(prop1Value, 1.23); var prop1Value_2 = testThing.GetProperty("Prop1").Value; Assert.AreEqual(prop1Value_2, 7.89); Assert.AreEqual(prop1Value, 1.23); Assert.AreNotSame(prop1Value_2, prop1Value); } // 3) with mutable objects, the differences are more substantial { // 3a) non-cloneable MyTestClass xObj = new MyTestClass { x = "123" }; testThing.SetProperty("Prop3", xObj); var prop2Value = testThing.GetProperty("Prop3").Value; Assert.AreEqual(prop2Value, xObj); Assert.AreSame(prop2Value, xObj); // This would be different if we cloned xObj.x = "456"; Assert.AreEqual(prop2Value, new MyTestClass { x = "456" }); // modifying the original object modifies the value of the property Assert.AreSame(prop2Value, xObj); testThing.SetProperty("Prop3", new MyTestClass { x = "789" }); Assert.IsTrue(String.Equals(xObj, new MyTestClass { x = "456" })); Assert.AreEqual(prop2Value, new MyTestClass { x = "456" }); var prop2Value_2 = testThing.GetProperty("Prop3").Value; Assert.AreEqual(prop2Value_2, new MyTestClass { x = "789" }); Assert.AreEqual(prop2Value, new MyTestClass { x = "456" }); Assert.AreNotSame(prop2Value_2, prop2Value); } { // 3b) cloneable MyTestClassCloneable xObj = new MyTestClassCloneable { x = "123" }; testThing.SetProperty("Prop4", xObj); var prop2Value = testThing.GetProperty("Prop4").Value; Assert.AreEqual(prop2Value, xObj); Assert.AreSame(prop2Value, xObj); // This would be different if we cloned xObj.x = "456"; Assert.AreEqual(prop2Value, new MyTestClassCloneable { x = "456" }); // modifying the original object modifies the value of the property: this would be different if we cloned Assert.AreSame(prop2Value, xObj); // this would be different if we cloned testThing.SetProperty("Prop4", new MyTestClassCloneable { x = "789" }); Assert.IsTrue(String.Equals(xObj, new MyTestClassCloneable { x = "456" })); Assert.AreEqual(prop2Value, new MyTestClassCloneable { x = "456" }); var prop2Value_2 = testThing.GetProperty("Prop4").Value; Assert.AreEqual(prop2Value_2, new MyTestClassCloneable { x = "789" }); Assert.AreEqual(prop2Value, new MyTestClassCloneable { x = "456" }); Assert.AreNotSame(prop2Value_2, prop2Value); } }
private async Task RunPlaybackAsync(IEnumerable <cdeP> allProperties, IEnumerable <object> thingUpdates, TimeSpan startupDelayRange, bool bFromAutoStart, double propCountBefore) { for (int j = 1; j <= ReplayCount; j++) { playbackCancel?.Cancel(); playbackCancel = new CancellationTokenSource(); var cancelCombined = CancellationTokenSource.CreateLinkedTokenSource(playbackCancel.Token, TheBaseAssets.MasterSwitchCancelationToken); IsStarted = true; MyBaseThing.StatusLevel = 1; MyBaseThing.LastMessage = $"Playback started: {ItemCount} items. {ParallelPlaybackCount} things."; for (int i = 1; i <= ParallelPlaybackCount; i++) { TheThing tThingOverride = null; if (!string.IsNullOrEmpty(PlaybackEngineName)) { if (TheThingRegistry.GetBaseEngine(PlaybackEngineName) == null) { TheCDEngines.RegisterNewMiniRelay(PlaybackEngineName); } var thingName = $"{MyBaseThing.FriendlyName}{i:D6}"; tThingOverride = TheThingRegistry.GetThingByID(PlaybackEngineName, thingName, true); if (tThingOverride == null) { tThingOverride = new TheThing { FriendlyName = thingName, ID = thingName, EngineName = PlaybackEngineName, DeviceType = PlaybackDeviceType, }; foreach (var prop in allProperties) { tThingOverride.SetProperty(prop.Name, prop.Value, prop.cdeT); } TheThingRegistry.RegisterThing(tThingOverride); } // This only works if the plug-in is actually installed, not with mini relay //var createThingInfo = new TheThingRegistry.MsgCreateThingRequestV1 //{ // EngineName = PlaybackEngineName, // DeviceType = PlaybackDeviceType, // InstanceId = thingName, // FriendlyName = thingName, // CreateIfNotExist = true, // DoNotModifyIfExists = true, // OwnerAddress = MyBaseThing, // Properties = new Dictionary<string, object> { { "ID", thingName } }, //}; //tThingOverride = await TheThingRegistry.CreateOwnedThingAsync(createThingInfo, new TimeSpan(0, 1, 0)); if (tThingOverride == null) { MyBaseThing.LastMessage = "Error creating playback thing"; break; } } var playbackTask = TheCommonUtils.cdeRunTaskChainAsync($"Playback{i}", o => PlaybackLoop(tThingOverride, cancelCombined.Token, thingUpdates, startupDelayRange, bFromAutoStart), true); playbackTasks.Add(playbackTask); } _ = await TheCommonUtils.TaskWhenAll(playbackTasks).ContinueWith(async(t) => { try { PlaybackDuration = sw.Elapsed; sw.Stop(); OnKpiUpdate(null); var propCount = Gen_Stats_PropertyCounter - propCountBefore; var message = $"Playback done. {propCount} props in {PlaybackDuration}. {propCount / PlaybackDuration.TotalSeconds} props/s. {ItemCount * ParallelPlaybackCount / PlaybackDuration.TotalSeconds} items/s."; //MyBaseThing.LastMessage = message; TheBaseAssets.MySYSLOG.WriteToLog(700, TSM.L(eDEBUG_LEVELS.ESSENTIALS) ? null : new TSM(MyBaseThing.EngineName, message, eMsgLevel.l6_Debug)); await StopPlaybackAsync(message); _ = new CDMyMeshManager.Contracts.MsgReportTestStatus { NodeId = TheBaseAssets.MyServiceHostInfo.MyDeviceInfo.DeviceID, PercentCompleted = (j * 1.0 / ReplayCount) * 100, SuccessRate = 100, Status = j == ReplayCount ? CDMyMeshManager.Contracts.eTestStatus.Success : CDMyMeshManager.Contracts.eTestStatus.Running, TestRunId = TheCommonUtils.CGuid(TheBaseAssets.MySettings.GetSetting("TestRunID")), Timestamp = DateTimeOffset.Now, ResultDetails = new Dictionary <string, object> { { "Message", message }, { "PropertyCount", propCount }, { "DurationInSeconds", PlaybackDuration.TotalSeconds } }, }.Publish().Result; } catch { } }, TaskContinuationOptions.OnlyOnRanToCompletion); } }