• phildunlap

    Hi Pikey4,

    The "starts" member reflects how many times the point value was logged in the state in the period the statistics are over (so consecutive polls of 'true' if logging all data will be multiple starts). If there are no samples in the period but there is a start value, the starts will show as 0 despite it being true for the whole period. In that sense the proportion may most clearly resemble what you're trying to look at. But, you could certainly figure out from the stats.startValue if it was true at the beginning.

    var stats = p.past(MINUTE, 5); //binary point
    print( stats.get(true).starts );
    print( stats.get(true).proportion );

    posted in How-To read more
  • phildunlap

    Hi carnecro,

    Hmm. I'm not sure, but maybe disabling it as a supported service on the device object, by doing something like modifying line 88 of com.serotonin.bacnet4j.obj.DeviceObject will do the trick, as then when other BACnet devices inquire about what services are supported reading multiple properties will not be among them. I'm not sure if there's a more explicit way to tell a certain device not to send multiple property requests.,,

    posted in BACnet4J general discussion read more
  • phildunlap

    That's during validation, right? Unfortunately for the email handler they're not quite the same. You could have a little preamble like this,

    if(typeof evt === 'undefined')
      evt = event;
    if(typeof event === 'undefined')
      event = evt;
    

    I did fix this for 3.6, adding event into the runtime of the Email Handler as an EventInstanceWrapper, and evt as the EventInstance in validation.

    posted in Scripting general Discussion read more
  • phildunlap

    It will be possible in 3.6 via

    //add ExcelReportVO available under 'report' variable name for post processing script
    for(var k = 0; k < report.getTimeSeries().length; k+=1) {
      //epoch time of time series beginning and end, for actual run
      var startTimestamp = report.getTimeSeries()[k].getStartTimestamp();
      var finishTimestamp = report.getTimeSeries()[k].getFinishTimestamp();
    }
    

    posted in User help read more
  • phildunlap

    Hi Ian,

    It looks like evt is the right key in email scripts. The validation code uses the same code as the set point handler, so that's why the validation would have worked. evt was probably used because it was already in the model under that key being passed to the Mango/ftl/ files. It's easy to have it under both keys, though. Thanks for bringing it to our attention!

    Edit:
    evt is not quite the same. It is an EventInstance instead of an EventInstanceWrapper, so you'd perhaps want evt.getMessageString() instead of evt.getMessage() if you use evt . I will add the EventInstanceWrapper under the event key.

    posted in Scripting general Discussion read more
  • phildunlap

    What issue did you foresee with the POP3 points? Multiple values per point per email?

    Here's the script I came up with,

    //JsonEmport.setImportDuringValidation(true);
    var pointIndexes = this.pointIndexes;
    var timezone = this.timezone;
    if(typeof pointIndexes === 'undefined') {
      pointIndexes = this.pointIndexes = pointIndexes = {
        windDirection: 2,
        windSpeed: 3,
        barometer: 4,
        rh: 5,
        temperature: 6,
        dewPoint: 7,
        sensorPower: 8
      }; //Create points on the scripting data source with those variable names
    }
    
    var lines = p.value.replace(/\r/g, "").split("\n");
    for( var k = 0; k < lines.length; k+=1 ) {
      var line = lines[k];
      var dateMatch = /^.*?(\d\d)\/(\d\d)\/(\d\d)\s*\|\s*(\d\d):(\d\d):(\d\d).*/.exec(line);
      if(dateMatch !== null) {
          //Assume times are in server timezone, if UTC use next line, else manual offset
        var date = new Date(parseInt("20" + dateMatch[3]), parseInt(dateMatch[2])-1, parseInt(dateMatch[1]),
                            parseInt(dateMatch[4]), parseInt(dateMatch[5]), parseInt(dateMatch[6]));
    //    var utcDate = new Date(Date.UTC(parseInt("20" + dateMatch[3]), parseInt(dateMatch[2])-1, parseInt(dateMatch[1]),
    //                        parseInt(dateMatch[4]), parseInt(dateMatch[5]), parseInt(dateMatch[6])))
        var lineData = line.split("|");
        for(var point in pointIndexes) {
            var nextValue = lineData[pointIndexes[point]];
            nextValue = nextValue.replace(/\s+/g, "");
            if(typeof this[point] === 'undefined') {
                //Create a data point using our base point,
                var newDp = JSON.parse(JsonEmport.dataPointQuery("eq(xid,DP_EmailParser_BasePoint)")).dataPoints[0];
                newDp.name = point;
                newDp.pointLocator.varName = point;
                newDp.enabled = true;
                delete newDp.xid; //get a randomly generated xid
                JsonEmport.doImportGetStatus(JSON.stringify({"dataPoints":[newDp]}));
                RuntimeManager.sleep(1000);
            }
            this[point].set(parseFloat(nextValue), date.getTime());
        }
      }
    }
    

    Where for a similar email you would only need to change the pointIndexes object to be the names you want the points in those columns to be created with. I did it with the formatting still in the email (just add \s* all around some regex!) Here's the JSON for the data source with the base point:

    {
       "dataSources":[
          {
             "xid":"DS_8ffb2983-dd50-42e1-9b43-706bc0a10eb2",
             "name":"Email Parser",
             "enabled":false,
             "type":"SCRIPTING",
             "alarmLevels":{
                "SCRIPT_ERROR":"URGENT",
                "DATA_TYPE_ERROR":"URGENT",
                "POLL_ABORTED":"URGENT",
                "LOG_ERROR":"URGENT"
             },
             "purgeType":"YEARS",
             "updateEvent":"CONTEXT_UPDATE",
             "context":[
                {
                   "varName":"p",
                   "dataPointXid":"DP_f4a4b495-f7a8-4ddc-a619-5172835ebe30",
                   "updateContext":true
                }
             ],
             "logLevel":"NONE",
             "cronPattern":"",
             "executionDelaySeconds":0,
             "historicalSetting":false,
             "logCount":5,
             "logSize":1.0,
             "script":"\/\/JsonEmport.setImportDuringValidation(true);\nvar pointIndexes = this.pointIndexes;\nvar timezone = this.timezone;\nif(typeof pointIndexes === 'undefined') {\n  pointIndexes = this.pointIndexes = pointIndexes = {\n    windDirection: 2,\n    windSpeed: 3,\n    barometer: 4,\n    rh: 5,\n    temperature: 6,\n    dewPoint: 7,\n    sensorPower: 8\n  }; \/\/Create points on the scripting data source with those variable names\n}\n\nvar lines = p.value.replace(\/\\r\/g, \"\").split(\"\\n\");\nfor( var k = 0; k < lines.length; k+=1 ) {\n  var line = lines[k];\n  var dateMatch = \/^.*?(\\d\\d)\\\/(\\d\\d)\\\/(\\d\\d)\\s*\\|\\s*(\\d\\d):(\\d\\d):(\\d\\d).*\/.exec(line);\n  if(dateMatch !== null) {\n      \/\/Assume times are in server timezone, if UTC use next line, else manual offset\n    var date = new Date(parseInt(\"20\" + dateMatch[3]), parseInt(dateMatch[2])-1, parseInt(dateMatch[1]),\n                        parseInt(dateMatch[4]), parseInt(dateMatch[5]), parseInt(dateMatch[6]));\n\/\/    var utcDate = new Date(Date.UTC(parseInt(\"20\" + dateMatch[3]), parseInt(dateMatch[2])-1, parseInt(dateMatch[1]),\n\/\/                        parseInt(dateMatch[4]), parseInt(dateMatch[5]), parseInt(dateMatch[6])))\n    var lineData = line.split(\"|\");\n    for(var point in pointIndexes) {\n        var nextValue = lineData[pointIndexes[point]];\n        nextValue = nextValue.replace(\/\\s+\/g, \"\");\n        if(typeof this[point] === 'undefined') {\n            \/\/Create a data point using our base point,\n            var newDp = JSON.parse(JsonEmport.dataPointQuery(\"eq(xid,DP_EmailParser_BasePoint)\")).dataPoints[0];\n            newDp.name = point;\n            newDp.pointLocator.varName = point;\n            newDp.enabled = true;\n            delete newDp.xid; \/\/get a randomly generated xid\n            JsonEmport.doImportGetStatus(JSON.stringify({\"dataPoints\":[newDp]}));\n            RuntimeManager.sleep(1000);\n        }\n        this[point].set(parseFloat(nextValue), date.getTime());\n    }\n  }\n}",
             "scriptPermissions":{
                "customPermissions":"",
                "dataPointReadPermissions":"superadmin",
                "dataPointSetPermissions":"superadmin",
                "dataSourcePermissions":"superadmin"
             },
             "editPermission":"",
             "purgeOverride":false,
             "purgePeriod":1
          }
       ],
       "dataPoints":[
          {
             "xid":"DP_EmailParser_BasePoint",
             "name":"BasePoint",
             "enabled":false,
             "loggingType":"ALL",
             "intervalLoggingPeriodType":"MINUTES",
             "intervalLoggingType":"AVERAGE",
             "purgeType":"YEARS",
             "pointLocator":{
                "dataType":"NUMERIC",
                "contextUpdate":false,
                "settable":true,
                "varName":"basePoint"
             },
             "eventDetectors":[
             ],
             "plotType":"SPLINE",
             "rollup":"NONE",
             "unit":"",
             "simplifyType":"NONE",
             "chartColour":"",
             "chartRenderer":{
                "type":"IMAGE",
                "timePeriodType":"DAYS",
                "numberOfPeriods":1
             },
             "dataSourceXid":"DS_8ffb2983-dd50-42e1-9b43-706bc0a10eb2",
             "defaultCacheSize":1,
             "deviceName":"Email Parser",
             "discardExtremeValues":false,
             "discardHighLimit":1.7976931348623157E308,
             "discardLowLimit":-1.7976931348623157E308,
             "intervalLoggingPeriod":1,
             "intervalLoggingSampleWindowSize":0,
             "overrideIntervalLoggingSamples":false,
             "preventSetExtremeValues":false,
             "purgeOverride":false,
             "purgePeriod":1,
             "readPermission":"",
             "setExtremeHighLimit":1.7976931348623157E308,
             "setExtremeLowLimit":-1.7976931348623157E308,
             "setPermission":"",
             "tags":{
             },
             "textRenderer":{
                "type":"ANALOG",
                "useUnitAsSuffix":true,
                "unit":"",
                "renderedUnit":"",
                "format":"0.000"
             },
             "tolerance":0.0
          }
       ]
    }
    

    posted in How-To read more
  • phildunlap

    Hi psysak,

    it's not a stupid question! But, it is a very hard question. "Best way," being largely personal. The bulk edit tool can empower you to disable all but the device name, the name, and the tag in question. Then editing a point is a matter of entering that point's tag value in the column header, checking the point's box, and hitting the start bulk edit button. Three actions to repeat, not so bad.

    Perhaps it's important to consider how you've structured the change you wish to make. If it's a simple mix, like maybe a JSON object of

    { "dataPointXid1": {
        "editTagKey": "newValue"
    }}
    

    It may be very simple to iterate over this object in a script and update the points via JsonEmport.

    It may also be that you've got intense Excel skills, in which case you could download a CSV for the points in question through the bulk editor, do the requisite Excel wizardry to alter the tags/interestedTagKey column to have the appropriate values, then reupload it.

    So the answer is we've provided a lot of ways to go about doing it. It's really just about what sort of method you find most suited to yourself, as all those would work.

    posted in User help read more
  • phildunlap

    Not currently. It's a relatively new feature and may not have had every possible use considered and accounted for yet. I do like the idea, I will see about adding it.

    posted in User help read more
  • phildunlap

    Hi bkeppel, welcome to the forum!

    I have tried to write a sleep function

    The JavaScript environment in Mango provide a sleep function, RuntimeManager.sleep(milliseconds)

    Pseudocode is as follows:
    If startTimer = True and it has been 10s since startTimer has been set to True, set startRules = true.
    Note: I currently have startTimer enabled to udate context.

    It sounds to me like what you want is to put a "State" event detector on the startTimer point, having a 10 second duration (to ensure it's been in the start timer state for 10 seconds), and then create an event handler to do the control that you would like to do after the event becomes active (eliminating the need to sleep in the script).

    You should do what makes sense to you though, if knowing about the existing sleep function is enough to achieve what you're working on.

    posted in Scripting general Discussion read more
  • phildunlap

    Hi Ian,

    The number next to the p is the dataPointId, its id column in the database. I would not consider it particularly important that all variable names abide this convention. In fact, should you recreate your system via JSON import at some point all those numbers may change!

    If you needed to know that while structuring the JSON to import a meta point, you could use the DataPointQuery.byXid("context point xid here").getId() for points that exist before the meta point. If you were trying to do the import at the same time you'd have to track the auto_increment state of the dataPoints table in the database, which is assuredly more trouble than its worth (in my eyes).

    posted in Scripting general Discussion read more