Abonnements d'objets
Les abonnements sont des services qui reçoivent des
événements et y répondent. Un abonnement contient une source, généralement un
objet. Un objet peut disposer d'un abonnement à un événement qui répond par une action. Par exemple, si une entité déclenche un événement
Surchauffe moteur, elle peut s'abonner à cet événement en déclenchant un abonnement
Eteindre le moteur. Les objets peuvent hériter des abonnements des
modèles d'objet et des
formes d'objet qu'ils utilisent.
Un abonnement est similaire à un service standard. Il est cependant lié de façon explicite à un ou plusieurs événements. Cela vous permet de découpler l'événement du code qui y répond. A l'instar d'un service, vous pouvez implémenter une logique métier personnalisée pour répondre à l'événement (ou aux événements). Il est possible d'exploiter les caractéristiques du modèle, par exemple en envoyant des e-mails par l'intermédiaire d'un objet serveur de messagerie, en accédant en écriture à une base de données ou en appelant un service disponible sur la plateforme. A la différence d'un service, un abonnement ne renvoie pas une valeur explicite. Il peut cependant appeler un autre service du modèle auquel le contexte de sécurité du thread a accès. Le contexte de sécurité de thread d'un abonnement est défini sur le même contexte de sécurité de thread que celui de l'événement qui a été déclenché. Vous pouvez utiliser le même environnement d'édition JavaScript utilisé pour implémenter des services.
Les abonnements ont une entrée définie, qui est le paquet de données émis par l'événement et appelé données d'événement. Si l'entité s'abonne à un événement défini, les données de cet événement sont transmises à la fonction d'abonnement et décrites par la
forme de données de l'événement. Dans l'implémentation de l'abonnement, les données transmises par l'événement agissent en tant qu'entrée pour la fonction de script. Par exemple, si une entité est abonnée à un événement de modification de données d'une propriété d'un objet, la fonction de script d'abonnement est appelée. La valeur de la propriété d'objet est alors transmise à la fonction avec d'autres données pertinentes de l'événement.
Avec un abonnement, de nombreuses entités peuvent souscrire au même événement. La fonction d'abonnement de chaque entité est appelée et les données d'événement lui sont transmises. Une entité peut appliquer toute action définie dans le script d'abonnement afin de répondre aux exigences de la solution.
Les principaux avantages de cette technique par rapport à l'utilisation d'un service appelé à partir d'un autre service sont notamment les suivants :
• Un événement accepte un ou plusieurs abonnements.
• Les événements sont appelés en fonction de l'activité du système sans qu'aucune interaction utilisateur ne soit nécessaire.
• Si plusieurs objets sont abonnés à un événement, vous pouvez utiliser un abonnement au lieu de créer une chaîne de services.
• Un abonnement peut souscrire à plusieurs événements provenant de différentes entités.
|
Le déclencheur d'événement et les abonnements s'exécutent de manière asynchrone. Par exemple, une requête API de mise à jour de propriété reçoit une réponse immédiate dès que l'action de mise à jour de propriété est terminée. Le déclencheur n'attend pas la fin des abonnements suivants qui répondent à l'événement de modification des données.
|
Abonnements multiples
Les abonnements possèdent un nom défini par l'utilisateur qui constitue leur identificateur unique. Les entités peuvent disposer de plusieurs abonnements à un événement sur un objet. Par exemple, si une entité déclenche un événement Surchauffe moteur, il peut s'abonner à cet événement avec un abonnement Eteindre le moteur et un abonnement Créer un bon de travail pour que le moteur soit vérifié par un technicien de maintenance. Vous pouvez également créer autant d'abonnements que vous le souhaitez pour cet événement.
Si un modèle d'objet ou une forme d'objet implémente un abonnement à un événement, les objets utilisant ce modèle d'objet ou cette forme d'objet peuvent également créer des abonnements au même événement, sans avoir à trouver des solutions de contournement pour déclencher des actions supplémentaires lorsque ces événements sont déclenchés.
Abonnements distribués
Cette fonctionnalité permet de distribuer l'exécution des abonnements sur tous les noeuds ThingWorx disponibles lorsqu'un événement déclenche de nombreuses instances d'abonnements ; par exemple, lorsque de nombreux objets s'abonnent à un même timer ou planificateur. Elle assure une adaptation horizontale de la capacité d'exécution des abonnements aux timers/planificateurs en mobilisant les différents noeuds ThingWorx de l'environnement haute disponibilité pour optimiser l'utilisation des ressources et les performances. La case à cocher Distribué dans l'onglet Infos abonnement permet d'activer ce comportement. Lorsque la case Distribué n'est pas cochée, les abonnements aux timers et planificateurs sont exécutés sur le seul noeud où l'événement timer ou planificateur est généré. Pour plus d'informations sur la configuration associée, consultez les rubriques suivantes :
Abonnements multi-événements
La fonctionnalité d'abonnement multi-événements permet aux clients de souscrire à plusieurs événements, même à partir de différents objets, avec un seul abonnement. Elle est utile principalement pour les abonnements complexes. Par exemple, les clients peuvent créer des abonnements déclenchés par plusieurs modifications de propriété différentes, qui exécutent une règle simple de type si-alors-sinon sur ces valeurs de propriété pour effectuer des actions en fonction des résultats. Pour obtenir un exemple, consultez la rubrique
Cas d'utilisation 2. Ils peuvent également créer un abonnement basé sur l'événement du timer pour définir la règle de la fenêtre de temps. Pour obtenir un exemple, consultez la rubrique
Cas d'utilisation 3.
|
La fonctionnalité d'abonnement multi-événements est visible uniquement lors de la création d'un nouvel abonnement. L'abonnement hérité, créé avec ThingWorx 9.4 ou une version ultérieure, reste inchangé et ne peut pas souscrire à plusieurs événements.
|
Avec les abonnements multi-événements, nous introduisons le concept d'ingestion par lots, qui transmet simultanément plusieurs événements à un abonnement, regroupés en fonction de l'horodatage des propriétés mises à jour. Pour plus d'informations, consultez la rubrique
Propriétés d'objet. Pour plus d'informations sur les objets distants, consultez la rubrique
Services avec objets distants.
|
Si vous utilisez actuellement la ou les API d'extension Java getSubscriptions ou getInstanceSubscriptions, vous devez utiliser les options getMultiEventSubscriptions ou getInstanceMultiEventSubscriptions à la place. getSubscriptions et getInstanceSubscriptions ne prennent pas en charge les formats d'abonnement ThingWorx 9.5 et ultérieurs. Cela s'applique uniquement si une entité contient une combinaison d'abonnements créés avant et après ThingWorx 9.5. Les nouvelles API prennent en charge le format d'abonnement hérité (avant ThingWorx 9.5) et le nouveau (ThingWorx 9.5 et versions ultérieures).
|
Le signe + en regard de la liste déroulante Evénements sous l'onglet Entrée de l'abonnement permet d'obtenir ce comportement. La liste des événements de l'abonnement peut être gérée. Par exemple, vous pouvez ajouter/supprimer des événements et modifier les informations des événements. Chaque événement ajouté reçoit un alias défini par l'utilisateur en tant qu'identificateur unique. Cet alias doit être utilisé pour accéder à l'eventData via JavaScript. Vous trouverez ci-dessous plus d'informations sur l'accès à l'eventData.
|
Lorsque vous mettez à jour plusieurs propriétés pour le même horodatage, les nouveaux services UpdatePropertyValuesBatched et UpdateSubscribedPropertyValuesBatched sont préférables aux services UpdatePropertyValues et UpdateSubscribedPropertyValues. Les nouveaux services permettent aux abonnements à plusieurs événements de s'exécuter exactement une fois par horodatage et de recevoir les nouvelles valeurs de toutes les propriétés modifiées lors de la même exécution. Cela réduit le nombre global d'exécutions d'abonnement par rapport aux services hérités et réduit la consommation de l'unité centrale.
|
Accès à l'eventData
L'accès aux informations eventData via JavaScript s'effectue avec l'alias de l'événement. Un développeur ne peut plus accéder à l'eventData par event.eventData.newValue. Il doit utiliser le niveau events[] et le nom d'alias unique pour accéder à un eventData spécifique, tel que events["AliasName"].eventData.newValue.value.
|
Cette consigne n'est valide que pour les nouveaux abonnements créés à partir de ThingWorx 9.5 et versions ultérieures. Les anciens abonnements créés avant la version 9.5 peuvent toujours accéder à l'eventData avec event.eventData.newValue.
|
Il est possible qu'une partie seulement des événements associés déclenche l'abonnement. Par conséquent, l'accès à l'un des eventData qui n'a pas déclenché l'abonnement peut entraîner une erreur. Vous devez donc vérifier si l'accès à l'eventData est défini. Par exemple :
try {
if (events["Me_DataChange_p1"].eventData.newValue.value ==44 {
…
} catch (error) {
logger.error(" p1 event is not defined " + error);
}
Voici un autre exemple.
if( events["Me_DataChange_p1"] !== undefined ){
if(events["Me_DataChange_p1"].eventData.newValue.value)==44;{
…
}
}
Abonnements ordonnés et avec état
Un abonnement ordonné est destiné aux cas d'utilisation qui nécessitent un ordre et une atomicité. Il est visible uniquement lors de la création d'un abonnement. Les abonnements créés avec ThingWorx 9.4 et versions antérieures restent les mêmes et ne peuvent pas être exécutés de manière séquentielle.
Sans cette fonctionnalité, les abonnements s'exécutent de manière asynchrone, en parallèle, et ne sont pas associés à un état (ils ne peuvent pas porter de valeurs entre les exécutions), ce qui, dans certains cas d'utilisation, peut entraîner des problèmes de simultanéité et générer des résultats erronés, principalement si les règles d'abonnement sont basées sur des valeurs précédentes. En revanche, un abonnement ordonné peut être exécuté séquentiellement en fonction de l'ordre d'horodatage d'un événement.
La case à cocher Exécuter les événements dans l'ordre dans l'onglet Infos abonnement permet d'activer ce comportement. En outre, une fois que cette case est cochée, pour chaque abonnement, un développeur acquiert un contrôle total sur le contenu d'un objet JSON dédié, ce qui lui permet de stocker les valeurs précédentes entre les exécutions de l'abonnement, d'accumuler des données et de réaliser des agrégations simples avec état. Pour plus d'informations sur l'utilisation de thisSub.JSONState, consultez la suite de cette rubrique.
|
Les abonnements ordonnés nécessitent plus de ressources de l'unité centrale que les non ordonnés. Par conséquent, le fait de marquer tous les abonnements comme ordonnés pourrait avoir un impact sur la consommation globale de l'unité centrale et entraîner un taux d'exécution des abonnements plus faible. Pour les abonnements qui n'ont pas besoin de conserver l'état et qui peuvent s'exécuter parallèlement sans effets de bord, il est recommandé de laisser la case Exécuter les événements dans l'ordre décochée.
|
Utilisation de thisSub.JSONState
Cet objet thisSub.JSONState est accessible via JavaScript comme suit :
• Pour une valeur native, utilisez thisSub.JSONState.X=5.
• Pour la table d'informations, utilisez JSONObject(), thisSub.JSONstate.myInfoTable = Y.toJSONObject().
Pour y accéder, utilisez thisSub.state.myInfoTable.rows[0].value.
Les données stockées dans l'objet JSONState doivent être utilisées avec précaution, seulement pour les champs nécessaires. Par exemple, il n'est pas conseillé de stocker des objets de toute la table d'informations à l'aide de la méthode toJSONObject() plutôt que des champs individuels, car cela consomme plus d'unité centrale et nécessite davantage de stockage en mémoire.
Pour supprimer une valeur de l'objet d'état, utilisez le mot-clé JavaScript delete delete thisSub.JSONState.myInfoTable.
Pour effacer l'objet d'état entier, affectez-lui un JSON vide {} : thisSub.JSONState = {}.
|
Pour éviter toute perte de données d'état due à une consommation excessive de mémoire, le développeur doit supprimer les champs d'état individuels ou effacer tout l'objet thisSub.JSONState une fois que les données stockées ne sont plus nécessaires.
|
L'élément thisSub.JSONState est stocké en mémoire et, par conséquent, effacé lors de l'arrêt de ThingWorx. En outre, l'état est effacé lorsque :
• L'entité déclarant l'abonnement est modifiée et enregistrée.
• L'entité déclarant l'abonnement est désactivée.
• L'abonnement est désactivé, par exemple lors de l'utilisation du service DisableSubscription.
• Le nouveau noeud ThingWorx démarre/s'arrête dans un cluster haute disponibilité et certains abonnements s'exécutent sur un autre noeud.
|
ThingWorx impose des limites de taille de mémoire pour l'objet thisSub.JSONState d'un abonnement unique et pour le stockage mémoire global de tous les états d'abonnement. Ces limites peuvent être configurées dans la section SubscriptionSettings des Détails de la configuration platform-settings.json.
|
Pour en savoir plus sur la surveillance de la mémoire d'état d'abonnement, consultez la rubrique
Performances des abonnements. Pour en savoir plus sur les bonnes pratiques en matière d'abonnements, consultez la rubrique
Présentation rapide des bonnes pratiques de création de solutions ThingWorx.
Pour obtenir un résumé sur l'utilisation des abonnements multi-événements et des abonnements ordonnés, consultez la rubrique
Multi-Event Subscriptions in ThingWorx 9.5 (en anglais).
La section suivante, Cas d'utilisation, contient des exemples de code démontrant la puissance des multi-événements et des fonctionnalités d'abonnement ordonnés et avec état, y compris les cas d'utilisation de gestion des collisions de type fenêtre de temps et de mise à jour de propriétés.
Cas d'utilisation
Cas d'utilisation 1
// Usecase 1: alert will be triggered if voltage is higher than 118V
// Check if the voltage value exceeds 118 and trigger the appropriate action.
if (events["Me_DataChange_useCase1_Voltage"].eventData.newValue.value > 118){
logger.warn("Use-Case 1 subscription has been triggered");
}
Cas d'utilisation 2
// Usecase 2: alert will be triggered if voltage is higher than 118V and current is lower than 2A
//********************** Default initialization ****************************************
if (thisSub.JSONState.Voltage === undefined)
thisSub.JSONState.Voltage = me.useCase2_Voltage; // default value from VTQ
if (thisSub.JSONState.Current === undefined)
thisSub.JSONState.Current = 0; // default value 0
//*************************************************************************************
//************************* storing the values in JSONState from eventData ***************
var aliasArray = Object.keys(events.dataShape.fields);
for (var i=0; i < aliasArray.length; i ++ ){
if (aliasArray[i] === "Me_DataChange_useCase2_Current")
// Assign the new current value to the state
thisSub.JSONState.Current = events["Me_DataChange_useCase2_Current"].eventData.newValue.value;
if (aliasArray[i] === "Me_DataChange_useCase2_Voltage") {
// Assign the new voltage value to the state
thisSub.JSONState.Voltage = events["Me_DataChange_useCase2_Voltage"].eventData.newValue.value;
}
}
//******************************************************************************************
// Check if both voltage and current meet the conditions - voltage is higher than 118V and current is lower than 2A
if (thisSub.JSONState.Voltage > 118 &&
thisSub.JSONState.Current < 2) {
logger.warn("Use-Case 2 alert !!!, Voltage is: "+thisSub.JSONState.Voltage+" and Current is: "+thisSub.JSONState.Current);
}
else
logger.warn("Use-Case 2 NO alert, Voltage is: "+thisSub.JSONState.Voltage+" and Current is: "+thisSub.JSONState.Current);
Cas d'utilisation 3
// Usecase 3: alert will be triggered if voltage is higher than 118V and it has last more than 3 minutes.
// Timer will tick every X seconds
/*
When the DataChange event causing the subscription to run,
The value of Voltage will be checked:
1. If it would be above 118, timer would start running in case its undefined.
2. If it would be below or equal to 118, timer would back to undefined.
In case time has gotten to 3 minutes (180000MS), subscription would be triggered.
*/
// Getting the name of the event that triggered the subscription
let aliasArray = Object.keys(events.dataShape.fields);
if (aliasArray[0] === "Me_DataChange_useCase3_Voltage") {
// Valtage was changed
// We want to manage thisSub.JSONState.StartTime only. We don't save the Valtage in state
var Voltage = events[aliasArray[0]].eventData.newValue.value;
if( thisSub.JSONState.StartTime === undefined ) {
if( Voltage > 118 ){
thisSub.JSONState.StartTime = Date.now();
logger.warn("Voltage = " + Voltage + " Start time was set");
}
} else{
if( Voltage <= 118 ){
delete thisSub.JSONState.StartTime; // this will set to undefine
logger.warn("Voltage = " + Voltage + " Start time was unset");
}
}
}
else{
// We are in timer event
if( thisSub.JSONState.StartTime !== undefined && Date.now() - thisSub.JSONState.StartTime > 40000 )
logger.warn("Use-Case 3 timer was triggered - Alert !!! thisSub.JSONState.StartTime = " + thisSub.JSONState.StartTime);
else
logger.warn("Use-Case 3 timer was triggered - no Alert. thisSub.JSONState.StartTime = " + thisSub.JSONState.StartTime);
}
Cas d'utilisation 4
// Usecase 4: Input 32 bit integer will be translated into 32 status properties if there is any change,
// and alert on status property potentially will be triggered.
logger.warn("Use-Case 4 subscription has been triggered");
Cas d'utilisation 5
// Usecase 5: Alert will be triggered only on Error code which has severity level 1 or 2,
// where Error severity is defined in a relational table
// Get reference to the data table
var dataTable = Things["useCase5_DataTable"];
// Get the new value of ErrorCode from the event
var ErrorCodeValue = events["Me_DataChange_useCase5_ErrorCode"].eventData.newValue.value;
// Set parameters for querying the data table
var params = {
maxItems: 100, // Maximum number of entries to retrieve
source: undefined, // Filter entries by a specific source if needed
values: undefined, // Filter entries by specific column values if needed
orderBy: undefined // Order the retrieved entries by a specific column if needed
};
// Query the data table entries
var queryResult = dataTable.QueryDataTableEntries(params);
var rows = queryResult.rows;
// Iterate through the rows and check for matching ErrorCode and severity level
for (var i = 0; i < rows.length; i++) {
var thisRow = rows[i];
// Check if the ErrorCode matches the given ErrorCodeValue
if (thisRow.ErrorCode == ErrorCodeValue) {
// Check if the Severity is either 1 or 2
if (thisRow.Severity == 1 || thisRow.Severity == 2) {
// Log a warning message indicating the matched ErrorCode and its Severity
logger.warn("Error code: " + thisRow.ErrorCode + " has a severity of: " + thisRow.Severity + ". Use-Case 5 subscription has been triggered.");
}
}
}
Cas d'utilisation 6
// Usecase 6: Alert will be trigged if sum of product count is less than 10 in past 10 minutes
// Timer will tick every 1 seconds (1000MS)
// Getting the name of the event that triggered the subscription
var alias = events.dataShape.fields;
// Check if the alias matches "Me_DataChange_useCase6_ProductCount"
if (alias == "Me_DataChange_useCase6_ProductCount") {
// Check if the productCountDict exists in the state
if (thisSub.JSONState.productCountDict != undefined) {
// Delete entries older than 10 minutes
deleteOlderThan10Minutes();
// Add a new entry with the current timestamp and the new value from the event
thisSub.JSONState.productCountDict[Date.now().toString()] = events[alias].eventData.newValue.value;
} else {
// Initialize the productCountDict as an empty object and add a new entry
thisSub.JSONState.productCountDict = {};
thisSub.JSONState.productCountDict[Date.now().toString()] = events[alias].eventData.newValue.value;
}
} else {
// Check if the startTime is undefined and set it to the current time
if (thisSub.JSONState.startTime == undefined) {
thisSub.JSONState.startTime = Date.now();
}
// Check if the productCountDict exists in the state
if (thisSub.JSONState.productCountDict != undefined) {
// Delete entries older than 10 minutes
deleteOlderThan10Minutes();
// Check if an alert should be triggered and log a warning message
if (shouldTriggerAlert()) {
logger.warn("Use-Case 6 subscription has been triggered");
}
}
}
// Function to delete entries older than 10 minutes from productCountDict
function deleteOlderThan10Minutes() {
var keys = Object.keys(thisSub.JSONState.productCountDict);
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
// Check if the time difference is greater than 10 minutes (600,000 milliseconds)
if (Date.now() - parseInt(key) > 600000) {
delete thisSub.JSONState.productCountDict[key];
}
}
}
// Function to check if an alert should be triggered
function shouldTriggerAlert() {
// Check if the sum of product count is less than 10 and if the time difference is greater than 10 minutes
if (productCountDictSUM() < 10 && Date.now() - thisSub.JSONState.startTime > 600000) {
return true;
} else {
return false;
}
}
// Function to calculate the sum of values in productCountDict
function productCountDictSUM() {
var keys = Object.keys(thisSub.JSONState.productCountDict);
var sum = 0;
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
var value = thisSub.JSONState.productCountDict[key];
sum += value;
}
return sum;
}
Cas d'utilisation 7
// Usecase 7: alert will be triggered if average current (5 minutes window) is higher than 2.5A,
// and it will be checked very 30 seconds.
// Timer will tick every 30 seconds (30000MS)
// Getting the name of the event that triggered the subscription
var alias = events.dataShape.fields;
// Checking if the data alias matches the expected value
if (alias == "Me_DataChange_useCase7_Current") {
// If the data dictionary exists, update it with the new current value
if (thisSub.JSONState.currentDict != undefined) {
deleteOlderThan5Minutes();
thisSub.JSONState.currentDict[Date.now().toString()] = events[alias].eventData.newValue.value;
} else {
// If the data dictionary doesn't exist, create it and set the start time
thisSub.JSONState.currentDict = {};
thisSub.JSONState.currentDict[Date.now().toString()] = events[alias].eventData.newValue.value;
}
} else {
if(thisSub.JSONState.startTime == undefined){
thisSub.JSONState.startTime = Date.now();
}
// If the data alias doesn't match, perform data management and potential alert triggering
if (thisSub.JSONState.currentDict != undefined) {
// Delete entries in the data dictionary older than 5 minutes
deleteOlderThan5Minutes();
// Check if conditions for triggering the alert are met
if (shouldTriggerAlert()) {
logger.warn("Use-Case 7 subscription has been triggered");
}
}
}
// Function to delete data entries older than 5 minutes
function deleteOlderThan5Minutes() {
var keys = Object.keys(thisSub.JSONState.currentDict);
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
if (Date.now() - parseInt(key) > 300000) {
delete thisSub.JSONState.currentDict[key];
}
}
}
// Function to determine if the alert should be triggered
// Only in case 5 minutes has been passed, and AVG is higher then 2.5
function shouldTriggerAlert() {
if (currentDictAVG() > 2.5 && Date.now() - thisSub.JSONState.startTime > 300000) {
return true;
} else {
return false;
}
}
// Function to calculate the average of currentDict
function currentDictAVG() {
var keys = Object.keys(thisSub.JSONState.currentDict);
var sum = 0;
var count = 0;
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
var value = thisSub.JSONState.currentDict[key];
sum += value;
count++;
}
var average = sum / count;
return average;
}
////at any data change i sum the current
////when 30 seconds has been passed i calculate AVG
//
//
//
////change property event:
//// check if there is any value older then 5, if so - delete. (can implement by get keys)
//// add the value to the dict with key of timestamp
////timer event:
//// check if there is any value older then 5, if so - delete. (can implement by get keys)
//// AVG calculation, and condition check. - not only per 30 seconds, but for all the data at 5 minutes
//
//// build function to: change if there is any value older then 5, if so - delete. (can implement by get keys)
//
// ** the first timer event will be the startTime point of the algorithm
Cas d'utilisation 8
// Usecase 8: the alert will be triggered when most recent value is greater than the previous 10 values
// Check if valuesArray state is undefined and initialize it
if (thisSub.JSONState.valuesArray === undefined) {
thisSub.JSONState.valuesArray = {};
}
// Check if valuesArray length is less than 10
let indexCounter = Object.keys(thisSub.JSONState.valuesArray).length;
if ( indexCounter < 3) {
// Add the new value to valuesArray
thisSub.JSONState.valuesArray["i" + indexCounter] = events["Me_DataChange_useCase8_prop"].eventData.newValue.value;
} else {
// The valuesArray is full
var max = 0;
// Find the maximum value in valuesArray
for (let i = 0; i < Object.keys(thisSub.JSONState.valuesArray).length; i++) {
if (thisSub.JSONState.valuesArray["i" + i] > max)
max = thisSub.JSONState.valuesArray["i" + i];
}
// Check if the new value is greater than the maximum value
if (events["Me_DataChange_useCase8_prop"].eventData.newValue.value > max)
logger.warn("Use-Case 8 Alert!!! max is " + events["Me_DataChange_useCase8_prop"].eventData.newValue.value);
// Shift the values in valuesArray by one position
for (var j = 0; j < parseInt((Object.keys(thisSub.JSONState.valuesArray)).length) - 1; j++) {
thisSub.JSONState.valuesArray["i" + j] = thisSub.JSONState.valuesArray["i" + (j + 1)];
}
// Add the new value to valuesArray using the current indexCounter
thisSub.JSONState.valuesArray["i" + (indexCounter - 1)] = events["Me_DataChange_useCase8_prop"].eventData.newValue.value;
}
logger.warn("Use-Case 8 End. valuesArray = " + thisSub.JSONState.valuesArray);