[ { "id": "e2588b9d824334f7", "type": "tab", "label": "controller_calibration_charge", "disabled": false, "info": "", "env": [] }, { "id": "322b256f0daf33ef", "type": "tab", "label": "controller_hold_min_soc&&charge_to_min_soc", "disabled": false, "info": "", "env": [] }, { "id": "32b2f9d4415d82ce", "type": "tab", "label": "controller_max_discharge", "disabled": false, "info": "", "env": [] }, { "id": "812b3c1b3d3fa76b", "type": "tab", "label": "parse_warnings_and_alarms", "disabled": false, "info": "", "env": [] }, { "id": "dd75eef8547a776f", "type": "tab", "label": "Node Red Dashboard", "disabled": false, "info": "", "env": [] }, { "id": "victron-client-id", "type": "victron-client" }, { "id": "e177392401620838", "type": "ui_group", "name": "Controller and Battery Info", "tab": "157862d37ae585b5", "order": 2, "disp": true, "width": "13", "collapse": false, "className": "" }, { "id": "157862d37ae585b5", "type": "ui_tab", "name": "Home", "icon": "check", "disabled": false, "hidden": false }, { "id": "e0e675d533a148b7", "type": "ui_base", "theme": { "name": "theme-light", "lightTheme": { "default": "#0094CE", "baseColor": "#0094CE", "baseFont": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif", "edited": true, "reset": false }, "darkTheme": { "default": "#097479", "baseColor": "#097479", "baseFont": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif", "edited": false }, "customTheme": { "name": "Untitled Theme 1", "default": "#4B7930", "baseColor": "#4B7930", "baseFont": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif", "reset": false }, "themeState": { "base-color": { "default": "#0094CE", "value": "#0094CE", "edited": false }, "page-titlebar-backgroundColor": { "value": "#0094CE", "edited": false }, "page-backgroundColor": { "value": "#fafafa", "edited": false }, "page-sidebar-backgroundColor": { "value": "#ffffff", "edited": false }, "group-textColor": { "value": "#1bbfff", "edited": false }, "group-borderColor": { "value": "#ffffff", "edited": false }, "group-backgroundColor": { "value": "#ffffff", "edited": false }, "widget-textColor": { "value": "#111111", "edited": false }, "widget-backgroundColor": { "value": "#0094ce", "edited": false }, "widget-borderColor": { "value": "#ffffff", "edited": false }, "base-font": { "value": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif" } }, "angularTheme": { "primary": "indigo", "accents": "blue", "warn": "red", "background": "grey", "palette": "light" } }, "site": { "name": "Node-RED Dashboard", "hideToolbar": "false", "allowSwipe": "false", "lockMenu": "false", "allowTempTheme": "true", "dateFormat": "DD/MM/YYYY", "sizes": { "sx": 48, "sy": 48, "gx": 6, "gy": 6, "cx": 6, "cy": 6, "px": 0, "py": 0 } } }, { "id": "3290bd5996bd3175", "type": "ui_group", "name": "Easy Input", "tab": "157862d37ae585b5", "order": 3, "disp": true, "width": 13, "collapse": false, "className": "" }, { "id": "d610b26df84f336e", "type": "ui_group", "name": "Calibration Charge", "tab": "157862d37ae585b5", "order": 1, "disp": true, "width": "13", "collapse": false, "className": "" }, { "id": "1c76b68292d58d7a", "type": "victron-input-custom", "z": "e2588b9d824334f7", "service": "com.victronenergy.battery/1", "path": "/TimeToTOCRequest", "serviceObj": { "service": "com.victronenergy.battery/1", "name": "FZS 48TL200 x2 (1)" }, "pathObj": { "path": "/TimeToTOCRequest", "name": "/TimeToTOCRequest", "type": "number" }, "name": "", "onlyChanges": false, "x": 580, "y": 280, "wires": [ [ "b18eaae1b2cf532a" ] ] }, { "id": "374a9784b13e6b91", "type": "ui_switch", "z": "e2588b9d824334f7", "name": "Start Calibration Charge Now", "label": "Start Calibration Charge Now", "tooltip": "", "group": "d610b26df84f336e", "order": 5, "width": 0, "height": 0, "passthru": true, "decouple": "false", "topic": "#:(file)::start_calibration_charge_now_button", "topicType": "global", "style": "", "onvalue": "true", "onvalueType": "bool", "onicon": "", "oncolor": "", "offvalue": "false", "offvalueType": "bool", "officon": "", "offcolor": "", "animate": false, "className": "", "x": 2440, "y": 100, "wires": [ [ "0eda66dbeeaa1361", "ff621c398de790e9" ] ] }, { "id": "0eda66dbeeaa1361", "type": "switch", "z": "e2588b9d824334f7", "name": "Button is on", "property": "payload", "propertyType": "msg", "rules": [ { "t": "true" } ], "checkall": "true", "repair": false, "outputs": 1, "x": 2670, "y": 100, "wires": [ [ "38a3f85186c86064" ] ] }, { "id": "e6c8eb42a10e21a3", "type": "switch", "z": "e2588b9d824334f7", "name": "Need to do calibration charge or not", "property": "payload", "propertyType": "msg", "rules": [ { "t": "eq", "v": "0", "vt": "num" }, { "t": "eq", "v": "1", "vt": "num" }, { "t": "else" } ], "checkall": "true", "repair": false, "outputs": 3, "x": 1620, "y": 240, "wires": [ [ "a0d686b515f76cae", "65fc8a93c348bd1e", "7404973d10f3a10a", "644fe572f173602e" ], [ "e3e9b1f4b7cabc16", "8678a63acdb5ee29", "985f0a278ffd922c" ], [ "0eda2d25df727b9a", "ce4254f159092244" ] ] }, { "id": "3ff4ceaaebe9defb", "type": "ui_text", "z": "e2588b9d824334f7", "group": "d610b26df84f336e", "order": 2, "width": 0, "height": 0, "name": "Time To Calibration Charge", "label": "Time To Calibration Charge", "format": "{{msg.payload}}", "layout": "row-spread", "className": "", "style": false, "font": "", "fontSize": 16, "color": "#000000", "x": 3360, "y": 420, "wires": [] }, { "id": "0b6f77eecb110736", "type": "ui_text_input", "z": "e2588b9d824334f7", "name": "Calibration Charge Start Time (hh:mm)", "label": "Calibration Charge Start Time (hh:mm:ss.sss)", "tooltip": "", "group": "d610b26df84f336e", "order": 4, "width": 0, "height": 0, "passthru": true, "mode": "time", "delay": "0", "topic": "#:(file)::calibration_charge_start_time", "sendOnBlur": false, "className": "", "topicType": "global", "x": 510, "y": 80, "wires": [ [ "f32edc8e22e6c4a6" ] ] }, { "id": "ff621c398de790e9", "type": "change", "z": "e2588b9d824334f7", "name": "", "rules": [ { "t": "set", "p": "#:(file)::start_calibration_charge_now_button", "pt": "global", "to": "payload", "tot": "msg" } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 2780, "y": 40, "wires": [ [ "8cd49df4ce393b99" ] ] }, { "id": "8cd49df4ce393b99", "type": "debug", "z": "e2588b9d824334f7", "name": "Debug for calibration button", "active": false, "tosidebar": true, "console": false, "tostatus": true, "complete": "payload", "targetType": "msg", "statusVal": "payload", "statusType": "auto", "x": 3180, "y": 40, "wires": [] }, { "id": "f32edc8e22e6c4a6", "type": "change", "z": "e2588b9d824334f7", "name": "", "rules": [ { "t": "set", "p": "#:(file)::calibration_charge_start_time", "pt": "global", "to": "payload", "tot": "msg" } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 900, "y": 80, "wires": [ [ "c2e5b1ab69e8b817" ] ] }, { "id": "38a3f85186c86064", "type": "change", "z": "e2588b9d824334f7", "name": "Set \"Calibration charge now\" to Time To CalibrationCharge", "rules": [ { "t": "set", "p": "payload", "pt": "msg", "to": "Calibration charge now", "tot": "str" } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 3010, "y": 100, "wires": [ [ "3ff4ceaaebe9defb" ] ] }, { "id": "28b4fe5478e59dcc", "type": "victron-input-custom", "z": "e2588b9d824334f7", "service": "com.victronenergy.settings", "path": "/Settings/Controller/LastEOC", "serviceObj": { "service": "com.victronenergy.settings", "name": "com.victronenergy.settings" }, "pathObj": { "path": "/Settings/Controller/LastEOC", "name": "/Settings/Controller/LastEOC", "type": "number" }, "name": "", "onlyChanges": false, "x": 530, "y": 360, "wires": [ [ "c08993a9535559b7", "5909342727c04466" ] ] }, { "id": "7404973d10f3a10a", "type": "change", "z": "e2588b9d824334f7", "name": "Get current timestamp to update LastEoc", "rules": [ { "t": "set", "p": "payload", "pt": "msg", "to": "", "tot": "date" } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 2140, "y": 200, "wires": [ [ "466d0ead739c355d" ] ] }, { "id": "f0b91188bb162f98", "type": "victron-output-custom", "z": "e2588b9d824334f7", "service": "com.victronenergy.settings", "path": "/Settings/Controller/LastEOC", "serviceObj": { "service": "com.victronenergy.settings", "name": "com.victronenergy.settings" }, "pathObj": { "path": "/Settings/Controller/LastEOC", "name": "/Settings/Controller/LastEOC", "type": "number" }, "name": "", "onlyChanges": false, "x": 2790, "y": 200, "wires": [] }, { "id": "466d0ead739c355d", "type": "function", "z": "e2588b9d824334f7", "name": "Millisecond_to_second", "func": "current_timestamp_in_second=Math.floor(msg.payload/1000);\nmsg.payload = current_timestamp_in_second;\nreturn msg;", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 2440, "y": 200, "wires": [ [ "f0b91188bb162f98" ] ] }, { "id": "7339dc97983bb77b", "type": "comment", "z": "e2588b9d824334f7", "name": "EOC reached ", "info": "", "x": 1890, "y": 220, "wires": [] }, { "id": "0fff2085b1eb8dcb", "type": "comment", "z": "e2588b9d824334f7", "name": "Do calibration charge now", "info": "", "x": 2230, "y": 400, "wires": [] }, { "id": "ed2bb3eadfa27747", "type": "comment", "z": "e2588b9d824334f7", "name": "Still some time left to do calibration charge", "info": "", "x": 2160, "y": 480, "wires": [] }, { "id": "615bdf17da1a6422", "type": "debug", "z": "e2588b9d824334f7", "name": "Debug for calibration charge function", "active": false, "tosidebar": true, "console": false, "tostatus": true, "complete": "payload", "targetType": "msg", "statusVal": "payload.count", "statusType": "auto", "x": 1630, "y": 140, "wires": [] }, { "id": "8678a63acdb5ee29", "type": "victron-output-custom", "z": "e2588b9d824334f7", "service": "com.victronenergy.hub4/0", "path": "/Overrides/ForceCharge", "serviceObj": { "service": "com.victronenergy.hub4/0", "name": "com.victronenergy.hub4 (0)" }, "pathObj": { "path": "/Overrides/ForceCharge", "name": "/Overrides/ForceCharge", "type": "number" }, "name": "", "onlyChanges": false, "x": 2620, "y": 460, "wires": [] }, { "id": "65fc8a93c348bd1e", "type": "change", "z": "e2588b9d824334f7", "name": "EOC reached", "rules": [ { "t": "set", "p": "payload", "pt": "msg", "to": "EOC reached", "tot": "str" } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 2060, "y": 240, "wires": [ [ "3ff4ceaaebe9defb" ] ] }, { "id": "e3e9b1f4b7cabc16", "type": "change", "z": "e2588b9d824334f7", "name": "Calibration charge now", "rules": [ { "t": "set", "p": "payload", "pt": "msg", "to": "Calibration charge now", "tot": "str" } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 2520, "y": 420, "wires": [ [ "3ff4ceaaebe9defb" ] ] }, { "id": "b077a48ff0831b2a", "type": "ui_dropdown", "z": "e2588b9d824334f7", "name": "Calibration Charge Day", "label": "Calibration Charge Day", "tooltip": "", "place": "", "group": "d610b26df84f336e", "order": 3, "width": 0, "height": 0, "passthru": true, "multiple": false, "options": [ { "label": "Sunday", "value": 0, "type": "num" }, { "label": "Monday", "value": 1, "type": "num" }, { "label": "Tuesday", "value": 2, "type": "num" }, { "label": "Wednesday", "value": 3, "type": "num" }, { "label": "Thursday", "value": 4, "type": "num" }, { "label": "Friday", "value": 5, "type": "num" }, { "label": "Saturday", "value": 6, "type": "num" } ], "payload": "", "topic": "#:(file)::calibration_charge_weekday", "topicType": "global", "className": "", "x": 510, "y": 180, "wires": [ [ "10605f48b99030d0" ] ] }, { "id": "10605f48b99030d0", "type": "change", "z": "e2588b9d824334f7", "name": "", "rules": [ { "t": "set", "p": "#:(file)::calibration_charge_start_weekday", "pt": "global", "to": "payload", "tot": "msg" } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 890, "y": 180, "wires": [ [ "c2e5b1ab69e8b817" ] ] }, { "id": "b18eaae1b2cf532a", "type": "change", "z": "e2588b9d824334f7", "name": "", "rules": [ { "t": "set", "p": "#:(file)::TimeToTOC", "pt": "global", "to": "payload", "tot": "msg" } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 960, "y": 280, "wires": [ [ "c2e5b1ab69e8b817" ] ] }, { "id": "c08993a9535559b7", "type": "debug", "z": "e2588b9d824334f7", "name": "Debug for LastEOC", "active": false, "tosidebar": true, "console": false, "tostatus": false, "complete": "payload", "targetType": "msg", "statusVal": "", "statusType": "auto", "x": 1010, "y": 440, "wires": [] }, { "id": "6a3d4d1cb2651151", "type": "inject", "z": "e2588b9d824334f7", "name": "", "props": [ { "p": "payload" }, { "p": "topic", "vt": "str" } ], "repeat": "", "crontab": "", "once": true, "onceDelay": 0.1, "topic": "", "payload": "#:(file)::calibration_charge_start_time", "payloadType": "global", "x": 150, "y": 80, "wires": [ [ "0b6f77eecb110736" ] ] }, { "id": "fdd85619255f4e81", "type": "inject", "z": "e2588b9d824334f7", "name": "", "props": [ { "p": "payload" }, { "p": "topic", "vt": "str" } ], "repeat": "", "crontab": "", "once": true, "onceDelay": 0.1, "topic": "", "payload": "#:(file)::calibration_charge_start_weekday", "payloadType": "global", "x": 160, "y": 180, "wires": [ [ "b077a48ff0831b2a" ] ] }, { "id": "761a8f1f11727873", "type": "inject", "z": "e2588b9d824334f7", "name": "", "props": [ { "p": "payload" }, { "p": "topic", "vt": "str" } ], "repeat": "5", "crontab": "", "once": true, "onceDelay": "0", "topic": "", "payload": "#:(file)::start_calibration_charge_now_button", "payloadType": "global", "x": 2030, "y": 80, "wires": [ [ "374a9784b13e6b91" ] ] }, { "id": "c2e5b1ab69e8b817", "type": "function", "z": "e2588b9d824334f7", "name": "Cal time left to do calibration charge", "func": "// Get minutes per day\nvar minutes_per_day = 1440;\n\n// Battery setting\nmax_day_wihthout_EOC = 7;\nmax_minutes_without_EOC = max_day_wihthout_EOC*minutes_per_day;\n\n// Get TimeToTOC which stores minutes from last EOC reached\ntime_to_TOC=global.get('TimeToTOC','file');\n\nif (time_to_TOC ==0){//EOC reahced\n msg.payload=0;\n return msg;\n}\n\n// Get calibration charge time (hh:mm) from user setting\nif(global.get('calibration_charge_start_time','file')!= null){\n minutes_from_midnight_calibration_charge = Math.floor(global.get('calibration_charge_start_time','file'));\n}else{\n minutes_from_midnight_calibration_charge = 0;//default value from midnight\n}\n\n// Get calibration charge weekday from user setting\nif(global.get('calibration_charge_start_weekday','file')!=null){\n weekday_calibration_charge = global.get('calibration_charge_start_weekday','file');\n}else{\n weekday_calibration_charge = 0;//default value from Sunday\n}\n\nfunction nextScheduleDay(adate, w) {\n var daysToAdd = (w - adate.getDay() + 7) % 7;\n var nextDate = new Date(adate);\n nextDate.setDate(adate.getDate() + daysToAdd);\n nextDate.setHours(0);\n nextDate.setMinutes(0);\n nextDate.setSeconds(0);\n return nextDate;\n}\n\n\nfunction chargeWindows(currentTime, weekday, starttime, timeToTOC) {\n var d1 = nextScheduleDay(currentTime, weekday);\n\n // Convert starttime to a Date object\n var startTime = new Date(starttime);\n\n // Calculate the next ScheduleDay considering if the sum of timeToTOC and timeLeftMinutes is less than 7 days\n var timeLeftMinutes = Math.ceil((d1.getTime() - currentTime.getTime() + starttime) / (1000 * 60));\n\n if (timeToTOC + timeLeftMinutes < max_minutes_without_EOC) {\n // If the sum is less than 7 days, push next ScheduleDay to next week\n d1.setDate(d1.getDate() + 7);\n }\n\n var startDateTimeD1 = new Date(d1);\n startDateTimeD1.setHours(startTime.getUTCHours(), startTime.getUTCMinutes(), 0, 0);\n\n // Check if current time is within the charge window\n if (currentTime < startDateTimeD1) {\n // Calculate time left until the end of the window\n var timeLeftMillis = startDateTimeD1 - currentTime;\n var daysLeft = Math.floor(timeLeftMillis / (1000 * 60 * 60 * 24));\n var hoursLeft = Math.floor((timeLeftMillis % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));\n var minutesLeft = Math.ceil((timeLeftMillis % (1000 * 60 * 60)) / (1000 * 60));\n \n days_str = (daysLeft > 0) ? (daysLeft + \"d\") : \"\";\n hours_str = (hoursLeft > 0) ? (hoursLeft + \"h\") : \"\";\n minutes_str = (minutesLeft > 0) ? (minutesLeft + \"m\") : \"\";\n \n time_to_calibration_str = days_str+hours_str+minutes_str;\n\n return time_to_calibration_str;\n } else {\n return 1;\n }\n}\n\nvar today = new Date(); // Assuming today's date\nvar timeLeft = chargeWindows(today, weekday_calibration_charge, minutes_from_midnight_calibration_charge, time_to_TOC);\n\nmsg.payload = timeLeft;\nreturn msg;", "outputs": 1, "timeout": "", "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 1280, "y": 200, "wires": [ [ "615bdf17da1a6422", "e6c8eb42a10e21a3" ] ] }, { "id": "a0d686b515f76cae", "type": "function", "z": "e2588b9d824334f7", "name": "Turn off calibration charge now button when EOC", "func": "if(global.get('start_calibration_charge_now_button','file')==true)\n{\n msg.payload = false;\n}else{\n msg.payload = false;\n}\n\nreturn msg;\n\n", "outputs": 1, "timeout": "", "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 2090, "y": 140, "wires": [ [ "374a9784b13e6b91" ] ] }, { "id": "0eda2d25df727b9a", "type": "function", "z": "e2588b9d824334f7", "name": "Check whether the calibration charge now button is on", "func": "if(global.get('start_calibration_charge_now_button','file')==true)\n{\n text= \"Calibration charge now\";\n}else{\n text = msg.payload;\n}\nmsg.payload = text;\n\nreturn msg;\n", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 2620, "y": 560, "wires": [ [ "3ff4ceaaebe9defb" ] ] }, { "id": "5909342727c04466", "type": "change", "z": "e2588b9d824334f7", "name": "LastEOC", "rules": [ { "t": "set", "p": "#:(file)::LastEOC", "pt": "global", "to": "payload", "tot": "msg" } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 1000, "y": 360, "wires": [ [ "c2e5b1ab69e8b817" ] ] }, { "id": "44264437fe17f23f", "type": "function", "z": "e2588b9d824334f7", "name": "Cal time left to do calibration charge_backup1", "func": "// Get minutes per day\nvar minutes_per_day = 1440;\n\n// Battery setting\nmax_day_wihthout_EOC = 7;\nmax_minutes_without_EOC = max_day_wihthout_EOC*minutes_per_day;\n\n// Get TimeToTOC which stores minutes from last EOC reached\ntime_to_TOC=global.get('TimeToTOC','file');\n//time_to_TOC=global.get('TimeToTOC');\n\nif (time_to_TOC ==0){//EOC reahced\n msg.payload=0;\n return msg;\n}\n\n// Get calibration charge time (hh:mm) from user setting\nif(global.get('calibration_charge_start_time','file')!= null){\n minutes_from_midnight_calibration_charge = Math.floor(global.get('calibration_charge_start_time','file')/1000/60);\n}else{\n minutes_from_midnight_calibration_charge = 0;//default value from midnight\n}\n\n// Get calibration charge weekday from user setting\nif(global.get('calibration_charge_start_weekday','file')!=null){\n weekday_calibration_charge = global.get('calibration_charge_start_weekday','file');\n}else{\n weekday_calibration_charge = 0;//default value from Sunday\n}\n\n// Get today's date\nvar today = new Date();\n\n// Find the current day of the week (0 = Sunday, 1 = Monday, ..., 6 = Saturday)\nvar currentDay = today.getDay();\nvar minutes_from_today_midnight = today.getHours()*60+today.getMinutes();\n\n// Calculate the number of days and minutes until next calibration weekday\nvar weekday_diff = weekday_calibration_charge - currentDay;\nvar minutes_diff = minutes_from_midnight_calibration_charge - minutes_from_today_midnight;\n\nif (weekday_diff < 0) {\n weekday_diff += 7; \n}\n\nif(weekday_diff==0 && minutes_diff<0){\n weekday_diff += 7;\n}\n\n// Calculate time difference in minutes from now to the set calibration charge time\nminutes_diff_all_from_now_to_calibration=weekday_diff*minutes_per_day+minutes_diff;\n\n// Calculate time difference in minutes from LastEOC to the set calibration charge time\nminutes_diff_all_from_LastEOC_to_calibration = time_to_TOC+ minutes_diff_all_from_now_to_calibration;\n\n// Set the time to next calibration time\nvar nextCalibrationDate = new Date(today);\nvar_setHours = Math.floor(minutes_from_midnight_calibration_charge/60);\nvar_setMinutes = minutes_from_midnight_calibration_charge - var_setHours*60;\n\nif(minutes_diff_all_from_LastEOC_to_calibration=minutes_fromLastEOCtoNextCalibrationTimestamp){// need to do first time calibration charge;if the calibration setting is too close to last EOC time, then skip the first time and do it next week\n msg.payload =1;\n return msg;\n}else{\n time_left_minutes_all = minutes_diff_all_from_now_to_calibration;\n time_left_days = Math.floor(time_left_minutes_all/60/24);\n time_left_days_display = time_left_days + (minutes_diff_all_from_LastEOC_to_calibration 0) ? (time_left_days_display + \"d\") : \"\";\n hours_str = (time_left_hours > 0) ? (time_left_hours + \"h\") : \"\";\n minutes_str = (time_left_minutes > 0) ? (time_left_minutes + \"m\") : \"\";\n \n time_to_calibration_str = days_str+hours_str+minutes_str;\n msg.payload=time_to_calibration_str;\n}\n\nreturn msg;", "outputs": 1, "timeout": "", "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 600, "y": 560, "wires": [ [] ] }, { "id": "011bad015cb995db", "type": "function", "z": "e2588b9d824334f7", "name": "Cal time left to do calibration charge_backup2", "func": "// Get minutes per day\nvar minutes_per_day = 1440;\n\n// Battery setting\nmax_day_wihthout_EOC = 7;\nmax_minutes_without_EOC = max_day_wihthout_EOC*minutes_per_day;\n\n// Get TimeToTOC which stores minutes from last EOC reached\ntime_to_TOC=global.get('TimeToTOC','file');\n\nif (time_to_TOC ==0){//EOC reahced\n msg.payload=0;\n return msg;\n}\n\n// Get calibration charge time (hh:mm) from user setting\nif(global.get('calibration_charge_start_time','file')!= null){\n minutes_from_midnight_calibration_charge = Math.floor(global.get('calibration_charge_start_time','file'));\n}else{\n minutes_from_midnight_calibration_charge = 0;//default value from midnight\n}\n\n// Get calibration charge weekday from user setting\nif(global.get('calibration_charge_start_weekday','file')!=null){\n weekday_calibration_charge = global.get('calibration_charge_start_weekday','file');\n}else{\n weekday_calibration_charge = 0;//default value from Sunday\n}\n\nfunction nextScheduleDay(adate, w) {\n w = w % 7;\n var daysToAdd = (w - adate.getDay() - 1 + 7) % 7;\n var nextDate = new Date(adate);\n nextDate.setDate(adate.getDate() + daysToAdd);\n return nextDate;\n}\n\nfunction prevScheduleDay(adate, w) {\n w = w % 7;\n var daysToSubtract = (adate.getDay() + 7 - w) % 7 + 1;\n var prevDate = new Date(adate);\n prevDate.setDate(adate.getDate() - daysToSubtract);\n return prevDate;\n}\n\nfunction chargeWindows(currentTime, weekday, starttime, timeToTOC) {\n var d0 = prevScheduleDay(currentTime, weekday);\n var d1 = nextScheduleDay(currentTime, weekday);\n\n // Convert starttime to a Date object\n var startTime = new Date(starttime);\n\n // Calculate the next ScheduleDay considering if the sum of timeToTOC and timeLeftMinutes is less than 7 days\n var timeLeftMinutes = Math.ceil((d1.getTime() - currentTime.getTime() + starttime) / (1000 * 60));\n\n if (timeToTOC + timeLeftMinutes < max_minutes_without_EOC) {\n // If the sum is less than 7 days, push next ScheduleDay to next week\n d1.setDate(d1.getDate() + 7);\n }\n\n // Set the start time for d0 and d1\n var startDateTimeD0 = new Date(d0);\n startDateTimeD0.setHours(startTime.getUTCHours(), startTime.getUTCMinutes(), 0, 0);\n\n var startDateTimeD1 = new Date(d1);\n startDateTimeD1.setHours(startTime.getUTCHours(), startTime.getUTCMinutes(), 0, 0);\n\n // Check if current time is within the charge window\n if (currentTime >= startDateTimeD0 && currentTime < startDateTimeD1) {\n // Calculate time left until the end of the window\n var timeLeftMillis = startDateTimeD1 - currentTime;\n var daysLeft = Math.floor(timeLeftMillis / (1000 * 60 * 60 * 24));\n var hoursLeft = Math.floor((timeLeftMillis % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));\n var minutesLeft = Math.ceil((timeLeftMillis % (1000 * 60 * 60)) / (1000 * 60));\n\n return daysLeft + 'd' + hoursLeft + 'h' + minutesLeft + 'm';\n } else {\n return 1;\n }\n}\n\nvar today = new Date(); // Assuming today's date\nvar timeLeft = chargeWindows(today, weekday_calibration_charge, minutes_from_midnight_calibration_charge, time_to_TOC);\n\nmsg.payload = timeLeft;\n\nreturn msg;", "outputs": 1, "timeout": "", "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 600, "y": 640, "wires": [ [] ] }, { "id": "985f0a278ffd922c", "type": "change", "z": "e2588b9d824334f7", "name": "", "rules": [ { "t": "set", "p": "#:(file)::start_calibration_charge_now", "pt": "global", "to": "payload", "tot": "msg" } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 2580, "y": 500, "wires": [ [ "3dc0dde6cbbd97c0" ] ] }, { "id": "de6a4357e8a1f15c", "type": "change", "z": "e2588b9d824334f7", "name": "", "rules": [ { "t": "set", "p": "#:(file)::start_calibration_charge_now", "pt": "global", "to": "payload", "tot": "msg" } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 2900, "y": 620, "wires": [ [ "3e2692e252d4b7ce" ] ] }, { "id": "ce4254f159092244", "type": "change", "z": "e2588b9d824334f7", "name": "set start_calibration_charge_now to 0", "rules": [ { "t": "set", "p": "payload", "pt": "msg", "to": "0", "tot": "num" } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 2570, "y": 620, "wires": [ [ "de6a4357e8a1f15c" ] ] }, { "id": "644fe572f173602e", "type": "change", "z": "e2588b9d824334f7", "name": "", "rules": [ { "t": "set", "p": "#:(file)::start_calibration_charge_now", "pt": "global", "to": "payload", "tot": "msg" } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 2440, "y": 240, "wires": [ [ "d1f75adc62fbfadb" ] ] }, { "id": "d1f75adc62fbfadb", "type": "debug", "z": "e2588b9d824334f7", "name": "Debug for calibration", "active": false, "tosidebar": true, "console": false, "tostatus": true, "complete": "payload", "targetType": "msg", "statusVal": "payload", "statusType": "auto", "x": 2720, "y": 240, "wires": [] }, { "id": "3dc0dde6cbbd97c0", "type": "debug", "z": "e2588b9d824334f7", "name": "Debug for calibration", "active": false, "tosidebar": true, "console": false, "tostatus": true, "complete": "payload", "targetType": "msg", "statusVal": "payload", "statusType": "auto", "x": 2860, "y": 500, "wires": [] }, { "id": "3e2692e252d4b7ce", "type": "debug", "z": "e2588b9d824334f7", "name": "Debug for calibration", "active": false, "tosidebar": true, "console": false, "tostatus": true, "complete": "payload", "targetType": "msg", "statusVal": "payload", "statusType": "auto", "x": 3180, "y": 620, "wires": [] }, { "id": "edf59fb9886b1048", "type": "victron-input-custom", "z": "322b256f0daf33ef", "service": "com.victronenergy.settings", "path": "/Settings/CGwacs/BatteryLife/MinimumSocLimit", "serviceObj": { "service": "com.victronenergy.settings", "name": "com.victronenergy.settings" }, "pathObj": { "path": "/Settings/CGwacs/BatteryLife/MinimumSocLimit", "name": "/Settings/CGwacs/BatteryLife/MinimumSocLimit", "type": "number" }, "name": "", "onlyChanges": false, "x": 310, "y": 200, "wires": [ [ "e31bd3d3a1c25da5" ] ] }, { "id": "e31bd3d3a1c25da5", "type": "change", "z": "322b256f0daf33ef", "name": "min_soc", "rules": [ { "t": "set", "p": "topic", "pt": "msg", "to": "min_soc", "tot": "str" } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 680, "y": 200, "wires": [ [ "ec4dfbf95393066c" ] ] }, { "id": "e96ae0338cc426e7", "type": "victron-input-custom", "z": "322b256f0daf33ef", "service": "com.victronenergy.battery/1", "path": "/Dc/0/Power", "serviceObj": { "service": "com.victronenergy.battery/1", "name": "com.victronenergy.battery (1)" }, "pathObj": { "path": "/Dc/0/Power", "name": "/Dc/0/Power", "type": "number" }, "name": "", "onlyChanges": false, "x": 200, "y": 260, "wires": [ [ "86d2d524dcca3330" ] ] }, { "id": "86d2d524dcca3330", "type": "change", "z": "322b256f0daf33ef", "name": "battery_power", "rules": [ { "t": "set", "p": "topic", "pt": "msg", "to": "battery_power", "tot": "str" } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 580, "y": 260, "wires": [ [ "ec4dfbf95393066c" ] ] }, { "id": "c21a992cf80c2d6f", "type": "function", "z": "322b256f0daf33ef", "name": "controller_hold_min_soc_&_charge_to_min_soc&heating", "func": "// get max charge power\nif(msg.payload.max_configured_charge_power==null ||msg.payload.max_configured_charge_power<0){\n max_charge_power=msg.payload.max_battery_charge_power;\n}else{\n max_charge_power=Math.min(msg.payload.max_configured_charge_power,msg.payload.max_battery_charge_power);\n}\n\nmax_inverter_power = msg.payload.num_phases*msg.payload.inverter_power;\n\n// variables for hold_min_soc controller\nBatterySelfDischargePower=200;//W\nn_batteries=msg.payload.num_batteries;\nHoldSocZone=1;\na=-2*BatterySelfDischargePower*n_batteries/HoldSocZone;\nb=-a*(msg.payload.min_soc+HoldSocZone);\nP_CONST = 0.5;\n// min soc among batteries\nsoc = msg.payload.lowest_soc;\ntarget_dc_power_to_hold_min_soc=soc*a+b;\n\n// current power setpoint\ninverter_power_setpoint= msg.payload.L1_AcPowerSetpoint+msg.payload.L2_AcPowerSetpoint+msg.payload.L3_AcPowerSetpoint;\n\nAC_in = msg.payload.AC_In;\nAC_out = msg.payload.AC_Out;\nPV_production =msg.payload.PVs_Power;\n\nif(global.get('start_calibration_charge_now_button','file') == true || global.get('start_calibration_charge_now','file')==1){\n d_p = max_charge_power-n_batteries*msg.payload.battery_power;\n power = AC_out+d_p;\n msg.payload.ess_mode =3;\n msg.payload.controller_info = \"Calibrtaion charge\";\n powerperphase=power/3;\n powerperphase=Math.max(powerperphase,-max_inverter_power);\n powerperphase=Math.floor(Math.min(powerperphase,max_inverter_power));\n msg.payload.power=powerperphase;\n return msg;\n}\n\nif(msg.payload.min_soc<=soc&&soc<=msg.payload.min_soc+1){\n d_p = target_dc_power_to_hold_min_soc-n_batteries*msg.payload.battery_power;\n delta = d_p*P_CONST;\n if(msg.payload.grid_setpoint>0){\n power = inverter_power_setpoint+delta;\n msg.payload.ess_mode =1;\n msg.payload.controller_info = \"Hold min SOC - ESS control\";\n }else{\n power = AC_out+delta-PV_production;\n msg.payload.ess_mode =3;\n msg.payload.controller_info = \"Hold min SOC - external control\";\n }\n}else if(soc Battery Monitor \n", "storeOutMessages": true, "fwdInMessages": true, "resendOnRefresh": true, "templateScope": "local", "className": "", "x": 200, "y": 480, "wires": [ [] ], "icon": "node-red/arrow-in.svg" } ]