// Version: 2.0.251218 var deviceGuid = ""; // GUID from the IO-unit "Kiona - Edge AI" var source = "edgeai.csv"; // Name of the tag-list for "Kiona - Edge AI" var username = ""; // API - Client ID var password = ""; // API - Client Secret var createSteeringsTags = false; // Set as "true" when you need to create new tags in edgeai.csv, otherwise leave as "false" var properties = [ { name: "", // Building name + Address (used for logging) contract: 9999999, // Contract number in Edge, will also act as prefix for all 5 AI-tags shouldUseSteering: true, // Is it "true" or "false" that we should steer this building? outdoortemp: "", // Tag used for the outdoor sensor (full tag name) circuitPrefix: "", // Tag prefix for the circuit we wish to steer, used to fetch correct X/Y values when creating new AI-tags for fallback curve. numberOfXY: 5, // Amount of setpoints/breakpoints in heating curve startsHigh: false, // Is it "true" or "false" that X1 has a positive value(ex 20ºC or 15ºC) (False is default) measuringTags: [ { name: "", valueType: "6", dataPoint: "1" }, // Secondary heating - Supply | ºC { name: "", valueType: "6", dataPoint: "1" }, // Secondary heating - Return | ºC { name: "", valueType: "6", dataPoint: "1" }, // Hot water - Supply | ºC { name: "", valueType: "6", dataPoint: "1" }, // Hot water - Return (Warm water circulation) | ºC { name: "", valueType: "6", dataPoint: "1" }, // Primary heating - Supply | ºC { name: "", valueType: "6", dataPoint: "1" }, // Primary heating - Return | ºC { name: "", valueType: "31", dataPoint: "140" }, // Tap water | m³ { name: "", valueType: "55", dataPoint: "140" }, // Tap water | l { name: "", valueType: "50", dataPoint: "110" }, // Electricity | kWh { name: "", valueType: "35", dataPoint: "110" }, // Heat energy | MWh { name: "", valueType: "8", dataPoint: "110" }, // Heat energy | kWh { name: "", valueType: "33", dataPoint: "140" }, // Cold water | m³ { name: "", valueType: "6", dataPoint: "1" }, // Temperature | ºC { name: "", valueType: "6", dataPoint: "1" }, // Temperature | ºC { name: "", valueType: "6", dataPoint: "1" }, // Temperature | ºC { name: "", valueType: "6", dataPoint: "1" } // Temperature | ºC ], roomSensors: { suffix: { tempSuffix: "_PV", // The end-part of the tag for the temperature datapoint humSuffix: "" // The end-part of the tag for the humidity datapoint. (If empty it will be ignored) }, prefix: [ "", "", "" ] } } ]; var edgeTimestampTag = "EDGEAI_TS"; var edgeForceReadTag = "EDGEAI_FORCEREAD"; var tokenTag = "EDGEAI_TOKEN"; var timestampTag = "EDGEAI_TOKENTS"; var lastStateSuffix = "LAST"; var baseUrl = "https://connector.egain.io/v1/"; var pushUrl = "https://connector.egain.io/v1/"; var tokenUrl = "https://identity.egain.io/connect/token"; var tagChange = false; var now = System.DateTime.Now; var force = false; var lastRun = ""; var token = ""; var aiTags = { iotag: "_AI_IO", manaienable: "_AI_MCMD", aienable: "_AI_V", aireg: "_AI", orgcsp: "_AI_CSP1" }; var DebugLevel = { NONE: 0, ERROR: 1, ALL: 2 }; var debugLevel = DebugLevel.ERROR; if (createSteeringsTags) { createEdgeTags(); } else if (tags.ContainsKey(edgeForceReadTag) && tags[edgeForceReadTag].Value == "1") { // Om icke init och forceradtaggen är satt till 1 sätt force till true logDebug("Doing forced reading"); logDebug("lastRun: " + lastRun); force = true; } tryUpdateLastRunTime(); //Om nyare än 5 min, kör bara uppdatera värden- //på alla kurvskirvning, kolla om X1 < Xmax. Vänd då ordning if (lastRun) { logDebug("lastRun: " + lastRun.ToString()); } run(); if (tagChange) { saveTagSource(); } tagWrite(edgeForceReadTag, "0"); if (!createSteeringsTags) { updateTags(); } return "OK"; function run() { var shouldRun = force || createSteeringsTags || !lastRun || lastRun < System.DateTime.Now.AddMinutes(-10); logDebug("run: " + shouldRun); if (shouldRun) { try { if (createSteeringsTags || handleToken()) { for (var i = 0; i < properties.length; i++) { var prop = properties[i]; if (createSteeringsTags) { createTags(prop); } else { try { getAndSetValues(prop); pushValues(prop); } catch (e) { logError("run: " + i); logError(e); } } } } else { logDebug("Token not handled"); } } catch (e) { logError("run"); logError(e); } tagWrite(edgeTimestampTag, now.ToString("s")); } } function getAndSetValues(build) { try { logDebug("getAndSetValues: " + build.name); if (!build.shouldUseSteering) { logDebug("Not using steering for: " + build.name); return true; } var response = getCurve(build); if (!response) { throw new Error("Failed to get response for: " + build.name); } logDebug("status:" + response.status); logDebug("result:" + response.result); var jObject = Newtonsoft.Json.Linq.JObject.Parse(response.result); jObject.Add(new Newtonsoft.Json.Linq.JProperty("timestamp", now.ToString("s"))); var ioTag = getFullName(build, aiTags.iotag); tagWrite(ioTag, jObject.ToString()); } catch (e) { logError("getAndSetValues"); logError(e); } try { //Hitta när utetempen kan bli 0. var ageAndJson = getAgeAndJson(build); if (!ageAndJson) { throw new Error("getAgeAndJson failed for: " + build.name); } var isActive = checkActive(build); logDebug("isActive: " + isActive); if (isActive) { setCurveFromJson(ageAndJson, build); return true; } } catch (e) { logError("getAndSetValues"); logError(e); return false; } } function pushValues(build) { try { logDebug("pushValues: " + build.name); var readings = JObject(); if (tryGetTag(build.outdoortemp)) { readings.Add(JProperty("outdoorTemp", getValidValue(build.outdoortemp))); } readings.Add(JProperty("timestamp", formatTimestamp(now))); var serializedSteeringBody = serializeJObject(readings); var url = pushUrl + "steering/groups/" + build.contract + "/readings"; logDebug("Steering request body:" + serializedSteeringBody); logDebug("Steering request url: " + url); var response = webPost(url, serializedSteeringBody, "application/json", token); logDebug("Steering response result: " + response.result); logDebug("Steering response status: " + response.status); var body = JArray(); addMeasuringData(build, body); addRoomSensors(build, body); var serializedMeteringBody = serializeJObject(body); var url = pushUrl + "metering/contracts/" + build.contract + "/values"; logDebug("serializedMeteringBody: " + serializedMeteringBody); logDebug("url: " + url); var meteringResponse = webPost(url, serializedMeteringBody, "application/json", token); logDebug("Metering response result:" + meteringResponse.result); logDebug("Metering response status:" + meteringResponse.status); return serializedMeteringBody; } catch (e) { logError("pushValues"); logError(e); } return null; } function addRoomSensors(build, values) { try { logDebug("addRoomSensors: " + build.name); var shouldAdd = build.roomSensors && build.roomSensors.prefix && build.roomSensors.prefix.length; if (!shouldAdd) { logDebug("No sensors to add"); return; } for (var i = 0; i < build.roomSensors.prefix.length; i++) { var sensor = build.roomSensors.prefix[i]; logDebug("Trying to add sensor: " + sensor); if (!sensor) { continue; } var sensorData = JObject({ externalId: sensor }); var dataPoints = JArray(); if (build.roomSensors.suffix.tempSuffix) { var tempTag = sensor + build.roomSensors.suffix.tempSuffix; var tag = tryGetTag(tempTag); if (!tag) { logDebug("Tag not found: " + tempTag); } else { var value = getValidValue(tempTag); dataPoints.Add( JObject({ dataPointTypeId: "1", valueTypeId: "1", values: JArray([ JObject({ timestamp: formatTimestamp(tag.Timestamp), value: value }) ]) }) ); } } if (build.roomSensors.suffix.humSuffix) { var humTag = sensor + build.roomSensors.suffix.humSuffix; var tag = tryGetTag(humTag); if (!tag) { logDebug("Tag not found: " + humTag); } else { var value = getValidValue(humTag); dataPoints.Add( JObject({ dataPointTypeId: "31", valueTypeId: "4", values: JArray([ JObject({ timestamp: formatTimestamp(tag.Timestamp), value: value }) ]) }) ); } } sensorData.Add(JProperty("dataPoints", dataPoints)); logDebug("sensorData: " + serializeJObject(sensorData)); values.Add(sensorData); } } catch (error) { logError("tryAddRoomSensors"); logError(error); } } function addMeasuringData(build, values) { var shouldAdd = build.measuringTags && build.measuringTags.length ? true : false; logDebug("tryAddOtherDataPoints: " + shouldAdd); if (!shouldAdd) { return; } for (var i = 0; i < build.measuringTags.length; i++) { var item = build.measuringTags[i]; var tag = tryGetTag(item.name); if (!tag) { continue; } values.Add(getDataPoint(tag, item.dataPoint, item.valueType)); } } function getDataPoint(tag, dataPointType, valueType) { var value = getValidValue(tag.Name); var values = JArray([ JObject({ value: value, timestamp: formatTimestamp(tag.Timestamp) }) ]); var dataPoints = JArray([ JObject({ dataPointTypeId: dataPointType, valueTypeId: valueType, values: values }) ]); var dataPoint = JObject({ externalId: tag.Name, dataPoints: dataPoints }); return dataPoint; } function formatTimestamp(dateTime) { return dateTime.ToUniversalTime().ToString("s") + "Z"; } function checkStateChange(state, lastState) { var current = parseInt(state); var last = parseInt(lastState); logDebug("checkStateChange: current:" + current + " - last:" + last); var state = { changed: current !== last, toDisabled: current < last, toEnabled: current > last }; return state; } function updateTags() { logDebug("updateTags"); for (var i = 0; i < properties.length; i++) { try { var build = properties[i]; logDebug("updateTags: " + build.name); if (!build.shouldUseSteering) { logDebug("Not using steering for: " + build.name); continue; } var manualAiEnableTag = getFullName(build, aiTags.manaienable); var manualAiEnabled = getValidValue(manualAiEnableTag); var manualAiEnabledLast = getValidValue(manualAiEnableTag + lastStateSuffix); var aiEnableTag = getFullName(build, aiTags.aienable); //Kolla hur gamla värden vi har på IO //Om äldre än 30-60 min? Återställ //Måste få med ai-enable också. var stateChange = checkStateChange(manualAiEnabled, manualAiEnabledLast); var toEnabled = stateChange.toEnabled; logDebug("toEnabled: " + toEnabled); var toDisabled = stateChange.toDisabled; logDebug("toDisabled: " + toDisabled); tags[manualAiEnableTag + lastStateSuffix].WriteValue(manualAiEnabled, false); var ageAndJson = null; if (toEnabled) { logDebug("Pushing curve for: " + build.name); handleToken(); pushCurve(build); // Skicka baskurva till edge få optimerad kurva tillbaks var getCurveResponse = getCurve(build); // Skicka temperatur till edge, få optimerad kurva tillbaks logDebug("getCurve response: " + getCurveResponse.result); logDebug("getCurve status: " + getCurveResponse.status); if (getCurveResponse && getCurveResponse.result) { saveCurve(build, getCurveResponse); // Spara optimerad kurva till tag } else { logDebug("Failed to get curve for: " + build.name); } ageAndJson = getAgeAndJson(build); // Kolla om datan är tillräckligt "färsk" if (checkAgeOfCurve(build, ageAndJson, 60)) { setCurveFromJson(ageAndJson, build); // Skriv kurva till styrtag? } //Pusha bara om olika från edge kurva //Hämta ny kurva. } var supplyPrefix = getFullName(build, aiTags.aireg); try { //Sätt en resetbool istället och gör utanför. var shouldReset = true; if (ageAndJson == null) { ageAndJson = getAgeAndJson(build); } if (ageAndJson != null) { var timestamp = ageAndJson["timestamp"].ToString("s"); logDebug("ageAndJson timestamp: " + timestamp); var lastUpdate = System.DateTime.ParseExact(timestamp, "yyyy-MM-ddTHH:mm:ss", System.Globalization.CultureInfo.InvariantCulture); logDebug("lastUpdate: " + lastUpdate); if (manualAiEnabled == "1") { if (lastUpdate > now.AddMinutes(-60)) { logDebug("Value is OK"); shouldReset = false; } else { logDebug("Value too old for: " + build.name); } } } logDebug("shouldReset: " + shouldReset); if (shouldReset) { logDebug("Reset for:" + aiEnableTag); tags[aiEnableTag].WriteValue("0", true); logDebug("toDisabled: " + toDisabled); if (toDisabled) { writeToCurve(supplyPrefix, build.circuitPrefix, build.numberOfXY); } } else { tagWrite(aiEnableTag, "1"); } } catch (e) { logError("updateTags"); logError(e); } if (aiTags.orgcsp) { var setPoint = getSetpoint(supplyPrefix, build.numberOfXY, 20, 80, getValidValue(build.outdoortemp)); logDebug("orgcsp: " + setPoint); tags[getFullName(build, aiTags.orgcsp)].WriteValue(setPoint, true); } } catch (e) { logError("updateTags"); logError(e); } } } function saveCurve(build, res) { logDebug("saveCurve: " + build.name); try { var jObject = Newtonsoft.Json.Linq.JObject.Parse(res.result); if (jObject) { var tagName = getFullName(build, aiTags.iotag); jObject.Add(new Newtonsoft.Json.Linq.JProperty("timestamp", now.ToString("s"))); tagWrite(tagName, jObject.ToString()); } else { logDebug("Failed parsing response: " + build.name); } } catch (e) { logError(e); } } function checkAgeOfCurve(build, ageAndJson, maxAge) { try { logDebug("checkAgeOfCurve: " + build.name); if (ageAndJson != null) { var timestamp = ageAndJson["timestamp"].ToString("s"); logDebug("timestamp: " + timestamp); var lastUpdate = System.DateTime.Parse(timestamp); logDebug("lastUpdate: " + lastUpdate); var minValidTimestamp = now.AddMinutes(-maxAge); logDebug("minValidTimestamp: " + minValidTimestamp); if (lastUpdate > minValidTimestamp && getValidValue(getFullName(build, aiTags.manaienable)) == "1") { logDebug("lastUpdate and value is OK"); return true; } else { logDebug("lastUpdate too old for: " + build.name); return false; } } logDebug("ageAndJson is null for: " + build.name); return false; } catch (e) { logError(e); return false; } } function writeToCurve(fromPrefix, toPrefix, numberOfXY) { for (var i = 1; i < numberOfXY + 1; i++) { var tagFromX = fromPrefix + "_X" + i; var tagFromY = fromPrefix + "_Y" + i; var tagToX = toPrefix + "_X" + i; var tagToY = toPrefix + "_Y" + i; if (tags.ContainsKey(tagToX) && tags.ContainsKey(tagToY) && tags.ContainsKey(tagFromX) && tags.ContainsKey(tagFromY)) { var newValueX = tags[tagFromX].Value; if (tags[tagToX].Value != newValueX) { logDebug("Writing from: " + tagFromX + " to " + tagToX + " with value: " + newValueX); tags[tagToX].WriteValue(newValueX, true); } var newValueY = tags[tagFromY].Value; if (tags[tagToY].Value != newValueY) { logDebug("Writing from: " + tagFromY + " to " + tagToY + " with value: " + newValueY); tags[tagToY].WriteValue(newValueY, true); } } } } function setCurveFromJson(jObject, build) { logDebug("setCurveFromJson: " + build.name); var steeringCurve = jObject["optimizedSteeringCurve"]; var newSteeringCurve = []; for (var point in steeringCurve) { newSteeringCurve.push([point["x"].ToString(), point["y"].ToString()]); } if (!build.startsHigh) { newSteeringCurve.reverse(); } logDebug("newSteeringCurve.length: " + newSteeringCurve.length + " - numberOfXY: " + build.numberOfXY + " - prefix: " + build.circuitPrefix + " - startsHigh: " + build.startsHigh); logDebug(newSteeringCurve); if (build.numberOfXY != newSteeringCurve.length) { logDebug("numberOfXY mismatch"); return; } for (var i = 1; i < build.numberOfXY + 1; i++) { var tagNameX = build.circuitPrefix + "_X" + i; var tagNameY = build.circuitPrefix + "_Y" + i; if (tags.ContainsKey(tagNameX) && tags.ContainsKey(tagNameY)) { var tagX = tags[tagNameX]; var tagY = tags[tagNameY]; var valueX = newSteeringCurve[i - 1][0]; var newPointX = toNumber(valueX); var oldPointX = toNumber(tagX.Value); logDebug("tagX: " + tagNameX + " - newPointX: " + newPointX + " (" + valueX + ") " + "- oldPointX: " + oldPointX); if (newPointX != oldPointX) { logDebug("Updating value for " + tagNameX); tagX.WriteValue(toString(newPointX), true); } var valueY = newSteeringCurve[i - 1][1]; var newPointY = toNumber(valueY); var oldPointY = toNumber(tagY.Value); var hasBoundaryY = tagY.Engmin < tagY.Engmax; if (hasBoundaryY) { var adjustedPointY = adjustForBoundary(newPointY, tagY); if (adjustedPointY != newPointY) { logDebug("Adjusted value for " + tagNameY + " from " + newPointY + " to " + adjustedPointY); } newPointY = adjustedPointY; } logDebug("tagY: " + tagNameY + " - newPointY: " + newPointY + " (" + valueY + ") " + "- oldPointY: " + oldPointY); if (newPointY != oldPointY) { logDebug("Updating value for " + tagNameY); tagY.WriteValue(toString(newPointY), true); } } } } function getAgeAndJson(build) { try { logDebug("getAgeAndJson: " + build.name); var tagName = getFullName(build, aiTags.iotag); logDebug("tagName: " + aiTags.iotag); var jObject = Newtonsoft.Json.Linq.JObject.Parse(tags[tagName].Value); var dateTimes = toDateTimeArray(jObject["utcDateTime"]); var first = new System.DateTime(dateTimes[0]).ToLocalTime(); var age = now - first; if (!jObject || (age && age > new System.TimeSpan(25, 0, 0))) { logDebug(build.name + " forecast too old: " + age); return null; } return jObject; } catch (e) { logError("getAgeAndJson"); logError(e); return null; } } function handleToken() { logDebug("handleToken"); var checkUrl = baseUrl + "steering/groups/" + properties[0].contract; logDebug("checkUrl: " + checkUrl); token = tagRead(tokenTag); logDebug("token: " + token); var tokenTimestamp = tagRead(timestampTag); logDebug("timestampTag: " + timestampTag); logDebug("tokenTimestamp: " + tokenTimestamp); var validUntil = 0; if (!(parseInt(tokenTimestamp) > -1)) { tokenTimestamp = 0; } try { validUntil = new System.DateTime(tokenTimestamp); var tokenIsInvalid = validUntil < now; if (!tokenIsInvalid) { var webcheck = webPost(checkUrl, "{}", "application/json", token); logDebug("webcheck:" + webcheck); tokenIsInvalid = !webcheck || !webcheck.result || webcheck.result.indexOf("401") != -1; } if (tokenIsInvalid) { var body = "grant_type=client_credentials&client_id=" + username + "&client_secret=" + password + "&scope=Connector Read Write Steering"; var response = webPost(tokenUrl, body, "application/x-www-form-urlencoded"); var result = Newtonsoft.Json.Linq.JObject.Parse(response.result); logDebug("result: " + result); var accessToken = result.GetValue("access_token").ToString(); if (accessToken) { token = accessToken; tagWrite(tokenTag, accessToken); var expiresIn = result.GetValue("expires_in"); logDebug("expiresIn: " + expiresIn); var ticks = new System.DateTime(parseFloat(expiresIn)).Ticks; logDebug("ticks: " + ticks); validUntil = now.AddSeconds(ticks); var validUntilTicks = validUntil.Ticks.ToString(); logDebug("validUntil: " + validUntil + " (" + validUntilTicks + ")"); tagWrite(timestampTag, validUntilTicks); } else { logDebug("Failed to get token"); } } } catch (e) { logError("handleToken"); logError(e); } //Hur koll att vi har ok? return validUntil >= now; } function webReq(url, token) { var res = ""; try { var webClient = new System.Net.WebClient(); webClient.Headers.Add("User-Agent:Web Port"); webClient.Headers[System.Net.HttpRequestHeader.Authorization] = "Bearer " + token; res = webClient.DownloadString(url); } catch (e) { logError("webReq: " + url); logError(e); } return res; } function getFullName(build, suffix) { suffix = typeof suffix === "string" || typeof suffix === "number" ? String(suffix) : ""; var contract = build && build.contract; var prefix = typeof contract === "string" || typeof contract === "number" ? String(contract) : ""; if (!prefix || !suffix) { return suffix; } var prefixIndex = prefix.lastIndexOf("_"); var suffixIndex = suffix.indexOf("_"); var endsWith = prefixIndex === prefix.length - 1; var startsWith = suffixIndex === 0; if (endsWith && startsWith) { prefix = prefix.substring(0, prefixIndex); } else if (!endsWith && !startsWith) { prefix += "_"; } return prefix + suffix; } function toDateTimeArray(utd) { var dta = []; for (var dt in utd) { dta.push(toLocalTime(dt).Ticks); } return dta; } function closestBottom(array, num) { var k = 0; var index = 0; var minDiff = 100000000000000000000; var ans; for (k in array) { var m = Math.abs(num - array[k]); if (m < minDiff && array[k] <= num) { minDiff = m; ans = array[k]; index = k; } } return [ans, index]; } function parseLocalTime(dt) { var d = dt.ToString(); return d.Substring(0, 4) + "" + d.Substring(5, 2) + "" + d.Substring(8, 2) + "" + d.Substring(11, 2) + "" + d.Substring(14, 2) + "" + d.Substring(17, 2); } function toLocalTime(dt) { //Newtonsoft converts data to local timeformat. return System.DateTime.Parse(dt); } function webPost(url, body, type, auth) { logDebug("webPost: " + url); var res = {}; try { var httpWebRequest = System.Net.WebRequest.Create(url); if (auth) httpWebRequest.Headers[System.Net.HttpRequestHeader.Authorization] = "Bearer " + auth; httpWebRequest.ContentType = type; httpWebRequest.Method = "POST"; var streamWriter = new System.IO.StreamWriter(httpWebRequest.GetRequestStream()); streamWriter.Write(body); streamWriter.Close(); streamWriter.Dispose(); var httpResponse = httpWebRequest.GetResponse(); var streamReader = new System.IO.StreamReader(httpResponse.GetResponseStream()); res.status = httpResponse.StatusCode; res.result = streamReader.ReadToEnd(); streamReader.Close(); streamReader.Dispose(); } catch (e) { logError("webPost"); logError(e); } return res; } function pushCurve(build) { try { logDebug("pushCurve: " + build.name); var currentSteeringCurve = JArray(); if (aiTags.aireg) { var aiPrefix = getFullName(build, aiTags.aireg); for (var i = 1; i < build.numberOfXY + 1; i++) { var tagX = aiPrefix + "_X" + i; var tagY = aiPrefix + "_Y" + i; var hasX = tags.ContainsKey(tagX); var hasY = tags.ContainsKey(tagY); if (hasX && hasY) { var x = getValidValue(tagX); var y = getValidValue(tagY); if (x && y) { var point = JObject({ x: x, y: y }); currentSteeringCurve.Add(point); } else { logDebug("Invalid values: " + tagX + " (" + tags[tagX].Value + ")" + " - " + tagY + " (" + tags[tagY].Value + ")"); return null; } } else { var missingTags = []; if (!hasX) { missingTags.push(tagX); } if (!hasY) { missingTags.push(tagY); } logDebug("Tags not found: " + missingTags.join(", ")); } } } var body = JObject({ Settings: JObject({ CurrentSteeringCurve: currentSteeringCurve }) }); var serializedBody = serializeJObject(body); logDebug("serializedBody: " + serializedBody); var response = webPost(pushUrl + "steering-settings/groups/" + build.contract, serializedBody, "application/json", token); return response; } catch (e) { logError(e); return null; } } function getCurve(build) { try { logDebug("getCurve: " + build.name); var body = JObject({ groupName: build.contract }); var outDoorTempTag = tryGetTag(build.outdoortemp); if (outDoorTempTag) { tagRead(build.outdoortemp); var outdoorTemp = tagValue(build.outdoortemp); if (outDoorTempTag.IsValid) { body.Add(JProperty("outdoorTemperature", outdoorTemp)); } else { logDebug("No valid value for " + build.outdoortemp + ": " + outDoorTempTag.Value); } } var serializedBody = serializeJObject(body); logDebug("body:" + serializedBody); var res = webPost(baseUrl + "steering/groups/" + build.contract, serializedBody, "application/json", token); return res; } catch (e) { logDebug("getCurve"); logError(e); return null; } } function checkActive(build) { logDebug("checkActive: " + build.name); if (aiTags.aienable) { var tagName = getFullName(build, aiTags.aienable); logDebug("tagName:" + tagName); if (tagName && tags.ContainsKey(tagName)) { var value = getValidValue(tagName); if (value == "1") { logDebug(build.name + " is active"); return true; } logDebug(build.name + " is inactive"); } else { logDebug("Tag not found: " + tagName); } } return false; } function linearr(max, min, x, y, val) { val = parseFloat(val); var xl = x.length; if (parseFloat(x[0]) > parseFloat(x[xl - 1])) { for (var k = 1; k < xl; k++) { logDebug("k= " + k + " val<=x[k] : val=" + val + " x[k]=" + x[k]); if (val >= x[k]) { return line(max, min, x[k], x[k - 1], y[k], y[k - 1], val); } } return max; } else { for (var k = 1; k < xl; k++) { logDebug("k= " + k + " val<=x[k] : val=" + val + " x[k]=" + x[k]); if (val <= x[k]) { return line(max, min, x[k], x[k - 1], y[k], y[k - 1], val); } } return min; } } function createTagArr(tagname, suffix, numbertags) { var rtaga = []; var valid = true; for (var j = 0; j < numbertags; j++) { var vv = getValidValue(tagname + "_" + suffix + (j + 1)); if (vv) rtaga[j] = parseFloat(vv); else valid = false; //rtaga[j] = parseFloat(tags[tagname + "_" + suffix + (j + 1)].Value); } if (valid) return rtaga; return false; } function line(max, min, bp1, bp2, t1, t2, x) { var r = t2 + (bp2 - x) * ((t1 - t2) / (bp2 - bp1)); logDebug("max=" + max + " min=" + min + " bp1=" + bp1 + " bp2=" + bp2 + " t1=" + t1 + " t2=" + t2 + " x=" + x); logDebug("r=" + r + ""); if (r > max) return max; if (r < min) return min; else return r; } function tryGetTag(tagName) { logDebug("tryGetTag"); if (!tagName) { logDebug("No tag name provided"); return null; } tagName = String(tagName); if (typeof tagName !== "string" || !tags.ContainsKey(tagName)) { logDebug("Tag not found: " + tagName); return null; } logDebug("Tag found: " + tagName); return tags[tagName]; } function getValidValue(tagName) { logDebug("getValidValue: " + tagName); var tag = tryGetTag(tagName); if (!tag) { logDebug("Tag not found: " + tagName); return ""; } var value = ""; if (tag.IsValid && !tag.IsOld) { value = tag.Value; } else if (tag.Timestamp != System.DateTime.MinValue) { value = tagValue(tagName); } else { value = tagRead(tagName); } logDebug("value: " + value); return value; } function createTags(build) { try { logDebug("createTags: " + build.name); if (!build.shouldUseSteering) { logDebug("Not using steering for: " + build.name); return; } if (!createSteeringsTags) { logDebug("Not creating tags for: " + build.name); return; } tryCreateEdgeTag({ name: getFullName(build, aiTags.iotag), description: "IO", dataType: Moldeo.WebPortCommon.DataType.STRING }); tryCreateEdgeTag({ name: getFullName(build, aiTags.manaienable), description: "Aktivera AI-styrning", dataType: Moldeo.WebPortCommon.DataType.DIGITAL, parameters: { trendoptions: "t:600" } }); tryCreateEdgeTag({ name: getFullName(build, aiTags.manaienable + lastStateSuffix), description: "Senaste tillstånd för manuella variabeln", dataType: Moldeo.WebPortCommon.DataType.DIGITAL }); logDebug("build.circuitPrefix: " + build.circuitPrefix); logDebug("aiTags.aireg: " + aiTags.aireg); if (build.circuitPrefix && aiTags.aireg) { for (var i = 1; i < build.numberOfXY + 1; i++) { var tagX = getFullName(build, aiTags.aireg + "_X" + i); var tagY = getFullName(build, aiTags.aireg + "_Y" + i); if (!tags.ContainsKey(tagX)) { tryCreateEdgeTag({ name: tagX, dataType: Moldeo.WebPortCommon.DataType.REAL, format: "0.0", unit: "°C" }); var originalTagX = build.circuitPrefix + "_X" + i; if (tags.ContainsKey(originalTagX)) { tagRead(originalTagX); var valueX = tagValue(originalTagX); logDebug("Writing " + valueX + " from " + originalTagX + " to " + tagX); tagWrite(tagX, valueX); } else { logDebug("No original X found: " + originalTagX); } } else { logDebug("X tag already exist: " + tagX); } if (!tags.ContainsKey(tagY)) { tryCreateEdgeTag({ name: tagY, dataType: Moldeo.WebPortCommon.DataType.REAL, format: "0.0", unit: "°C" }); var originalTagY = build.circuitPrefix + "_Y" + i; if (tags.ContainsKey(originalTagY)) { tagRead(originalTagY); var valueY = tagValue(originalTagY); logDebug("Writing " + valueY + " from " + originalTagY + " to " + tagY); tagWrite(tagY, valueY); } else { logDebug("No original Y found: " + originalTagY); } } else { logDebug("Y tag already exist: " + tagY); } } if (aiTags.orgcsp) { tryCreateEdgeTag({ name: getFullName(build, aiTags.orgcsp), description: "Orginal börvärde", dataType: Moldeo.WebPortCommon.DataType.REAL, format: "0.0", unit: "°C", parameters: { trendoptions: "t:600" } }); } if (aiTags.aienable) { tryCreateEdgeTag({ name: getFullName(build, aiTags.aienable), description: "Edge överstyrning aktiv", dataType: Moldeo.WebPortCommon.DataType.DIGITAL, parameters: { trendoptions: "t:600" } }); } } } catch (e) { logError("createTags"); logError(e); } } function createEdgeTags() { logDebug("createEdgeTags: " + source); if (!Moldeo.WebPortCommon.TagManager.Manager.Sources.ContainsKey(source)) { try { var sourceList = Moldeo.WebPortCommon.TagManager.Manager.NewFileSource(source); Moldeo.WebPortCommon.TagManager.Manager.Sources.Add(source, sourceList); Moldeo.WebPortCommon.TagManager.Manager.Sources[source].Save(); } catch (e) { logError("source: " + source + " need to be created manually"); } } tryCreateEdgeTag({ name: tokenTag, description: "Token till Edge API", dataType: Moldeo.WebPortCommon.DataType.STRING }); tryCreateEdgeTag({ name: edgeTimestampTag, description: "Last Edge get data", dataType: Moldeo.WebPortCommon.DataType.STRING }); tryCreateEdgeTag({ name: edgeForceReadTag, description: "Forces new read from Edge", dataType: Moldeo.WebPortCommon.DataType.DIGITAL }); tryCreateEdgeTag({ name: timestampTag, description: "Giltighetstid för Edge token", dataType: Moldeo.WebPortCommon.DataType.STRING }); } function tryCreateEdgeTag(options) { try { logDebug("tryCreateEdgeTag: " + options.name); if (!options.name || typeof options.name !== "string") { logDebug("Invalid tag name"); return false; } var name = options.name.toUpperCase(); if (tags.ContainsKey(name)) { logDebug("Already exists: " + name); return false; } var tag = new Moldeo.WebPortCommon.Tag(name); tag.State = Moldeo.WebPortCommon.TagState.NEW; tag.Description = options.description || ""; tag.Datatype = options.dataType; tag.Source = source; tag.Deviceguid = deviceGuid; tag.Format = options.format || ""; tag.Unit = options.unit || ""; if (options.parameters) { try { for (var key in options) { tag.SetParameter(key, options[key]); } } catch (error) { logError("Error setting tag parameters: " + error); } } tags.Add(name, tag); tagChange = true; // Global variable, show that tags have changed return true; } catch (error) { logError("tryCreateEdgeTag"); logError(error); return false; } } function getSetpoint(supplyprefix, numbertags, min, max, outdoortemp) { logDebug("supplyprefix: " + supplyprefix); var tarrx = createTagArr(supplyprefix, "X", numbertags); var tarry = createTagArr(supplyprefix, "Y", numbertags); if (!tarrx || !tarry) { logDebug("X/Y arrays not complete"); return false; } logDebug("xy : " + tarrx.length); var min = 20; var max = 80; var sp = linearr(max, min, tarrx, tarry, outdoortemp); logDebug("sp: " + sp); return sp; } function getValueFromJson(build, value) { try { logDebug("getValueFromJson for " + build.name + ": " + value); //Scenarios. Hämta outdoor, hämta steering, hämta supply var ageAndJson = getAgeAndJson(build); var datetimeArray = null; if (ageAndJson) { datetimeArray = toDateTimeArray(ageAndJson["utcDateTime"]); } else { return false; } var closest = closestBottom(datetimeArray, now.Ticks); var h1 = new System.TimeSpan(1, 0, 0).Ticks; var delta = (System.DateTime.Now.Ticks - closest[0]) / h1; var outdoorTemperature = ageAndJson["outdoorTemperature"][System.Int32.Parse(closest[1])]; logDebug("outdoorTemperature: " + outdoorTemperature); if (value == "outdoorTemperature") { return outdoorTemperature; } if (value == "setpointTemperature") { return ageAndJson["setpointTemperature"][System.Int32.Parse(closest[1])]; } var newOutdoorTemperature = outdoorTemperature - (outdoorTemperature - ageAndJson["outdoorTemperature"][System.Int32.Parse(parseInt(closest[1]) + 1)]) * delta; logDebug("newOutdoorTemperature: " + newOutdoorTemperature); var currentOutdoorTemperature = parseFloat(tagRead(build.outdoortemp)); logDebug("currentOutdoorTemperature: " + currentOutdoorTemperature); var steeringTemperature = ageAndJson["steeringTemperature"][System.Int32.Parse(closest[1])]; logDebug("steeringTemperature: " + steeringTemperature); var newSteeringTemperature = steeringTemperature - (steeringTemperature - ageAndJson["steeringTemperature"][System.Int32.Parse(parseInt(closest[1]) + 1)]) * delta; logDebug("newSteeringTemperature: " + newSteeringTemperature); var useCompensation = ageAndJson["useCompensation"].ToString() == "True"; logDebug("useCompensation : " + useCompensation); if (useCompensation) { newSteeringTemperature = newSteeringTemperature + (currentOutdoorTemperature - newOutdoorTemperature); logDebug("Adjusted newSteeringTemperature: " + newSteeringTemperature); } if (value == "steeringTemperature") { return newSteeringTemperature; } } catch (e) { logError("getValueFromJson"); logError(e); return false; } } function saveTagSource() { logDebug("saveTagSource: " + source); Moldeo.WebPortCommon.TagManager.Manager.Sources[source].Save(); Moldeo.WebPortCommon.TagManager.Manager.UpdateTagDatabase(source); } function serializeJObject(jObject) { removeThousandSeparators(jObject); return Newtonsoft.Json.JsonConvert.SerializeObject(jObject, Newtonsoft.Json.Formatting.None); } function removeThousandSeparators(jObject) { var item; if (jObject.Type == Newtonsoft.Json.Linq.JTokenType.Object) { for (item in jObject) { if (item.Type == Newtonsoft.Json.Linq.JTokenType.Property) { if (item.Value.Type == Newtonsoft.Json.Linq.JTokenType.Array || item.Value.Type == Newtonsoft.Json.Linq.JTokenType.Object) { removeThousandSeparators(item.Value); } else { jObject[item.Name] = JValue(normalizeValue(item.Value)); } } else { removeThousandSeparators(item); } } } else if (jObject.Type == Newtonsoft.Json.Linq.JTokenType.Array) { for (var index = 0; index < jObject.Count; index++) { var int32 = System.Int32.Parse(index); item = jObject[int32]; if (item.Type == Newtonsoft.Json.Linq.JTokenType.Object || item.Type == Newtonsoft.Json.Linq.JTokenType.Array) { removeThousandSeparators(item); } else { item = normalizeValue(item); jObject[int32] = JValue(item); } } } } function normalizeValue(value) { if (typeof value !== "string") { value = String(value); } value = value.replace(/,/g, ""); return value; } function logError(message) { if (debugLevel >= DebugLevel.ERROR) { debug("[ERROR]: " + String(message)); } } function logDebug(message) { if (debugLevel >= DebugLevel.ALL) { debug("[DEBUG]: " + String(message)); } } function JObject(props) { var jo = new Newtonsoft.Json.Linq.JObject(); if (props) { for (var key in props) { var value = props[key]; jo.Add(JProperty(key, value)); } } return jo; } function JProperty(key, value) { return new Newtonsoft.Json.Linq.JProperty(key, value); } function JValue(value) { return new Newtonsoft.Json.Linq.JValue(value); } function JArray(array) { var ja = new Newtonsoft.Json.Linq.JArray(); if (array) { for (var index in array) { var item = array[index]; ja.Add(item); } } return ja; } function tryUpdateLastRunTime() { var edgeTimestampValue; try { edgeTimestampValue = tagRead(edgeTimestampTag); lastRun = toLocalTime(edgeTimestampValue); } catch (e) { logError("Error parsing edgeTimestampTag :" + e); logError("edgeTimestampValue :" + edgeTimestampValue); lastRun = System.DateTime.MinValue; } } function toString(value) { return typeof value === "string" ? value : String(value); } function toNumber(value) { if (typeof value === "number") { return value; } if (typeof value === "string") { value = value.replace(",", "."); } return parseFloat(value); } function adjustForBoundary(value, tag) { value = Math.max(tag.Engmin, value); value = Math.min(tag.Engmax, value); return value; }