[
{
"id": "3fbc028716abf7a9",
"type": "tab",
"label": "RaspiPeso",
"disabled": false,
"info": "",
"env": []
},
{
"id": "744bdf3a0e08d754",
"type": "tab",
"label": "Supervisor",
"disabled": false,
"info": "",
"env": []
},
{
"id": "576f3807f61f5098",
"type": "tab",
"label": "Gestion Semaforos",
"disabled": false,
"info": "",
"env": []
},
{
"id": "aadbe4b53903ecda",
"type": "group",
"z": "3fbc028716abf7a9",
"name": "Bascula3",
"style": {
"label": true,
"fill": "#ffbfbf",
"fill-opacity": "0.42",
"stroke": "#000000",
"color": "#000000"
},
"nodes": [
"03b120c9713eb333",
"14fbc044898db247",
"f4cda14c866fa39a",
"fad252cfbd6688a3",
"eb3ddc79e17c0fd3",
"6dbef4dc022becea",
"2d9d86f7a82a37eb",
"9673d34b6f177b10",
"1381985c88fc5dbb",
"34291c27b4f82d26",
"ac8abf3f7ec09ac9",
"f89e176a87f0d923",
"a8d75f0609d7f8ed",
"6731bec53435c2d5",
"1457adeb1e1238af",
"5fe3f27656425fc8",
"f178e2126d397565",
"183a0ae1b9315d02"
],
"x": 54,
"y": 799,
"w": 1992,
"h": 322
},
{
"id": "3c69eb8420f499d7",
"type": "group",
"z": "3fbc028716abf7a9",
"name": "Bascula1",
"style": {
"label": true,
"color": "#000000",
"stroke": "#000000",
"fill": "#ffffbf",
"fill-opacity": "0.52"
},
"nodes": [
"2d26d00c4530ee1f",
"9e837a6339e80936",
"7a6fc5c1767b38b0",
"8a0cc762a8039217",
"bdad189ae1181c82",
"f0bede4dc0ffef68",
"a65499ac760347b5",
"47f84415577e771f",
"4c97abf1728d0d77",
"760d3fa03416427f",
"5191ba50d8f57c1f",
"aacff5572bb6eb47",
"69b2b79597b10613",
"81d1e1eceb078857",
"b3bf54cc42094984",
"9dbc4cf2cb9ad616",
"5704a222c2e6219e",
"4d122b4d7ef323dd"
],
"x": 54,
"y": 139,
"w": 1992,
"h": 302
},
{
"id": "414eb1569dd15f60",
"type": "group",
"z": "3fbc028716abf7a9",
"name": "Bascula2",
"style": {
"label": true,
"fill": "#ffefbf",
"fill-opacity": "0.62",
"stroke": "#000000",
"color": "#000000"
},
"nodes": [
"c03cc7b1afedcdad",
"5aa551b29b8cdca3",
"a979e217b6375370",
"745217e56d674e38",
"c90a867aaba4b730",
"af2b7b939331dbb9",
"c9a6ce7c276cd0cf",
"e45740d360367781",
"92f2a1b83feac9b1",
"69c5f45de39463f0",
"91bdf33233cd6dc8",
"751f20d5500f0560",
"662de6fd08e45fb2",
"c7ac3675f9f951fe",
"fa67b799f5270de3",
"e59e292827957f03",
"a567f5be5995d73c",
"4c71a5b6fc6bd505"
],
"x": 54,
"y": 459,
"w": 1992,
"h": 322
},
{
"id": "21cb55110b1bae57",
"type": "group",
"z": "3fbc028716abf7a9",
"name": "Bascula4",
"style": {
"label": true,
"fill": "#bfdbef",
"fill-opacity": "0.37",
"stroke": "#000000",
"color": "#000000"
},
"nodes": [
"83659a5fa5422105",
"d03daa5335e2514c",
"5cf1088cefafd3a7",
"b8228cdac41b95c1",
"5d3d7b9e427ebd33",
"d216a46466968eae",
"b0ed51791aa894e6",
"631799398328acdf",
"c5d5fe49f9134079",
"061c9a2b5eb5e8b5",
"6d6370556efac085",
"6680d1b3b7ad8918",
"63c34626c74e9d80",
"12fbd1e2e0da99f5",
"55d810bcd402f1c0",
"d6e380c9415bfb72",
"98c9a64ec995f9d2",
"a1792e1b8fb6e6be"
],
"x": 54,
"y": 1139,
"w": 1992,
"h": 282
},
{
"id": "e3bc37824e09b774",
"type": "group",
"z": "3fbc028716abf7a9",
"name": "CONFIGURACION CON UIBUILDER",
"style": {
"stroke": "#0070c0",
"fill-opacity": "0.75",
"label": true,
"fill": "#bfdbef",
"color": "#000000"
},
"nodes": [
"2ec53aa1784087c1",
"b8fdc502b1bf35b7",
"be843dd4381d45d5",
"88b9b4d1c5aa7359",
"7a4c757e6f6ddfcd",
"6d39924d1d2081dd",
"882ee24d1489b06c",
"9667a055f9c967a7",
"7058b9e632aa584b",
"ba5e6b2b40fba02b",
"1ba8bfe271072a91"
],
"x": 2174,
"y": 799,
"w": 892,
"h": 322
},
{
"id": "05b4bc2ccdd08ee9",
"type": "group",
"z": "3fbc028716abf7a9",
"name": "SERVIDOR API PARA TEST",
"style": {
"stroke": "#ff0000",
"fill": "#ffffbf",
"fill-opacity": "0.24",
"label": true,
"color": "#000000"
},
"nodes": [
"960b4b6685af7406",
"f806cc253d96d34a",
"363afa81160f118e"
],
"x": 2714,
"y": 1199,
"w": 352,
"h": 142
},
{
"id": "9a733b496ecde31c",
"type": "group",
"z": "3fbc028716abf7a9",
"name": "RECEPTOR DE MOVIMIENTOS VIA MQTT",
"style": {
"stroke": "#3f93cf",
"fill": "#bfdbef",
"fill-opacity": "0.27",
"label": true,
"color": "#000000"
},
"nodes": [
"87154c99372a5f9c",
"bfe9a33e26aa7cf4",
"56b300da80743113",
"d4174078d4cb9375",
"93bfa84a0f29e988"
],
"x": 2174,
"y": 1199,
"w": 512,
"h": 222
},
{
"id": "f4b1c07e498c11be",
"type": "group",
"z": "3fbc028716abf7a9",
"name": "PANTALLA ON / OFF",
"style": {
"stroke": "#ff0000",
"fill-opacity": "0.16",
"label": true,
"color": "#000000",
"fill": "#a4a4a4"
},
"nodes": [
"780c6c61.a0cb04",
"49500a91.3971e4",
"97ec61bc.7e2aa",
"afb26e9306481251",
"2c73d02c99e64b37"
],
"x": 2174,
"y": 139,
"w": 432,
"h": 202
},
{
"id": "bfec5f4fae03e5b4",
"type": "group",
"z": "3fbc028716abf7a9",
"name": "envio websocket",
"style": {
"label": true
},
"nodes": [
"b47df6182a4c503b",
"a96c9a3676aad816",
"6537522d1838f799",
"3e6f6384f2ec9846"
],
"x": 2164,
"y": 599,
"w": 642,
"h": 142
},
{
"id": "663dc04a15a0be0c",
"type": "group",
"z": "3fbc028716abf7a9",
"name": "archivo configuracion",
"style": {
"label": true
},
"nodes": [
"f450a2dee7246e4b",
"e9767bab493f578b",
"aa13d9f112f46a30",
"d7b0f75b37ad9f53"
],
"x": 2174,
"y": 399,
"w": 632,
"h": 122
},
{
"id": "610fa44452654078",
"type": "group",
"z": "744bdf3a0e08d754",
"name": "CONFIGURACION CON UIBUILDER",
"style": {
"stroke": "#0070c0",
"fill-opacity": "0.75",
"label": true,
"fill": "#bfdbef",
"color": "#000000"
},
"nodes": [
"57ecdac37231f743",
"b75f8eb3486aed8d",
"4657b0f0cefa89d7",
"7698b09169dc1c99",
"7004d0202704343a",
"58bb0cd40f89ba0d",
"fcce285ce4cc49d0",
"17547c6cf2cc8bdf",
"e4f5cd90a27363a0",
"0f3cd31839136f10",
"9559a621baa2e0e5",
"9a0817ab9b58c626",
"eaf1c8163a641d84",
"22a6d78769eca8a4",
"38d2c6565b5ca0bf",
"c8010702a8431461",
"4ae8af66b4ef6232",
"508d57d90e013805",
"7e099ddf6bfcb9fb",
"dd15174f0db1d58c"
],
"x": 3734,
"y": 479,
"w": 1012,
"h": 482
},
{
"id": "68b92de7bc4c82f6",
"type": "group",
"z": "744bdf3a0e08d754",
"name": "SINCRONIZA REGISTROS ANTERIORES",
"style": {
"stroke": "#0070c0",
"fill": "#bfdbef",
"fill-opacity": "0.25",
"label": true,
"color": "#000000"
},
"nodes": [
"34bf8ec19b0e78a7",
"950c8115e364a493",
"f41c4b5c61a58ea2",
"b1c81347b429091a",
"911f1b379de9ba3a",
"76d19764677ebc8a",
"3c3867c2a2185f63",
"2b9158681d5eb09b",
"d4a053997921cfc0",
"89f64c532d3d78e7",
"e944fa11afc97160",
"f4c6152e8ed7f5a6",
"a42d3e0da2d9dbda",
"7b7da2e1e640ca0a",
"1387a80aca617b59",
"fdddf9cbc2fa6622",
"9eda24ef14d3892c",
"867c5654842a1210",
"d144432c1ab94daf"
],
"x": 1994,
"y": 1059,
"w": 2172,
"h": 488
},
{
"id": "6ec963a99751b141",
"type": "group",
"z": "744bdf3a0e08d754",
"name": "GENERACION DE MENU PARA UNA MATRICULA REGISTRADA",
"style": {
"stroke": "#0070c0",
"fill": "#bfdbef",
"fill-opacity": "0.31",
"label": true,
"color": "#000000"
},
"nodes": [
"f0815d4d5c1e13e0",
"8013dc387b26381b",
"cb22a118da74ca79"
],
"x": 1988,
"y": 1553,
"w": 2044,
"h": 614
},
{
"id": "f968efec7b96ec13",
"type": "group",
"z": "744bdf3a0e08d754",
"name": "REVISA SI QR / TARJETA CORRESPONDE A UN CONTRATO",
"style": {
"stroke": "#ff0000",
"fill": "#ffffff",
"fill-opacity": "0.94",
"label": true,
"color": "#000000"
},
"nodes": [
"9b47806ebf017d2c",
"7269e082a5989305",
"8e56735bbb37a598",
"ea115b9b38cdaea1",
"ed04082db0d29b5b",
"52c76e5f8d275432",
"57fd039f5dadf4fb",
"8558335b01c7e441"
],
"x": 1994,
"y": 2279,
"w": 942,
"h": 182
},
{
"id": "0a78f1a176dd7ad5",
"type": "group",
"z": "744bdf3a0e08d754",
"name": "RECEPCION DE TABLAS VIA API",
"style": {
"stroke": "#92d04f",
"fill": "#ffffff",
"label": true,
"color": "#000000"
},
"nodes": [
"a8e9253184936edf",
"24855820a3fadbb8",
"20976aa7738e9650",
"a091c51915be9f0a",
"9d2b6d3b968d3fcf",
"c95d9408ac6b1595",
"5cd2d2a7d0b81e81",
"e87018791b56046f",
"ae259a894fbedeb4",
"eca4e2e077d64f16"
],
"x": 1994,
"y": 2559,
"w": 1112,
"h": 202
},
{
"id": "75dd57faa07f33b9",
"type": "group",
"z": "744bdf3a0e08d754",
"name": "ENVIA DATOS AL SERVIDOR - ACTUALIZA BD Y ARCHIVOS DIARIAMENTE - SIRVE DATOS DE MOVIMIENTOS",
"style": {
"stroke": "#ffC000",
"fill": "#ffefbf",
"fill-opacity": "0.19",
"label": true
},
"nodes": [
"0b16f0b9cb1c5ab9",
"c858b37cb0eeba4f",
"e192c564f2050e94",
"1bdaeab49913082e",
"b8029f9a251a232e",
"fd9524c0921c0990",
"8778197111d2ab0d",
"d47200e5f67c772a",
"b4caaec341e453e0",
"d19507c7bbe4bb32",
"e2fca1c2499902fa",
"6a2b1526103aa3b0",
"26f04e07495817eb",
"d5ce4cb6b3abd807",
"49bed5a59768fbef",
"a58ecf0f246aff6a",
"4e6d5242d6531d79",
"732068c66d8e0a25",
"e716da8ff5c458c9",
"10affb7590fa95f1",
"ee2f281460418d2a",
"6455539b9fac2063",
"7ff54b99b5b933b2",
"08bf0c9897d6e815",
"65b220fc46d55c96"
],
"x": 2004,
"y": 459,
"w": 1462,
"h": 562
},
{
"id": "800484a81d9dc5ee",
"type": "group",
"z": "744bdf3a0e08d754",
"name": "CONFIGURACION",
"style": {
"stroke": "#9363b7",
"fill": "#dbcbe7",
"fill-opacity": "0.22",
"label": true,
"color": "#000000"
},
"nodes": [
"1279bc86f850f792",
"c18ea33c86d877db",
"9b375376ea8f2a66",
"844805b92ed8fc5c",
"b1eb9bccca87f0f9",
"29b2dd9b01235f99",
"0471bc0a0beb44fd",
"c5a3e80cc446b610",
"323c2ee052338182",
"a8218646952839ba",
"8435634f3b8cf017",
"f372e14a6921bc3a",
"cc890d832924b5d5"
],
"x": 614,
"y": 999,
"w": 992,
"h": 229.5
},
{
"id": "8aa65df7a757f075",
"type": "group",
"z": "744bdf3a0e08d754",
"name": "RECEPCION DE MOVIMIENTOS TEMPORALES",
"style": {
"stroke": "#92d04f",
"fill": "#e3f3d3",
"fill-opacity": "0.39",
"label": true,
"color": "#000000"
},
"nodes": [
"73752ba2ae415953",
"182712df6587926f",
"de78dc5fb92df251",
"6703fb916aef8f4b",
"b264dc44d4344d72",
"4e1f0ff54eef4e7b",
"2dc5c2942966b097",
"3d7afdefd9a5d353",
"138317044e960cfa",
"4f30b9755ed381bb",
"66630f04db34f483",
"2d857220b76dda21",
"8a7201a76e53897f",
"1b2d9af6c1e13e5f",
"5c4679ed20761208",
"c61c445897350722",
"9fa72afe7d97828e",
"dc3fe6bd2494cab0"
],
"x": 614,
"y": 1319,
"w": 1052,
"h": 442
},
{
"id": "a57600b79a898710",
"type": "group",
"z": "744bdf3a0e08d754",
"name": "GENERA MOVIMIENTOS CERRADOS",
"style": {
"stroke": "#7fb7df",
"stroke-opacity": "0.42",
"fill": "#bfdbef",
"fill-opacity": "0.34",
"label": true,
"color": "#000000"
},
"nodes": [
"c674aac5bef7594a",
"405f569c2efbf22d",
"417e71d002f16583",
"b1b9ac8e8b543dbb",
"0ba07aef43c4a226",
"cc229c10e3f4e423",
"65bfea0283dfab05",
"a1c2fb59c438c1c5",
"2e4cf9271d9a78d8",
"dd4decb988977915"
],
"x": 614,
"y": 1779,
"w": 1082,
"h": 242
},
{
"id": "6fd6ce44bb173af7",
"type": "group",
"z": "744bdf3a0e08d754",
"name": "NODOS PARA PRUEBAS EN BD",
"style": {
"stroke": "#ff0000",
"fill": "#ffffbf",
"fill-opacity": "0.86",
"label": true,
"color": "#000000"
},
"nodes": [
"81f447b69bd2c603",
"d2082aabd7021a2e",
"1d44274163bba745",
"17b55fb17b52609b",
"f769af64083450ed",
"a550322a7e6cafd2",
"9e4bc4c1c4148e95",
"e22e7f0f1cea9cb5"
],
"x": 614,
"y": 2179,
"w": 792,
"h": 242
},
{
"id": "e03fd8b52121427a",
"type": "group",
"z": "744bdf3a0e08d754",
"name": "PANTALLA ON / OFF",
"style": {
"stroke": "#ff0000",
"fill-opacity": "0.16",
"label": true,
"color": "#000000",
"fill": "#a4a4a4"
},
"nodes": [
"6a066066436f56ef",
"245c965f41b35b43",
"bad546e70128edf8",
"ff7604277bbc1810",
"0a768660b1756f9a",
"1ca5e534fa4962de"
],
"x": 614,
"y": 2479,
"w": 432,
"h": 222
},
{
"id": "1e7a7eecbf88e674",
"type": "group",
"z": "744bdf3a0e08d754",
"name": "PRUEBAS DEL API",
"style": {
"label": true
},
"nodes": [
"422ffdb7a80b308a",
"5c03e30bd6a7dd59",
"150a34e02c2def17",
"bebcf89f4daa2d2c",
"ecd2ede4ce450c22"
],
"x": 1694,
"y": 319,
"w": 912,
"h": 102
},
{
"id": "6c0376d0b4669415",
"type": "group",
"z": "744bdf3a0e08d754",
"name": "SALIDA WEBSOCKET",
"style": {
"label": true
},
"nodes": [
"cb6c28fe05c25229",
"d7d43c051dffd484",
"e5d5709dcfce538e"
],
"x": 4114,
"y": 1619,
"w": 372,
"h": 202
},
{
"id": "3276a28141ee1362",
"type": "group",
"z": "576f3807f61f5098",
"name": "LAZOS (L1 L2 L3)",
"style": {
"stroke": "#999",
"fill": "#fff3e0",
"fill-opacity": "0.6",
"label": true,
"label-position": "nw"
},
"nodes": [
"e4e47d1f029304cb",
"20a7ce11f2e8216a",
"e4a843aa2e13ce4d",
"46f02d9f8d41ee03",
"d4986d948f5f65a7",
"15c2a5fd76e103ac",
"69f5087384c48122",
"f38d6d27851d0e2b",
"be21a62a431574d0",
"0dde5e928354be6c",
"fb6d5180204e6bce",
"918df2f63a69c6c9",
"c4159a812594fc62"
],
"x": 884,
"y": 299,
"w": 682,
"h": 322
},
{
"id": "f4f4ca47869551e3",
"type": "group",
"z": "576f3807f61f5098",
"name": "PESO (histeresis)",
"style": {
"stroke": "#999",
"fill": "#e8f5e9",
"fill-opacity": "0.6",
"label": true,
"label-position": "nw"
},
"nodes": [
"a7c59fef860efa52",
"e0b22a42652448d3",
"29994ee556e17a85",
"74ea72530de212a4",
"29b8ed91354ac1e2",
"b0c185b2e9fb99d4",
"e616701e86d299ad"
],
"x": 894,
"y": 679,
"w": 682,
"h": 182
},
{
"id": "6457918bac63ba59",
"type": "group",
"z": "576f3807f61f5098",
"name": "CONTROL (FSM + INIT)",
"style": {
"stroke": "#999",
"fill": "#e1f5fe",
"fill-opacity": "0.6",
"label": true,
"label-position": "nw"
},
"nodes": [
"04796f17dd930bad",
"c9d524190cbe7f96",
"e7754eeae28a6256",
"0e30fb1a5ee58746",
"67e977418231666d",
"798cfaee0987cdca",
"b017ceea4285eb76"
],
"x": 1634,
"y": 299,
"w": 602,
"h": 189.5
},
{
"id": "e51291e8f6dc7282",
"type": "group",
"z": "576f3807f61f5098",
"name": "SCHEDULER (WRR 2>3>1)",
"style": {
"stroke": "#999",
"fill": "#ede7f6",
"fill-opacity": "0.6",
"label": true,
"label-position": "nw"
},
"nodes": [
"606dc6f9453f5e0d",
"891cfa9c8d6e464d",
"15131de35b7d0425"
],
"x": 1644,
"y": 539,
"w": 462,
"h": 82
},
{
"id": "fdde9120f66c6c06",
"type": "group",
"z": "576f3807f61f5098",
"name": "TIMERS (T_i y G_i)",
"style": {
"stroke": "#999",
"fill": "#fce4ec",
"fill-opacity": "0.6",
"label": true,
"label-position": "nw"
},
"nodes": [
"00ce5615a6247bec",
"e4543a6e7a7f0ddb",
"2dc673e9f13e0f0b"
],
"x": 1644,
"y": 679,
"w": 442,
"h": 82
},
{
"id": "e928e581c722fe25",
"type": "group",
"z": "576f3807f61f5098",
"name": "GESTION DE LOS SEMAFOROS (salida)",
"style": {
"stroke": "#999",
"fill": "#f3e5f5",
"fill-opacity": "0.6",
"label": true
},
"nodes": [
"8da927c794b40546",
"bc4418d271c32af0",
"e6e4d33e9470fbca",
"2f0ca87e8afc0d73",
"06c0e45f61e0d364",
"49a1dfc2d343bdbb",
"4878484f33b6ea38",
"d8112ca72e0a342d",
"b6317c2d3d6d070c",
"dd43a77de63f2443",
"217697ee4e884321",
"967aada409fe59f3"
],
"x": 1638,
"y": 839,
"w": 968,
"h": 448
},
{
"id": "4a6a83419193ac56",
"type": "group",
"z": "576f3807f61f5098",
"name": "DEBUG",
"style": {
"stroke": "#999",
"fill": "#eeeeee",
"fill-opacity": "0.6",
"label": true,
"label-position": "nw"
},
"nodes": [
"d95551b3e1091e71",
"3e36b8139450735b"
],
"x": 2174,
"y": 679,
"w": 387,
"h": 82
},
{
"id": "350484a2e764ef67",
"type": "group",
"z": "576f3807f61f5098",
"name": "CONFIGURACION CON UIBUILDER",
"style": {
"stroke": "#0070c0",
"fill-opacity": "0.75",
"label": true,
"fill": "#bfdbef",
"color": "#000000"
},
"nodes": [
"c42bdd023acc8dc3",
"85b34266c740174d",
"4333f08dfc27e390",
"ad67876e3fd4b4bb",
"adbe70916e20904f",
"1e52a0895d377969",
"2f80166bb47a752f",
"6a6d080c74f2da43",
"081628af33270569",
"49e2b8c24d0ee245",
"a4992bd24a1acc12",
"4847a19f86d704fc",
"711b20eac053af7f",
"37b8b001f2c455d2",
"7aa2adada5ca928e",
"77eb0df85abf7f16",
"c398ee4ceb30937f",
"136e09e5233e209b",
"d6c429592c6cfdab",
"764c5454d21fc52b"
],
"x": 2684,
"y": 299,
"w": 1012,
"h": 482
},
{
"id": "6f7ca09a49c974d3",
"type": "group",
"z": "576f3807f61f5098",
"name": "CARGA CONFIGURACION",
"style": {
"stroke": "#ff0000",
"fill-opacity": "0.12",
"label": true,
"color": "#000000",
"fill": "#92d04f"
},
"nodes": [
"a2fcbe68dc4e70bd",
"6b89fc341280ad29",
"e02d2079825430db",
"53019ab29a07f8f5",
"7c6babaed4914e41",
"4232fc1ec89537af"
],
"x": 2694,
"y": 839,
"w": 632,
"h": 142
},
{
"id": "fdddf9cbc2fa6622",
"type": "group",
"z": "744bdf3a0e08d754",
"g": "68b92de7bc4c82f6",
"name": "ENVIO MQTT",
"style": {
"stroke": "#ff0000",
"fill": "#ffbfbf",
"fill-opacity": "0.2",
"label": true,
"color": "#000000"
},
"nodes": [
"ca758e5b6e2d880e",
"4a2f600ba9ca7a15",
"a62dedb280979227",
"d5ced8b11f124c67",
"6d9ec47623128a2d",
"1d3c5c1f2e5be4b0",
"0a4223f208ebf170",
"fc3b9b3c65b9e540"
],
"x": 2994,
"y": 1219,
"w": 552,
"h": 302
},
{
"id": "f0815d4d5c1e13e0",
"type": "group",
"z": "744bdf3a0e08d754",
"g": "6ec963a99751b141",
"name": "ACTUALIZACION DE TABLAS DE MENUS",
"style": {
"stroke": "#92d04f",
"fill": "#ffffff",
"label": true,
"color": "#000000"
},
"nodes": [
"d683e9cccedc561f",
"0c144d19f803250f",
"105c677a40b6461b",
"f0f98e25ebd44ac3",
"67e7ede89ac08b87",
"36b9bf6834913595",
"c7a7b3e8c093ae1e",
"afd34cb6f61a6475",
"ac9d1ea0c4c9dfbe",
"b9b6a469329290a5",
"76a88549a38a751d",
"e0c3a3c33ed7aaca",
"7aee3c3ea10fd9a5",
"aa870f8d73e4f504",
"1dae889e8697bf15"
],
"x": 2014,
"y": 1579,
"w": 1712,
"h": 322
},
{
"id": "8013dc387b26381b",
"type": "group",
"z": "744bdf3a0e08d754",
"g": "6ec963a99751b141",
"name": "REVISA SI MATRICULA EXISTE EN BASE DE DATOS",
"style": {
"stroke": "#ff0000",
"fill": "#ffffff",
"fill-opacity": "0.94",
"label": true,
"color": "#000000"
},
"nodes": [
"1d9e5518c4689f75",
"a1ac4bf3b8c14b21",
"7f282cc6ba963043",
"c470404997683059",
"008f41b960a6d13f",
"9a65971441f7579f",
"9c65121589e6726e",
"b76ed973b1299bb9",
"a16351341e1a09da",
"22f142c6e6d19334",
"ceba75f09293b8f4"
],
"x": 2014,
"y": 1939,
"w": 972,
"h": 202
},
{
"id": "cb22a118da74ca79",
"type": "group",
"z": "744bdf3a0e08d754",
"g": "6ec963a99751b141",
"name": "GENERACION DE LA LISTA DEL MENU",
"style": {
"stroke": "#ff0000",
"stroke-opacity": "0.9",
"label": true,
"color": "#000000",
"fill": "#ffffff",
"fill-opacity": "0.97"
},
"nodes": [
"dcd65882403b8019",
"39ce9cfd3bee5c07",
"eb466c2601840e18",
"3605e72173f34f32",
"4526132d69e6c210",
"b49f7a7bb3272707",
"96d638d71a74e652",
"ce3029f2688a0aba",
"6ef121d9eb28bd91",
"74375df4238da5a5",
"4f214eb96e82b18b",
"dfdc2a0fceee62b1"
],
"x": 2994,
"y": 1939,
"w": 1012,
"h": 202
},
{
"id": "e716da8ff5c458c9",
"type": "group",
"z": "744bdf3a0e08d754",
"g": "75dd57faa07f33b9",
"name": "ENVIO MQTT",
"style": {
"stroke": "#ff0000",
"fill": "#ffbfbf",
"fill-opacity": "0.21",
"label": true,
"color": "#000000"
},
"nodes": [
"32e555ac8f46a0f1",
"e36bb58515b89053",
"6b1ae2b4cdb257fb",
"7ef59a8ac4d8a7f3",
"aba034491220fa28",
"ac94ab2b132fab52",
"774b2334aae0188c",
"9a5e603ea67d2991",
"9606dbbee4d79399"
],
"x": 2074,
"y": 599,
"w": 552,
"h": 282
},
{
"id": "967aada409fe59f3",
"type": "group",
"z": "576f3807f61f5098",
"g": "e928e581c722fe25",
"name": "TEST",
"style": {
"stroke": "#0070c0",
"fill": "#7fb7df",
"fill-opacity": "0.19",
"label": true
},
"nodes": [
"a4b79d82d1b9e587",
"b5cb8dc6dff15a6f",
"63ef47eeab45cd59",
"406737da0cea85cd",
"0626ad89ffb60829",
"18202cf1d5012bd6"
],
"x": 1664,
"y": 979,
"w": 192,
"h": 282
},
{
"id": "f928d3e1f28c1f5b",
"type": "serial-port",
"z": "3fbc028716abf7a9",
"name": "",
"serialport": "/dev/ttyAMA1",
"serialbaud": "9600",
"databits": "8",
"parity": "none",
"stopbits": "1",
"waitfor": "",
"dtr": "none",
"rts": "none",
"cts": "none",
"dsr": "none",
"newline": "0",
"bin": "bin",
"out": "interbyte",
"addchar": "",
"responsetimeout": "10000"
},
{
"id": "f962fbcc832a11c1",
"type": "ui_tab",
"name": "PESOS",
"icon": "dashboard",
"order": 1,
"disabled": false,
"hidden": true
},
{
"id": "4eb9ee9b8ea93b8a",
"type": "mqtt-broker",
"name": "",
"broker": "trypton02.vservers.es",
"port": 1883,
"clientid": "",
"autoConnect": true,
"usetls": false,
"protocolVersion": 4,
"keepalive": 60,
"cleansession": true,
"autoUnsubscribe": true,
"birthTopic": "",
"birthQos": "0",
"birthRetain": "false",
"birthPayload": "",
"birthMsg": {},
"closeTopic": "",
"closeQos": "0",
"closeRetain": "false",
"closePayload": "",
"closeMsg": {},
"willTopic": "",
"willQos": "0",
"willRetain": "false",
"willPayload": "",
"willMsg": {},
"userProps": "",
"sessionExpiry": ""
},
{
"id": "e18837acff3382fd",
"type": "websocket-listener",
"path": "/",
"wholemsg": "false"
},
{
"id": "cff29c99ac572574",
"type": "ui_group",
"name": "PESOS",
"tab": "f962fbcc832a11c1",
"order": 1,
"disp": false,
"width": "14",
"collapse": false,
"className": "{{msg.payload.bascula}}"
},
{
"id": "97828a64326a9969",
"type": "ui_base",
"theme": {
"name": "theme-dark",
"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": true,
"reset": 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"
},
"themeState": {
"base-color": {
"default": "#097479",
"value": "#097479",
"edited": false
},
"page-titlebar-backgroundColor": {
"value": "#097479",
"edited": false
},
"page-backgroundColor": {
"value": "#111111",
"edited": false
},
"page-sidebar-backgroundColor": {
"value": "#333333",
"edited": false
},
"group-textColor": {
"value": "#0eb8c0",
"edited": false
},
"group-borderColor": {
"value": "#555555",
"edited": false
},
"group-backgroundColor": {
"value": "#333333",
"edited": false
},
"widget-textColor": {
"value": "#eeeeee",
"edited": false
},
"widget-backgroundColor": {
"value": "#097479",
"edited": false
},
"widget-borderColor": {
"value": "#333333",
"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": "true",
"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": "da59cb3b7385573e",
"type": "mqtt-broker",
"name": "",
"broker": "trypton02.vservers.es",
"port": 1883,
"clientid": "",
"autoConnect": true,
"usetls": false,
"protocolVersion": 4,
"keepalive": 60,
"cleansession": true,
"autoUnsubscribe": true,
"birthTopic": "",
"birthQos": "0",
"birthRetain": "false",
"birthPayload": "",
"birthMsg": {},
"closeTopic": "",
"closeQos": "0",
"closeRetain": "false",
"closePayload": "",
"closeMsg": {},
"willTopic": "",
"willQos": "0",
"willRetain": "false",
"willPayload": "",
"willMsg": {},
"userProps": "",
"sessionExpiry": ""
},
{
"id": "79757467241b200a",
"type": "sqlitedb",
"db": "/home/trypton/sqlite/totemdb",
"mode": "RWC"
},
{
"id": "93f25b867f297ffe",
"type": "websocket-listener",
"path": "/",
"wholemsg": "false"
},
{
"id": "a8e74bb3a1610500",
"type": "sqlitedb",
"db": "/home/trypton/sqlite/supervisordb",
"mode": "RWC"
},
{
"id": "3966bb218232f6b3",
"type": "mqtt-broker",
"name": "",
"broker": "trypton02.vservers.es",
"port": 1883,
"clientid": "",
"autoConnect": true,
"usetls": false,
"protocolVersion": 4,
"keepalive": 60,
"cleansession": true,
"autoUnsubscribe": true,
"birthTopic": "",
"birthQos": "0",
"birthRetain": "false",
"birthPayload": "",
"birthMsg": {},
"closeTopic": "",
"closeQos": "0",
"closeRetain": "false",
"closePayload": "",
"closeMsg": {},
"willTopic": "",
"willQos": "0",
"willRetain": "false",
"willPayload": "",
"willMsg": {},
"userProps": "",
"sessionExpiry": ""
},
{
"id": "790906edd33b7f43",
"type": "websocket-listener",
"path": "/",
"wholemsg": "false"
},
{
"id": "1c8613764b7f685e",
"type": "MySQLdatabase",
"name": "supervisor",
"host": "127.0.0.1",
"port": "3306",
"db": "supervisordb",
"tz": "",
"charset": "UTF8"
},
{
"id": "a00b83865450b74d",
"type": "modbus-client",
"name": "lazos",
"clienttype": "tcp",
"bufferCommands": true,
"stateLogEnabled": false,
"queueLogEnabled": false,
"failureLogEnabled": true,
"tcpHost": "192.168.11.200",
"tcpPort": 502,
"tcpType": "DEFAULT",
"serialPort": "/dev/ttyUSB",
"serialType": "RTU-BUFFERD",
"serialBaudrate": 9600,
"serialDatabits": 8,
"serialStopbits": 1,
"serialParity": "none",
"serialConnectionDelay": 100,
"serialAsciiResponseStartDelimiter": "0x3A",
"unit_id": 1,
"commandDelay": 1,
"clientTimeout": 1000,
"reconnectOnTimeout": true,
"reconnectTimeout": 2000,
"parallelUnitIdsAllowed": true,
"showErrors": true,
"showWarnings": true,
"showLogs": true
},
{
"id": "f450a2dee7246e4b",
"type": "file in",
"z": "3fbc028716abf7a9",
"g": "663dc04a15a0be0c",
"name": "read-file",
"filename": "/home/trypton/.node-red/projects/config_raspeso_test.txt",
"filenameType": "str",
"format": "utf8",
"chunk": false,
"sendError": false,
"encoding": "none",
"allProps": false,
"x": 2480,
"y": 440,
"wires": [
[
"d7b0f75b37ad9f53"
]
]
},
{
"id": "e9767bab493f578b",
"type": "inject",
"z": "3fbc028716abf7a9",
"g": "663dc04a15a0be0c",
"name": "",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": true,
"onceDelay": "1.5",
"topic": "",
"payload": "1",
"payloadType": "num",
"x": 2270,
"y": 440,
"wires": [
[
"f450a2dee7246e4b"
]
]
},
{
"id": "aa13d9f112f46a30",
"type": "link in",
"z": "3fbc028716abf7a9",
"g": "663dc04a15a0be0c",
"name": "link in 2",
"links": [
"1ba8bfe271072a91"
],
"x": 2355,
"y": 480,
"wires": [
[
"f450a2dee7246e4b"
]
]
},
{
"id": "b47df6182a4c503b",
"type": "function",
"z": "3fbc028716abf7a9",
"g": "bfec5f4fae03e5b4",
"name": "ENVIO WEBSOCKET PESO ",
"func": "// Función Node-RED para WebSocket con lógica avanzada de envío condicional\n\nlet payload = msg.payload;\n\n// Validación\nif (typeof payload !== \"object\" || payload === null) {\n node.error(\"Payload no es un objeto JSON válido\", msg);\n return null;\n}\n\n// === Extraer datos ===\nlet peso = Number(payload.peso);\nlet estable = payload.estable;\nlet bascula = payload.bascula || \"\";\nlet totem_id='*';\n\nif (!Number.isFinite(peso) || !bascula.startsWith(\"Bascula\")) return null;\n\nlet numeroBascula = bascula.substring(7);\nlet vial = parseInt(numeroBascula);\n\n// === Claves por báscula ===\nlet keyUltimoPeso = `ws_ultimoPeso_${bascula}`;\nlet keyUltimoEstable = `ws_ultimoEstable_${bascula}`;\n\nlet ultimoPeso = flow.get(keyUltimoPeso);\nlet ultimoEstable = flow.get(keyUltimoEstable);\n\n// === Lógica de envío ===\nlet debeEnviar = false;\n\nif (estable) {\n // Si es estable, solo lo envío si es diferente del último estable enviado\n if (peso !== ultimoEstable) {\n flow.set(keyUltimoEstable, peso);\n flow.set(keyUltimoPeso, peso); // Actualiza también el último peso general\n debeEnviar = true;\n }\n} else {\n // No es estable → enviar solo si cambió respecto al último peso enviado\n if (peso !== ultimoPeso) {\n flow.set(keyUltimoPeso, peso);\n debeEnviar = true;\n }\n}\n\n// === Preparar y enviar si corresponde ===\n\n//node.warn(\"Debe Enviar:\" + debeEnviar);\nif (debeEnviar) {\n let nuevoPayload = {\n totem_id: totem_id,\n numero_vial: vial,\n peso: peso,\n peso_estable: estable\n };\n msg.payload = JSON.stringify(nuevoPayload);\n return msg;\n}\n\nreturn null; // No cumple condiciones de envío\n",
"outputs": 2,
"timeout": "",
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 2440,
"y": 640,
"wires": [
[
"6537522d1838f799",
"33449eadf7bfd3da",
"3e6f6384f2ec9846"
],
[]
]
},
{
"id": "969714a9a2b1ef72",
"type": "link out",
"z": "3fbc028716abf7a9",
"name": "websocket1",
"mode": "link",
"links": [
"a96c9a3676aad816"
],
"x": 2125,
"y": 640,
"wires": []
},
{
"id": "a96c9a3676aad816",
"type": "link in",
"z": "3fbc028716abf7a9",
"g": "bfec5f4fae03e5b4",
"name": "WEBSOCKET",
"links": [
"969714a9a2b1ef72"
],
"x": 2205,
"y": 640,
"wires": [
[
"b47df6182a4c503b"
]
]
},
{
"id": "6537522d1838f799",
"type": "debug",
"z": "3fbc028716abf7a9",
"g": "bfec5f4fae03e5b4",
"name": "debug 69",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 2700,
"y": 700,
"wires": []
},
{
"id": "d7b0f75b37ad9f53",
"type": "function",
"z": "3fbc028716abf7a9",
"g": "663dc04a15a0be0c",
"name": "config edit",
"func": "const config = JSON.parse(msg.payload);\nmsg ={}; // clear msg object\nlet read;\nlet config_general = []\nlet config_parametros = []\nlet config_rs232 = []\n\n//read = config.peso;\n\nconfig.general = toObject(config.general);\nconfig.parametros = toObject(config.parametros);\nconfig.rs232 = toObject(config.rs232);\n\n\n\n\n/************************************************NUEVOS***********************************************************************************/\n\nread = config.general.numeroBasculas;\nconfig_parametros.push({ \"parametro\": \"numeroBasculas\", \"valor\": read }) \nflow.set(\"numeroBasculas\", read); // crea variable numero de Basculas\n\n\n\nread = config.general.numeroTrama;\nconfig_parametros.push({ \"parametro\": \"numeroTrama\", \"valor\": read }) \nflow.set(\"numeroTrama\", read); // crea variable numero de trama\n//especificar las basculas que queremos\n\n//*********************BASCULA 1**************************************\nread = config.parametros.nombreBascula1;\nconfig_parametros.push({ \"parametro\": \"nombreBascula1\", \"valor\": read }) \nflow.set(\"nombreBascula1\", read); // crea variable nombreBascula1\n\nread = config.parametros.tcp_flag_Bascula1;\nconfig_parametros.push({ \"parametro\": \"tcp_flag_Bascula1\", \"valor\": read }) \nflow.set(\"tcp_flag_Bascula1\", read); // crea variable tcp_flag_bascula1\n\nread = config.parametros.InicioTramaBascula1;\nconfig_parametros.push({ \"parametro\": \"InicioTramaBascula1\", \"valor\": read }) \nflow.set(\"InicioTramaBascula1\", read); // crea variable InicioTramaBascula1\n\nread = config.parametros.FinalTramaBascula1;\nconfig_parametros.push({ \"parametro\": \"FinalTramaBascula1\", \"valor\": read }) \nflow.set(\"FinalTramaBascula1\", read); // crea variable FinalTramaBascula1\n\nread = config.parametros.LongitudTramaBascula1;\nconfig_parametros.push({ \"parametro\": \"LongitudTramaBascula1\", \"valor\": read }) \nflow.set(\"LongitudTramaBascula1\", read); // crea variable LongitudTramaBascula1\n\nread = config.parametros.InicioPesoBascula1;\nconfig_parametros.push({ \"parametro\": \"InicioPesoBascula1\", \"valor\": read }) \nflow.set(\"InicioPesoBascula1\", read); // crea variable InicioPesoBascula1\n\nread = config.parametros.LongitudPesoBascula1;\nconfig_parametros.push({ \"parametro\": \"LongitudPesoBascula1\", \"valor\": read }) \nflow.set(\"LongitudPesoBascula1\", read); // crea variable LongitudPesoBascula1\n\nread = config.parametros.hostTCPBascula1;\nconfig_parametros.push({ \"parametro\": \"hostTCPBascula1\", \"valor\": read }) \nflow.set(\"hostTCPBascula1\", read); // crea variable InicioTrama\n\nread = config.parametros.puertoTCPBascula1;\nconfig_parametros.push({ \"parametro\": \"puertoTCPBascula1\", \"valor\": read }) \nflow.set(\"puertoTCPBascula1\", read); // crea variable InicioTrama\n\n\n//*********************BASCULA 2**************************************\n\n\nread = config.parametros.nombreBascula2;\nconfig_parametros.push({ \"parametro\": \"nombreBascula2\", \"valor\": read }) \nflow.set(\"nombreBascula2\", read); // crea variable nombreBascula2\n\nread = config.parametros.tcp_flag_Bascula2;\nconfig_parametros.push({ \"parametro\": \"tcp_flag_Bascula2\", \"valor\": read }) \nflow.set(\"tcp_flag_Bascula2\", read); // crea variable tcp_flag_bascula2\n\nread = config.parametros.InicioTramaBascula2;\nconfig_parametros.push({ \"parametro\": \"InicioTramaBascula2\", \"valor\": read }) \nflow.set(\"InicioTramaBascula2\", read); // crea variable InicioTramaBascula2\n\nread = config.parametros.FinalTramaBascula2;\nconfig_parametros.push({ \"parametro\": \"FinalTramaBascula2\", \"valor\": read }) \nflow.set(\"FinalTramaBascula2\", read); // crea variable FinalTramaBascula2\n\nread = config.parametros.LongitudTramaBascula2;\nconfig_parametros.push({ \"parametro\": \"LongitudTramaBascula2\", \"valor\": read }) \nflow.set(\"LongitudTramaBascula2\", read); // crea variable LongitudTramaBascula2\n\nread = config.parametros.InicioPesoBascula2;\nconfig_parametros.push({ \"parametro\": \"InicioPesoBascula2\", \"valor\": read }) \nflow.set(\"InicioPesoBascula2\", read); // crea variable InicioPesoBascula2\n\nread = config.parametros.LongitudPesoBascula2;\nconfig_parametros.push({ \"parametro\": \"LongitudPesoBascula2\", \"valor\": read }) \nflow.set(\"LongitudPesoBascula2\", read); // crea variable LongitudPesoBascula2\n\nread = config.parametros.hostTCPBascula2;\nconfig_parametros.push({ \"parametro\": \"hostTCPBascula2\", \"valor\": read }) \nflow.set(\"hostTCPBascula2\", read); // crea variable InicioTrama\n\nread = config.parametros.puertoTCPBascula2;\nconfig_parametros.push({ \"parametro\": \"puertoTCPBascula2\", \"valor\": read }) \nflow.set(\"puertoTCPBascula2\", read); // crea variable InicioTrama\n\n//*********************BASCULA 3**************************************\n\n\nread = config.parametros.nombreBascula3;\nconfig_parametros.push({ \"parametro\": \"nombreBascula3\", \"valor\": read }) \nflow.set(\"nombreBascula3\", read); // crea variable nombreBascula3\n\nread = config.parametros.tcp_flag_Bascula3;\nconfig_parametros.push({ \"parametro\": \"tcp_flag_Bascula3\", \"valor\": read }) \nflow.set(\"tcp_flag_Bascula3\", read); // crea variable tcp_flag_bascula3\n\nread = config.parametros.InicioTramaBascula3;\nconfig_parametros.push({ \"parametro\": \"InicioTramaBascula3\", \"valor\": read }) \nflow.set(\"InicioTramaBascula3\", read); // crea variable InicioTramaBascula3\n\nread = config.parametros.FinalTramaBascula3;\nconfig_parametros.push({ \"parametro\": \"FinalTramaBascula3\", \"valor\": read }) \nflow.set(\"FinalTramaBascula3\", read); // crea variable FinalTramaBascula3\n\nread = config.parametros.LongitudTramaBascula3;\nconfig_parametros.push({ \"parametro\": \"LongitudTramaBascula3\", \"valor\": read })\nflow.set(\"LongitudTramaBascula3\", read); // crea variable LongitudTramaBascula3\n\nread = config.parametros.InicioPesoBascula3;\nconfig_parametros.push({ \"parametro\": \"InicioPesoBascula3\", \"valor\": read }) \nflow.set(\"InicioPesoBascula3\", read); // crea variable InicioPesoBascula3\n\nread = config.parametros.LongitudPesoBascula3;\nconfig_parametros.push({ \"parametro\": \"LongitudPesoBascula3\", \"valor\": read }) \nflow.set(\"LongitudPesoBascula3\", read); // crea variable LongitudPesoBascula3\n\nread = config.parametros.hostTCPBascula3;\nconfig_parametros.push({ \"parametro\": \"hostTCPBascula3\", \"valor\": read }) \nflow.set(\"hostTCPBascula3\", read); // crea variable InicioTrama3\n\nread = config.parametros.puertoTCPBascula3;\nconfig_parametros.push({ \"parametro\": \"puertoTCPBascula3\", \"valor\": read }) \nflow.set(\"puertoTCPBascula3\", read); // crea variable InicioTram3\n//*********************BASCULA 4**************************************\n\nread = config.parametros.nombreBascula4;\nconfig_parametros.push({ \"parametro\": \"nombreBascula4\", \"valor\": read }) \nflow.set(\"nombreBascula4\", read); // crea variable nombreBascula3\n\nread = config.parametros.tcp_flag_Bascula4;\nconfig_parametros.push({ \"parametro\": \"tcp_flag_Bascula4\", \"valor\": read }) \nflow.set(\"tcp_flag_Bascula4\", read); // crea variable tcp_flag_bascula2\n\nread = config.parametros.InicioTramaBascula4;\nconfig_parametros.push({ \"parametro\": \"InicioTramaBascula4\", \"valor\": read }) \nflow.set(\"InicioTramaBascula4\", read); // crea variable InicioTramaBascula4\n\nread = config.parametros.FinalTramaBascula4;\nconfig_parametros.push({ \"parametro\": \"FinalTramaBascula4\", \"valor\": read }) \nflow.set(\"FinalTramaBascula4\", read); // crea variable FinalTramaBascula4\n\nread = config.parametros.LongitudTramaBascula4;\nconfig_parametros.push({ \"parametro\": \"LongitudTramaBascula4\", \"valor\": read })\nflow.set(\"LongitudTramaBascula4\", read); // crea variable LongitudTramaBascula4\n\nread = config.parametros.InicioPesoBascula4;\nconfig_parametros.push({ \"parametro\": \"InicioPesoBascula4\", \"valor\": read }) \nflow.set(\"InicioPesoBascula4\", read); // crea variable InicioPesoBascula3\n\nread = config.parametros.LongitudPesoBascula4;\nconfig_parametros.push({ \"parametro\": \"LongitudPesoBascula4\", \"valor\": read }) \nflow.set(\"LongitudPesoBascula4\", read); // crea variable LongitudPesoBascula3\n\nread = config.parametros.hostTCPBascula4;\nconfig_parametros.push({ \"parametro\": \"hostTCPBascula4\", \"valor\": read }) \nflow.set(\"hostTCPBascula4\", read); // crea variable InicioTrama3\n\nread = config.parametros.puertoTCPBascula4;\nconfig_parametros.push({ \"parametro\": \"puertoTCPBascula4\", \"valor\": read }) \nflow.set(\"puertoTCPBascula4\", read); // crea variable InicioTram3\n\n\nread = config.rs232.puertoSerie1;\nconfig_rs232.push({ \"parametro\": \"puertoSerie1\", \"valor\": read }) \nflow.set(\"puertoSerie1\", read); // crea variable puertoSerie1\n\nread = config.rs232.velocidadBaudiosSerie1;\nconfig_rs232.push({ \"parametro\": \"velocidadBaudiosSerie1\", \"valor\": read }) \nflow.set(\"velocidadBaudiosSerie1\", read); // crea variable velocidadBaudiosSerie1\n\nread = config.rs232.databitsSerie1;\nconfig_rs232.push({ \"parametro\": \"databitsSerie1\", \"valor\": read }) \nflow.set(\"databitsSerie1\", read); // crea variable databitsSerie1\n\nread =config.rs232.paridadSerie1;\nconfig_rs232.push({ \"parametro\": \"paridadSerie1\", \"valor\": read }) \nflow.set(\"paridadSerie1\", read); // crea variable paridadSerie1\n\nread = config.rs232.stopbitsSerie1;\nconfig_rs232.push({ \"parametro\": \"stopbitsSerie1\", \"valor\": read }) \nflow.set(\"stopbitsSerie1\", read); // crea variable stopbitsSerie1\n\nread = config.rs232.puertoHabilitadoSerie1;\nconfig_rs232.push({ \"parametro\": \"puertoHabilitadoSerie1\", \"valor\": read }) \nflow.set(\"puertoHabilitadoSerie1\", read); // crea variable puertoHabilitadoSerie1\n\n\nread = config.rs232.puertoSerie2;\nconfig_rs232.push({ \"parametro\": \"puertoSerie2\", \"valor\": read }) \nflow.set(\"puertoSerie2\", read); // crea variable puertoSerie2\n\nread = config.rs232.velocidadBaudiosSerie2;\nconfig_rs232.push({ \"parametro\": \"velocidadBaudiosSerie2\", \"valor\": read }) \nflow.set(\"velocidadBaudiosSerie2\", read); // crea variable velocidadBaudiosSerie2\n\nread = config.rs232.databitsSerie2;\nconfig_rs232.push({ \"parametro\": \"databitsSerie2\", \"valor\": read }) \nflow.set(\"databitsSerie2\", read); // crea variable databitsSerie2\n\nread = config.rs232.paridadSerie2;\nconfig_rs232.push({ \"parametro\": \"paridadSerie2\", \"valor\": read }) \nflow.set(\"paridadSerie2\", read); // crea variable paridadSerie2\n\nread = config.rs232.stopbitsSerie2;\nconfig_rs232.push({ \"parametro\": \"stopbitsSerie2\", \"valor\": read }) \nflow.set(\"stopbitsSerie2\", read); // crea variable stopbitsSerie2\n\nread = config.rs232.puertoHabilitadoSerie2;\nconfig_rs232.push({ \"parametro\": \"puertoHabilitadoSerie2\", \"valor\": read }) \nflow.set(\"puertoHabilitadoSerie2\", read); // crea variable puertoHabilitadoSerie2\n\n\nread = config.rs232.puertoSerie3;\nconfig_rs232.push({ \"parametro\": \"puertoSerie3\", \"valor\": read }) \nflow.set(\"puertoSerie3\", read); // crea variable puertoSerie3\n\nread = config.rs232.velocidadBaudiosSerie3;\nconfig_rs232.push({ \"parametro\": \"velocidadBaudiosSerie3\", \"valor\": read }) \nflow.set(\"velocidadBaudiosSerie3\", read); // crea variable velocidadBaudiosSerie3\n\nread = config.rs232.databitsSerie3;\nconfig_rs232.push({ \"parametro\": \"databitsSerie3\", \"valor\": read }) \nflow.set(\"databitsSerie3\", read); // crea variable databitsSerie3\n\nread =config.rs232.paridadSerie3;\nconfig_rs232.push({ \"parametro\": \"paridadSerie3\", \"valor\": read }) \nflow.set(\"paridadSerie3\", read); // crea variable paridadSerie3\n\nread = config.rs232.stopbitsSerie3;\nconfig_rs232.push({ \"parametro\": \"stopbitsSerie3\", \"valor\": read }) \nflow.set(\"stopbitsSerie3\", read); // crea variable stopbitsSerie3\n\nread = config.rs232.puertoHabilitadoSerie3;\nconfig_rs232.push({ \"parametro\": \"puertoHabilitadoSerie3\", \"valor\": read }) \nflow.set(\"puertoHabilitadoSerie3\", read); // crea variable puertoHabilitadoSerie3\n\n\n\nread = config.rs232.puertoSerie4;\nconfig_rs232.push({ \"parametro\": \"puertoSerie4\", \"valor\": read }) \nflow.set(\"puertoSerie4\", read); // crea variable puertoSerie4\n\nread = config.rs232.velocidadBaudiosSerie4;\nconfig_rs232.push({ \"parametro\": \"velocidadBaudiosSerie4\", \"valor\": read }) \nflow.set(\"velocidadBaudiosSerie4\", read); // crea variable velocidadBaudiosSerie4\n\nread = config.rs232.databitsSerie4;\nconfig_rs232.push({ \"parametro\": \"databitsSerie4\", \"valor\": read }) \nflow.set(\"databitsSerie4\", read); // crea variable databitsSerie4\n\nread = config.rs232.paridadSerie4;\nconfig_rs232.push({ \"parametro\": \"paridadSerie4\", \"valor\": read }) \nflow.set(\"paridadSerie4\", read); // crea variable paridadSerie4\n\nread = config.rs232.stopbitsSerie4;\nconfig_rs232.push({ \"parametro\": \"stopbitsSerie4\", \"valor\": read }) \nflow.set(\"stopbitsSerie4\", read); // crea variable stopbitsSerie4\n\nread = config.rs232.puertoHabilitadoSerie4;\nconfig_rs232.push({ \"parametro\": \"puertoHabilitadoSerie4\", \"valor\": read }) \nflow.set(\"puertoHabilitadoSerie4\", read); // crea variable puertoHabilitadoSerie4\n\n\n\n\n//***************************pantalla configuracion***********************//\n\nread = config.general.auth_pin;\nconfig_general.push({ \"parametro\": \"auth_pin\", \"valor\": read }) \nflow.set(\"auth_pin\", read)\n\nread = config.general.numero_clicks; // numero de clicks necesarios en la ventana de tiempo para poder acceder a la pantalla de mantenimiento \nconfig_general.push({ \"parametro\": \"numero_clicks\", \"valor\": read }) \nflow.set(\"numero_clicks\", read)\n\nread = config.general.mante_window_time; // tiempo de duracion de la ventana de espera del numero de clicks para autorizar el pase a la pantalla de mantenimiento\nconfig_general.push({ \"parametro\": \"mante_window_time\", \"valor\": read }) \nflow.set(\"mante_window_time\", read)\n\nread = config.general.pin_timeout; // timeout de espera del pin\nconfig_general.push({ \"parametro\": \"pin_timeout\", \"valor\": read }) \nflow.set(\"pin_timeout\", read)\n\nread = config.general.mante_timeout; // timeout de la pantalla de mantenimiento\nconfig_general.push({ \"parametro\": \"mante_timeout\", \"valor\": read }) \nflow.set(\"mante_timeout\", read)\n\nread = config.general.sw_bkgnd_on; // timeout de la pantalla de mantenimiento\nconfig_general.push({ \"parametro\": \"sw_bkgnd_on\", \"valor\": read }) \nflow.set(\"sw_bkgnd_on\", read)\n\nread = config.general.sw_bkgnd_off; // timeout de la pantalla de mantenimiento\nconfig_general.push({ \"parametro\": \"sw_bkgnd_off\", \"valor\": read }) \nflow.set(\"sw_bkgnd_off\", read)\n\nread = config.general.titulo_param_color; // timeout de la pantalla de mantenimiento\nconfig_general.push({ \"parametro\": \"titulo_param_color\", \"valor\": read }) \nflow.set(\"titulo_param_color\", read)\n\nread = config.general.value_param_color; // timeout de la pantalla de mantenimiento\nconfig_general.push({ \"parametro\": \"value_param_color\", \"valor\": read }) \nflow.set(\"value_param_color\", read)\n\nread = config.general.namefixedparams_color; // timeout de la pantalla de namefixedparams_color\nconfig_general.push({ \"parametro\": \"namefixedparams_color\", \"valor\": read }) \nflow.set(\"namefixedparams_color\", read)\n\n\nflow.set(\"config_general\", config_general)\nflow.set(\"config_parametros\", config_parametros)\nflow.set(\"config_rs232\", config_rs232)\n\nreturn;\n\n//*********************************************************** FUNCIONES **************************************************/\nfunction toObject(array) {\n const obj = {};\n array.forEach(el => {\n obj[el.parametro] = el.valor;\n });\n return obj;\n}",
"outputs": 1,
"timeout": "",
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 2710,
"y": 440,
"wires": [
[]
]
},
{
"id": "03b120c9713eb333",
"type": "ui_template",
"z": "3fbc028716abf7a9",
"g": "aadbe4b53903ecda",
"group": "cff29c99ac572574",
"name": "PesoBascula3",
"order": 8,
"width": "7",
"height": "2",
"format": "\n\n\n\n\t
\n\t
\n \t\t
\n\t\t\t{{msg.payload.pesoString}}
{{msg.payload.mensaje}}
\n\t\t\n\t
\n",
"storeOutMessages": true,
"fwdInMessages": true,
"resendOnRefresh": true,
"templateScope": "local",
"className": "",
"x": 1940,
"y": 980,
"wires": [
[]
]
},
{
"id": "14fbc044898db247",
"type": "ui_text",
"z": "3fbc028716abf7a9",
"g": "aadbe4b53903ecda",
"group": "cff29c99ac572574",
"order": 6,
"width": "7",
"height": "2",
"name": "Bascula3",
"label": "",
"format": "{{msg.payload.bascula}}",
"layout": "row-left",
"className": "",
"x": 1920,
"y": 1020,
"wires": []
},
{
"id": "f4cda14c866fa39a",
"type": "function",
"z": "3fbc028716abf7a9",
"g": "aadbe4b53903ecda",
"name": "envio WEBSOCKET, TRAMA,PESO Bascula3",
"func": "// ======= Parámetros =======\nconst muestras = flow.get(\"muestras\") || 5;\nconst escalon = flow.get(\"escalon\") || 20;\nconst minPesoVal = flow.get(\"minPesoVal\") || 0;\nconst TIMEOUT_MS = flow.get(\"pesoTimeoutMs\") || 5000;\n\n// ======= Entradas =======\nlet bascula = flow.get(\"nombreBascula3\") || \"Bascula3\";\nlet payloadIn = msg.payload || {};\nlet comunicacion = msg.topic;\nlet trama = payloadIn.trama;\nlet mensaje = payloadIn.mensaje;\n\n// ======= Timeout por báscula =======\nconst keyTimer = `b3_${bascula}_timer`;\nlet t = context.get(keyTimer);\nif (t) clearTimeout(t);\nt = setTimeout(() => {\n const aviso = {\n payload: {\n bascula,\n peso: null,\n pesoString: \"\",\n peso_estable: null,\n estable: false,\n comunicacion,\n trama: \"\",\n mensaje: \"No se han recibido datos en los últimos 5 segundos\",\n statuscolor: \"yellow\"\n }\n };\n flow.set(\"peso3\",\"---\")\n node.send([aviso, null]);\n}, TIMEOUT_MS);\ncontext.set(keyTimer, t);\n\n// ======= Normaliza el peso =======\nconst tienePeso = Object.prototype.hasOwnProperty.call(payloadIn, \"peso\");\nconst rawPeso = payloadIn.peso;\nconst vacio = (rawPeso === \"\" || rawPeso === null || typeof rawPeso === \"undefined\");\nconst pesoNum = Number(rawPeso);\nconst esNumero = Number.isFinite(pesoNum);\n\n// ======= Caso SIN lectura válida -> mensaje, NO 0 =======\nif (!tienePeso || vacio || !esNumero || pesoNum < minPesoVal) {\n const aviso = {\n payload: {\n bascula,\n peso: null,\n pesoString: \"\",\n peso_estable: null,\n estable: false,\n comunicacion,\n trama: trama || \"\",\n mensaje: (typeof mensaje === \"string\" ? mensaje : \"NO LLEGA PESO\"),\n statuscolor: \"yellow\"\n }\n };\n flow.set(\"peso3\",\"---\")\n return [aviso, null];\n}\n\n// ======= Lectura válida =======\nlet peso = pesoNum;\nlet pesoString = String(peso);\nlet colorStatus = peso <= 500 ? \"lime\" : \"yellow\";\n\n// ======= Estado por báscula (claves únicas) =======\nconst keyBase = `b3_${bascula}`;\nconst keyUltimo = `${keyBase}_ultimoPeso`;\nconst keyUltimoEst = `${keyBase}_ultimoPesoEstable`;\nconst keyBuffer = `${keyBase}_buffer`;\n\n// ======= Buffer circular de lecturas (solo válidas) =======\nlet buffer = flow.get(keyBuffer) || [];\nbuffer.unshift(peso);\nif (buffer.length > muestras) buffer.pop();\nflow.set(keyBuffer, buffer);\n\n// ======= Evaluación de estabilidad =======\nlet esEstable = buffer.length === muestras &&\n buffer.slice(1).every(v => Math.abs(buffer[0] - v) <= escalon);\n\nlet pesoEstable = esEstable ? buffer[0] : null;\n\n// ======= Control de repeticiones =======\nlet ultimoEst = flow.get(keyUltimoEst);\nif (esEstable && ultimoEst !== pesoEstable) {\n flow.set(keyUltimoEst, pesoEstable);\n}\n\n// ======= Salida =======\nflow.set(keyUltimo, peso);\n\nreturn [{\n payload: {\n bascula,\n peso,\n pesoString,\n peso_estable: pesoEstable,\n estable: esEstable,\n comunicacion,\n trama,\n mensaje: (typeof mensaje === \"string\" ? mensaje : \"\"),\n statuscolor: colorStatus\n }\n}, null];\n",
"outputs": 1,
"timeout": "",
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1540,
"y": 1020,
"wires": [
[
"969714a9a2b1ef72",
"14fbc044898db247",
"03b120c9713eb333"
]
]
},
{
"id": "fad252cfbd6688a3",
"type": "tcp-client",
"z": "3fbc028716abf7a9",
"g": "aadbe4b53903ecda",
"action": "action",
"actionType": "msg",
"host": "host",
"hostType": "msg",
"port": "port",
"portType": "msg",
"datamode": "stream",
"datatype": "buffer",
"newline": "",
"write": " ",
"writeType": "str",
"maxRetries": "",
"retryDelay": "",
"indefiniteRetries": true,
"topic": "basculaTCP3",
"name": "BASCULATCP3",
"debug": "info",
"x": 380,
"y": 900,
"wires": [
[
"6dbef4dc022becea",
"1457adeb1e1238af"
]
]
},
{
"id": "eb3ddc79e17c0fd3",
"type": "inject",
"z": "3fbc028716abf7a9",
"g": "aadbe4b53903ecda",
"name": "Enviar Datos",
"props": [
{
"p": "host",
"v": "hostTCPBascula3",
"vt": "flow"
},
{
"p": "port",
"v": "puertoTCPBascula3",
"vt": "flow"
},
{
"p": "action",
"v": "connect",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": true,
"onceDelay": "2",
"topic": "",
"x": 190,
"y": 900,
"wires": [
[
"fad252cfbd6688a3"
]
]
},
{
"id": "6dbef4dc022becea",
"type": "change",
"z": "3fbc028716abf7a9",
"g": "aadbe4b53903ecda",
"name": "TCPBascula3",
"rules": [
{
"t": "set",
"p": "topic",
"pt": "msg",
"to": "TCP",
"tot": "str"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 600,
"y": 900,
"wires": [
[
"34291c27b4f82d26"
]
]
},
{
"id": "2d9d86f7a82a37eb",
"type": "change",
"z": "3fbc028716abf7a9",
"g": "aadbe4b53903ecda",
"name": "RS-232Bascula3",
"rules": [
{
"t": "set",
"p": "topic",
"pt": "msg",
"to": "RS-232",
"tot": "str"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 570,
"y": 1020,
"wires": [
[
"ac8abf3f7ec09ac9"
]
]
},
{
"id": "9673d34b6f177b10",
"type": "function",
"z": "3fbc028716abf7a9",
"g": "aadbe4b53903ecda",
"name": "TRAMABascula3",
"func": "\nvar payload = msg.payload; // Obtener los datos del mensaje\nvar buffer = Buffer.from(payload); // Convertir a Buffer para manipulación de bytes\nconst stx = flow.get(\"InicioTramaBascula3\"); // Suponiendo que STX es 0x02 (ajusta según tu protocolo)\nconst etx = flow.get(\"FinalTramaBascula3\"); // Suponiendo que ETX es 0x03 (ajusta según tu protocolo)\nvar frameStart = 0;\nvar frames = [];\nvar remainingData = flow.get(\"remainingData3\") || Buffer.alloc(0); // Inicializar o usar el buffer restante de la llamada anterior\n\n// Concatenar los datos restantes de la llamada anterior con los nuevos datos\nbuffer = Buffer.concat([remainingData, buffer]);\n\nfor (var i = 0; i < buffer.length; i++) {\n if (buffer[i] === stx) {\n frameStart = i;\n } else if (buffer[i] === etx) {\n frames.push(buffer.slice(frameStart, i + 1));\n buffer = buffer.slice(i + 1); // Eliminar la trama procesada\n i = -1; // Reiniciar el índice para el siguiente bucle\n }\n}\n\n// Si queda un fragmento de trama al final, almacenarlo para la próxima llamada\nflow.set(\"remainingData3\", buffer);\nif(frames.length != 0){\n msg.payload = frames;\n node.send(msg)\n}\nreturn ;",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1010,
"y": 1020,
"wires": [
[
"1381985c88fc5dbb"
]
]
},
{
"id": "1381985c88fc5dbb",
"type": "function",
"z": "3fbc028716abf7a9",
"g": "aadbe4b53903ecda",
"name": "PESOBascula3",
"func": "\nvar payload = msg.payload; // Obtener las tramas (array de buffers)\nconst LongitudTrama = flow.get(\"LongitudTramaBascula3\"); // Longitud esperada de la trama (ajusta según tu caso)\nconst InicioPeso = flow.get(\"InicioPesoBascula3\"); // Posición inicial del peso en la trama (ajusta según tu caso)\nconst LongitudPeso = flow.get(\"LongitudPesoBascula3\"); // Longitud del valor del peso en la trama (ajusta según tu caso)\nlet tramaString='';\nvar processedFrames = [];\n\n// Obtener el temporizador previamente almacenado\nlet timeoutId = flow.get(\"timeoutIdBascula3\") || null;\nconst timeoutDuration = 5000; // Tiempo de espera en milisegundos\n\n// Limpiar el temporizador anterior si existe\nif (timeoutId) {\n clearTimeout(timeoutId);\n}\n\n// Establecer un nuevo temporizador para enviar el mensaje vacío si no hay actividad\ntimeoutId = setTimeout(() => {\n node.warn(\"No se han recibido datos en los últimos 5 segundos.\");\n msg.payload = {\n 'peso': '',\n 'pesoString': '',\n 'trama': '',\n 'mensaje': 'No se han recibido datos en los últimos 5 segundos'\n };\n node.send(msg);\n}, timeoutDuration);\n\n// Guardar el nuevo temporizador en el contexto de flujo para futuras referencias\nflow.set(\"timeoutIdBascula3\", timeoutId);\n\nfor (var i = 0; i < payload.length; i++) {\n var frame = payload[i];\n tramaString += payload[i].toString();\n\n if (frame.length === LongitudTrama) {\n // Extraer el valor del peso y convertirlo a string\n var pesoBuffer = frame.slice(InicioPeso, InicioPeso + LongitudPeso);\n var pesoString = pesoBuffer.toString('utf8'); // Asumiendo que el peso es un string UTF-8\n\n // Agregar la trama procesada al array de salida\n processedFrames.push({\n trama: frame,\n peso: pesoString,\n pesoBuffer: pesoBuffer\n });\n\n // Enviar el mensaje con datos válidos\n msg.payload = {\n 'peso': parseInt(pesoString),\n 'pesoString': pesoString,\n 'trama': tramaString,\n 'mensaje': ''\n };\n flow.set(\"peso3\",pesoString)\n node.send(msg);\n\n // Reiniciar el temporizador al recibir datos válidos\n clearTimeout(timeoutId);\n timeoutId = setTimeout(() => {\n node.warn(\"No se han recibido datos en los últimos 5 segundos.\");\n msg.payload = {\n 'peso': '',\n 'pesoString': '',\n 'trama': '',\n 'mensaje': 'No se han recibido datos en los últimos 5 segundos'\n };\n // node.send(msg);\n let msg_close = {action:\"close\", host:flow.get(\"hostTCPBascula3\"), port:flow.get(\"puertoTCPBascula3\")}\n node.send([msg, msg_close]);\n }, timeoutDuration);\n flow.set(\"timeoutIdBascula3\", timeoutId);\n } else {\n // Manejamos la trama inválida\n node.warn(\"Trama inválida\");\n msg.payload = {\n 'peso': '',\n 'pesoString': '',\n 'trama': tramaString,\n 'mensaje': 'TRAMA INVALIDA'\n };\n node.send(msg);\n }\n}\n",
"outputs": 2,
"timeout": "",
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1240,
"y": 1020,
"wires": [
[
"f4cda14c866fa39a"
],
[
"6731bec53435c2d5"
]
]
},
{
"id": "34291c27b4f82d26",
"type": "switch",
"z": "3fbc028716abf7a9",
"g": "aadbe4b53903ecda",
"name": "TCPBascula3",
"property": "tcp_flag_Bascula3",
"propertyType": "flow",
"rules": [
{
"t": "true"
}
],
"checkall": "true",
"repair": false,
"outputs": 1,
"x": 800,
"y": 900,
"wires": [
[
"9673d34b6f177b10"
]
]
},
{
"id": "ac8abf3f7ec09ac9",
"type": "switch",
"z": "3fbc028716abf7a9",
"g": "aadbe4b53903ecda",
"name": "RS-232 Bascula3",
"property": "tcp_flag_Bascula3",
"propertyType": "flow",
"rules": [
{
"t": "false"
}
],
"checkall": "true",
"repair": false,
"outputs": 1,
"x": 790,
"y": 1020,
"wires": [
[
"9673d34b6f177b10"
]
]
},
{
"id": "f89e176a87f0d923",
"type": "serial in",
"z": "3fbc028716abf7a9",
"g": "aadbe4b53903ecda",
"name": "",
"serial": "f928d3e1f28c1f5b",
"x": 370,
"y": 1020,
"wires": [
[
"2d9d86f7a82a37eb"
]
]
},
{
"id": "a8d75f0609d7f8ed",
"type": "serial control",
"z": "3fbc028716abf7a9",
"g": "aadbe4b53903ecda",
"name": "",
"serial": "f928d3e1f28c1f5b",
"x": 370,
"y": 1080,
"wires": [
[]
]
},
{
"id": "6731bec53435c2d5",
"type": "link out",
"z": "3fbc028716abf7a9",
"g": "aadbe4b53903ecda",
"name": "tcp3close_out",
"mode": "link",
"links": [
"f178e2126d397565"
],
"x": 1385,
"y": 1060,
"wires": []
},
{
"id": "1457adeb1e1238af",
"type": "link out",
"z": "3fbc028716abf7a9",
"g": "aadbe4b53903ecda",
"name": "bascula1",
"mode": "link",
"links": [
"9667a055f9c967a7"
],
"x": 575,
"y": 840,
"wires": []
},
{
"id": "5fe3f27656425fc8",
"type": "function",
"z": "3fbc028716abf7a9",
"g": "aadbe4b53903ecda",
"name": "function 3",
"func": "/*msg.action = close\nmsg.host = flow.get(\"hostTCPBascula1\")\nmsg.port = flow.get(\"puertoTCPBascula1\")\n*/\nnode.send(msg)\nsetTimeout(() => {\n let msg_close = {action:\"connect\", host:flow.get(\"hostTCPBascula3\"), port:flow.get(\"puertoTCPBascula3\")}\n node.send(msg_close);\n }, 2000);\nreturn;",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 360,
"y": 980,
"wires": [
[
"fad252cfbd6688a3"
]
]
},
{
"id": "f178e2126d397565",
"type": "link in",
"z": "3fbc028716abf7a9",
"g": "aadbe4b53903ecda",
"name": "tcp3close",
"links": [
"6731bec53435c2d5"
],
"x": 215,
"y": 1020,
"wires": [
[
"5fe3f27656425fc8"
]
]
},
{
"id": "183a0ae1b9315d02",
"type": "inject",
"z": "3fbc028716abf7a9",
"g": "aadbe4b53903ecda",
"name": "Enviar Datos",
"props": [
{
"p": "payload"
}
],
"repeat": "",
"crontab": "",
"once": true,
"onceDelay": "3",
"topic": "",
"payload": "{ \"serialport\": puertoSerie3, \"serialbaud\": velocidadBaudiosSerie3, \"databits\": databitsSerie3, \"parity\": paridadSerie3, \"stopbits\": stopbitsSerie3, \"enabled\": puertoHabilitadoSerie3 }",
"payloadType": "str",
"x": 170,
"y": 1080,
"wires": [
[
"a8d75f0609d7f8ed"
]
]
},
{
"id": "2d26d00c4530ee1f",
"type": "function",
"z": "3fbc028716abf7a9",
"g": "3c69eb8420f499d7",
"name": "envio WEBSOCKET, TRAMA,PESO Bascula1",
"func": "// ======= Parámetros =======\nconst muestras = flow.get(\"muestras\") || 5;\nconst escalon = flow.get(\"escalon\") || 20;\nconst minPesoVal = flow.get(\"minPesoVal\") || 0; // < a esto = sin peso\nconst TIMEOUT_MS = flow.get(\"pesoTimeoutMs\") || 5000; // 5s sin dato => aviso\n\n// ======= Entradas =======\nlet bascula = flow.get(\"nombreBascula1\") || \"Bascula1\";\nlet payloadIn = msg.payload || {};\nlet comunicacion = msg.topic;\nlet trama = payloadIn.trama;\nlet mensaje = payloadIn.mensaje;\n\n// ======= Watchdog por báscula (5s) =======\nconst keyTimer = `b1_${bascula}_timer`;\nlet t = context.get(keyTimer);\nif (t) clearTimeout(t);\nt = setTimeout(() => {\n const aviso = {\n payload: {\n bascula,\n peso: null, // 👈 nunca '' para no caer en 0\n pesoString: \"\",\n peso_estable: null,\n estable: false,\n comunicacion,\n trama: \"\",\n mensaje: \"No se han recibido datos en los últimos 5 segundos\",\n statuscolor: \"yellow\"\n }\n };\n flow.set(\"peso1\",\"---\")\n node.send([aviso, null]);\n}, TIMEOUT_MS);\ncontext.set(keyTimer, t);\n\n// ======= Normaliza el peso (evita Number('') === 0) =======\nlet tienePeso = Object.prototype.hasOwnProperty.call(payloadIn, \"peso\");\nlet rawPeso = payloadIn.peso;\nlet vacio = (rawPeso === \"\" || rawPeso === null || typeof rawPeso === \"undefined\");\nlet pesoNum = Number(rawPeso);\nlet esNumero = Number.isFinite(pesoNum);\n\n// ======= Caso SIN lectura válida -> mensaje (no 0) =======\nif (!tienePeso || vacio || !esNumero || pesoNum < minPesoVal) {\n let aviso = {\n payload: {\n bascula: bascula,\n peso: null,\n pesoString: \"\",\n peso_estable: null,\n estable: false,\n comunicacion: comunicacion,\n trama: trama || \"\",\n mensaje: (typeof mensaje === \"string\" ? mensaje : \"NO LLEGA PESO\"),\n statuscolor: \"yellow\"\n }\n };\n flow.set(\"peso1\",\"---\")\n return [aviso, null];\n}\n\n// ======= Lectura válida =======\nlet peso = pesoNum;\nlet pesoString = String(peso);\nlet colorStatus = peso <= 500 ? 'lime' : 'yellow';\n\n// ======= Estado por báscula (claves únicas) =======\nconst keyBase = `b1_${bascula}`;\nconst keyUltimo = `${keyBase}_ultimoPeso`;\nconst keyUltimoEst = `${keyBase}_ultimoPesoEstable`;\nconst keyBuffer = `${keyBase}_buffer`;\n\n// ======= Buffer circular de lecturas (solo válidas) =======\nlet buffer = flow.get(keyBuffer) || [];\nbuffer.unshift(peso);\nif (buffer.length > muestras) buffer.pop();\nflow.set(keyBuffer, buffer);\n\n// ======= Evaluación de estabilidad =======\nlet esEstable = buffer.length === muestras &&\n buffer.slice(1).every(v => Math.abs(buffer[0] - v) <= escalon);\n\nlet pesoEstable = esEstable ? buffer[0] : null;\n\n// ======= Control de repeticiones =======\nlet ultimoEst = flow.get(keyUltimoEst);\nif (esEstable && ultimoEst !== pesoEstable) {\n flow.set(keyUltimoEst, pesoEstable);\n}\n\n// ======= Mensaje final (lectura válida) =======\nlet mensajeComun = {\n payload: {\n bascula: bascula,\n peso: peso,\n pesoString: pesoString,\n peso_estable: pesoEstable,\n estable: esEstable,\n comunicacion: comunicacion,\n trama: trama,\n mensaje: (typeof mensaje === \"string\" ? mensaje : \"\"),\n statuscolor: colorStatus\n }\n};\n\n// ======= Salida =======\nflow.set(keyUltimo, peso); // actualizar último peso válido\nreturn [mensajeComun, null];\n",
"outputs": 1,
"timeout": "",
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1580,
"y": 300,
"wires": [
[
"760d3fa03416427f",
"9e837a6339e80936",
"969714a9a2b1ef72"
]
]
},
{
"id": "9e837a6339e80936",
"type": "ui_template",
"z": "3fbc028716abf7a9",
"g": "3c69eb8420f499d7",
"group": "cff29c99ac572574",
"name": "PesoBascula1",
"order": 3,
"width": "7",
"height": "2",
"format": "\n\n\n\n\t\n\t
\n \t\t
\n\t\t\t{{msg.payload.pesoString}}
\n\t\t\t
{{msg.payload.mensaje}}
\n\t\t\n\t
\n",
"storeOutMessages": true,
"fwdInMessages": true,
"resendOnRefresh": true,
"templateScope": "local",
"className": "",
"x": 1940,
"y": 300,
"wires": [
[]
]
},
{
"id": "7a6fc5c1767b38b0",
"type": "tcp-client",
"z": "3fbc028716abf7a9",
"g": "3c69eb8420f499d7",
"action": "action",
"actionType": "msg",
"host": "host",
"hostType": "msg",
"port": "port",
"portType": "msg",
"datamode": "stream",
"datatype": "buffer",
"newline": "",
"write": "",
"writeType": "str",
"maxRetries": "5",
"retryDelay": "3000",
"indefiniteRetries": true,
"topic": "basculaTCP1",
"name": "BASCULATCP1",
"debug": "info",
"x": 380,
"y": 220,
"wires": [
[
"8a0cc762a8039217",
"4d122b4d7ef323dd"
]
]
},
{
"id": "8a0cc762a8039217",
"type": "change",
"z": "3fbc028716abf7a9",
"g": "3c69eb8420f499d7",
"name": "TCPBascula1",
"rules": [
{
"t": "set",
"p": "topic",
"pt": "msg",
"to": "TCP",
"tot": "str"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 600,
"y": 240,
"wires": [
[
"47f84415577e771f"
]
]
},
{
"id": "bdad189ae1181c82",
"type": "change",
"z": "3fbc028716abf7a9",
"g": "3c69eb8420f499d7",
"name": "RS-232Bascula1",
"rules": [
{
"t": "set",
"p": "topic",
"pt": "msg",
"to": "RS-232",
"tot": "str"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 550,
"y": 340,
"wires": [
[
"4c97abf1728d0d77"
]
]
},
{
"id": "f0bede4dc0ffef68",
"type": "function",
"z": "3fbc028716abf7a9",
"g": "3c69eb8420f499d7",
"name": "TRAMA Bascula1",
"func": "var payload = msg.payload; // Obtener los datos del mensaje\nvar buffer = Buffer.from(payload); // Convertir a Buffer para manipulación de bytes\nconst stx = flow.get(\"InicioTramaBascula1\"); // Suponiendo que STX es 0x02 (ajusta según tu protocolo)\nconst etx = flow.get(\"FinalTramaBascula1\"); // Suponiendo que ETX es 0x03 (ajusta según tu protocolo)\nvar frameStart = 0;\nvar frames = [];\nvar remainingData = flow.get(\"remainingData1\") || Buffer.alloc(0); // Inicializar o usar el buffer restante de la llamada anterior\n\n// Concatenar los datos restantes de la llamada anterior con los nuevos datos\nbuffer = Buffer.concat([remainingData, buffer]);\n\nfor (var i = 0; i < buffer.length; i++) {\n if (buffer[i] === stx) {\n frameStart = i;\n } else if (buffer[i] === etx) {\n frames.push(buffer.slice(frameStart, i + 1));\n buffer = buffer.slice(i + 1); // Eliminar la trama procesada\n i = -1; // Reiniciar el índice para el siguiente bucle\n }\n}\n\n// Si queda un fragmento de trama al final, almacenarlo para la próxima llamada\nflow.set(\"remainingData1\", buffer);\nif(frames.length != 0){\n msg.payload = frames;\n node.send(msg)\n}\n\nreturn ;",
"outputs": 1,
"timeout": "",
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1030,
"y": 300,
"wires": [
[
"a65499ac760347b5"
]
]
},
{
"id": "a65499ac760347b5",
"type": "function",
"z": "3fbc028716abf7a9",
"g": "3c69eb8420f499d7",
"name": "PESO Bascula1",
"func": "var payload = msg.payload;\nconst LongitudTrama = flow.get(\"LongitudTramaBascula1\");\nconst InicioPeso = flow.get(\"InicioPesoBascula1\");\nconst LongitudPeso = flow.get(\"LongitudPesoBascula1\");\nlet tramaString = '';\nvar processedFrames = [];\n\n// Obtener el temporizador previamente almacenado\nlet timeoutId = flow.get(\"timeoutIdBascula1\") || null;\nconst timeoutDuration = 5000; // Tiempo de espera en milisegundos\n\n// Limpiar el temporizador anterior si existe\nif (timeoutId) {\n clearTimeout(timeoutId);\n}\n\n// Establecer un nuevo temporizador para enviar el mensaje vacío si no hay actividad\ntimeoutId = setTimeout(() => {\n node.warn(\"No se han recibido datos en los últimos 5 segundos.\");\n msg.payload = {\n 'peso': '',\n 'pesoString': '',\n 'trama': '',\n 'mensaje': 'No se han recibido datos en los últimos 5 segundos'\n };\n node.send(msg);\n}, timeoutDuration);\n\n// Guardar el nuevo temporizador en el contexto de flujo para futuras referencias\nflow.set(\"timeoutIdBascula1\", timeoutId);\n\nfor (var i = 0; i < payload.length; i++) {\n var frame = payload[i];\n tramaString += payload[i].toString();\n\n if (frame.length === LongitudTrama) {\n // Extraer el valor del peso y convertirlo a string\n var pesoBuffer = frame.slice(InicioPeso, InicioPeso + LongitudPeso);\n var pesoString = pesoBuffer.toString('utf8'); // Asumiendo que el peso es un string UTF-8\n\n // Agregar la trama procesada al array de salida\n processedFrames.push({\n trama: frame,\n peso: pesoString,\n pesoBuffer: pesoBuffer\n });\n\n // Enviar el mensaje con datos válidos\n msg.payload = {\n 'peso': parseInt(pesoString),\n 'pesoString': pesoString,\n 'trama': tramaString,\n 'mensaje': ''\n };\n flow.set(\"peso1\",pesoString)\n node.send(msg);\n\n // Reiniciar el temporizador al recibir datos válidos\n clearTimeout(timeoutId);\n timeoutId = setTimeout(() => {\n //node.warn(\"No se han recibido datos en los últimos 5 segundos.\");\n msg.payload = {\n 'peso': '',\n 'pesoString': '',\n 'trama': '',\n 'mensaje': 'No se han recibido datos en los últimos 5 segundos'\n };\n // node.send(msg);\n let msg_close = {action:\"close\", host:flow.get(\"hostTCPBascula1\"), port:flow.get(\"puertoTCPBascula1\")}\n node.send([msg, msg_close]);\n }, timeoutDuration);\n flow.set(\"timeoutIdBascula1\", timeoutId);\n } else {\n // Manejamos la trama inválida\n // node.warn(\"Trama inválida\");\n msg.payload = {\n 'peso': '',\n 'pesoString': '',\n 'trama': tramaString,\n // 'mensaje': 'TRAMA INVALIDA'\n };\n // node.send(msg);\n }\n}\n",
"outputs": 2,
"timeout": "",
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1260,
"y": 300,
"wires": [
[
"2d26d00c4530ee1f"
],
[
"9dbc4cf2cb9ad616"
]
]
},
{
"id": "47f84415577e771f",
"type": "switch",
"z": "3fbc028716abf7a9",
"g": "3c69eb8420f499d7",
"name": "TCPBascula1",
"property": "tcp_flag_Bascula1",
"propertyType": "flow",
"rules": [
{
"t": "true"
}
],
"checkall": "true",
"repair": false,
"outputs": 1,
"x": 800,
"y": 240,
"wires": [
[
"f0bede4dc0ffef68"
]
]
},
{
"id": "4c97abf1728d0d77",
"type": "switch",
"z": "3fbc028716abf7a9",
"g": "3c69eb8420f499d7",
"name": "RS-232 Bascula1",
"property": "tcp_flag_Bascula1",
"propertyType": "flow",
"rules": [
{
"t": "false"
}
],
"checkall": "true",
"repair": false,
"outputs": 1,
"x": 770,
"y": 340,
"wires": [
[
"f0bede4dc0ffef68"
]
]
},
{
"id": "760d3fa03416427f",
"type": "ui_text",
"z": "3fbc028716abf7a9",
"g": "3c69eb8420f499d7",
"group": "cff29c99ac572574",
"order": 1,
"width": "7",
"height": "2",
"name": "Bascula1",
"label": "",
"format": "{{msg.payload.bascula}}",
"layout": "row-left",
"className": "",
"x": 1920,
"y": 340,
"wires": []
},
{
"id": "5191ba50d8f57c1f",
"type": "inject",
"z": "3fbc028716abf7a9",
"g": "3c69eb8420f499d7",
"name": "Enviar Datos",
"props": [
{
"p": "host",
"v": "hostTCPBascula1",
"vt": "flow"
},
{
"p": "port",
"v": "puertoTCPBascula1",
"vt": "flow"
},
{
"p": "action",
"v": "connect",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": true,
"onceDelay": "3",
"topic": "",
"x": 170,
"y": 220,
"wires": [
[
"7a6fc5c1767b38b0"
]
]
},
{
"id": "aacff5572bb6eb47",
"type": "serial control",
"z": "3fbc028716abf7a9",
"g": "3c69eb8420f499d7",
"name": "",
"serial": "f928d3e1f28c1f5b",
"x": 370,
"y": 400,
"wires": [
[]
]
},
{
"id": "69b2b79597b10613",
"type": "inject",
"z": "3fbc028716abf7a9",
"g": "3c69eb8420f499d7",
"name": "Enviar Datos",
"props": [
{
"p": "payload"
}
],
"repeat": "",
"crontab": "",
"once": true,
"onceDelay": "3",
"topic": "",
"payload": "{ \"serialport\": puertoSerie1, \"serialbaud\": velocidadBaudiosSerie1, \"databits\": databitsSerie1, \"parity\": paridadSerie1, \"stopbits\": stopbitsSerie1, \"enabled\": puertoHabilitadoSerie1 }",
"payloadType": "str",
"x": 190,
"y": 400,
"wires": [
[
"aacff5572bb6eb47"
]
]
},
{
"id": "81d1e1eceb078857",
"type": "serial in",
"z": "3fbc028716abf7a9",
"g": "3c69eb8420f499d7",
"name": "",
"serial": "f928d3e1f28c1f5b",
"x": 370,
"y": 340,
"wires": [
[
"bdad189ae1181c82"
]
]
},
{
"id": "b3bf54cc42094984",
"type": "link in",
"z": "3fbc028716abf7a9",
"g": "3c69eb8420f499d7",
"name": "tcp1close",
"links": [
"9dbc4cf2cb9ad616"
],
"x": 105,
"y": 320,
"wires": [
[
"5704a222c2e6219e"
]
]
},
{
"id": "9dbc4cf2cb9ad616",
"type": "link out",
"z": "3fbc028716abf7a9",
"g": "3c69eb8420f499d7",
"name": "tcp1close_out",
"mode": "link",
"links": [
"b3bf54cc42094984"
],
"x": 1375,
"y": 340,
"wires": []
},
{
"id": "5704a222c2e6219e",
"type": "function",
"z": "3fbc028716abf7a9",
"g": "3c69eb8420f499d7",
"name": "function 1",
"func": "/*msg.action = close\nmsg.host = flow.get(\"hostTCPBascula1\")\nmsg.port = flow.get(\"puertoTCPBascula1\")\n*/\nnode.send(msg)\nsetTimeout(() => {\n let msg_close = {action:\"connect\", host:flow.get(\"hostTCPBascula1\"), port:flow.get(\"puertoTCPBascula1\")}\n node.send(msg_close);\n }, 2000);\nreturn;",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 180,
"y": 260,
"wires": [
[
"7a6fc5c1767b38b0"
]
]
},
{
"id": "4d122b4d7ef323dd",
"type": "link out",
"z": "3fbc028716abf7a9",
"g": "3c69eb8420f499d7",
"name": "bascula1",
"mode": "link",
"links": [
"9667a055f9c967a7"
],
"x": 525,
"y": 180,
"wires": []
},
{
"id": "c03cc7b1afedcdad",
"type": "ui_template",
"z": "3fbc028716abf7a9",
"g": "414eb1569dd15f60",
"group": "cff29c99ac572574",
"name": "PesoBascula2",
"order": 5,
"width": "7",
"height": "2",
"format": "\n\n\n\n\t\n\t
\n \t\t
\n\t\t\t{{msg.payload.pesoString}}
{{msg.payload.mensaje}}
\n\t\t\n\t
\n",
"storeOutMessages": true,
"fwdInMessages": true,
"resendOnRefresh": true,
"templateScope": "local",
"className": "",
"x": 1940,
"y": 600,
"wires": [
[]
]
},
{
"id": "5aa551b29b8cdca3",
"type": "ui_text",
"z": "3fbc028716abf7a9",
"g": "414eb1569dd15f60",
"group": "cff29c99ac572574",
"order": 2,
"width": "7",
"height": "2",
"name": "Bascula2",
"label": "",
"format": "{{msg.payload.bascula}}",
"layout": "row-left",
"className": "",
"x": 1920,
"y": 640,
"wires": []
},
{
"id": "a979e217b6375370",
"type": "function",
"z": "3fbc028716abf7a9",
"g": "414eb1569dd15f60",
"name": "envio WEBSOCKET, TRAMA,PESO Bascula2",
"func": "// ======= Parámetros =======\nconst muestras = flow.get(\"muestras\") || 5;\nconst escalon = flow.get(\"escalon\") || 20;\nconst minPesoVal = flow.get(\"minPesoVal\") || 0;\nconst TIMEOUT_MS = flow.get(\"pesoTimeoutMs\") || 5000;\n\n// ======= Entradas =======\nlet bascula = flow.get(\"nombreBascula2\") || \"Bascula2\";\nlet payloadIn = msg.payload || {};\nlet comunicacion = msg.topic;\nlet trama = payloadIn.trama;\nlet mensaje = payloadIn.mensaje;\n\n// ======= Timeout por báscula =======\nconst keyTimer = `b2_${bascula}_timer`;\nlet t = context.get(keyTimer);\nif (t) clearTimeout(t);\nt = setTimeout(() => {\n const aviso = {\n payload: {\n bascula,\n peso: null,\n pesoString: \"\",\n peso_estable: null,\n estable: false,\n comunicacion,\n trama: \"\",\n mensaje: \"No se han recibido datos en los últimos 5 segundos\",\n statuscolor: \"yellow\"\n }\n };\n flow.set(\"peso2\",\"---\")\n node.send([aviso, null]);\n}, TIMEOUT_MS);\ncontext.set(keyTimer, t);\n\n// ======= Normaliza el peso =======\nconst tienePeso = Object.prototype.hasOwnProperty.call(payloadIn, \"peso\");\nconst rawPeso = payloadIn.peso;\nconst vacio = (rawPeso === \"\" || rawPeso === null || typeof rawPeso === \"undefined\");\nconst pesoNum = Number(rawPeso);\nconst esNumero = Number.isFinite(pesoNum);\n\n// ======= Caso SIN lectura válida -> mensaje, NO 0 =======\nif (!tienePeso || vacio || !esNumero || pesoNum < minPesoVal) {\n const aviso = {\n payload: {\n bascula,\n peso: null,\n pesoString: \"\",\n peso_estable: null,\n estable: false,\n comunicacion,\n trama: trama || \"\",\n mensaje: (typeof mensaje === \"string\" ? mensaje : \"NO LLEGA PESO\"),\n statuscolor: \"yellow\"\n }\n };\n flow.set(\"peso2\",\"---\")\n return [aviso, null];\n}\n\n// ======= Lectura válida =======\nlet peso = pesoNum;\nlet pesoString = String(peso);\nlet colorStatus = peso <= 500 ? \"lime\" : \"yellow\";\n\n// ======= Estado por báscula (claves únicas) =======\nconst keyBase = `b2_${bascula}`;\nconst keyUltimo = `${keyBase}_ultimoPeso`;\nconst keyUltimoEst = `${keyBase}_ultimoPesoEstable`;\nconst keyBuffer = `${keyBase}_buffer`;\n\n// ======= Buffer circular de lecturas (solo válidas) =======\nlet buffer = flow.get(keyBuffer) || [];\nbuffer.unshift(peso);\nif (buffer.length > muestras) buffer.pop();\nflow.set(keyBuffer, buffer);\n\n// ======= Evaluación de estabilidad =======\nlet esEstable = buffer.length === muestras &&\n buffer.slice(1).every(v => Math.abs(buffer[0] - v) <= escalon);\n\nlet pesoEstable = esEstable ? buffer[0] : null;\n\n// ======= Control de repeticiones =======\nlet ultimoEst = flow.get(keyUltimoEst);\nif (esEstable && ultimoEst !== pesoEstable) {\n flow.set(keyUltimoEst, pesoEstable);\n}\n\n// ======= Salida =======\nflow.set(keyUltimo, peso);\n\nreturn [{\n payload: {\n bascula,\n peso,\n pesoString,\n peso_estable: pesoEstable,\n estable: esEstable,\n comunicacion,\n trama,\n mensaje: (typeof mensaje === \"string\" ? mensaje : \"\"),\n statuscolor: colorStatus\n }\n}, null];\n",
"outputs": 1,
"timeout": "",
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1520,
"y": 620,
"wires": [
[
"5aa551b29b8cdca3",
"c03cc7b1afedcdad",
"969714a9a2b1ef72"
]
]
},
{
"id": "745217e56d674e38",
"type": "tcp-client",
"z": "3fbc028716abf7a9",
"g": "414eb1569dd15f60",
"action": "action",
"actionType": "msg",
"host": "host",
"hostType": "msg",
"port": "port",
"portType": "msg",
"datamode": "stream",
"datatype": "buffer",
"newline": "",
"write": "",
"writeType": "str",
"maxRetries": "5",
"retryDelay": "2000",
"indefiniteRetries": true,
"topic": "basculaTCP2",
"name": "BASCULATCP2",
"debug": "error",
"x": 380,
"y": 560,
"wires": [
[
"af2b7b939331dbb9",
"e59e292827957f03"
]
]
},
{
"id": "c90a867aaba4b730",
"type": "inject",
"z": "3fbc028716abf7a9",
"g": "414eb1569dd15f60",
"name": "Enviar Datos",
"props": [
{
"p": "host",
"v": "hostTCPBascula2",
"vt": "flow"
},
{
"p": "port",
"v": "puertoTCPBascula2",
"vt": "flow"
},
{
"p": "action",
"v": "connect",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": true,
"onceDelay": "3",
"topic": "",
"x": 170,
"y": 580,
"wires": [
[
"745217e56d674e38"
]
]
},
{
"id": "af2b7b939331dbb9",
"type": "change",
"z": "3fbc028716abf7a9",
"g": "414eb1569dd15f60",
"name": "TCPBascula2",
"rules": [
{
"t": "set",
"p": "topic",
"pt": "msg",
"to": "TCP",
"tot": "str"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 600,
"y": 580,
"wires": [
[
"69c5f45de39463f0"
]
]
},
{
"id": "c9a6ce7c276cd0cf",
"type": "change",
"z": "3fbc028716abf7a9",
"g": "414eb1569dd15f60",
"name": "RS-232Bascula2",
"rules": [
{
"t": "set",
"p": "topic",
"pt": "msg",
"to": "RS-232",
"tot": "str"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 590,
"y": 680,
"wires": [
[
"91bdf33233cd6dc8"
]
]
},
{
"id": "e45740d360367781",
"type": "function",
"z": "3fbc028716abf7a9",
"g": "414eb1569dd15f60",
"name": "TRAMA Bascula2",
"func": "var payload = msg.payload; // Obtener los datos del mensaje\nvar buffer = Buffer.from(payload); // Convertir a Buffer para manipulación de bytes\nconst stx = flow.get(\"InicioTramaBascula2\"); // Suponiendo que STX es 0x02 (ajusta según tu protocolo)\nconst etx = flow.get(\"FinalTramaBascula2\"); // Suponiendo que ETX es 0x03 (ajusta según tu protocolo)\nvar frameStart = 0;\nvar frames = [];\nvar remainingData = flow.get(\"remainingData2\") || Buffer.alloc(0); // Inicializar o usar el buffer restante de la llamada anterior\n\n// Concatenar los datos restantes de la llamada anterior con los nuevos datos\nbuffer = Buffer.concat([remainingData, buffer]);\n\nfor (var i = 0; i < buffer.length; i++) {\n if (buffer[i] === stx) {\n frameStart = i;\n } else if (buffer[i] === etx) {\n frames.push(buffer.slice(frameStart, i + 1));\n buffer = buffer.slice(i + 1); // Eliminar la trama procesada\n i = -1; // Reiniciar el índice para el siguiente bucle\n }\n}\n\n// Si queda un fragmento de trama al final, almacenarlo para la próxima llamada\nflow.set(\"remainingData2\", buffer);\nif(frames.length != 0){\n msg.payload = frames;\n node.send(msg)\n}\n\nreturn ;",
"outputs": 1,
"timeout": "",
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1010,
"y": 620,
"wires": [
[
"92f2a1b83feac9b1"
]
]
},
{
"id": "92f2a1b83feac9b1",
"type": "function",
"z": "3fbc028716abf7a9",
"g": "414eb1569dd15f60",
"name": "PESOBascula2",
"func": "var payload = msg.payload;\nconst LongitudTrama = flow.get(\"LongitudTramaBascula2\");\nconst InicioPeso = flow.get(\"InicioPesoBascula2\");\nconst LongitudPeso = flow.get(\"LongitudPesoBascula2\");\nlet tramaString = '';\nvar processedFrames = [];\n\n// Obtener el temporizador previamente almacenado\nlet timeoutId = flow.get(\"timeoutIdBascula2\") || null;\nconst timeoutDuration = 5000; // Tiempo de espera en milisegundos\n\n// Limpiar el temporizador anterior si existe\nif (timeoutId) {\n clearTimeout(timeoutId);\n}\n\n// Establecer un nuevo temporizador para enviar el mensaje vacío si no hay actividad\ntimeoutId = setTimeout(() => {\n node.warn(\"No se han recibido datos en los últimos 5 segundos.\");\n msg.payload = {\n 'peso': '',\n 'pesoString': '',\n 'trama': '',\n 'mensaje': 'No se han recibido datos en los últimos 5 segundos RS232'\n };\n node.send(msg);\n}, timeoutDuration);\n\n// Guardar el nuevo temporizador en el contexto de flujo para futuras referencias\nflow.set(\"timeoutIdBascula2\", timeoutId);\n\nfor (var i = 0; i < payload.length; i++) {\n var frame = payload[i];\n tramaString += payload[i].toString();\n\n if (frame.length === LongitudTrama) {\n // Extraer el valor del peso y convertirlo a string\n var pesoBuffer = frame.slice(InicioPeso, InicioPeso + LongitudPeso);\n var pesoString = pesoBuffer.toString('utf8'); // Asumiendo que el peso es un string UTF-8\n\n // Agregar la trama procesada al array de salida\n processedFrames.push({\n trama: frame,\n peso: pesoString,\n pesoBuffer: pesoBuffer\n });\n\n // Enviar el mensaje con datos válidos\n msg.payload = {\n 'peso': parseInt(pesoString),\n 'pesoString': pesoString,\n 'trama': tramaString,\n 'mensaje': ''\n };\n flow.set(\"peso2\",pesoString)\n node.send(msg);\n\n // Reiniciar el temporizador al recibir datos válidos\n clearTimeout(timeoutId);\n timeoutId = setTimeout(() => {\n //node.warn(\"No se han recibido datos en los últimos 5 segundos.\");\n msg.payload = {\n 'peso': '',\n 'pesoString': '',\n 'trama': '',\n 'mensaje': 'No se han recibido datos en los últimos 5 segundos RED'\n };\n // node.send(msg);\n let msg_close = {action:\"close\", host:flow.get(\"hostTCPBascula2\"), port:flow.get(\"puertoTCPBascula2\")}\n node.send([msg, msg_close]);\n }, timeoutDuration);\n flow.set(\"timeoutIdBascula2\", timeoutId);\n } else {\n // Manejamos la trama inválida\n // node.warn(\"Trama inválida\");\n msg.payload = {\n 'peso': '',\n 'pesoString': '',\n 'trama': tramaString,\n // 'mensaje': 'TRAMA INVALIDA'\n };\n // node.send(msg);\n }\n}\n",
"outputs": 2,
"timeout": "",
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1220,
"y": 620,
"wires": [
[
"a979e217b6375370"
],
[
"fa67b799f5270de3"
]
]
},
{
"id": "69c5f45de39463f0",
"type": "switch",
"z": "3fbc028716abf7a9",
"g": "414eb1569dd15f60",
"name": "TCPBascula2",
"property": "tcp_flag_Bascula2",
"propertyType": "flow",
"rules": [
{
"t": "true"
}
],
"checkall": "true",
"repair": false,
"outputs": 1,
"x": 800,
"y": 580,
"wires": [
[
"e45740d360367781"
]
]
},
{
"id": "91bdf33233cd6dc8",
"type": "switch",
"z": "3fbc028716abf7a9",
"g": "414eb1569dd15f60",
"name": "RS-232 Bascula2",
"property": "tcp_flag_Bascula2",
"propertyType": "flow",
"rules": [
{
"t": "false"
}
],
"checkall": "true",
"repair": false,
"outputs": 1,
"x": 810,
"y": 680,
"wires": [
[
"e45740d360367781"
]
]
},
{
"id": "751f20d5500f0560",
"type": "serial control",
"z": "3fbc028716abf7a9",
"g": "414eb1569dd15f60",
"name": "",
"serial": "f928d3e1f28c1f5b",
"x": 390,
"y": 740,
"wires": [
[]
]
},
{
"id": "662de6fd08e45fb2",
"type": "inject",
"z": "3fbc028716abf7a9",
"g": "414eb1569dd15f60",
"name": "Enviar Datos",
"props": [
{
"p": "payload"
}
],
"repeat": "",
"crontab": "",
"once": true,
"onceDelay": "3",
"topic": "",
"payload": "{ \"serialport\": puertoSerie3, \"serialbaud\": velocidadBaudiosSerie2, \"databits\": databitsSerie2, \"parity\": paridadSerie2, \"stopbits\": stopbitsSerie2, \"enabled\": puertoHabilitadoSerie2 }",
"payloadType": "str",
"x": 170,
"y": 740,
"wires": [
[
"751f20d5500f0560"
]
]
},
{
"id": "c7ac3675f9f951fe",
"type": "serial in",
"z": "3fbc028716abf7a9",
"g": "414eb1569dd15f60",
"name": "",
"serial": "f928d3e1f28c1f5b",
"x": 390,
"y": 680,
"wires": [
[
"c9a6ce7c276cd0cf"
]
]
},
{
"id": "fa67b799f5270de3",
"type": "link out",
"z": "3fbc028716abf7a9",
"g": "414eb1569dd15f60",
"name": "tcp2close_out",
"mode": "link",
"links": [
"4c71a5b6fc6bd505"
],
"x": 1325,
"y": 660,
"wires": []
},
{
"id": "e59e292827957f03",
"type": "link out",
"z": "3fbc028716abf7a9",
"g": "414eb1569dd15f60",
"name": "bascula1",
"mode": "link",
"links": [
"9667a055f9c967a7"
],
"x": 545,
"y": 500,
"wires": []
},
{
"id": "a567f5be5995d73c",
"type": "function",
"z": "3fbc028716abf7a9",
"g": "414eb1569dd15f60",
"name": "function 2",
"func": "/*msg.action = close\nmsg.host = flow.get(\"hostTCPBascula1\")\nmsg.port = flow.get(\"puertoTCPBascula1\")\n*/\nnode.send(msg)\nsetTimeout(() => {\n let msg_close = {action:\"connect\", host:flow.get(\"hostTCPBascula2\"), port:flow.get(\"puertoTCPBascula2\")}\n node.send(msg_close);\n }, 2000);\nreturn;",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 380,
"y": 620,
"wires": [
[
"745217e56d674e38"
]
]
},
{
"id": "4c71a5b6fc6bd505",
"type": "link in",
"z": "3fbc028716abf7a9",
"g": "414eb1569dd15f60",
"name": "tcp2close",
"links": [
"fa67b799f5270de3"
],
"x": 245,
"y": 680,
"wires": [
[
"a567f5be5995d73c"
]
]
},
{
"id": "83659a5fa5422105",
"type": "ui_template",
"z": "3fbc028716abf7a9",
"g": "21cb55110b1bae57",
"group": "cff29c99ac572574",
"name": "PesoBascula4",
"order": 9,
"width": "7",
"height": "2",
"format": "\n\n\n\n\t\n\t
\n \t\t
\n\t\t\t{{msg.payload.pesoString}}
{{msg.payload.mensaje}}
\n\t\t\n\t
",
"storeOutMessages": true,
"fwdInMessages": true,
"resendOnRefresh": true,
"templateScope": "local",
"className": "",
"x": 1940,
"y": 1300,
"wires": [
[]
]
},
{
"id": "d03daa5335e2514c",
"type": "ui_text",
"z": "3fbc028716abf7a9",
"g": "21cb55110b1bae57",
"group": "cff29c99ac572574",
"order": 7,
"width": "7",
"height": "2",
"name": "Bascula4",
"label": "",
"format": "{{msg.payload.bascula}}",
"layout": "row-left",
"className": "",
"x": 1920,
"y": 1340,
"wires": []
},
{
"id": "5cf1088cefafd3a7",
"type": "function",
"z": "3fbc028716abf7a9",
"g": "21cb55110b1bae57",
"name": "envio WEBSOCKET, TRAMA,PESO Bascula4",
"func": "// ======= Parámetros =======\nconst muestras = flow.get(\"muestras\") || 5;\nconst escalon = flow.get(\"escalon\") || 20;\nconst minPesoVal = flow.get(\"minPesoVal\") || 0;\nconst TIMEOUT_MS = flow.get(\"pesoTimeoutMs\") || 5000;\n\n// ======= Entradas =======\nlet bascula = flow.get(\"nombreBascula4\") || \"Bascula4\";\nlet payloadIn = msg.payload || {};\nlet comunicacion = msg.topic;\nlet trama = payloadIn.trama;\nlet mensaje = payloadIn.mensaje;\n\n// ======= Timeout por báscula =======\nconst keyTimer = `b4_${bascula}_timer`;\nlet t = context.get(keyTimer);\nif (t) clearTimeout(t);\nt = setTimeout(() => {\n const aviso = {\n payload: {\n bascula,\n peso: null,\n pesoString: \"\",\n peso_estable: null,\n estable: false,\n comunicacion,\n trama: \"\",\n mensaje: \"No se han recibido datos en los últimos 5 segundos\",\n statuscolor: \"yellow\"\n }\n };\n flow.set(\"peso4\",\"---\")\n node.send([aviso, null]);\n}, TIMEOUT_MS);\ncontext.set(keyTimer, t);\n\n// ======= Normaliza el peso =======\nconst tienePeso = Object.prototype.hasOwnProperty.call(payloadIn, \"peso\");\nconst rawPeso = payloadIn.peso;\nconst vacio = (rawPeso === \"\" || rawPeso === null || typeof rawPeso === \"undefined\");\nconst pesoNum = Number(rawPeso);\nconst esNumero = Number.isFinite(pesoNum);\n\n// ======= Caso SIN lectura válida -> mensaje, NO 0 =======\nif (!tienePeso || vacio || !esNumero || pesoNum < minPesoVal) {\n const aviso = {\n payload: {\n bascula,\n peso: null,\n pesoString: \"\",\n peso_estable: null,\n estable: false,\n comunicacion,\n trama: trama || \"\",\n mensaje: (typeof mensaje === \"string\" ? mensaje : \"NO LLEGA PESO\"),\n statuscolor: \"yellow\"\n }\n };\n flow.set(\"peso4\",\"---\")\n return [aviso, null];\n}\n\n// ======= Lectura válida =======\nlet peso = pesoNum;\nlet pesoString = String(peso);\nlet colorStatus = peso <= 500 ? \"lime\" : \"yellow\";\n\n// ======= Estado por báscula (claves únicas) =======\nconst keyBase = `b4_${bascula}`;\nconst keyUltimo = `${keyBase}_ultimoPeso`;\nconst keyUltimoEst = `${keyBase}_ultimoPesoEstable`;\nconst keyBuffer = `${keyBase}_buffer`;\n\n// ======= Buffer circular de lecturas (solo válidas) =======\nlet buffer = flow.get(keyBuffer) || [];\nbuffer.unshift(peso);\nif (buffer.length > muestras) buffer.pop();\nflow.set(keyBuffer, buffer);\n\n// ======= Evaluación de estabilidad =======\nlet esEstable = buffer.length === muestras &&\n buffer.slice(1).every(v => Math.abs(buffer[0] - v) <= escalon);\n\nlet pesoEstable = esEstable ? buffer[0] : null;\n\n// ======= Control de repeticiones =======\nlet ultimoEst = flow.get(keyUltimoEst);\nif (esEstable && ultimoEst !== pesoEstable) {\n flow.set(keyUltimoEst, pesoEstable);\n}\n\n// ======= Salida =======\nflow.set(keyUltimo, peso);\n\nreturn [{\n payload: {\n bascula,\n peso,\n pesoString,\n peso_estable: pesoEstable,\n estable: esEstable,\n comunicacion,\n trama,\n mensaje: (typeof mensaje === \"string\" ? mensaje : \"\"),\n statuscolor: colorStatus\n }\n}, null];\n",
"outputs": 1,
"timeout": "",
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1600,
"y": 1320,
"wires": [
[
"969714a9a2b1ef72",
"d03daa5335e2514c",
"83659a5fa5422105"
]
]
},
{
"id": "b8228cdac41b95c1",
"type": "tcp-client",
"z": "3fbc028716abf7a9",
"g": "21cb55110b1bae57",
"action": "action",
"actionType": "msg",
"host": "host",
"hostType": "msg",
"port": "port",
"portType": "msg",
"datamode": "stream",
"datatype": "buffer",
"newline": "",
"write": "",
"writeType": "str",
"maxRetries": "",
"retryDelay": "",
"indefiniteRetries": true,
"topic": "basculaTCP4",
"name": "BASCULATCP4",
"debug": "error",
"x": 380,
"y": 1220,
"wires": [
[
"d216a46466968eae",
"d6e380c9415bfb72"
]
]
},
{
"id": "5d3d7b9e427ebd33",
"type": "inject",
"z": "3fbc028716abf7a9",
"g": "21cb55110b1bae57",
"name": "Enviar Datos",
"props": [
{
"p": "host",
"v": "hostTCPBascula4",
"vt": "flow"
},
{
"p": "port",
"v": "puertoTCPBascula4",
"vt": "flow"
},
{
"p": "action",
"v": "connect",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": true,
"onceDelay": "2",
"topic": "",
"x": 170,
"y": 1220,
"wires": [
[
"b8228cdac41b95c1"
]
]
},
{
"id": "d216a46466968eae",
"type": "change",
"z": "3fbc028716abf7a9",
"g": "21cb55110b1bae57",
"name": "TCPBascula4",
"rules": [
{
"t": "set",
"p": "topic",
"pt": "msg",
"to": "TCP",
"tot": "str"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 600,
"y": 1220,
"wires": [
[
"061c9a2b5eb5e8b5"
]
]
},
{
"id": "b0ed51791aa894e6",
"type": "change",
"z": "3fbc028716abf7a9",
"g": "21cb55110b1bae57",
"name": "RS-232Bascula4",
"rules": [
{
"t": "set",
"p": "topic",
"pt": "msg",
"to": "RS-232",
"tot": "str"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 590,
"y": 1320,
"wires": [
[
"6d6370556efac085"
]
]
},
{
"id": "631799398328acdf",
"type": "function",
"z": "3fbc028716abf7a9",
"g": "21cb55110b1bae57",
"name": "TRAMABascula4",
"func": "\nvar payload = msg.payload; // Obtener los datos del mensaje\nvar buffer4 = Buffer.from(payload); // Convertir a Buffer para manipulación de bytes\nconst stx = flow.get(\"InicioTramaBascula4\"); // Suponiendo que STX es 0x02 (ajusta según tu protocolo)\nconst etx = flow.get(\"FinalTramaBascula4\"); // Suponiendo que ETX es 0x03 (ajusta según tu protocolo)\nvar frameStart = 0;\nvar frames = [];\nvar remainingData4 = flow.get(\"remainingData4\") || Buffer.alloc(0); // Inicializar o usar el buffer restante de la llamada anterior\n\n// Concatenar los datos restantes de la llamada anterior con los nuevos datos\nbuffer4 = Buffer.concat([remainingData4, buffer4]);\n\nfor (var i = 0; i < buffer4.length; i++) {\n if (buffer4[i] === stx) {\n frameStart = i;\n } else if (buffer4[i] === etx) {\n frames.push(buffer4.slice(frameStart, i + 1));\n buffer4 = buffer4.slice(i + 1); // Eliminar la trama procesada\n i = -1; // Reiniciar el índice para el siguiente bucle\n }\n}\n\n// Si queda un fragmento de trama al final, almacenarlo para la próxima llamada\nflow.set(\"remainingData4\", buffer4);\nif(frames.length != 0){\n msg.payload = frames;\n node.send(msg)\n\n}\nreturn ;",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1070,
"y": 1320,
"wires": [
[
"c5d5fe49f9134079"
]
]
},
{
"id": "c5d5fe49f9134079",
"type": "function",
"z": "3fbc028716abf7a9",
"g": "21cb55110b1bae57",
"name": "PESOBascula4",
"func": "var payload = msg.payload;\nconst LongitudTrama = flow.get(\"LongitudTramaBascula4\");\nconst InicioPeso = flow.get(\"InicioPesoBascula4\");\nconst LongitudPeso = flow.get(\"LongitudPesoBascula4\");\nlet tramaString = '';\nvar processedFrames = [];\n\n// Obtener el temporizador previamente almacenado\nlet timeoutId = flow.get(\"timeoutIdBascula4\") || null;\nconst timeoutDuration = 5000; // Tiempo de espera en milisegundos\n\n// Limpiar el temporizador anterior si existe\nif (timeoutId) {\n clearTimeout(timeoutId);\n}\n\n// Establecer un nuevo temporizador para enviar el mensaje vacío si no hay actividad\ntimeoutId = setTimeout(() => {\n node.warn(\"No se han recibido datos en los últimos 5 segundos.\");\n msg.payload = {\n 'peso': '',\n 'pesoString': '',\n 'trama': '',\n 'mensaje': 'No se han recibido datos en los últimos 5 segundos'\n };\n node.send(msg);\n}, timeoutDuration);\n\n// Guardar el nuevo temporizador en el contexto de flujo para futuras referencias\nflow.set(\"timeoutIdBascula4\", timeoutId);\n\nfor (var i = 0; i < payload.length; i++) {\n var frame = payload[i];\n tramaString += payload[i].toString();\n\n if (frame.length === LongitudTrama) {\n // Extraer el valor del peso y convertirlo a string\n var pesoBuffer = frame.slice(InicioPeso, InicioPeso + LongitudPeso);\n var pesoString = pesoBuffer.toString('utf8'); // Asumiendo que el peso es un string UTF-8\n\n // Agregar la trama procesada al array de salida\n processedFrames.push({\n trama: frame,\n peso: pesoString,\n pesoBuffer: pesoBuffer\n });\n\n // Enviar el mensaje con datos válidos\n msg.payload = {\n 'peso': parseInt(pesoString),\n 'pesoString': pesoString,\n 'trama': tramaString,\n 'mensaje': ''\n };\n flow.set(\"peso4\",pesoString)\n node.send(msg);\n\n // Reiniciar el temporizador al recibir datos válidos\n clearTimeout(timeoutId);\n timeoutId = setTimeout(() => {\n node.warn(\"No se han recibido datos en los últimos 5 segundos.\");\n msg.payload = {\n 'peso': '',\n 'pesoString': '',\n 'trama': '',\n 'mensaje': 'No se han recibido datos en los últimos 5 segundos'\n };\n //node.send(msg);\n let msg_close = {action:\"close\", host:flow.get(\"hostTCPBascula4\"), port:flow.get(\"puertoTCPBascula4\")}\n node.send([msg, msg_close]);\n }, timeoutDuration);\n flow.set(\"timeoutIdBascula4\", timeoutId);\n } else {\n // Manejamos la trama inválida\n // node.warn(\"Trama inválida\");\n msg.payload = {\n 'peso': '',\n 'pesoString': '',\n 'trama': tramaString,\n 'mensaje': 'TRAMA INVALIDA'\n };\n node.send(msg);\n }\n}\n",
"outputs": 2,
"timeout": "",
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1280,
"y": 1320,
"wires": [
[
"5cf1088cefafd3a7"
],
[
"55d810bcd402f1c0"
]
]
},
{
"id": "061c9a2b5eb5e8b5",
"type": "switch",
"z": "3fbc028716abf7a9",
"g": "21cb55110b1bae57",
"name": "TCPBascula4",
"property": "tcp_flag_Bascula4",
"propertyType": "flow",
"rules": [
{
"t": "true"
}
],
"checkall": "true",
"repair": false,
"outputs": 1,
"x": 800,
"y": 1220,
"wires": [
[
"631799398328acdf"
]
]
},
{
"id": "6d6370556efac085",
"type": "switch",
"z": "3fbc028716abf7a9",
"g": "21cb55110b1bae57",
"name": "RS-232 Bascula4",
"property": "tcp_flag_Bascula4",
"propertyType": "flow",
"rules": [
{
"t": "false"
}
],
"checkall": "true",
"repair": false,
"outputs": 1,
"x": 810,
"y": 1320,
"wires": [
[
"631799398328acdf"
]
]
},
{
"id": "6680d1b3b7ad8918",
"type": "inject",
"z": "3fbc028716abf7a9",
"g": "21cb55110b1bae57",
"name": "Enviar Datos",
"props": [
{
"p": "payload"
}
],
"repeat": "",
"crontab": "",
"once": true,
"onceDelay": "3",
"topic": "",
"payload": "{ \"serialport\": puertoSerie4, \"serialbaud\": velocidadBaudiosSerie4, \"databits\": databitsSerie4, \"parity\": paridadSerie4, \"stopbits\": stopbitsSerie4, \"enabled\": puertoHabilitadoSerie4 }",
"payloadType": "str",
"x": 190,
"y": 1380,
"wires": [
[
"63c34626c74e9d80"
]
]
},
{
"id": "63c34626c74e9d80",
"type": "serial control",
"z": "3fbc028716abf7a9",
"g": "21cb55110b1bae57",
"name": "",
"serial": "f928d3e1f28c1f5b",
"x": 370,
"y": 1380,
"wires": [
[]
]
},
{
"id": "12fbd1e2e0da99f5",
"type": "serial in",
"z": "3fbc028716abf7a9",
"g": "21cb55110b1bae57",
"name": "",
"serial": "f928d3e1f28c1f5b",
"x": 370,
"y": 1320,
"wires": [
[
"b0ed51791aa894e6"
]
]
},
{
"id": "55d810bcd402f1c0",
"type": "link out",
"z": "3fbc028716abf7a9",
"g": "21cb55110b1bae57",
"name": "tcp4close_out",
"mode": "link",
"links": [
"a1792e1b8fb6e6be"
],
"x": 1425,
"y": 1380,
"wires": []
},
{
"id": "d6e380c9415bfb72",
"type": "link out",
"z": "3fbc028716abf7a9",
"g": "21cb55110b1bae57",
"name": "bascula1",
"mode": "link",
"links": [
"9667a055f9c967a7"
],
"x": 565,
"y": 1180,
"wires": []
},
{
"id": "98c9a64ec995f9d2",
"type": "function",
"z": "3fbc028716abf7a9",
"g": "21cb55110b1bae57",
"name": "function 4",
"func": "/*msg.action = close\nmsg.host = flow.get(\"hostTCPBascula1\")\nmsg.port = flow.get(\"puertoTCPBascula1\")\n*/\nnode.send(msg)\nsetTimeout(() => {\n let msg_close = {action:\"connect\", host:flow.get(\"hostTCPBascula4\"), port:flow.get(\"puertoTCPBascula4\")}\n node.send(msg_close);\n }, 2000);\nreturn;",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 360,
"y": 1280,
"wires": [
[
"b8228cdac41b95c1"
]
]
},
{
"id": "a1792e1b8fb6e6be",
"type": "link in",
"z": "3fbc028716abf7a9",
"g": "21cb55110b1bae57",
"name": "tcp4close",
"links": [
"55d810bcd402f1c0"
],
"x": 235,
"y": 1320,
"wires": [
[
"98c9a64ec995f9d2"
]
]
},
{
"id": "2ec53aa1784087c1",
"type": "uibuilder",
"z": "3fbc028716abf7a9",
"g": "e3bc37824e09b774",
"name": "",
"topic": "",
"url": "totem",
"okToGo": true,
"fwdInMessages": false,
"allowScripts": false,
"allowStyles": false,
"copyIndex": true,
"templateFolder": "blank",
"extTemplate": "",
"showfolder": false,
"reload": true,
"sourceFolder": "src",
"deployedVersion": "7.4.3",
"showMsgUib": false,
"title": "",
"descr": "",
"editurl": "vscode://file/home/trypton/.node-red/projects/raspipeso20_10_25/uibuilder/totem/?windowId=_blank",
"x": 2540,
"y": 960,
"wires": [
[
"be843dd4381d45d5"
],
[]
]
},
{
"id": "b8fdc502b1bf35b7",
"type": "inject",
"z": "3fbc028716abf7a9",
"g": "e3bc37824e09b774",
"name": "",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "{\"payload\":{\"pantalla\":\"menu-principal\"}}",
"payloadType": "json",
"x": 2350,
"y": 900,
"wires": [
[
"2ec53aa1784087c1"
]
]
},
{
"id": "be843dd4381d45d5",
"type": "function",
"z": "3fbc028716abf7a9",
"g": "e3bc37824e09b774",
"name": "buttons \\n decoder",
"func": "// === BOTONES / UIBUILDER ===\nif (msg.payload && msg.payload.hasOwnProperty(\"seccion\")) {\n\n switch (msg.payload.seccion) {\n\n // ──────────────────────────────────────────────────────────────\n // MENÚ PRINCIPAL (selección de báscula)\n // ──────────────────────────────────────────────────────────────\n case \"menu-principal1\":\n flow.set(\"bascula\", \"Bascula1\");\n node.warn(\"Bascula 1 \" + flow.get(\"bascula\"));\n return ([{ payload: { pantalla: \"menu-principal\" }, topic: \"bascula1\" }, null, null]);\n\n case \"menu-principal2\":\n flow.set(\"bascula\", \"Bascula2\");\n node.warn(\"Bascula 2 \" + flow.get(\"bascula\"));\n return ([{ payload: { pantalla: \"menu-principal\" }, topic: \"bascula2\" }, null, null]);\n\n case \"menu-principal3\":\n flow.set(\"bascula\", \"Bascula3\");\n node.warn(\"Bascula 3 \" + flow.get(\"bascula\"));\n return ([{ payload: { pantalla: \"menu-principal\" }, topic: \"bascula3\" }, null, null]);\n\n case \"menu-principal4\":\n flow.set(\"bascula\", \"Bascula4\");\n node.warn(\"Bascula 4 \" + flow.get(\"bascula\"));\n return ([{ payload: { pantalla: \"menu-principal\" }, topic: \"bascula4\" }, null, null]);\n\n // ──────────────────────────────────────────────────────────────\n // NAVEGACIÓN BÁSICA\n // ──────────────────────────────────────────────────────────────\n case \"editar\":\n return { payload: { pantalla: \"editar\" } };\n\n case \"otras\":\n return { payload: { pantalla: \"otras\" } };\n\n case \"regresar\":\n return { payload: { pantalla: \"menu-principal\" } };\n\n case \"salvar\":\n return ([{\n payload: { pantalla: \"editar\" },\n modalText: \"Desea MODIFICAR el archivo de CONFIGURACION ?\",\n modalCallback: \"modificar\"\n }, null, null, null]);\n\n case \"cancelar\":\n return ([{ payload: { pantalla: \"menu-principal\" } }, null]);\n\n // ──────────────────────────────────────────────────────────────\n // TABLAS: GENERAL (sin filtro)\n // ──────────────────────────────────────────────────────────────\n case \"general\":\n return ([\n { payload: { pantalla: \"tabla-general\" }, topic: \"general\", tabla: flow.get(\"config_general\") },\n null, null, null, null, null\n ]);\n\n // ──────────────────────────────────────────────────────────────\n // TABLAS: PARÁMETROS (mostrar SOLO báscula activa, sin globales)\n // ──────────────────────────────────────────────────────────────\n case \"parametros\": {\n const bascula = flow.get(\"bascula\") || \"Bascula1\"; // p.ej. \"Bascula3\"\n const sufijo = bascula.replace(\"Bascula\",\"\"); // \"3\"\n const isEsta = new RegExp(`Bascula${sufijo}$`, \"i\");\n\n const todos = flow.get(\"config_parametros\") || [];\n\n // Oculta globales y muestra solo claves de la báscula activa\n const ocultar = new Set([\"numerobasculas\",\"numerotrama\"]);\n const tabla = todos.filter(p => {\n if (!p || typeof p.parametro !== \"string\") return false;\n const key = p.parametro.toLowerCase().trim();\n if (ocultar.has(key)) return false;\n return isEsta.test(p.parametro);\n });\n\n // Orden legible (opcional)\n const orden = [\n `nombreBascula${sufijo}`, `tcp_flag_Bascula${sufijo}`,\n `InicioTramaBascula${sufijo}`, `FinalTramaBascula${sufijo}`, `LongitudTramaBascula${sufijo}`,\n `InicioPesoBascula${sufijo}`, `LongitudPesoBascula${sufijo}`,\n `hostTCPBascula${sufijo}`, `puertoTCPBascula${sufijo}`\n ];\n tabla.sort((a,b)=> orden.indexOf(a.parametro) - orden.indexOf(b.parametro));\n\n // Defaults visibles si faltan valores\n tabla.forEach(p => {\n if (p.parametro === `tcp_flag_Bascula${sufijo}` && (p.valor === undefined || p.valor === \"\")) {\n p.valor = false;\n }\n });\n\n return ([\n { payload: { pantalla: \"tabla-general\" }, topic: \"parametros\", tabla },\n null, null, null, null\n ]);\n }\n\n // ──────────────────────────────────────────────────────────────\n // TABLAS: RS232 (mostrar SOLO SerieN de la báscula activa)\n // ──────────────────────────────────────────────────────────────\n case \"rs232\": {\n const bascula = flow.get(\"bascula\") || \"Bascula1\";\n const sufijo = bascula.replace(\"Bascula\",\"\");\n const isEsta = new RegExp(`Serie${sufijo}$`, \"i\");\n\n const todos = flow.get(\"config_rs232\") || [];\n\n const tabla = todos.filter(p => {\n if (!p || typeof p.parametro !== \"string\") return false;\n return isEsta.test(p.parametro);\n });\n\n const orden = [\n `puertoSerie${sufijo}`,\n `velocidadBaudiosSerie${sufijo}`,\n `databitsSerie${sufijo}`,\n `paridadSerie${sufijo}`,\n `stopbitsSerie${sufijo}`,\n `puertoHabilitadoSerie${sufijo}`,\n ];\n tabla.sort((a,b)=> orden.indexOf(a.parametro) - orden.indexOf(b.parametro));\n\n tabla.forEach(p => {\n if (p.parametro === `paridadSerie${sufijo}` && (p.valor === undefined || p.valor === \"\")) p.valor = \"none\";\n if (p.parametro === `puertoHabilitadoSerie${sufijo}` && (p.valor === undefined || p.valor === \"\")) p.valor = 0;\n });\n\n return ([\n { payload: { pantalla: \"tabla-general\" }, topic: \"rs232\", tabla },\n null, null\n ]);\n }\n\n // ──────────────────────────────────────────────────────────────\n // OTRAS ACCIONES\n // ──────────────────────────────────────────────────────────────\n case \"aplicar\":\n return ([{\n payload: { pantalla: \"menu-principal\" },\n topic: \"menu-principal\",\n modalText: \"¿Desea Recargar la CONFIGURACION ?\",\n modalCallback: \"recargar\"\n }, null, null]);\n\n case \"reboot\":\n return ([{\n payload: { pantalla: \"otras\" },\n topic: \"otras\",\n modalText: \"Desea Reiniciar el TOTEM ?\",\n modalCallback: \"reiniciar\"\n }, null, null]);\n\n case \"apagar\":\n return ([{\n payload: { pantalla: \"otras\" },\n topic: \"otras\",\n modalText: \"Desea APAGAR el TOTEM ?\",\n modalCallback: \"apagar\"\n }, null, null]);\n\n case \"cancel-table\":\n return ({ payload: { pantalla: \"editar\" } });\n\n case \"salir\":\n return ({ payload: { pantalla: \"principal\" } });\n\n // ──────────────────────────────────────────────────────────────\n // DEFAULT: GUARDADOS PARCIALES DESDE EL FRONT\n // ──────────────────────────────────────────────────────────────\n default:\n // reenviar a última salida para depuración (opcional)\n node.send([null, null, null, null, null, null, msg]);\n\n // 🧩 ACTUALIZAR CONFIGURACIÓN EN MEMORIA (desde uibuilder)\n if (msg.origen === \"actualizar\") {\n const myconfig = msg.payload.table || [];\n const topic = msg.topic || \"\"; // \"general\" | \"parametros\" | \"rs232\"\n\n switch (topic) {\n // ----- GENERAL -----\n case \"general\":\n flow.set(\"config_general\", myconfig);\n break;\n\n // ----- RS232 (merge por SerieN de la báscula activa) -----\n case \"rs232\": {\n const basculaNombre = flow.get(\"bascula\") || \"Bascula1\";\n const sufijo = basculaNombre.replace(\"Bascula\", \"\"); // \"4\"\n const regexEsta = new RegExp(`Serie${sufijo}$`, \"i\");\n\n const numericKeys = [\n /^velocidadBaudiosSerie\\d+$/i,\n /^databitsSerie\\d+$/i,\n /^stopbitsSerie\\d+$/i,\n /^puertoHabilitadoSerie\\d+$/i,\n ];\n const stringKeys = [\n /^puertoSerie\\d+$/i,\n /^paridadSerie\\d+$/i,\n ];\n\n const normalizado = (myconfig || []).map(it => {\n if (!it || typeof it.parametro !== \"string\") return it;\n\n let p = String(it.parametro).normalize('NFKC').trim();\n if (/Serie\\d+$/i.test(p)) p = p.replace(/Serie\\d+$/i, `Serie${sufijo}`);\n else if (/Serie$/i.test(p)) p = p.replace(/Serie$/i, `Serie${sufijo}`);\n\n let v = (typeof it.valor === 'string') ? it.valor.trim() : it.valor;\n if (numericKeys.some(rx => rx.test(p))) {\n const n = Number(v); if (Number.isFinite(n)) v = n;\n } else if (stringKeys.some(rx => rx.test(p))) {\n v = String(v);\n if (/^paridadSerie\\d+$/i.test(p)) v = v.toLowerCase();\n }\n\n return { parametro: p, valor: v };\n });\n\n const todos = flow.get(\"config_rs232\") || [];\n const resto = todos.filter(p => !(p && typeof p.parametro === \"string\" && regexEsta.test(p.parametro)));\n const fusion = [...resto, ...normalizado];\n\n flow.set(\"config_rs232\", fusion);\n break;\n }\n\n // ----- PARÁMETROS (merge por BasculaN de la báscula activa) -----\n case \"parametros\": {\n const bascula = flow.get(\"bascula\") || \"Bascula1\";\n const sufijo = bascula.replace(\"Bascula\",\"\"); // \"1\"\n const rxEsta = new RegExp(`Bascula${sufijo}$`, \"i\");\n\n const NUMERIC_KEYS = [\n /^InicioTramaBascula\\d+$/i,\n /^FinalTramaBascula\\d+$/i,\n /^LongitudTramaBascula\\d+$/i,\n /^InicioPesoBascula\\d+$/i,\n /^LongitudPesoBascula\\d+$/i,\n /^puertoTCPBascula\\d+$/i,\n /^numeroBasculas$/i,\n /^numeroTrama$/i,\n ];\n const BOOL_KEYS = [\n new RegExp(`^tcp_flag_Bascula${sufijo}$`, 'i'),\n ];\n const STRING_KEYS = [\n new RegExp(`^nombreBascula${sufijo}$`, 'i'),\n new RegExp(`^hostTCPBascula${sufijo}$`, 'i'),\n ];\n\n const normalizado = (myconfig || []).map(it => {\n if (!it || typeof it.parametro !== \"string\") return it;\n\n let p = String(it.parametro).normalize('NFKC').trim();\n let v = (typeof it.valor === 'string') ? it.valor.trim() : it.valor;\n\n if (/Bascula\\d+$/i.test(p)) p = p.replace(/Bascula\\d+$/i, `Bascula${sufijo}`);\n else if (/Bascula$/i.test(p)) p = p.replace(/Bascula$/i, `Bascula${sufijo}`);\n\n if (NUMERIC_KEYS.some(rx => rx.test(p))) {\n const n = Number(v); if (Number.isFinite(n)) v = n;\n } else if (BOOL_KEYS.some(rx => rx.test(p))) {\n if (v === \"true\") v = true;\n else if (v === \"false\") v = false;\n else v = Boolean(v);\n } else if (STRING_KEYS.some(rx => rx.test(p))) {\n v = String(v);\n }\n\n return { parametro: p, valor: v };\n });\n\n const todos = flow.get(\"config_parametros\") || [];\n const resto = todos.filter(p => !(p && typeof p.parametro === \"string\" && rxEsta.test(p.parametro)));\n const fusion = [...resto, ...normalizado];\n flow.set(\"config_parametros\", fusion);\n\n // vuelve directo a la pantalla de edición\n return { payload: { pantalla: \"editar\" } };\n }\n }\n\n // si no era \"parametros\", mantenemos navegación por defecto:\n return { payload: { pantalla: \"editar\" } };\n }\n\n return null;\n }\n}\n\n// === MODALES / ACCIONES CONFIRMADAS ===\nelse if (msg.modalCallback === \"modificar\") {\n // Normalización final antes de guardar a archivo (opcional, pero recomendado)\n function normParam(arr = []) {\n const NUMERIC_PARAM_KEYS = [\n /^InicioTramaBascula\\d+$/i, /^FinalTramaBascula\\d+$/i, /^LongitudTramaBascula\\d+$/i,\n /^InicioPesoBascula\\d+$/i, /^LongitudPesoBascula\\d+$/i, /^puertoTCPBascula\\d+$/i,\n /^numeroBasculas$/i, /^numeroTrama$/i\n ];\n return (arr || []).map(it => {\n if (!it || typeof it.parametro !== 'string') return it;\n const k = String(it.parametro).normalize('NFKC').trim();\n let v = it.valor;\n if (typeof v === 'string') v = v.trim();\n if (NUMERIC_PARAM_KEYS.some(rx => rx.test(k))) {\n const n = Number(v); if (Number.isFinite(n)) v = n;\n }\n return { parametro: k, valor: v };\n });\n }\n function normRS232(arr = []) {\n const NUMERIC_RS232_KEYS = [/^velocidadBaudiosSerie\\d+$/i, /^databitsSerie\\d+$/i, /^stopbitsSerie\\d+$/i, /^puertoHabilitadoSerie\\d+$/i];\n const STRING_RS232_KEYS = [/^puertoSerie\\d+$/i, /^paridadSerie\\d+$/i];\n return (arr || []).map(it => {\n if (!it || typeof it.parametro !== 'string') return it;\n const k = String(it.parametro).normalize('NFKC').trim();\n let v = it.valor;\n if (typeof v === 'string') v = v.trim();\n if (NUMERIC_RS232_KEYS.some(rx => rx.test(k))) {\n const n = Number(v); if (Number.isFinite(n)) v = n;\n } else if (STRING_RS232_KEYS.some(rx => rx.test(k))) {\n v = String(v).toLowerCase();\n }\n return { parametro: k, valor: v };\n });\n }\n\n const config_totem = {\n general: flow.get(\"config_general\") || [],\n parametros: normParam(flow.get(\"config_parametros\") || []),\n rs232: normRS232(flow.get(\"config_rs232\") || [])\n };\n\n flow.set(\"config_totem\", config_totem);\n\n msg.payload = JSON.stringify(config_totem, null, 2) + \"\\n\\r\";\n return ([{ payload: { pantalla: \"menu-principal\" } }, msg, null]);\n}\n\nelse if (msg.modalCallback === \"recargar\") {\n return ([null, null, null, { payload: 1 }]);\n}\n\nelse if (msg.modalCallback === \"reiniciar\") {\n return ([null, null, null, null, { payload: \"sudo shutdown -r now\" }]);\n}\n\nelse if (msg.modalCallback === \"apagar\") {\n return ([null, null, null, null, { payload: \"sudo poweroff\" }]);\n}\n",
"outputs": 5,
"timeout": "",
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 2720,
"y": 940,
"wires": [
[
"88b9b4d1c5aa7359"
],
[
"6d39924d1d2081dd"
],
[],
[
"1ba8bfe271072a91"
],
[
"ba5e6b2b40fba02b"
]
]
},
{
"id": "88b9b4d1c5aa7359",
"type": "link out",
"z": "3fbc028716abf7a9",
"g": "e3bc37824e09b774",
"name": "feedback",
"mode": "link",
"links": [
"7058b9e632aa584b"
],
"x": 2865,
"y": 840,
"wires": []
},
{
"id": "7a4c757e6f6ddfcd",
"type": "comment",
"z": "3fbc028716abf7a9",
"g": "e3bc37824e09b774",
"name": "CONFIGURACION DE PARAMETROS Y PRUEBAS ",
"info": "Este grupo de nodos permiten servir una pagina web para que el usuario desde un movil pueda realizar la gestion de configuracion\nde los principales parametros del Totem asi como realizar algunas pruebas en la camara y en el semaforo.\nEl punto central de este grupo es el paquete UIBUILDER para nodered el cual convive sin inconvenientes con el DASHBOARD de nodered.\nEl uibuilder posee un nodo para cada path de acceso que deseemos habilitar. Cada nodo es independiente del otro en el sentido que \ncada uno dispone de un index.html, un index.js y un index.css ue permiten configurar la pagina que sirve el nodo. \nRecordemos que estos archivos se ejecutan en el cliente, por lo tanto para que el index.js tenga acceso a los datos de nodered y nodered \na los datos generados por el cliente, el nodo uibuilder establece una comunicacion websocket transparente para el usuario de manera\na poder intercambiar informacion entre nodered y el browser. Los elementos fundamentales en index.js para llevar a cabo esta comunicacion\nson las funciones \"uibuilder.onChange()\" para recibir los mensajes de nodered y \"uibuilder.send()\" para enviar a nodered los datos generados \npor el cliente.\nEl nodo \"buttons decoder\" fundamentalmente se dedica a interpretar los comandos del cliente (botones que ha pulsado el usuario) de manera a recopilar\nla informacion adecuada para enviar al uibuilder nuevamente de manera que cambie de pagina o muestre ciertos datos, o tambien a ejecutar\nlos comandos solicitados como reboot, apagar dispositivo, encender / apagar semaforo, ...\nEl nodo de entrada \"datos de pesada y snapshot\" recibe por un lado los snapshots enviados por la camara y extrae la informacion necesaria \npara enviar al cliente, y por otro lado siempre que haya alguna recepcion de datos del Totem se preparan los datos correspondientes para \ncolocarlos en la pantalla principal del uibuilder o pantalla de monitoreo.",
"x": 2420,
"y": 1080,
"wires": []
},
{
"id": "6d39924d1d2081dd",
"type": "file",
"z": "3fbc028716abf7a9",
"g": "e3bc37824e09b774",
"name": "guardar config test",
"filename": "/home/trypton/.node-red/projects/config_raspeso_test.txt",
"filenameType": "str",
"appendNewline": false,
"createDir": true,
"overwriteFile": "true",
"encoding": "none",
"x": 2950,
"y": 880,
"wires": [
[]
]
},
{
"id": "882ee24d1489b06c",
"type": "function",
"z": "3fbc028716abf7a9",
"g": "e3bc37824e09b774",
"name": "datos de\\n pesada",
"func": "// =============================================================\n// 📦 DATOS DE PESADA Y SNAPSHOT (versión depurada)\n// =============================================================\n\nlet topic = (msg.topic || \"\").toLowerCase();\nlet parametros = flow.get(\"config_parametros\") || [];\nlet rs232 = flow.get(\"config_rs232\") || [];\n\n// =============================================================\n// 🧭 Detección de báscula activa\n// =============================================================\n\nlet bascula = flow.get(\"bascula\")||\"Bascula1\"; // valor por defecto\n\nif (topic.includes(\"bascula1\")) bascula = \"Bascula1\";\nelse if (topic.includes(\"bascula2\")) bascula = \"Bascula2\";\nelse if (topic.includes(\"bascula3\")) bascula = \"Bascula3\";\nelse if (topic.includes(\"bascula4\")) bascula = \"Bascula4\";\n\n// Guardamos siempre la báscula actual\n//flow.set(\"bascula\", bascula);\nnode.status({ fill: \"blue\", shape: \"dot\", text: \"📟 \" + bascula });\n\n// =============================================================\n// ⚙️ Filtrar configuración de la báscula actual\n// =============================================================\nlet sufijo = bascula.replace(\"Bascula\", \"\");\n\nlet bascula_parametros = parametros.filter(p =>\n p.parametro.toLowerCase().includes(\"bascula\" + sufijo)\n);\nflow.set(\"config_bascula_parametros\", bascula_parametros);\n\nlet bascula_rs232 = rs232.filter(p =>\n p.parametro.toLowerCase().includes(sufijo)\n);\nflow.set(\"config_bascula_rs232\", bascula_rs232);\n\n// =============================================================\n// 🧩 Mezclar datos del flujo actuales\n// =============================================================\nlet msgmix = Object.assign({}, msg, {\n bascula: bascula,\n peso1: flow.get(\"peso1\") || 0,\n peso2: flow.get(\"peso2\") || 0,\n peso3: flow.get(\"peso3\") || 0,\n peso4: flow.get(\"peso4\") || 0,\n \n});\n\n\n// =============================================================\n// 🧭 DEBUG (opcional)\n// =============================================================\n//node.warn(\"📟 Actualizada \" + bascula + \" → snapshot: \" + JSON.stringify(snapshot));\n\n// =============================================================\n// 🚀 Enviar datos al frontend\n// =============================================================\nreturn msgmix;\n",
"outputs": 1,
"timeout": "",
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 2340,
"y": 960,
"wires": [
[
"2ec53aa1784087c1"
]
]
},
{
"id": "9667a055f9c967a7",
"type": "link in",
"z": "3fbc028716abf7a9",
"g": "e3bc37824e09b774",
"name": "link in 2",
"links": [
"4d122b4d7ef323dd",
"e59e292827957f03",
"1457adeb1e1238af",
"d6e380c9415bfb72"
],
"x": 2215,
"y": 960,
"wires": [
[
"882ee24d1489b06c"
]
]
},
{
"id": "7058b9e632aa584b",
"type": "link in",
"z": "3fbc028716abf7a9",
"g": "e3bc37824e09b774",
"name": "entrada uibuilder-1",
"links": [
"88b9b4d1c5aa7359"
],
"x": 2215,
"y": 900,
"wires": [
[
"882ee24d1489b06c"
]
]
},
{
"id": "ba5e6b2b40fba02b",
"type": "exec",
"z": "3fbc028716abf7a9",
"g": "e3bc37824e09b774",
"command": "",
"addpay": "payload",
"append": "",
"useSpawn": "false",
"timer": "",
"winHide": false,
"oldrc": false,
"name": "",
"x": 2920,
"y": 960,
"wires": [
[],
[],
[]
]
},
{
"id": "1ba8bfe271072a91",
"type": "link out",
"z": "3fbc028716abf7a9",
"g": "e3bc37824e09b774",
"name": "feedback",
"mode": "link",
"links": [
"aa13d9f112f46a30"
],
"x": 2875,
"y": 920,
"wires": []
},
{
"id": "960b4b6685af7406",
"type": "http in",
"z": "3fbc028716abf7a9",
"g": "05b4bc2ccdd08ee9",
"name": "API",
"url": "/api",
"method": "post",
"upload": false,
"swaggerDoc": "",
"x": 2790,
"y": 1240,
"wires": [
[
"f806cc253d96d34a",
"363afa81160f118e"
]
]
},
{
"id": "f806cc253d96d34a",
"type": "debug",
"z": "3fbc028716abf7a9",
"g": "05b4bc2ccdd08ee9",
"name": "debug 8",
"active": false,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 2960,
"y": 1240,
"wires": []
},
{
"id": "363afa81160f118e",
"type": "http response",
"z": "3fbc028716abf7a9",
"g": "05b4bc2ccdd08ee9",
"name": "",
"statusCode": "",
"headers": {},
"x": 2950,
"y": 1300,
"wires": []
},
{
"id": "87154c99372a5f9c",
"type": "function",
"z": "3fbc028716abf7a9",
"g": "9a733b496ecde31c",
"name": "function 1",
"func": "let uuid = msg.payload.uuid\nmsg.payload = {movimiento_ok: true, uuid: uuid}\n\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 2440,
"y": 1320,
"wires": [
[
"93bfa84a0f29e988",
"bfe9a33e26aa7cf4"
]
]
},
{
"id": "bfe9a33e26aa7cf4",
"type": "mqtt out",
"z": "3fbc028716abf7a9",
"g": "9a733b496ecde31c",
"name": "tx ack",
"topic": "totemautonomo/movimientos/movimiento_ok",
"qos": "1",
"retain": "",
"respTopic": "",
"contentType": "",
"userProps": "",
"correl": "",
"expiry": "",
"broker": "4eb9ee9b8ea93b8a",
"x": 2610,
"y": 1320,
"wires": []
},
{
"id": "56b300da80743113",
"type": "mqtt in",
"z": "3fbc028716abf7a9",
"g": "9a733b496ecde31c",
"name": "rx movimiento",
"topic": "totemautonomo/movimientos/movimiento",
"qos": "1",
"datatype": "auto-detect",
"broker": "4eb9ee9b8ea93b8a",
"nl": false,
"rap": true,
"rh": 0,
"inputs": 0,
"x": 2270,
"y": 1320,
"wires": [
[
"87154c99372a5f9c",
"d4174078d4cb9375"
]
]
},
{
"id": "d4174078d4cb9375",
"type": "debug",
"z": "3fbc028716abf7a9",
"g": "9a733b496ecde31c",
"name": "rx mov",
"active": false,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 2430,
"y": 1240,
"wires": []
},
{
"id": "93bfa84a0f29e988",
"type": "debug",
"z": "3fbc028716abf7a9",
"g": "9a733b496ecde31c",
"name": "tx ack",
"active": false,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 2590,
"y": 1380,
"wires": []
},
{
"id": "780c6c61.a0cb04",
"type": "exec",
"z": "3fbc028716abf7a9",
"g": "f4b1c07e498c11be",
"command": " ",
"addpay": "payload",
"append": "",
"useSpawn": "false",
"timer": "",
"oldrc": false,
"name": "Screen ON/OFF",
"x": 2500,
"y": 240,
"wires": [
[],
[],
[]
]
},
{
"id": "49500a91.3971e4",
"type": "inject",
"z": "3fbc028716abf7a9",
"g": "f4b1c07e498c11be",
"name": "screen off",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "echo 1 | sudo tee /sys/class/backlight/0-0045/bl_power",
"payloadType": "str",
"x": 2280,
"y": 220,
"wires": [
[
"780c6c61.a0cb04"
]
]
},
{
"id": "97ec61bc.7e2aa",
"type": "inject",
"z": "3fbc028716abf7a9",
"g": "f4b1c07e498c11be",
"name": "screen on",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "echo 0 | sudo tee /sys/class/backlight/0-0045/bl_power",
"payloadType": "str",
"x": 2280,
"y": 180,
"wires": [
[
"780c6c61.a0cb04"
]
]
},
{
"id": "afb26e9306481251",
"type": "inject",
"z": "3fbc028716abf7a9",
"g": "f4b1c07e498c11be",
"name": "reboot",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "sudo shutdown -r now",
"payloadType": "str",
"x": 2270,
"y": 260,
"wires": [
[
"780c6c61.a0cb04"
]
]
},
{
"id": "2c73d02c99e64b37",
"type": "inject",
"z": "3fbc028716abf7a9",
"g": "f4b1c07e498c11be",
"name": "poweroff",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "sudo poweroff",
"payloadType": "str",
"x": 2280,
"y": 300,
"wires": [
[
"780c6c61.a0cb04"
]
]
},
{
"id": "1b496d68c6bda310",
"type": "link in",
"z": "3fbc028716abf7a9",
"name": "wsout",
"links": [
"e5d5709dcfce538e"
],
"x": 2975,
"y": 600,
"wires": [
[
"33449eadf7bfd3da"
]
]
},
{
"id": "33449eadf7bfd3da",
"type": "websocket out",
"z": "3fbc028716abf7a9",
"name": "",
"server": "e18837acff3382fd",
"client": "",
"x": 3090,
"y": 520,
"wires": []
},
{
"id": "c5acaeca734b57ad",
"type": "websocket in",
"z": "3fbc028716abf7a9",
"name": "input",
"server": "e18837acff3382fd",
"client": "",
"x": 3090,
"y": 640,
"wires": [
[
"7f35b38b82e9d3d1",
"63dfdbc95bd896d0"
]
]
},
{
"id": "7f35b38b82e9d3d1",
"type": "debug",
"z": "3fbc028716abf7a9",
"name": "debug 41",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 3320,
"y": 580,
"wires": []
},
{
"id": "63dfdbc95bd896d0",
"type": "link out",
"z": "3fbc028716abf7a9",
"name": "wsin",
"mode": "link",
"links": [
"c61c445897350722"
],
"x": 3255,
"y": 640,
"wires": []
},
{
"id": "3e6f6384f2ec9846",
"type": "link out",
"z": "3fbc028716abf7a9",
"g": "bfec5f4fae03e5b4",
"name": "salida RaspiPeso",
"mode": "link",
"links": [
"29b8ed91354ac1e2"
],
"x": 2725,
"y": 640,
"wires": []
},
{
"id": "57ecdac37231f743",
"type": "uibuilder",
"z": "744bdf3a0e08d754",
"g": "610fa44452654078",
"name": "",
"topic": "",
"url": "supervisor",
"okToGo": true,
"fwdInMessages": false,
"allowScripts": false,
"allowStyles": false,
"copyIndex": true,
"templateFolder": "blank",
"extTemplate": "",
"showfolder": false,
"reload": true,
"sourceFolder": "src",
"deployedVersion": "7.5.0",
"showMsgUib": false,
"title": "",
"descr": "",
"editurl": "vscode://file/home/trypton/.node-red/projects/raspipeso20_10_25/uibuilder/supervisor/?windowId=_blank",
"x": 4110,
"y": 680,
"wires": [
[
"4657b0f0cefa89d7",
"7698b09169dc1c99"
],
[]
]
},
{
"id": "b75f8eb3486aed8d",
"type": "inject",
"z": "744bdf3a0e08d754",
"g": "610fa44452654078",
"name": "",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "{\"payload\":{\"pantalla\":\"menu-principal\"}}",
"payloadType": "json",
"x": 3910,
"y": 560,
"wires": [
[
"57ecdac37231f743"
]
]
},
{
"id": "4657b0f0cefa89d7",
"type": "function",
"z": "744bdf3a0e08d754",
"g": "610fa44452654078",
"name": "buttons \\n decoder",
"func": "\nif (msg.topic === \"tipoconfig\"){\n let tiposDatosGlobal = flow.get(\"tiposDatosGlobal\") || {\"1\": [], \"2\": [], \"3\": [], \"4\": [], \"5\": [], \"6\": []};\n\nconst tipo = msg.payload.tipo; // ej: 2\nconst datos = msg.payload.datos; // array con los parámetros\n\ntiposDatosGlobal[tipo] = datos;\n\nflow.set(\"tiposDatosGlobal\", tiposDatosGlobal);\n\nreturn ;\n}\nelse if (msg.payload.hasOwnProperty(\"seccion\")) {\n\n\n /* Los diferentes \"case\" corresponden a la pulsacion de botones de los diferentes menus de configuracion\n Cada menu solicitado mediante el click de un boton devuelve el nombre del \"div\" en el archivo html que debemos habilitar\n para que se muestre en pantalla.\n Si la entrada no consigue ningun case que se ajuste entonces caemos en el \"default\" y ahi buscamos primero si hay\n algun mensaje en \"origen\", lo que corresponde a la actualizacion de alguna de las tablas dinamicas que se generan\n para modificar cada tipo de parametro. Luego se revisan los \"modalCallback\" los cuales no indican que la respuesta\n de la notificacion de alguna accion importante ha sido positiva (\"SI\").\n \n */\n\n switch (msg.payload.seccion) {\n\n case \"menu-principal\": // boton de configuracion en pantalla principal para acceder al menu principal \n flow.set(\"snapshot_cam\", false)\n flow.set(\"escuchar_cam\", false)\n return { payload: { pantalla: \"menu-principal\" } }\n\n case \"editar\": // boton para acceder al menu de edicion de la configuracion\n return { payload: { pantalla: \"editar\" } }\n\n case \"pruebas\": // boton para acceder al menu de pruebas del sistema\n return { payload: { pantalla: \"pruebas\" } }\n\n case \"semaforo\": // boton para acceder al menu prueba del semaforo\n return { payload: { pantalla: \"semaforo\" } }\n\n case \"otras\": // boton para acceder al menu de otras acciones\n return { payload: { pantalla: \"otras\" } }\n\n case \"regresar\": // boton para regresar al menu principal desde otras acciones y desde menu de pruebas del sistema\n return { payload: { pantalla: \"menu-principal\" } }\n\n case \"salvar\": // boton para almacenar los cambios en el archivo de configuracion en el menu de edicion de la configuracion\n return ([{ payload: { pantalla: \"editar\" }, modalText: \"Desea MODIFICAR el archivo de CONFIGURACION ?\", modalCallback: \"modificar\" }, null, null, null])\n\n case \"cancelar\": // boton para cancelar y salir del menu de editar configuracion\n return ([{ payload: { pantalla: \"menu-principal\" } }, null])\n\n case \"semaforo_on\": // boton para poner el semaforo en rojo\n flow.set(\"semaforo\", \"red\")\n return ([{ payload: { pantalla: \"semaforo\" }, semaforo: \"firebrick\" }, null, { payload: 0 }])\n\n case \"semaforo_off\": // boton para poner el semaforo en verde\n flow.set(\"semaforo\", \"green\")\n return ([{ payload: { pantalla: \"semaforo\" }, semaforo: \"green\" }, null, { payload: 1 }])\n\n case \"salir-semaforo\": // boton de salida de la pantalla de prueba del semaforo\n return ({ payload: { pantalla: \"pruebas\" } })\n\n case \"barrera\": // boton de activar barrera\n return ({ payload: { pantalla: \"barrera\" }, barrera: false })\n\n case \"general\": // boton para ver / modificar tabla con los parametros generales\n return ([{ payload: { pantalla: \"tabla-general\" }, topic: \"general\", tabla: flow.get(\"config_general\") }, null, null])\n\n case \"camara\": // boton para ver / modificar tabla con los parametros de la camara\n return ([{ payload: { pantalla: \"tabla-general\" }, topic: \"camara\", tabla: flow.get(\"config_camara\") }, null, null])\n\n case \"conexion\": // boton para ver / modificar tabla con los parametros de conexion\n return ([{ payload: { pantalla: \"tabla-general\" }, topic: \"conexion\", tabla: flow.get(\"config_conex\") }, null, null])\n\n case \"sensores\": // boton para ver / modificar tabla con los parametros de sensores\n return ([{ payload: { pantalla: \"tabla-general\" }, topic: \"sensores\", tabla: flow.get(\"config_sensor\") }, null, null])\n\n case \"mensajes\": // boton para ver / modificar tabla con los parametros de los mensajes\n return ([{ payload: { pantalla: \"tabla-general\" }, topic: \"mensajes\", tabla: flow.get(\"config_mensajes\") }, null, null])\n\n case \"tiempos\": // boton para ver / modificar tabla con los parametros del tiempo\n return ([{ payload: { pantalla: \"tabla-general\" }, topic: \"tiempos\", tabla: flow.get(\"config_tiempos\") }, null, null])\n\n case \"aplicar\": // boton para aplicar la configuracion al RPi, en el menu-principal\n return ([{ payload: { pantalla: \"menu-principal\" }, topic: \"menu-principal\", modalText: \"Desea Recargar la CONFIGURACION ?\", modalCallback: \"recargar\" }, null, null])\n\n case \"reboot\": // boton para resetear el RPi en el menu de otras acciones\n return ([{ payload: { pantalla: \"otras\" }, topic: \"otras\", modalText: \"Desea Reiniciar el TOTEM ?\", modalCallback: \"reiniciar\" }, null, null])\n\n case \"apagar\": // boton para apagar el RPi en el menu de otras acciones\n return ([{ payload: { pantalla: \"otras\" }, topic: \"otras\", modalText: \"Desea APAGAR el TOTEM ?\", modalCallback: \"apagar\" }, null, null])\n\n case \"snapshot\": // boton para pedir snapshot\n flow.set(\"snapshot_cam\", true)\n flow.set(\"escuchar_cam\", false)\n let msgrqt = {}\n msgrqt.payload = {}\n msgrqt.url = \"http://\" + flow.get(\"camara_IP\") + \"/cgi-bin/trafficSnap.cgi?action=manSnap&channel=1\"\n msgrqt.topic = \"snapshot_rqtd\"\n return ([{ payload: { pantalla: \"snapshot\" }, topic: \"snapshot\" }, null, null, null, null, msgrqt])\n\n case \"escuchar\": // boton para escuchar camara ( recibir todas la imagenes que envie)\n flow.set(\"snapshot_cam\", false)\n flow.set(\"escuchar_cam\", true)\n return ([{ payload: { pantalla: \"escuchar\" }, topic: \"escuchar\" }, null, null])\n\n case \"left\": // Boton para ir a la imagen anterior en escuchar camara\n flow.set(\"getfromarray\", \"left\")\n return ([{ payload: { pantalla: \"escuchar\" }, topic: \"escuchar\" }, null, null])\n\n case \"right\": // Boton para ir a la siguiente imagen en escuchar camara\n flow.set(\"getfromarray\", \"right\")\n return ([{ payload: { pantalla: \"escuchar\" }, topic: \"escuchar\" }, null, null])\n\n case \"salir-snapshot\": // boton de salida de la pantalla de ver snapshot\n flow.set(\"snapshot_cam\", false)\n flow.set(\"escuchar_cam\", false)\n return ({ payload: { pantalla: \"pruebas\" } })\n\n case \"salir-barrera\": // boton de salida de la barrera\n\n return ({ payload: { pantalla: \"pruebas\" }, barrera: false })\n\n case \"salir-escuchar\": // boton de salida de la pantalla de escuchar la camara\n flow.set(\"snapshot_cam\", false)\n flow.set(\"escuchar_cam\", false)\n flow.set(\"snapshot_array\", [])\n flow.set(\"snapshot_readindex\", 0)\n flow.set(\"snapshot_saveindex\", 0)\n flow.set(\"snapshot_indexmax\", 0)\n flow.set(\"getfromarray\", \"\")\n return ({ payload: { pantalla: \"pruebas\" } })\n\n case \"activar-barrera\": // boton de activar barrera\n node.send([{ payload: { pantalla: \"barrera\" }, barrera: true }, null, null, null, null, null, null, { payload: 0 }])\n setTimeout(function () {\n node.send([null, null, null, null, null, null, null, { payload: 1 }])\n }, flow.get(\"relay2_pulse\"));\n return\n\n case \"cancel-table\": // boton de CANCEL de las tablas de configuracion de parametros\n return ({ payload: { pantalla: \"editar\" } })\n\n case \"salir\": // boton de salida del menu principal\n return ({ payload: { pantalla: \"principal\" } })\n\n case \"tipos\": // boton de salida al menu editar\n msg.payload = {pantalla: \"tipos\", tiposDatos: flow.get(\"tiposDatosGlobal\") || {\"1\": [], \"2\": [], \"3\": [], \"4\": [], \"5\": [], \"6\": []}};\n return (msg)\n\n case \"volver_tipos\": // boton de salida al menu editar\n return ({ payload: { pantalla: \"editar\" } })\n\n case \"ayuda\": // boton de salida al menu editar\n return ({ payload: { pantalla: \"ayuda\" } })\n\n case \"salir-ayuda\": // boton de salida al menu editar\n return ({ payload: { pantalla: \"editar\" } })\n\n default: // chequeamos si tenemos acciones a realizar para lo cual revisamos el \"origen\" (actualizar en memoria sin preguntar usuario) y/o el \"modalCallback\" con esto sabemos de que tabla es el mensaje enviado por el MODAL\n node.send([null, null, null, null, null, null, msg])\n if (msg.origen == \"actualizar\") {// salvar en memoria la configuracion que ha sido modificada (el Topic nos dice cual configuracion)\n let myconfig = msg.payload.table // table contiene el resultado de leer la tabla con todos los valores normalizados en uibuilder\n \n switch (msg.topic) { // guardamos cambios en el tipo de configuracion correspondiente\n case \"general\":\n flow.set(\"config_general\", myconfig)\n break;\n case \"conexion\":\n // ==================================== VALIDAMOS todas las IP de la configuracion ==================================================\n //node.warn(\"valor 1 = \" + myconfig[1].valor)\n \n if (validarIP(myconfig[0].valor)){\n flow.set(\"config_conex\", myconfig)\n }\n else {\n return ([{ payload: { pantalla: \"editar\" }, topic: \"error\", modalText: \"La IP del DISPOSITIVO NO es correcta ?\", modalType:\"notificacion\" }, null, null])\n }\n \n if (validarIP(myconfig[1].valor)) {\n flow.set(\"config_conex\", myconfig)\n }\n \n else {\n return ([{ payload: { pantalla: \"editar\" }, topic: \"error\", modalText: \"La IP del ROUTER NO es correcta ?\", modalType: \"notificacion\" }, null, null])\n }\n /*\n if (validarIP(myconfig[5].valor)) {\n flow.set(\"config_conex\", myconfig)\n }\n else {\n return ([{ payload: { pantalla: \"editar\" }, topic: \"error\", modalText: \"La IP del WEBSOCKET NO es correcta ?\", modalType: \"notificacion\" }, null, null])\n }\n \n if (validarIP(myconfig[15].valor)) {\n flow.set(\"config_conex\", myconfig)\n }\n else {\n return ([{ payload: { pantalla: \"editar\" }, topic: \"error\", modalText: \"La IP del TOTEM pareja NO es correcta ?\", modalType: \"notificacion\" }, null, null])\n }\n */\n break;\n case \"camara\":\n flow.set(\"config_camara\", myconfig)\n break;\n case \"sensores\":\n flow.set(\"config_sensor\", myconfig)\n break;\n case \"tiempos\":\n flow.set(\"config_tiempos\", myconfig)\n break;\n case \"mensajes\":\n flow.set(\"config_mensajes\", myconfig)\n break;\n }\n return { payload: { pantalla: \"editar\" } }\n }\n\n \n msg.hello = \"hello 3\"\n node.send([null, null, null, null, null, null, msg])\n return null\n\n }\n} // final del SWITCH\nelse if (msg.modalCallback == \"modificar\") {\n /*\n let config_totem = []\n config_totem = flow.get(\"config_general\").concat(flow.get(\"config_camara\"), flow.get(\"config_conex\"), flow.get(\"config_sensor\"), flow.get(\"config_mensajes\"), flow.get(\"config_tiempos\"), flow.get(\"tiposDatosGlobal\"))\n flow.set(\"config_totem\", config_totem)\n let jsontest = toJson(flow.get(\"config_totem\"));\n */\n let config_totem = {\n general: flow.get(\"config_general\") || [],\n camara: flow.get(\"config_camara\") || [],\n conexion: flow.get(\"config_conex\") || [],\n sensores: flow.get(\"config_sensor\") || [],\n mensajes: flow.get(\"config_mensajes\") || [],\n tiempos: flow.get(\"config_tiempos\") || [],\n tipos: flow.get(\"tiposDatosGlobal\") || {}\n };\n\n flow.set(\"config_totem\", config_totem);\n\n msg.payload = JSON.stringify(config_totem, null, 2) + \"\\n\\r\";\n //msg.payload = JSON.stringify(jsontest, null, 2) + \"\\n\\r\"; // el factor 2 agrega 2 espacio al inicio de cada elemento\n //y agrega tambien una linea lo cual es interesante para la legibilidad del formato y para que los clientes SFTP puedan\n //leer bien el archivo, como es el caso de Mobaxterm.\n\n return ([{ payload: { pantalla: \"menu-principal\" } }, msg, null])\n}\nelse if (msg.modalCallback == \"recargar\") {\n return ([null, null, null, { payload: 1 }])\n}\n\nelse if (msg.modalCallback == \"reiniciar\") {\n return ([null, null, null, null, { payload: \"sudo shutdown -r now\" }])\n}\n\nelse if (msg.modalCallback == \"apagar\") {\n return ([null, null, null, null, { payload: \"sudo poweroff\" }])\n}\n\n//============================================== FUNCIONES ==============================================================\n//esta funcion convierte un array con elementos [parametro, valor] en un objeto Json respetando los tipos de parametro (numerico, booleano, texto)\nfunction toJson(data) {\n let newJson = {}\n /*\n data.forEach(item => {\n let param = item.parametro;\n let value = item.valor;\n */\n for (let i = 0; i < data.length; i++) {\n let param = data[i].parametro;\n let value = data[i].valor;\n\n // Parseo de tipos igual que antes\n if (typeof value === 'string') {\n if (value.toLowerCase() === \"true\") {\n newJson[param] = true;\n } else if (value.toLowerCase() === \"false\") {\n newJson[param] = false;\n } else if (!isNaN(value) && value.trim() !== \"\") {\n newJson[param] = Number(value);\n } else {\n newJson[param] = value;\n }\n } else {\n // Por si el value ya viene como booleano o número real\n newJson[param] = value;\n }\n }\n return (newJson)\n}\n\n//=======================================================================================================================================\n\nfunction validarIP(ip) {\n const regex = /^(25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]?\\d)\\.(25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]?\\d)\\.(25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]?\\d)\\.(25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]?\\d)$/;\n return regex.test(ip);\n}",
"outputs": 8,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 4320,
"y": 660,
"wires": [
[
"58bb0cd40f89ba0d",
"fcce285ce4cc49d0"
],
[
"4ae8af66b4ef6232",
"508d57d90e013805"
],
[
"7004d0202704343a"
],
[
"17547c6cf2cc8bdf"
],
[
"e4f5cd90a27363a0"
],
[
"9a0817ab9b58c626"
],
[
"eaf1c8163a641d84"
],
[
"38d2c6565b5ca0bf",
"c8010702a8431461"
]
]
},
{
"id": "7698b09169dc1c99",
"type": "debug",
"z": "744bdf3a0e08d754",
"g": "610fa44452654078",
"name": "debug 13",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 4300,
"y": 560,
"wires": []
},
{
"id": "7004d0202704343a",
"type": "link out",
"z": "744bdf3a0e08d754",
"g": "610fa44452654078",
"name": "set semaforo",
"mode": "link",
"links": [],
"x": 4535,
"y": 680,
"wires": []
},
{
"id": "58bb0cd40f89ba0d",
"type": "link out",
"z": "744bdf3a0e08d754",
"g": "610fa44452654078",
"name": "feedback",
"mode": "link",
"links": [
"dd15174f0db1d58c"
],
"x": 4485,
"y": 600,
"wires": []
},
{
"id": "fcce285ce4cc49d0",
"type": "debug",
"z": "744bdf3a0e08d754",
"g": "610fa44452654078",
"name": "debug 14",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 4520,
"y": 520,
"wires": []
},
{
"id": "17547c6cf2cc8bdf",
"type": "link out",
"z": "744bdf3a0e08d754",
"g": "610fa44452654078",
"name": "aplicar_config",
"mode": "link",
"links": [
"c5a3e80cc446b610"
],
"x": 4535,
"y": 720,
"wires": []
},
{
"id": "e4f5cd90a27363a0",
"type": "link out",
"z": "744bdf3a0e08d754",
"g": "610fa44452654078",
"name": "reiniciar",
"mode": "link",
"links": [
"1ca5e534fa4962de"
],
"x": 4535,
"y": 760,
"wires": []
},
{
"id": "0f3cd31839136f10",
"type": "function",
"z": "744bdf3a0e08d754",
"g": "610fa44452654078",
"name": "datos de\\n pesada y \\n snapshot",
"func": "if(msg.payload.hasOwnProperty(\"tiposDatos\")){\n return(msg)\n\n}\nelse {\n let msgmix = {}\n if (flow.get(\"snapshot_cam\") && msg.payload.hasOwnProperty(\"Picture\")) { // si recibimos snapshot extraemos informacion necesaria para mostrar en la pagina de snapshot\n msgmix.plate = msg.payload.Picture.Plate.PlateNumber // guardamos el numero de matricula\n msgmix.picture = \"data:image/png;base64,\" + msg.payload.Picture.NormalPic.Content\n msgmix.plate_time = msg.payload.Picture.SnapInfo.SnapTime\n msgmix.camera_id = msg.payload.Picture.SnapInfo.DeviceID\n }\n\n\n else if (flow.get(\"escuchar_cam\")) { // Si estamos esperando snapshots de la camara o acciones del usuario en botones left o right\n let snapshot_array = flow.get('snapshot_array') || [];\n let snapshot_readindex = flow.get('snapshot_readindex') || 0;\n let snapshot_saveindex = flow.get('snapshot_saveindex') || 0;\n let snapshot_indexmax = flow.get('snapshot_indexmax') || 0;\n\n\n if (flow.get(\"getfromarray\") == \"left\") {\n msgmix.buttons = true // indica al index.js (del uibuilder) que no tiene que inicializar la pantalla de escuchar camara\n if (snapshot_readindex > 0) {\n snapshot_readindex = snapshot_readindex - 1\n flow.set(\"snapshot_readindex\", snapshot_readindex)\n msgmix = snapshot_array[snapshot_readindex]\n msgmix.snapshot_readindex = snapshot_readindex\n msgmix.snapshot_indexmax = snapshot_indexmax\n }\n }\n else if (flow.get(\"getfromarray\") == \"right\") {\n msgmix.buttons = true // indica al index.js (del uibuilder) que no tiene que inicializar la pantalla de escuchar camara\n if (snapshot_readindex < flow.get(\"snapshot_indexmax\") - 1) {\n snapshot_readindex = snapshot_readindex + 1\n flow.set(\"snapshot_readindex\", snapshot_readindex)\n msgmix = snapshot_array[snapshot_readindex]\n msgmix.snapshot_readindex = snapshot_readindex\n msgmix.snapshot_indexmax = snapshot_indexmax\n\n }\n }\n else {\n if (msg.payload.hasOwnProperty(\"Picture\")) { // si recibimos snapshot extraemos informacion necesaria para mostrar en la pagina de escuchar camara\n msgmix.plate = msg.payload.Picture.Plate.PlateNumber // guardamos el numero de matricula\n msgmix.picture = \"data:image/png;base64,\" + msg.payload.Picture.NormalPic.Content\n msgmix.plate_time = msg.payload.Picture.SnapInfo.SnapTime\n msgmix.camera_id = msg.payload.Picture.SnapInfo.DeviceID\n msgmix.buttons = true\n\n\n snapshot_array[snapshot_saveindex] = RED.util.cloneMessage(msgmix); // Clonamos para evitar referencias\n flow.set(\"snapshot_array\", snapshot_array)\n flow.set(\"snapshot_saveindex\", snapshot_saveindex + 1)\n flow.set(\"snapshot_indexmax\", snapshot_indexmax + 1)\n\n if (snapshot_readindex == snapshot_saveindex - 1) {\n flow.set(\"snapshot_readindex\", snapshot_readindex + 1) // en caso que el index de lectura este al final, se incrementa al llegar una nueva imagen\n }\n msgmix.snapshot_indexmax = flow.get(\"snapshot_indexmax\")\n msgmix.snapshot_readindex = flow.get(\"snapshot_readindex\")\n }\n }\n }\n\n else {\n\n let msgmix = {}\n\n }\n flow.set(\"getfromarray\", \"\")\n\n //*****************************************Agrega datos de la pesada para PAGINA PRINCIPAL************************************************************ */\n if (!msg.payload.hasOwnProperty(\"Picture\")) {\n msgmix = Object.assign({}, msg, msgmix); // msg sobrescribe valores si hay conflictos\n }\n msgmix.peso = flow.get(\"peso\")\n msgmix.semaforo_color = flow.get(\"semaforo\")\n msgmix.matricula = flow.get(\"matricula\")\n msgmix.peso_estable_flag = flow.get(\"peso_estable_flag\")\n msgmix.vial = flow.get(\"numero_vial\")\n msgmix.totem_id = flow.get(\"totem_id\")\n msgmix.iplan = flow.get(\"myip\")\n msgmix.ipsupervisor = flow.get(\"websocket_url\")\n msgmix.snapshot_cam = flow.get(\"snapshot_cam\")\n msgmix.escuchar_cam = flow.get(\"escuchar_cam\")\n return msgmix;\n}",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 3880,
"y": 680,
"wires": [
[
"57ecdac37231f743",
"9559a621baa2e0e5"
]
]
},
{
"id": "9559a621baa2e0e5",
"type": "debug",
"z": "744bdf3a0e08d754",
"g": "610fa44452654078",
"name": "debug 17",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 4100,
"y": 580,
"wires": []
},
{
"id": "9a0817ab9b58c626",
"type": "link out",
"z": "744bdf3a0e08d754",
"g": "610fa44452654078",
"name": "snapshot_rqst",
"mode": "link",
"links": [],
"x": 4535,
"y": 800,
"wires": []
},
{
"id": "eaf1c8163a641d84",
"type": "debug",
"z": "744bdf3a0e08d754",
"g": "610fa44452654078",
"name": "debug 18",
"active": false,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 4580,
"y": 840,
"wires": []
},
{
"id": "22a6d78769eca8a4",
"type": "comment",
"z": "744bdf3a0e08d754",
"g": "610fa44452654078",
"name": "CONFIGURACION DE PARAMETROS Y PRUEBAS ",
"info": "Este grupo de nodos permiten servir una pagina web para que el usuario desde un movil pueda realizar la gestion de configuracion\nde los principales parametros del Totem asi como realizar algunas pruebas en la camara y en el semaforo.\nEl punto central de este grupo es el paquete UIBUILDER para nodered el cual convive sin inconvenientes con el DASHBOARD de nodered.\nEl uibuilder posee un nodo para cada path de acceso que deseemos habilitar. Cada nodo es independiente del otro en el sentido que \ncada uno dispone de un index.html, un index.js y un index.css ue permiten configurar la pagina que sirve el nodo. \nRecordemos que estos archivos se ejecutan en el cliente, por lo tanto para que el index.js tenga acceso a los datos de nodered y nodered \na los datos generados por el cliente, el nodo uibuilder establece una comunicacion websocket transparente para el usuario de manera\na poder intercambiar informacion entre nodered y el browser. Los elementos fundamentales en index.js para llevar a cabo esta comunicacion\nson las funciones \"uibuilder.onChange()\" para recibir los mensajes de nodered y \"uibuilder.send()\" para enviar a nodered los datos generados \npor el cliente.\nEl nodo \"buttons decoder\" fundamentalmente se dedica a interpretar los comandos del cliente (botones que ha pulsado el usuario) de manera a recopilar\nla informacion adecuada para enviar al uibuilder nuevamente de manera que cambie de pagina o muestre ciertos datos, o tambien a ejecutar\nlos comandos solicitados como reboot, apagar dispositivo, encender / apagar semaforo, ...\nEl nodo de entrada \"datos de pesada y snapshot\" recibe por un lado los snapshots enviados por la camara y extrae la informacion necesaria \npara enviar al cliente, y por otro lado siempre que haya alguna recepcion de datos del Totem se preparan los datos correspondientes para \ncolocarlos en la pantalla principal del uibuilder o pantalla de monitoreo.",
"x": 4010,
"y": 820,
"wires": []
},
{
"id": "38d2c6565b5ca0bf",
"type": "link out",
"z": "744bdf3a0e08d754",
"g": "610fa44452654078",
"name": "activa-barrera",
"mode": "link",
"links": [],
"x": 4535,
"y": 880,
"wires": []
},
{
"id": "c8010702a8431461",
"type": "debug",
"z": "744bdf3a0e08d754",
"g": "610fa44452654078",
"name": "debug 27",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 4580,
"y": 920,
"wires": []
},
{
"id": "4ae8af66b4ef6232",
"type": "debug",
"z": "744bdf3a0e08d754",
"g": "610fa44452654078",
"name": "debug 26",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 4640,
"y": 720,
"wires": []
},
{
"id": "508d57d90e013805",
"type": "file",
"z": "744bdf3a0e08d754",
"g": "610fa44452654078",
"name": "guardar config",
"filename": "/home/trypton/.node-red/projects/config_supervisor.txt",
"filenameType": "str",
"appendNewline": true,
"createDir": true,
"overwriteFile": "true",
"encoding": "none",
"x": 4620,
"y": 640,
"wires": [
[]
]
},
{
"id": "cb6c28fe05c25229",
"type": "link in",
"z": "744bdf3a0e08d754",
"g": "6c0376d0b4669415",
"name": "ws_out",
"links": [
"4526132d69e6c210",
"7f282cc6ba963043",
"ce3029f2688a0aba",
"ed04082db0d29b5b"
],
"x": 4155,
"y": 1720,
"wires": [
[
"d7d43c051dffd484",
"e5d5709dcfce538e"
]
]
},
{
"id": "d7d43c051dffd484",
"type": "debug",
"z": "744bdf3a0e08d754",
"g": "6c0376d0b4669415",
"name": "salida websocket",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 4350,
"y": 1660,
"wires": []
},
{
"id": "e5d5709dcfce538e",
"type": "link out",
"z": "744bdf3a0e08d754",
"g": "6c0376d0b4669415",
"name": "link out 4",
"mode": "link",
"links": [
"1b496d68c6bda310"
],
"x": 4305,
"y": 1780,
"wires": []
},
{
"id": "7ced20377dba5074",
"type": "debug",
"z": "744bdf3a0e08d754",
"name": "debug 20",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 3020,
"y": 2440,
"wires": []
},
{
"id": "73ab7179bc4cf185",
"type": "inject",
"z": "744bdf3a0e08d754",
"name": "",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "SELECT * FROM movimientos WHERE matricula = '6421FPG' AND tipo = 'inicial'",
"payload": "1",
"payloadType": "num",
"x": 650,
"y": 2060,
"wires": [
[
"cc229c10e3f4e423"
]
]
},
{
"id": "558309d02524b5f9",
"type": "inject",
"z": "744bdf3a0e08d754",
"name": "",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "0420MMR",
"payloadType": "str",
"x": 3320,
"y": 2420,
"wires": [
[
"9dd64e7e2d2a77c9"
]
]
},
{
"id": "1f6ee5fb9fb53d0d",
"type": "debug",
"z": "744bdf3a0e08d754",
"name": "debug 29",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 3940,
"y": 2340,
"wires": []
},
{
"id": "6c42dc5e8e906084",
"type": "debug",
"z": "744bdf3a0e08d754",
"name": "debug 30",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 4200,
"y": 2400,
"wires": []
},
{
"id": "3d15b8a25709d397",
"type": "inject",
"z": "744bdf3a0e08d754",
"name": "",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "1334DEF",
"payloadType": "str",
"x": 3320,
"y": 2500,
"wires": [
[
"9dd64e7e2d2a77c9"
]
]
},
{
"id": "44c64f7eb7bdf346",
"type": "debug",
"z": "744bdf3a0e08d754",
"name": "debug 35",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 1800,
"y": 1820,
"wires": []
},
{
"id": "85f07bfdecf73577",
"type": "inject",
"z": "744bdf3a0e08d754",
"name": "Matriculas",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "Matriculas",
"payloadType": "str",
"x": 1880,
"y": 2680,
"wires": [
[
"c95d9408ac6b1595"
]
]
},
{
"id": "4aa21914e723be52",
"type": "inject",
"z": "744bdf3a0e08d754",
"name": "Identificadores",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "Identificadores",
"payloadType": "str",
"x": 1860,
"y": 2760,
"wires": [
[
"c95d9408ac6b1595"
]
]
},
{
"id": "cecb419ca825ee49",
"type": "inject",
"z": "744bdf3a0e08d754",
"name": "Matriculas_Identificadores",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "Matriculas_Identificadores",
"payloadType": "str",
"x": 1830,
"y": 2720,
"wires": [
[
"c95d9408ac6b1595"
]
]
},
{
"id": "381ba7abb9ab0be7",
"type": "mysql",
"z": "744bdf3a0e08d754",
"mydb": "1c8613764b7f685e",
"name": "check matricula",
"x": 3760,
"y": 2420,
"wires": [
[
"8586621b0d788c6c",
"1f6ee5fb9fb53d0d"
]
]
},
{
"id": "9dd64e7e2d2a77c9",
"type": "function",
"z": "744bdf3a0e08d754",
"name": "check matricula",
"func": "let matricula = msg.payload\nflow.set(\"matricula\", matricula)\n//Solicitamos un solo record donde aparezca registrada la matricula (tabla de Vehiculos) y revisamos si se encuentra en transito (tabla movimientos)\n// en caso que no este registrada la matricula, la BD nos entrega un record con los parametros en cero o en null\nmsg.topic = `SELECT\n CASE WHEN ma.matricula IS NOT NULL THEN 1 ELSE 0 END AS registrada,\n CAST(EXISTS(SELECT 1 FROM movimientos m WHERE m.matricula='${matricula}') AS INTEGER) AS transito,\n ma.tara, ma.pma\n FROM (SELECT '${matricula}' AS MatriculaInput) AS input\n LEFT JOIN Matriculas ma ON ma.matricula = input.MatriculaInput\n LIMIT 1;`;\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 3520,
"y": 2420,
"wires": [
[
"381ba7abb9ab0be7"
]
]
},
{
"id": "8586621b0d788c6c",
"type": "function",
"z": "744bdf3a0e08d754",
"name": "check results",
"func": "let result = msg.payload\nflow.set(\"matricula_revisada\", true)\nmsg.payload = {}\nmsg.topic = \"\"\nmsg.payload.totem_id = flow.get(\"totem_id\")\nmsg.payload.numero_vial = flow.get(\"numero_vial\")\n\nif (result.length > 0){ // Tenemos al menos un record de respuesta de la BD\n if (result[0].registrada == 1){\n msg.payload.registrada = !!result[0].registrada // la doble negacion convierte a booleano ( true / false)\n msg.payload.pma = result[0].pma\n msg.payload.tara = result[0].tara\n msg.payload.transito = !!result[0].transito\n if(!msg.payload.transito){ // si esta en transito no busca lista de menu\n setTimeout(function () {\n node.send([null, { payload: flow.get(\"matricula\") }]) //genera lista de opciones para presentar el menu en pantalla\n }, 1000)\n }\n }\n else{\n msg.payload.registrada = result[0].registrada\n }\n}\nelse{\n msg.payload.respuestaBD = \"ningun record\"\n}\nreturn ([msg, null]);\n",
"outputs": 2,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 3970,
"y": 2420,
"wires": [
[
"6c42dc5e8e906084"
],
[]
]
},
{
"id": "9bd381438eec96eb",
"type": "debug",
"z": "744bdf3a0e08d754",
"name": "debug 40",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 3020,
"y": 2340,
"wires": []
},
{
"id": "914c1af9a76e0d6b",
"type": "inject",
"z": "744bdf3a0e08d754",
"name": "",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "Select * from Matriculas_Identificadores where matricula = \"6421FPG\";",
"payload": "1",
"payloadType": "num",
"x": 2150,
"y": 2480,
"wires": [
[
"8558335b01c7e441"
]
]
},
{
"id": "1bcbf5de4c44ac05",
"type": "inject",
"z": "744bdf3a0e08d754",
"name": "",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "INSERT INTO Matriculas_Identificadores (matricula, id) VALUES (\"6423FPG\", \"546DA2A4-B07B-482D-B98F-B4EB0FFABCAE\") ;",
"payload": "1",
"payloadType": "num",
"x": 2150,
"y": 2520,
"wires": [
[
"8558335b01c7e441"
]
]
},
{
"id": "4df94cef4bd6c3bf",
"type": "inject",
"z": "744bdf3a0e08d754",
"name": "",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "INSERT INTO Identificadores (id, id_padre,nivel, descripcion) VALUES (\"546DA2A4-B07B-482D-B98F-B4EB0FFABCAE\", null , 0, \"AYTO ALLER, ESCOMBROS\") ;",
"payload": "1",
"payloadType": "num",
"x": 2310,
"y": 2520,
"wires": [
[
"8558335b01c7e441"
]
]
},
{
"id": "bf207ef538347e56",
"type": "comment",
"z": "744bdf3a0e08d754",
"name": "Version 1.0",
"info": "Version 1.10. Se modifica la forma de actualizar las tablas. Actualmente se hace un comando SQL multiple que consiste en comenzar una Transaccion,\nBorrar el contenido de la tabla con DELETE, hacer los INSERT necesarios del nuevo contenido tomando la precaucion de que el array que ha llegado\ntiene registros y finalmente se realiza un COMMIT. De esta forma hacemos un espejo de la tavla del Servidor.\nSe agrego una nueva tabla \"UbicacionesInternas\" que contiene basicamente dos campos, un id que representa el tipo de residuos y una descripcion\nque contiene el texto a mostrar en pantalla y que le dice al conductor hacia donde debe dirigirse.\n\nSe agrego el envio de movimiento inicial al servidor de Urgesbas. Esto lo hacemos en el nodo \"comandos via websockets\". Para ello hemos agregado\ntambien una nueva salida y un nuevo nodo https. En esta ocasion no esperamos por ningun ACK simplemente enviamos.\n\nSe agrego almacenamiento de imagen de firma en el nodo \"comandos via websockets\" para ello utilizamos un nuevo nodo de escritura de archivos\ny se agrego una nueva salida al nodo.\n\nSe agrego en el nodo \"send data\", el \"nombre_imagen_f\" para tambien considerar las imagenes del movimiento final a la hora de borrar \nlos archivos de imagenes vencidas\n\nSe agregaron los campos \"qr_f\" y \"rfid_f\" en el nodo \"movimiento cerrado\" correspondientes a la identificacion en el movimiento final\n\n",
"x": 1120,
"y": 880,
"wires": []
},
{
"id": "422ffdb7a80b308a",
"type": "https-node",
"z": "744bdf3a0e08d754",
"g": "1e7a7eecbf88e674",
"name": "https request",
"method": "POST",
"ret": "txt",
"paytoqs": "ignore",
"url": "",
"tls": "",
"persist": false,
"proxy": "",
"authType": "",
"senderr": false,
"x": 2130,
"y": 380,
"wires": [
[
"ecd2ede4ce450c22"
]
]
},
{
"id": "5c03e30bd6a7dd59",
"type": "function",
"z": "744bdf3a0e08d754",
"g": "1e7a7eecbf88e674",
"name": "function 5",
"func": "msg.url = \"https://pre-urbgesbas.urbaser.com/WebService/api/Totem/InsertarMovimientos?token=FDEB4C00-5F58-4C2D-AD80-9E7064D8FC4E\"\nmsg.payload = flow.get(\"record_test\")\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1940,
"y": 380,
"wires": [
[
"6455539b9fac2063"
]
]
},
{
"id": "150a34e02c2def17",
"type": "inject",
"z": "744bdf3a0e08d754",
"g": "1e7a7eecbf88e674",
"name": "",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "1",
"payloadType": "num",
"x": 1790,
"y": 380,
"wires": [
[
"5c03e30bd6a7dd59"
]
]
},
{
"id": "bebcf89f4daa2d2c",
"type": "debug",
"z": "744bdf3a0e08d754",
"g": "1e7a7eecbf88e674",
"name": "debug 42",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 2500,
"y": 360,
"wires": []
},
{
"id": "ecd2ede4ce450c22",
"type": "json",
"z": "744bdf3a0e08d754",
"g": "1e7a7eecbf88e674",
"name": "",
"property": "payload",
"action": "",
"pretty": false,
"x": 2330,
"y": 380,
"wires": [
[
"bebcf89f4daa2d2c"
]
]
},
{
"id": "54bb5d92c06ce75c",
"type": "debug",
"z": "744bdf3a0e08d754",
"name": "debug 44",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 3580,
"y": 660,
"wires": []
},
{
"id": "34bf8ec19b0e78a7",
"type": "link in",
"z": "744bdf3a0e08d754",
"g": "68b92de7bc4c82f6",
"name": "updateBD",
"links": [
"a58ecf0f246aff6a"
],
"x": 2055,
"y": 1160,
"wires": [
[
"950c8115e364a493"
]
]
},
{
"id": "950c8115e364a493",
"type": "function",
"z": "744bdf3a0e08d754",
"g": "68b92de7bc4c82f6",
"name": "SELECT pendientes",
"func": "setTimeout(function () {\n msg.topic = \"SELECT * FROM movimientos_cerrados WHERE sincro = 0 ORDER BY ts_cierre ASC\";\n flow.set(\"indiceActual\", 0); //inicializamos el indice que recorre el array de records pendientes en los siguientes nodos\n msg.inicio = true // indica que estamos iniciando el array. Con esto en el proximo nodo se hace diferencia en el procesamiento\n node.send(msg)\n}, 2000);\n\nreturn",
"outputs": 1,
"timeout": "",
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 2200,
"y": 1160,
"wires": [
[
"d144432c1ab94daf"
]
]
},
{
"id": "f41c4b5c61a58ea2",
"type": "http request",
"z": "744bdf3a0e08d754",
"g": "68b92de7bc4c82f6",
"name": "Enviar a API",
"method": "POST",
"ret": "txt",
"paytoqs": "ignore",
"url": "",
"headers": [
{
"keyType": "Content-Type",
"keyValue": "application/json",
"valueType": "other",
"valueValue": "application/json"
}
],
"x": 3270,
"y": 1160,
"wires": [
[
"b1c81347b429091a"
]
]
},
{
"id": "b1c81347b429091a",
"type": "function",
"z": "744bdf3a0e08d754",
"g": "68b92de7bc4c82f6",
"name": "Procesar respuesta",
"func": "let pendientes = flow.get(\"pendientes\") || [];\nlet i = flow.get(\"indiceActual\") || 0;\nlet actual = pendientes[i];\nif (flow.get(\"movimientosxapi\")) {\n if (msg.statusCode === 200) {\n msg.topic = `UPDATE movimientos_cerrados SET sincro = 1 WHERE uuid = '${actual.uuid}'`;\n msg.payload = undefined;\n return [msg, null];\n } else {\n node.warn(`Error al enviar UUID ${actual.uuid}, código HTTP: ${msg.statusCode}`);\n return [null, { payload: 1 }];\n }\n}\nif (flow.get(\"movimientosxmqtt\")){\n if(flow.get(\"ultimo_movimiento_ok\")){\n msg.topic = `UPDATE movimientos_cerrados SET sincro = 1 WHERE uuid = '${actual.uuid}'`;\n msg.payload = undefined;\n return [msg, null];\n }\n else{\n node.warn(`Error al enviar UUID ${actual.uuid} via MQTT`);\n return [null, { payload: 1 }];\n }\n}",
"outputs": 2,
"timeout": "",
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 3490,
"y": 1160,
"wires": [
[
"76d19764677ebc8a",
"867c5654842a1210"
],
[
"911f1b379de9ba3a"
]
]
},
{
"id": "911f1b379de9ba3a",
"type": "function",
"z": "744bdf3a0e08d754",
"g": "68b92de7bc4c82f6",
"name": "Siguiente registro",
"func": "let pendientes = flow.get(\"pendientes\") || [];\nlet i = flow.get(\"indiceActual\") || 0;\n\ni += 1;\nflow.set(\"indiceActual\", i);\nmsg.inicio = false; // con esto indicamos al nodo \"extrae files\" que continue el recorrido del array\n\nif (i < pendientes.length) {\n msg.payload = pendientes[i];\n msg.url = flow.get(\"url_API\")\n msg.requestTimeout = 5000\n return msg;\n} else {\n flow.set(\"pendientes\", []);\n flow.set(\"indiceActual\", 0);\n node.log(\"Sincronización completa.\");\n return null;\n}",
"outputs": 1,
"timeout": "",
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 3930,
"y": 1160,
"wires": [
[
"2b9158681d5eb09b",
"f4c6152e8ed7f5a6"
]
]
},
{
"id": "76d19764677ebc8a",
"type": "debug",
"z": "744bdf3a0e08d754",
"g": "68b92de7bc4c82f6",
"name": "debug 1",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 3680,
"y": 1100,
"wires": []
},
{
"id": "3c3867c2a2185f63",
"type": "debug",
"z": "744bdf3a0e08d754",
"g": "68b92de7bc4c82f6",
"name": "debug 7",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 2600,
"y": 1100,
"wires": []
},
{
"id": "2b9158681d5eb09b",
"type": "debug",
"z": "744bdf3a0e08d754",
"g": "68b92de7bc4c82f6",
"name": "debug 9",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 4060,
"y": 1100,
"wires": []
},
{
"id": "d4a053997921cfc0",
"type": "file in",
"z": "744bdf3a0e08d754",
"g": "68b92de7bc4c82f6",
"name": "lee imagen_i",
"filename": "filename",
"filenameType": "msg",
"format": "utf8",
"chunk": false,
"sendError": false,
"encoding": "none",
"allProps": false,
"x": 2870,
"y": 1100,
"wires": [
[
"89f64c532d3d78e7"
]
]
},
{
"id": "89f64c532d3d78e7",
"type": "function",
"z": "744bdf3a0e08d754",
"g": "68b92de7bc4c82f6",
"name": "agrega imagenes",
"func": "\nif (msg.tipo == \"imagen_i\"){\n flow.set(\"imagen_i\", msg.payload)\n flow.set(\"imagen_i_rx_flag\", true)\n}\nelse if (msg.tipo == \"imagen_f\"){\n flow.set(\"imagen_f\", msg.payload)\n flow.set(\"imagen_f_rx_flag\", true)\n}\nelse if (msg.tipo == \"firma\"){\n flow.set(\"firma\", msg.payload)\n flow.set(\"firma_rx_flag\", true)\n}\n//**********************************************Hay que revisar esto porque solamente tenemos que enviar cuando ************************************************** */\n//********************************************** tengamos todas las imagenes a enviar. por lo tanto es necesario *************************** */\n//********************************************** revisar si existen imagenes sobre todo de la firma que no siempre existe *******************************************************/\n\n\nif (msg.files == 1 && flow.get(\"imagen_i_rx_flag\")){\n let reg2send = flow.get(\"pendientes\")[flow.get(\"indiceActual\")]\n reg2send.imagen_entrada = flow.get(\"imagen_i\")\n if (flow.get(\"movimientosxapi\")){\n node.send([{ payload: reg2send, url: flow.get(\"url_API\"), requestTimeout : 5000}, null, null])\n }\n if (flow.get(\"movimientosxmqtt\")) { // ponemos if en vez de else if para que se pueda hacer por las dos vias si estan habilitadas\n flow.set(\"ultimo_movimiento_ok\", false) // inicializamos el flag de acknowledge que hemos implementado\n flow.set(\"ultimo_movimiento_id\", reg2send.uuid) // guardamos uuid como marca para el ack que debemos recibir\n flow.set(\"ack_old_movimientos\", true)\n node.send([null, { payload: reg2send}, {payload:reg2send.uuid}])\n }\n}\nelse if (msg.files == 2 && flow.get(\"imagen_f_rx_flag\") && flow.get(\"firma_rx_flag\")){\n let reg2send = flow.get(\"pendientes\")[flow.get(\"indiceActual\")]\n reg2send.imagen_entrada = flow.get(\"imagen_i\")\n reg2send.imagen_salida = flow.get(\"imagen_f\")\n reg2send.firma = flow.get(\"firma\")\n if (flow.get(\"movimientosxapi\")) {\n node.send([{ payload: reg2send, url: flow.get(\"url_API\"), requestTimeout: 5000 }, null, null])\n }\n if (flow.get(\"movimientosxmqtt\")) {\n flow.set(\"ultimo_movimiento_ok\", false)\n flow.set(\"ultimo_movimiento_id\", reg2send.uuid)\n flow.set(\"ack_old_movimientos\", true)// indicamos que el ack que esperamos es de la sincronizacion de viejos movimientos\n node.send([null, { payload: reg2send }, { payload: reg2send.uuid }])\n }\n}\nreturn",
"outputs": 3,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 3070,
"y": 1160,
"wires": [
[
"a42d3e0da2d9dbda",
"f41c4b5c61a58ea2"
],
[
"4a2f600ba9ca7a15",
"d5ced8b11f124c67"
],
[
"1d3c5c1f2e5be4b0"
]
]
},
{
"id": "e944fa11afc97160",
"type": "file in",
"z": "744bdf3a0e08d754",
"g": "68b92de7bc4c82f6",
"name": "lee firma",
"filename": "filename",
"filenameType": "msg",
"format": "utf8",
"chunk": false,
"sendError": false,
"encoding": "none",
"allProps": false,
"x": 2880,
"y": 1240,
"wires": [
[
"89f64c532d3d78e7"
]
]
},
{
"id": "f4c6152e8ed7f5a6",
"type": "function",
"z": "744bdf3a0e08d754",
"g": "68b92de7bc4c82f6",
"name": "extrae nombres de archivos",
"func": "if (msg.inicio){\n flow.set(\"pendientes\", msg.payload);\n}\n\n//flow.set(\"indiceActual\", 0);\nlet indiceActual = flow.get(\"indiceActual\")\nlet pendientes = flow.get(\"pendientes\")\nmsg = {} //atencion a esta inicializacion, verificar que este msg no influye en ninguna respuesta a ningun totem\nlet msg2 = {}\nlet msg_s = {}\nif (pendientes.length > 0) {\n let imagename_i = pendientes[indiceActual].nombre_imagen_i\n let imagename_f = pendientes[indiceActual].nombre_imagen_f\n\n // Procesamos imagen de entrada\n if(imagename_i != \"Sin Matricula\"){\n msg.filename = flow.get(\"path_images\") + imagename_i // extrae el nombre de la imagen de entrada del record pendiente de sincronizar\n }\n else{\n msg.filename = flow.get(\"path_images\") + imagename_i + \".jpg\"\n }\n msg.tipo = \"imagen\"\n // Procesamos imagen de salida\n if(imagename_f != \"Sin Matricula\"){\n msg_s.filename = flow.get(\"path_images\") + imagename_f // extrae el nombre de la imagen de salida del record pendiente de sincronizar\n }\n else{\n msg.filename = flow.get(\"path_images\") + imagename_f + \".jpg\"\n }\n msg_s.tipo = \"imagen_s\"\n \n if (pendientes[indiceActual].nombre_firma != \"No Habilitada\" && pendientes[indiceActual].nombre_firma != \"firma-No Picture\"){\n msg2.filename = flow.get(\"path_firmas\") + pendientes[indiceActual].nombre_firma //extrae el nombre de la firma del record pendiente de sincronizar en caso de existir\n msg2.tipo = \"firma\"\n msg.files = 2 // indica que hay dos archivos a procesar\n node.send([msg, msg_s, msg2])\n }\n else{\n msg.files = 1 // indica que solo esta el archivo de la imagen de la matricula a procesar\n node.send([msg, msg_s, null])\n }\n return null\n}\nreturn null; ",
"outputs": 3,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 2660,
"y": 1160,
"wires": [
[
"d4a053997921cfc0"
],
[
"9eda24ef14d3892c"
],
[
"e944fa11afc97160"
]
]
},
{
"id": "a42d3e0da2d9dbda",
"type": "debug",
"z": "744bdf3a0e08d754",
"g": "68b92de7bc4c82f6",
"name": "debug 15",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 3280,
"y": 1120,
"wires": []
},
{
"id": "7b7da2e1e640ca0a",
"type": "inject",
"z": "744bdf3a0e08d754",
"g": "68b92de7bc4c82f6",
"name": "",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "1",
"payloadType": "num",
"x": 2090,
"y": 1240,
"wires": [
[
"950c8115e364a493"
]
]
},
{
"id": "1387a80aca617b59",
"type": "comment",
"z": "744bdf3a0e08d754",
"g": "68b92de7bc4c82f6",
"name": "Sincronizacion de registros anteriores",
"info": "Este grupo de nodos realiza las siguiente acciones:\n1. Extrae de la BD un array con todos los records que aun no han sido sincronizados con el servidor central (sincro = 0)\n2. Extrae del record actual (el que este en el apuntador del indice) los nombres de los archivos de la matricula y de la firma\n3. Lee el contenido de las imagenes de matricula y de firma.\n4. Agrega estas imagenes al contenido del json que se va a enviar al servidor. El cual consta de los campos del record mas las imagenes\n5. Envia el contenido del json al servidor:\n\n - A traves del nodo http / https con un timeout de 5 segundos (la url esta en el archivo de configuracion)\n Si la respuesta del servidor es satisfactoria (Codigo = 200) se procede a marcar en la BD el bit de sincronizacion a 1\n Lo cual indica que ese record ya ha sido sincronizado. Posteriormente se procede a procesar el proximo record del array.\n En caso de timeout (Codigo diferente a 200) se procede a recuperar el proximo record del array para realizarle el\n procedimiento a partir del paso 2. Para diferenciar si es el primer record o es la continuacion del scan del array se utiliza el parametro\n msg.inicio = true (primer record) en caso de que sea false el record es el que se indique en el indice del array actual.\n\n\n - A traves de MQTT con un timeout de 5 segundos para esperar un ACK donde el UUID que envíe debe coincidir con el UUID enviado\n Si el nodo de ACK recibe la confirmacion de la recepcion del UUID correspondiente, entonces se procede a marcar en la BD el bit de sincronizacion a 1\n Lo cual indica que ese record ya ha sido sincronizado. Posteriormente se procede a procesar el proximo record del array.\n En caso que el nodo trigger produzca el time out se procede a borrar la referencia del ultimo uuid (nodo init uuid) la cual sirve para comparar\n con el uuid recibido cuando llegue el ack (esto es necesario hacerlo por si llega un ack retrasado) y se continua con el proceso del proximo record si existe",
"x": 2750,
"y": 1380,
"wires": []
},
{
"id": "ca758e5b6e2d880e",
"type": "mqtt in",
"z": "744bdf3a0e08d754",
"g": "fdddf9cbc2fa6622",
"name": "ack",
"topic": "totemautonomo/movimientos/movimiento_ok",
"qos": "1",
"datatype": "auto-detect",
"broker": "3966bb218232f6b3",
"nl": false,
"rap": true,
"rh": 0,
"inputs": 0,
"x": 3130,
"y": 1440,
"wires": [
[
"a62dedb280979227",
"6d9ec47623128a2d"
]
]
},
{
"id": "4a2f600ba9ca7a15",
"type": "mqtt out",
"z": "744bdf3a0e08d754",
"g": "fdddf9cbc2fa6622",
"name": "send data",
"topic": "totemautonomo/movimientos/movimiento",
"qos": "2",
"retain": "",
"respTopic": "",
"contentType": "",
"userProps": "",
"correl": "",
"expiry": "",
"broker": "3966bb218232f6b3",
"x": 3280,
"y": 1260,
"wires": []
},
{
"id": "a62dedb280979227",
"type": "function",
"z": "744bdf3a0e08d754",
"g": "fdddf9cbc2fa6622",
"name": "rx ACK",
"func": "/*\nAqui recibimos el ack enviado por el programa del servidor que recibe los movimientos. Este programa debe enviar\nel topic:totemautonomo/movimientos/movimiento_ok ; payload: true / false ; uuid: el uuid del movimiento\n*/\n\nif (msg.payload.uuid === flow.get(\"ultimo_movimiento_id\") && msg.payload.movimiento_ok && flow.get(\"ack_old_movimientos\")){\n flow.set(\"ack_old_movimientos\", false) // inicializamos el ack\n flow.set(\"ultimo_movimiento_ok\", true) // indicamos que el ultimo envio ha sido recibido\n flow.set(\"ultimo_movimiento_id\", \"\")\n msg.reset = true // esto es para parar el timeout counter\n return msg\n}\n \nreturn ;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 3280,
"y": 1420,
"wires": [
[
"b1c81347b429091a",
"1d3c5c1f2e5be4b0"
]
]
},
{
"id": "d5ced8b11f124c67",
"type": "debug",
"z": "744bdf3a0e08d754",
"g": "fdddf9cbc2fa6622",
"name": "tx mqtt",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 3270,
"y": 1320,
"wires": []
},
{
"id": "6d9ec47623128a2d",
"type": "debug",
"z": "744bdf3a0e08d754",
"g": "fdddf9cbc2fa6622",
"name": "rx ack",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 3270,
"y": 1480,
"wires": []
},
{
"id": "1d3c5c1f2e5be4b0",
"type": "trigger",
"z": "744bdf3a0e08d754",
"g": "fdddf9cbc2fa6622",
"name": "timeout",
"op1": "",
"op2": "1",
"op1type": "nul",
"op2type": "num",
"duration": "5",
"extend": false,
"overrideDelay": false,
"units": "s",
"reset": "",
"bytopic": "all",
"topic": "topic",
"outputs": 1,
"x": 3280,
"y": 1360,
"wires": [
[
"0a4223f208ebf170",
"b1c81347b429091a"
]
]
},
{
"id": "0a4223f208ebf170",
"type": "change",
"z": "744bdf3a0e08d754",
"g": "fdddf9cbc2fa6622",
"name": "init uuid",
"rules": [
{
"t": "set",
"p": "ultimo_movimiento_id",
"pt": "flow",
"to": "",
"tot": "str"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 3460,
"y": 1360,
"wires": [
[]
]
},
{
"id": "fc3b9b3c65b9e540",
"type": "comment",
"z": "744bdf3a0e08d754",
"g": "fdddf9cbc2fa6622",
"name": "",
"info": "Este grupo se encarga de enviar via mqtt el json correspondiente al contenido del movimiento\ndel vehiculo en la bascula. \nAdicionalmente espera un ACK con un timeout de 5 segundos. Este timeout se consigue con el uso de un nodo\ntrigger configurado en 5 segundos. Este nodo al cabo de 5 segundos encia un mensaje {payload: 1} al nodo\nde procesamiento para que continue el proceso con el proximo record.\nEn caso de recibir el acknoledge por parete del servidor se envia un mensaje de reset al nodo trigger\ncon la finalidad de parar el contador.\nCuando se recibe el ack se borra la referencia con el ultimo uuid para evitar recepciones de ack falsas",
"x": 3080,
"y": 1380,
"wires": []
},
{
"id": "9eda24ef14d3892c",
"type": "file in",
"z": "744bdf3a0e08d754",
"g": "68b92de7bc4c82f6",
"name": "lee imagen_f",
"filename": "filename",
"filenameType": "msg",
"format": "utf8",
"chunk": false,
"sendError": false,
"encoding": "none",
"allProps": false,
"x": 2870,
"y": 1160,
"wires": [
[
"89f64c532d3d78e7"
]
]
},
{
"id": "867c5654842a1210",
"type": "mysql",
"z": "744bdf3a0e08d754",
"g": "68b92de7bc4c82f6",
"mydb": "1c8613764b7f685e",
"name": "Actualiza tabla",
"x": 3720,
"y": 1140,
"wires": [
[
"911f1b379de9ba3a"
]
]
},
{
"id": "d144432c1ab94daf",
"type": "mysql",
"z": "744bdf3a0e08d754",
"g": "68b92de7bc4c82f6",
"mydb": "1c8613764b7f685e",
"name": "Actualiza tabla",
"x": 2420,
"y": 1160,
"wires": [
[
"f4c6152e8ed7f5a6",
"3c3867c2a2185f63"
]
]
},
{
"id": "d683e9cccedc561f",
"type": "mqtt in",
"z": "744bdf3a0e08d754",
"g": "f0815d4d5c1e13e0",
"name": "Contratos Update",
"topic": "totemautonomo/contratos/update",
"qos": "1",
"datatype": "utf8",
"broker": "3966bb218232f6b3",
"nl": false,
"rap": true,
"rh": 0,
"inputs": 0,
"x": 2120,
"y": 1700,
"wires": [
[
"36b9bf6834913595"
]
]
},
{
"id": "0c144d19f803250f",
"type": "switch",
"z": "744bdf3a0e08d754",
"g": "f0815d4d5c1e13e0",
"name": "Tabla destino",
"property": "payload.tabla",
"propertyType": "msg",
"rules": [
{
"t": "eq",
"v": "Matriculas",
"vt": "str"
},
{
"t": "eq",
"v": "Identificadores",
"vt": "str"
},
{
"t": "eq",
"v": "Matriculas_Identificadores",
"vt": "str"
},
{
"t": "eq",
"v": "UbicacionesInternas",
"vt": "str"
}
],
"checkall": "true",
"repair": false,
"outputs": 4,
"x": 2450,
"y": 1700,
"wires": [
[
"ac9d1ea0c4c9dfbe"
],
[
"e0c3a3c33ed7aaca",
"b9b6a469329290a5"
],
[
"76a88549a38a751d"
],
[
"1dae889e8697bf15"
]
]
},
{
"id": "105c677a40b6461b",
"type": "debug",
"z": "744bdf3a0e08d754",
"g": "f0815d4d5c1e13e0",
"name": "✅ Éxito actualización",
"active": false,
"tosidebar": true,
"console": false,
"tostatus": true,
"complete": "true",
"targetType": "full",
"statusVal": "✓ OK",
"statusType": "str",
"x": 3580,
"y": 1640,
"wires": []
},
{
"id": "f0f98e25ebd44ac3",
"type": "function",
"z": "744bdf3a0e08d754",
"g": "f0815d4d5c1e13e0",
"name": "delete records",
"func": "let tabla = msg.payload.tabla\nlet datos = msg.payload.datos\nlet queries = []\nswitch (msg.payload.tabla){\n case \"Vehiculos\":\n for (let row of datos) {\n const matricula = row.Matricula;\n queries.push(`DELETE FROM ('${tabla}') WHERE Matricula = ('${matricula}')`);\n }\n msg.topic = queries\n break;\n\n case \"Centros\":\n let IdCentro = msg.payload.IdCentro\n msg.topic = `DELETE FROM ('${tabla}') WHERE IdCentro = ('${IdCentro}')`;\n break;\n}\n\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 2840,
"y": 1860,
"wires": [
[]
]
},
{
"id": "67e7ede89ac08b87",
"type": "mqtt in",
"z": "744bdf3a0e08d754",
"g": "f0815d4d5c1e13e0",
"name": "Delete a record",
"topic": "totemautonomo/contratos/delete",
"qos": "1",
"datatype": "json",
"broker": "3966bb218232f6b3",
"nl": false,
"rap": true,
"rh": 0,
"inputs": 0,
"x": 2180,
"y": 1860,
"wires": [
[
"f0f98e25ebd44ac3"
]
]
},
{
"id": "36b9bf6834913595",
"type": "json",
"z": "744bdf3a0e08d754",
"g": "f0815d4d5c1e13e0",
"name": "",
"property": "payload",
"action": "",
"pretty": false,
"x": 2290,
"y": 1700,
"wires": [
[
"0c144d19f803250f"
]
]
},
{
"id": "c7a7b3e8c093ae1e",
"type": "link in",
"z": "744bdf3a0e08d754",
"g": "f0815d4d5c1e13e0",
"name": "data_tables",
"links": [
"24855820a3fadbb8"
],
"x": 2315,
"y": 1760,
"wires": [
[
"0c144d19f803250f"
]
]
},
{
"id": "afd34cb6f61a6475",
"type": "debug",
"z": "744bdf3a0e08d754",
"g": "f0815d4d5c1e13e0",
"name": "debug 37",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 3080,
"y": 1620,
"wires": []
},
{
"id": "ac9d1ea0c4c9dfbe",
"type": "function",
"z": "744bdf3a0e08d754",
"g": "f0815d4d5c1e13e0",
"name": "Matriculas",
"func": "// Verifica que payload sea un array\nif (!Array.isArray(msg.payload.datos) || msg.payload.datos.length === 0) {\n node.warn(\"msg.payload.datos no es un array o está vacío.\");\n return null;\n}\n\n// Función para escapar comillas simples\nconst esc = s => (s ?? \"\").toString().replace(/'/g, \"''\");\n\n// Construimos las tuplas\nflow.set(\"matriculas_size\", msg.payload.datos.length)\nconst valuesArray = msg.payload.datos.map(obj => {\n const matricula = esc(obj.matricula || \"\").trim().toUpperCase();\n const tara = Number(obj.tara) || 0;\n const pma = Number(obj.pma) || 0;\n return `('${matricula}', ${tara}, ${pma})`;\n});\nflow.set(\"matriculas_counter\", valuesArray.length )\n// Query final\n//Borra la tabla actual y si la tabla recibida es diferente de 0 records guarda la nueva tabla, si no pone \"\"\n// finalmente hace un commit\nmsg.topic = `\nSTART TRANSACTION;\nDELETE FROM Matriculas;\n${valuesArray.length ? `\nINSERT INTO Matriculas (matricula, tara, pma)\nVALUES ${valuesArray.join(\",\")};\n` : \"\"}\nCOMMIT;\n`;\nmsg.payload = \"\"\nreturn msg;\n",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 2810,
"y": 1660,
"wires": [
[
"afd34cb6f61a6475",
"7aee3c3ea10fd9a5"
]
]
},
{
"id": "b9b6a469329290a5",
"type": "function",
"z": "744bdf3a0e08d754",
"g": "f0815d4d5c1e13e0",
"name": "Identificadores",
"func": "// Verifica que payload sea un array\nif (!Array.isArray(msg.payload.datos) || msg.payload.datos.length === 0) {\n node.warn(\"msg.payload.datos no es un array o está vacío.\");\n return null;\n}\n\n// Función para escapar comillas simples\nconst esc = s => (s ?? \"\").toString().replace(/'/g, \"''\");\n\n// Construimos las tuplas\nflow.set(\"matriculas_size\", msg.payload.datos.length);\n\nconst valuesArray = msg.payload.datos.map(obj => {\n // Si el campo es null, undefined o la cadena \"null\", lo tratamos como NULL\n const toSQL = val => {\n if (val === null || val === undefined || val === \"null\" || val === \"NULL\") {\n return \"NULL\";\n }\n const txt = esc(val.toString().trim());\n return `'${txt}'`;\n };\n\n const id = toSQL(obj.id);\n const id_padre = toSQL(obj.id_padre);\n const nivel = Number(obj.nivel) || 0;\n const descripcion = toSQL(obj.descripcion);\n\n return `(${id}, ${id_padre}, ${nivel}, ${descripcion})`;\n});\n\nflow.set(\"matriculas_counter\", valuesArray.length);\n\n// Query final\n//En esta tabla puede haber varios registros con la misma matricula por lo tanto hacemos solo INSERT INTO ( SIN IGNORE o REPLACE)\n//Ademas estos campos no pueden ser Primary Key ya que sino no puede haber registros con el mismo valor\n// Query final\n//Borra la tabla actual y si la tabla recibida es diferente de 0 records guarda la nueva tabla, si no pone \"\"\n// finalmente hace un commit\nmsg.topic = `\nSTART TRANSACTION;\nDELETE FROM Identificadores;\n${valuesArray.length ? `\nINSERT INTO Identificadores (id, id_padre, nivel, descripcion)\nVALUES ${valuesArray.join(\",\")};\n` : \"\"}\nCOMMIT;\n`;\n\nmsg.payload = \"\";\nreturn msg;\n\n\n\n\n\n/*\n// Verifica que payload sea un array\nif (!Array.isArray(msg.payload.datos) || msg.payload.datos.length === 0) {\n node.warn(\"msg.payload.datos no es un array o está vacío.\");\n return null;\n}\n\n// Función para escapar comillas simples\nconst esc = s => (s ?? \"\").toString().replace(/'/g, \"''\");\n\n// Construimos las tuplas\nflow.set(\"matriculas_size\", msg.payload.datos.length)\n\nconst valuesArray = msg.payload.datos.map(obj => {\n const id = esc(obj.id || \"\").trim().toUpperCase();\n const id_padre = esc(obj.id_padre || \"\").trim().toUpperCase();\n const nivel = Number(obj.nivel) || 0;\n const descripcion = `'${esc(obj.descripcion)}'`; // ¡entre comillas y escapado!\n return `('${id}', '${id_padre}', ${nivel}, ${descripcion})`;\n});\nflow.set(\"matriculas_counter\", valuesArray.length )\n// Query final\n\nmsg.topic = `\nINSERT INTO Identificadores (id, id_padre, nivel, descripcion)\nVALUES ${valuesArray.join(\",\")};\n`;\nmsg.payload = \"\"\nreturn msg;\n*/\n",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 2820,
"y": 1700,
"wires": [
[
"afd34cb6f61a6475",
"7aee3c3ea10fd9a5"
]
]
},
{
"id": "76a88549a38a751d",
"type": "function",
"z": "744bdf3a0e08d754",
"g": "f0815d4d5c1e13e0",
"name": "Matriculas_Identificadores",
"func": "// Verifica que payload sea un array\nif (!Array.isArray(msg.payload.datos) || msg.payload.datos.length === 0) {\n node.warn(\"msg.payload.datos no es un array o está vacío.\");\n return null;\n}\n\n// Función para escapar comillas simples\nconst esc = s => (s ?? \"\").toString().replace(/'/g, \"''\");\n\n// Construimos las tuplas\nflow.set(\"matriculas_size\", msg.payload.datos.length)\nconst valuesArray = msg.payload.datos.map(obj => {\n const id = esc(obj.id || \"\").trim().toUpperCase();\n const matricula = esc(obj.matricula || \"\").trim().toUpperCase();\n return `('${matricula}','${id}')`;\n});\nflow.set(\"matriculas_counter\", valuesArray.length )\n// Query final\n//En esta tabla puede haber varios registros con la misma matricula por lo tanto hacemos solo INSERT INTO ( SIN IGNORE o REPLACE)\n//Ademas estos campos no pueden ser Primary Key ya que sino no puede haber registros con el mismo valor\n// Query final\n//Borra la tabla actual y si la tabla recibida es diferente de 0 records guarda la nueva tabla, si no pone \"\"\n// finalmente hace un commit\nmsg.topic = `\nSTART TRANSACTION;\nDELETE FROM Matriculas_Identificadores;\n${valuesArray.length ? `\nINSERT INTO Matriculas_Identificadores (matricula, id)\nVALUES ${valuesArray.join(\",\")};\n` : \"\"}\nCOMMIT;\n`;\nmsg.payload = \"\"\nreturn msg;\n",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 2850,
"y": 1740,
"wires": [
[
"afd34cb6f61a6475",
"7aee3c3ea10fd9a5"
]
]
},
{
"id": "e0c3a3c33ed7aaca",
"type": "debug",
"z": "744bdf3a0e08d754",
"g": "f0815d4d5c1e13e0",
"name": "debug 36",
"active": false,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 2620,
"y": 1620,
"wires": []
},
{
"id": "7aee3c3ea10fd9a5",
"type": "mysql",
"z": "744bdf3a0e08d754",
"g": "f0815d4d5c1e13e0",
"mydb": "1c8613764b7f685e",
"name": "Actualiza tablas",
"x": 3260,
"y": 1680,
"wires": [
[
"105c677a40b6461b"
]
]
},
{
"id": "aa870f8d73e4f504",
"type": "link in",
"z": "744bdf3a0e08d754",
"g": "f0815d4d5c1e13e0",
"name": "delete_items_in",
"links": [
"6a2b1526103aa3b0"
],
"x": 3135,
"y": 1820,
"wires": [
[
"7aee3c3ea10fd9a5"
]
]
},
{
"id": "1d9e5518c4689f75",
"type": "inject",
"z": "744bdf3a0e08d754",
"g": "8013dc387b26381b",
"name": "",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "0236MWK",
"payloadType": "str",
"x": 2120,
"y": 2040,
"wires": [
[
"b76ed973b1299bb9"
]
]
},
{
"id": "a1ac4bf3b8c14b21",
"type": "link in",
"z": "744bdf3a0e08d754",
"g": "8013dc387b26381b",
"name": "entrada matricula",
"links": [
"6703fb916aef8f4b"
],
"x": 2345,
"y": 2080,
"wires": [
[
"9a65971441f7579f",
"ceba75f09293b8f4"
]
]
},
{
"id": "7f282cc6ba963043",
"type": "link out",
"z": "744bdf3a0e08d754",
"g": "8013dc387b26381b",
"name": "salida resultados",
"mode": "link",
"links": [
"cb6c28fe05c25229"
],
"x": 2915,
"y": 2020,
"wires": []
},
{
"id": "c470404997683059",
"type": "comment",
"z": "744bdf3a0e08d754",
"g": "8013dc387b26381b",
"name": "",
"info": "Este conjunto de nodos revisa la existencia de una matricula en base de datos. Para ello recibe la matricula en msg.paylad.\nHace entonces un select en la BD para buscar la matricula recibida. En caso que la BD devuelva un array vacio no se activa\nel flag correspondiente a matricula OK por lo que aparecera un mensaje en la pantalla indicando al usuario que esa matricula\nno esta registrada. \nEn caso de existrir la matricula en la BD entonces el proceso continua y lo que hacemos es pasar la matricula al grupo de nodos\nque busca los datos correspondientes a los posibles contratos para dicha matricula y se elabora un menu para que el usuario\nseleccione en la pantalla los valores adecuados de manera a poder finalizar la pesada.\n",
"x": 2180,
"y": 2100,
"wires": []
},
{
"id": "008f41b960a6d13f",
"type": "debug",
"z": "744bdf3a0e08d754",
"g": "8013dc387b26381b",
"name": "debug 10",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 2720,
"y": 2000,
"wires": []
},
{
"id": "9a65971441f7579f",
"type": "debug",
"z": "744bdf3a0e08d754",
"g": "8013dc387b26381b",
"name": "debug 11",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 2500,
"y": 2100,
"wires": []
},
{
"id": "9c65121589e6726e",
"type": "debug",
"z": "744bdf3a0e08d754",
"g": "8013dc387b26381b",
"name": "debug 19",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 2860,
"y": 2100,
"wires": []
},
{
"id": "b76ed973b1299bb9",
"type": "function",
"z": "744bdf3a0e08d754",
"g": "8013dc387b26381b",
"name": "check matricula",
"func": "let matricula = msg.payload\nmsg.matricula = matricula\n//flow.set(\"matricula\", matricula)\n//Solicitamos un solo record donde aparezca registrada la matricula (tabla de Vehiculos) y revisamos si se encuentra en transito (tabla movimientos)\n// en caso que no este registrada la matricula, la BD nos entrega un record con los parametros en cero o en null\nmsg.topic = `\nWITH input AS (SELECT TRIM('${matricula}') AS MatriculaInput)\nSELECT\n CASE WHEN ma.matricula IS NOT NULL THEN 1 ELSE 0 END AS registrada,\n CAST(EXISTS(SELECT 1 FROM movimientos m JOIN input i ON UPPER(TRIM(m.matricula)) = UPPER(i.MatriculaInput) LIMIT 1) AS INTEGER) AS transito,\n ma.tara, ma.pma, (SELECT CASE WHEN COUNT(*)=1 THEN MAX(idf.descripcion) ELSE NULL END FROM Matriculas_Identificadores mi JOIN input i ON UPPER(TRIM(mi.matricula)) = UPPER(i.MatriculaInput) LEFT JOIN Identificadores idf ON idf.id = mi.id) AS descripcion,\n (SELECT COUNT(*) FROM Matriculas_Identificadores mi JOIN input i ON UPPER(TRIM(mi.matricula)) = UPPER(i.MatriculaInput)) AS repeticion_matricula\nFROM input i\nLEFT JOIN Matriculas ma ON UPPER(TRIM(ma.matricula)) = UPPER(i.MatriculaInput)\nLIMIT 1;\n`;\nreturn msg;\n",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 2300,
"y": 2040,
"wires": [
[
"ceba75f09293b8f4"
]
]
},
{
"id": "a16351341e1a09da",
"type": "function",
"z": "744bdf3a0e08d754",
"g": "8013dc387b26381b",
"name": "check results",
"func": "let result = msg.payload\nflow.set(\"matricula_revisada\", true)\nmsg.payload = {}\nmsg.topic = \"\"\n//msg.payload.totem_id = flow.get(\"totem_id\")\n//msg.payload.numero_vial = flow.get(\"numero_vial\")\n\nif (result.length > 0){ // Tenemos al menos un record de respuesta de la BD\n if (result[0].registrada == 1){\n msg.payload.registrada = !!result[0].registrada // la doble negacion convierte a booleano ( true / false)\n msg.payload.pma = result[0].pma\n msg.payload.tara = result[0].tara\n msg.payload.transito = !!result[0].transito\n msg.payload.matricula = msg.matricula\n let msgOut = RED.util.cloneMessage(msg);\n msg.payload.repeticion_matricula = result[0].repeticion_matricula\n msg.payload.aviso_identificacion = false // por defecto comenzamos con false\n if (result[0].repeticion_matricula == 1){\n let descripcion = result[0].descripcion || \"\" // en caso de null o undefined ponemos cadena vacia\n msg.payload.aviso_identificacion = descripcion.includes(\",\"); // esto es true si la descripcion tiene una \",\" lo cual indica que es para un QR o RFID\n }\n if (!msg.payload.transito && !msg.payload.aviso_identificacion){ // si esta en transito o si NO hay que sacar aviso de identificacion, buscamos menu en BD\n setTimeout(function () {\n node.send([null, msgOut]) //genera lista de opciones para presentar el menu en pantalla\n }, 1000)\n }\n }\n else{\n msg.payload.registrada = result[0].registrada\n }\n}\nelse{\n msg.payload.respuestaBD = \"ningun record\"\n}\nmsg.payload.totem_id = msg.totem_id\nmsg.payload.numero_vial = msg.numero_vial\nreturn ([msg, null]);\n",
"outputs": 2,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 2750,
"y": 2040,
"wires": [
[
"22f142c6e6d19334",
"7f282cc6ba963043"
],
[
"9c65121589e6726e",
"dcd65882403b8019"
]
]
},
{
"id": "22f142c6e6d19334",
"type": "debug",
"z": "744bdf3a0e08d754",
"g": "8013dc387b26381b",
"name": "debug 32",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 2880,
"y": 1980,
"wires": []
},
{
"id": "ceba75f09293b8f4",
"type": "mysql",
"z": "744bdf3a0e08d754",
"g": "8013dc387b26381b",
"mydb": "1c8613764b7f685e",
"name": "check matricula",
"x": 2540,
"y": 2040,
"wires": [
[
"a16351341e1a09da",
"008f41b960a6d13f"
]
]
},
{
"id": "dcd65882403b8019",
"type": "function",
"z": "744bdf3a0e08d754",
"g": "cb22a118da74ca79",
"name": "genera lista",
"func": "// Asegura que recibimos la matrícula\nconst matricula = msg.matricula;\nif (!matricula) {\n node.error(\"No se recibió matrícula en msg.payload o msg.matricula\");\n return null;\n}\n\nmsg.topic = `\nWITH\nseeds AS (\n SELECT mi.id AS id_root\n FROM Matriculas_Identificadores mi\n WHERE UPPER(TRIM(mi.matricula)) = UPPER(TRIM('${matricula}'))\n)\nSELECT\n 0 AS nivel,\n i.descripcion,\n i.id,\n NULL AS id_padre\nFROM Identificadores i\nJOIN seeds s ON i.id = s.id_root\n\nUNION ALL\n\nSELECT\n 1 AS nivel,\n c.descripcion,\n c.id,\n c.id_padre\nFROM Identificadores c\nJOIN seeds s ON c.id_padre = s.id_root\n\nORDER BY nivel, id;\n`;\n\n/* Esto es lo que estaba con Sqlite\nmsg.topic = `\nWITH\nseeds AS (\n SELECT mi.id AS id_root\n FROM Matriculas_Identificadores mi\n WHERE TRIM(mi.matricula) = TRIM('${matricula}') COLLATE NOCASE\n)\nSELECT\n 0 AS nivel,\n i.descripcion,\n i.id,\n NULL AS id_padre\nFROM Identificadores i\nJOIN seeds s ON i.id = s.id_root\n\nUNION ALL\n\nSELECT\n 1 AS nivel,\n c.descripcion,\n c.id,\n c.id_padre\nFROM Identificadores c\nJOIN seeds s ON c.id_padre = s.id_root\n\nORDER BY nivel, id;\n`;\n*/\nreturn msg;\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n/* codigo anterior\n// Armamos el SELECT combinado\nmsg.topic = `\n-- Nivel 0: Centros asociados a la matrícula\nSELECT \n 0 AS nivel,\n c.Nombre AS descripcion,\n c.IdCentro AS id,\n NULL AS id_padre\nFROM Matriculas_Centros mc\nJOIN Centros c ON mc.IdCentro = c.IdCentro\nWHERE mc.Matricula = '${matricula}'\n\nUNION ALL\n\n-- Nivel 1: Servicios asociados a los centros de esa matrícula\nSELECT \n 1 AS nivel,\n s.Descripcion AS descripcion,\n cs.IdCentroServicio AS id,\n cs.IdCentro AS id_padre\nFROM Matriculas_Centros mc\nJOIN Centros_Servicios cs ON mc.IdCentro = cs.IdCentro\nJOIN Servicios s ON cs.IdServicio = s.IdServicio\nWHERE mc.Matricula = '${matricula}';\n`;\n\nreturn msg;\n*/",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 3270,
"y": 2020,
"wires": [
[
"4f214eb96e82b18b",
"dfdc2a0fceee62b1"
]
]
},
{
"id": "39ce9cfd3bee5c07",
"type": "inject",
"z": "744bdf3a0e08d754",
"g": "cb22a118da74ca79",
"name": "",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "4265HGS",
"payloadType": "str",
"x": 3100,
"y": 2060,
"wires": [
[
"dcd65882403b8019"
]
]
},
{
"id": "eb466c2601840e18",
"type": "function",
"z": "744bdf3a0e08d754",
"g": "cb22a118da74ca79",
"name": "genera json",
"func": "\nif (msg.payload.length > 0) { // tenemos un menu asociado a la matricula\n if (msg.payload.length > 1) {\n msg.metadatos = {\n //numero_vial: flow.get(\"numero_vial\"),\n //totem_id: flow.get(\"totem_id\"),\n encabezado_lista: flow.get(\"encabezado_lista\") ?? \"Seleccione opción\",\n button: \"button1\",\n button1: \"COMPLETAR INFO\",\n menu_inicio: true,\n lista: msg.payload\n };\n msg.payload = msg.metadatos\n msg.metadatos = \"\"\n msg.topic = \"\"\n msg.payload.totem_id = msg.totem_id\n msg.payload.numero_vial = msg.numero_vial\n node.send([msg, null])\n }\n}\nelse{\n //node.warn(\"array size <= 0\")\n msg.payload = {\n numero_vial: msg.numero_vial,\n totem_id: msg.totem_id,\n registrada: false,\n \n };\n \n node.send([null, msg])\n}\nreturn;",
"outputs": 2,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 3710,
"y": 2020,
"wires": [
[
"3605e72173f34f32",
"4526132d69e6c210"
],
[
"ce3029f2688a0aba",
"6ef121d9eb28bd91"
]
]
},
{
"id": "3605e72173f34f32",
"type": "debug",
"z": "744bdf3a0e08d754",
"g": "cb22a118da74ca79",
"name": "debug 21",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 3900,
"y": 1980,
"wires": []
},
{
"id": "4526132d69e6c210",
"type": "link out",
"z": "744bdf3a0e08d754",
"g": "cb22a118da74ca79",
"name": "salida lista",
"mode": "link",
"links": [
"cb6c28fe05c25229"
],
"x": 3895,
"y": 2020,
"wires": []
},
{
"id": "b49f7a7bb3272707",
"type": "link in",
"z": "744bdf3a0e08d754",
"g": "cb22a118da74ca79",
"name": "Generacion de Lista",
"links": [],
"x": 3145,
"y": 2100,
"wires": [
[
"dcd65882403b8019"
]
]
},
{
"id": "96d638d71a74e652",
"type": "comment",
"z": "744bdf3a0e08d754",
"g": "cb22a118da74ca79",
"name": "",
"info": "Este conjunto de nodos recibe primeramente la matricula a buscar y elabora un select con los join y union adecuados \npara buscar en las diferentes tablas los posibles valores asociados a la matricula para entonces elaborar un json\ncon la lista de opciones que se van a presentar al usuario. Este json se injecta en el nodo de entrada del sistema.\n",
"x": 3320,
"y": 2100,
"wires": []
},
{
"id": "ce3029f2688a0aba",
"type": "link out",
"z": "744bdf3a0e08d754",
"g": "cb22a118da74ca79",
"name": "link out 11",
"mode": "link",
"links": [
"cb6c28fe05c25229"
],
"x": 3895,
"y": 2060,
"wires": []
},
{
"id": "6ef121d9eb28bd91",
"type": "debug",
"z": "744bdf3a0e08d754",
"g": "cb22a118da74ca79",
"name": "debug 16",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 3900,
"y": 2100,
"wires": []
},
{
"id": "74375df4238da5a5",
"type": "debug",
"z": "744bdf3a0e08d754",
"g": "cb22a118da74ca79",
"name": "debug 8",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 3680,
"y": 1980,
"wires": []
},
{
"id": "4f214eb96e82b18b",
"type": "mysql",
"z": "744bdf3a0e08d754",
"g": "cb22a118da74ca79",
"mydb": "1c8613764b7f685e",
"name": "get menu",
"x": 3460,
"y": 2020,
"wires": [
[
"eb466c2601840e18",
"74375df4238da5a5"
]
]
},
{
"id": "dfdc2a0fceee62b1",
"type": "debug",
"z": "744bdf3a0e08d754",
"g": "cb22a118da74ca79",
"name": "debug 39",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 3420,
"y": 1980,
"wires": []
},
{
"id": "9b47806ebf017d2c",
"type": "function",
"z": "744bdf3a0e08d754",
"g": "f968efec7b96ec13",
"name": "check BD",
"func": "if (msg.tipo == \"rfid\") {\n let tarjeta = msg.payload\n msg.topic = `SELECT\n CASE WHEN mi.id IS NOT NULL THEN TRUE ELSE FALSE END AS autorizado,\n mi.matricula AS matricula,\n i.descripcion AS descripcion,\n i.nivel AS nivel\nFROM (SELECT '${tarjeta}' AS id) AS input\nLEFT JOIN Matriculas_Identificadores mi\n ON TRIM(mi.id) = TRIM(input.id)\nLEFT JOIN Identificadores i\n ON TRIM(i.id) = TRIM(input.id)\nLIMIT 1;`;\n}\nelse if (msg.tipo == \"qr\") {\n let qr = msg.payload\n msg.topic = `\nSELECT\n CASE WHEN mi.id IS NOT NULL THEN TRUE ELSE FALSE END AS autorizado,\n mi.matricula AS matricula,\n i.descripcion AS descripcion,\n i.nivel AS nivel\nFROM (SELECT '${qr}' AS id) AS input\nLEFT JOIN Matriculas_Identificadores mi\n ON TRIM(mi.id) = TRIM(input.id)\nLEFT JOIN Identificadores i\n ON TRIM(i.id) = TRIM(input.id)\nLIMIT 1;`;\n}\nreturn msg;\n\n\n",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 2240,
"y": 2360,
"wires": [
[
"57fd039f5dadf4fb",
"8558335b01c7e441"
]
]
},
{
"id": "7269e082a5989305",
"type": "inject",
"z": "744bdf3a0e08d754",
"g": "f968efec7b96ec13",
"name": "qr",
"props": [
{
"p": "payload"
},
{
"p": "tipo",
"v": "qr",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "546DA2A4-B07B-482D-B98F-B4EB0FFABCAE",
"payloadType": "str",
"x": 2090,
"y": 2360,
"wires": [
[
"9b47806ebf017d2c"
]
]
},
{
"id": "8e56735bbb37a598",
"type": "link in",
"z": "744bdf3a0e08d754",
"g": "f968efec7b96ec13",
"name": "check qr rfid",
"links": [
"4e1f0ff54eef4e7b"
],
"x": 2095,
"y": 2320,
"wires": [
[
"9b47806ebf017d2c"
]
]
},
{
"id": "ea115b9b38cdaea1",
"type": "function",
"z": "744bdf3a0e08d754",
"g": "f968efec7b96ec13",
"name": "check results",
"func": " msg.topic = \"\"\n let descripcion = \"\"\n let partes = []\nif (msg.tipo == \"rfid\") {\n let result = msg.payload\n msg.payload = {\n numero_vial: msg.numero_vial,\n totem_id: msg.totem_id,\n rfid_revisado: true,\n rfid_verificado: false\n };\n if (result.length > 0) { // Tarjeta tiene contrato\n msg.payload.rfid_verificado = result[0].autorizado\n descripcion = result[0].descripcion || \"\"\n partes = descripcion.split(\",\");\n msg.payload.cliente = (partes[0] || \"\").trim();\n msg.payload.residuo = (partes[1] || \"\").trim();\n msg.payload.matricula = result[0].matricula || \"\"\n msg.payload.nivel = result[0].nivel || 0\n }\n \n}\nelse if (msg.tipo == \"qr\") {\n let result = msg.payload\n msg.payload = {\n numero_vial: msg.numero_vial,\n totem_id: msg.totem_id,\n qr_revisado: true,\n qr_verificado: false\n };\n if (result.length > 0) { // QR tiene contrato\n msg.payload.qr_verificado = result[0].autorizado\n descripcion = result[0].descripcion || \"\"\n partes = descripcion.split(\",\");\n msg.payload.cliente = (partes[0] || \"\").trim();\n msg.payload.residuo = (partes[1] || \"\").trim();\n //msg.payload.matricula = result[0].matricula || \"\" // no enviamos matricula cuando se trata de QR, ya que el QR puede estar asociado a varias matriculas\n msg.payload.nivel = result[0].nivel || 0\n \n }\n}\nreturn ([msg, null]);",
"outputs": 2,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 2750,
"y": 2360,
"wires": [
[
"9bd381438eec96eb",
"ed04082db0d29b5b"
],
[]
]
},
{
"id": "ed04082db0d29b5b",
"type": "link out",
"z": "744bdf3a0e08d754",
"g": "f968efec7b96ec13",
"name": "salida resultados",
"mode": "link",
"links": [
"cb6c28fe05c25229"
],
"x": 2895,
"y": 2320,
"wires": []
},
{
"id": "52c76e5f8d275432",
"type": "inject",
"z": "744bdf3a0e08d754",
"g": "f968efec7b96ec13",
"name": "rfid",
"props": [
{
"p": "payload"
},
{
"p": "tipo",
"v": "rfid",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "1983696218",
"payloadType": "str",
"x": 2090,
"y": 2400,
"wires": [
[
"9b47806ebf017d2c"
]
]
},
{
"id": "57fd039f5dadf4fb",
"type": "debug",
"z": "744bdf3a0e08d754",
"g": "f968efec7b96ec13",
"name": "debug 12",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 2380,
"y": 2420,
"wires": []
},
{
"id": "8558335b01c7e441",
"type": "mysql",
"z": "744bdf3a0e08d754",
"g": "f968efec7b96ec13",
"mydb": "1c8613764b7f685e",
"name": "check QR / Tarjeta",
"x": 2530,
"y": 2360,
"wires": [
[
"ea115b9b38cdaea1",
"7ced20377dba5074"
]
]
},
{
"id": "a8e9253184936edf",
"type": "function",
"z": "744bdf3a0e08d754",
"g": "0a78f1a176dd7ad5",
"name": "Matriculas",
"func": "\nmsg.url = `api/Totem/SeleccionarMatriculasTotem?token=${flow.get(\"tokenapi\")}&idCentroProveedor=${flow.get(\"IdCentroProveedor\") }`\nmsg.url = flow.get(\"url_API\") + msg.url \nmsg.tabla = \"Matriculas\"\nreturn msg;\n\n\n//https://pre-urbgesbas.urbaser.com/WebService/api/Totem/SeleccionarMatriculasTotem?token=FDEB4C00-5F58-4C2D-AD80-9E7064D8FC4E&idCentroProveedor=A1ADAC83-AEFA-4E1F-8944-56722AC43F4F",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 2450,
"y": 2600,
"wires": [
[
"e87018791b56046f"
]
]
},
{
"id": "24855820a3fadbb8",
"type": "link out",
"z": "744bdf3a0e08d754",
"g": "0a78f1a176dd7ad5",
"name": "link out 3",
"mode": "link",
"links": [
"c7a7b3e8c093ae1e"
],
"x": 2985,
"y": 2640,
"wires": []
},
{
"id": "20976aa7738e9650",
"type": "function",
"z": "744bdf3a0e08d754",
"g": "0a78f1a176dd7ad5",
"name": "function 2",
"func": "let datos = msg.payload\nlet tabla = msg.tabla\nmsg = {payload:{}}\nmsg.payload.datos = datos\nmsg.payload.tabla = tabla\nif (typeof msg.payload.datos === \"string\") {\n try {\n // Convierte el texto a array\n msg.payload.datos = JSON.parse(msg.payload.datos);\n } catch (err) {\n node.error(\"Error al parsear msg.payload.datos: \" + err.message);\n return null;\n }\n}\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 2840,
"y": 2640,
"wires": [
[
"ae259a894fbedeb4",
"24855820a3fadbb8"
]
]
},
{
"id": "a091c51915be9f0a",
"type": "function",
"z": "744bdf3a0e08d754",
"g": "0a78f1a176dd7ad5",
"name": "Identificadores",
"func": "\nmsg.url = `api/Totem/SeleccionarIdentificadoresTotem?token=${flow.get(\"tokenapi\")}&idCentroProveedor=${flow.get(\"IdCentroProveedor\")}`\nmsg.url = flow.get(\"url_API\") + msg.url\nmsg.tabla = \"Identificadores\"\nreturn msg;\n\n//https://pre-urbgesbas.urbaser.com/WebService/api/Totem/SeleccionarIdentificadoresTotem?token=FDEB4C00-5F58-4C2D-AD80-9E7064D8FC4E&idCentroProveedor=A1ADAC83-AEFA-4E1F-8944-56722AC43F4F",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 2440,
"y": 2680,
"wires": [
[
"e87018791b56046f"
]
]
},
{
"id": "9d2b6d3b968d3fcf",
"type": "function",
"z": "744bdf3a0e08d754",
"g": "0a78f1a176dd7ad5",
"name": "Matriculas_Identificadores",
"func": "\nmsg.url = `api/Totem/SeleccionarMatriculasIdentificadoresTotem?token=${flow.get(\"tokenapi\")}&idCentroProveedor=${flow.get(\"IdCentroProveedor\")}`\nmsg.url = flow.get(\"url_API\") + msg.url\nmsg.tabla = \"Matriculas_Identificadores\"\nreturn msg;\n\n//https://pre-urbgesbas.urbaser.com/WebService/api/Totem/SeleccionarMatriculasIdentificadoresTotem?token=FDEB4C00-5F58-4C2D-AD80-9E7064D8FC4E&idCentroProveedor=A1ADAC83-AEFA-4E1F-8944-56722AC43F4F\n",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 2410,
"y": 2640,
"wires": [
[
"e87018791b56046f"
]
]
},
{
"id": "c95d9408ac6b1595",
"type": "function",
"z": "744bdf3a0e08d754",
"g": "0a78f1a176dd7ad5",
"name": "Select",
"func": "switch(msg.payload){\n case \"Matriculas\":\n node.send([{payload:1}, null, null])\n break;\n case \"Matriculas_Identificadores\":\n node.send([null, { payload: 1 }, null])\n break;\n case \"Identificadores\":\n node.send([null, null, { payload: 1 }])\n break;\n case \"UbicacionesInternas\":\n node.send([null, null, null, { payload: 1 }])\n break;\n}\nreturn ;",
"outputs": 4,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 2150,
"y": 2640,
"wires": [
[
"a8e9253184936edf"
],
[
"9d2b6d3b968d3fcf"
],
[
"a091c51915be9f0a"
],
[
"eca4e2e077d64f16"
]
]
},
{
"id": "5cd2d2a7d0b81e81",
"type": "link in",
"z": "744bdf3a0e08d754",
"g": "0a78f1a176dd7ad5",
"name": "pedir_tablas",
"links": [],
"x": 2035,
"y": 2620,
"wires": [
[
"c95d9408ac6b1595"
]
]
},
{
"id": "e87018791b56046f",
"type": "https-node",
"z": "744bdf3a0e08d754",
"g": "0a78f1a176dd7ad5",
"name": "https request",
"method": "GET",
"ret": "txt",
"paytoqs": "ignore",
"url": "",
"tls": "",
"persist": false,
"proxy": "",
"authType": "",
"senderr": false,
"x": 2670,
"y": 2640,
"wires": [
[
"20976aa7738e9650"
]
]
},
{
"id": "ae259a894fbedeb4",
"type": "debug",
"z": "744bdf3a0e08d754",
"g": "0a78f1a176dd7ad5",
"name": "debug 38",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 3000,
"y": 2600,
"wires": []
},
{
"id": "0b16f0b9cb1c5ab9",
"type": "function",
"z": "744bdf3a0e08d754",
"g": "75dd57faa07f33b9",
"name": "envia a API",
"func": "if(flow.get(\"movimientosxapi\")){\n //msg.payload contiene el movimiento a enviar\n msg.url = flow.get(\"url_API\")\n msg.url = msg.url + `api/Totem/InsertarMovimientos?token=${flow.get(\"tokenapi\")}`\n //msg.requestTimeout = \"20000\"\n node.send([msg, null, null])\n \n}\n\nelse if (flow.get(\"movimientosxmqtt\") ){\n //msg.payload contiene el movimiento a enviar\n node.warn(\"tipoenvio = \" + msg.payload.tipoenvio)\n flow.set(\"ultimo_movimiento_ok\", false)\n flow.set(\"ultimo_movimiento_id\", msg.payload.uuid) \n node.send([null, msg, { payload: msg.payload.uuid }])\n}\nreturn ;\n\n//\"https://pre-urbgesbas.urbaser.com/WebService/api/Totem/InsertarMovimientos?token=FDEB4C00-5F58-4C2D-AD80-9E7064D8FC4E\"",
"outputs": 3,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 2150,
"y": 560,
"wires": [
[
"6455539b9fac2063",
"08bf0c9897d6e815"
],
[
"e36bb58515b89053",
"7ef59a8ac4d8a7f3"
],
[
"ac94ab2b132fab52"
]
]
},
{
"id": "c858b37cb0eeba4f",
"type": "link in",
"z": "744bdf3a0e08d754",
"g": "75dd57faa07f33b9",
"name": "API",
"links": [
"b1b9ac8e8b543dbb"
],
"x": 2045,
"y": 560,
"wires": [
[
"0b16f0b9cb1c5ab9"
]
]
},
{
"id": "e192c564f2050e94",
"type": "debug",
"z": "744bdf3a0e08d754",
"g": "75dd57faa07f33b9",
"name": "debug 3",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 2680,
"y": 500,
"wires": []
},
{
"id": "1bdaeab49913082e",
"type": "debug",
"z": "744bdf3a0e08d754",
"g": "75dd57faa07f33b9",
"name": "debug 4",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 3340,
"y": 540,
"wires": []
},
{
"id": "b8029f9a251a232e",
"type": "debug",
"z": "744bdf3a0e08d754",
"g": "75dd57faa07f33b9",
"name": "debug 5",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 3360,
"y": 620,
"wires": []
},
{
"id": "fd9524c0921c0990",
"type": "debug",
"z": "744bdf3a0e08d754",
"g": "75dd57faa07f33b9",
"name": "debug 6",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 3120,
"y": 580,
"wires": []
},
{
"id": "8778197111d2ab0d",
"type": "exec",
"z": "744bdf3a0e08d754",
"g": "75dd57faa07f33b9",
"command": "",
"addpay": "payload",
"append": "",
"useSpawn": "false",
"timer": "",
"winHide": false,
"oldrc": false,
"name": "delete files",
"x": 3350,
"y": 680,
"wires": [
[
"54bb5d92c06ce75c"
],
[],
[]
]
},
{
"id": "d47200e5f67c772a",
"type": "function",
"z": "744bdf3a0e08d754",
"g": "75dd57faa07f33b9",
"name": "UPDATE SINCRO",
"func": "\n\nlet sincro = true\nif (flow.get(\"movimientosxapi\")) {\n //let lastmsg = JSON.parse(msg) // enviado por el servidor como respuesta. Debe enviar el IdMovimiento que hemos sincronizado para procesarlo\n let lastmsg = msg\n \n node.warn(\"idmovimiento = \" + lastmsg.payload.IdMovimiento)\n \n msg = {}\n msg.tipo = \"sincro\";\n //node.send([null, lastmsg])\n if (lastmsg.statusCode === 200 && lastmsg.payload.Resultado) {\n //marcamos este record como enviado al API.\n msg.topic = `UPDATE movimientos_cerrados SET sincro = ${sincro} WHERE uuid = '${lastmsg.payload.IdMovimiento}'; `.trim(); // trim para eliminar espacios en blanco en los extremos\n msg.payload = \"\";\n return [{ payload: 1 }, msg];\n } \n else {\n node.warn(`Error al enviar UUID ${lastmsg.uuid}, código HTTP: ${lastmsg.statusCode}`);\n return [{ payload: 1 }, null];\n }\n}\nif (flow.get(\"movimientosxmqtt\")) {\n if (flow.get(\"ultimo_movimiento_ok\")) {\n msg.topic = `UPDATE movimientos_cerrados SET sincro = ${sincro} WHERE uuid = '${msg.payload.uuid}'`;\n msg.payload = \"\";\n return [{ payload: 1 }, msg];\n }\n else {\n node.warn(`Error al enviar UUID ${msg.payload.uuid} via MQTT`);\n return [{ payload: 1 }, null];\n }\n}\n\n\nreturn \n",
"outputs": 2,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 2690,
"y": 540,
"wires": [
[],
[
"732068c66d8e0a25",
"10affb7590fa95f1"
]
]
},
{
"id": "b4caaec341e453e0",
"type": "function",
"z": "744bdf3a0e08d754",
"g": "75dd57faa07f33b9",
"name": "send data",
"func": "// msg.payload viene del SELECT o del UPDATE previo (array de filas)\nif(msg.tipo == \"check\"){ //revisamos la BD para detectar los archivos que ya estan sincronizados\n const rows = msg.payload;\n const filesToDelete = [];\n const uuids = [];\n\n // 1) Recorremos todas las filas y recopilamos rutas de los archivos y uuids que han sido sincronizados\n rows.forEach(row => {\n if (row.sincro == 1) {\n // si tiene foto válida, la añadimos\n if (row.nombre_imagen_i && row.nombre_imagen_i !== \"Sin Matricula\") {\n filesToDelete.push(flow.get(\"path_images\") + row.nombre_imagen_i);\n }\n if (row.nombre_imagen_f && row.nombre_imagen_f !== \"Sin Matricula\") {\n filesToDelete.push(flow.get(\"path_images\") + row.nombre_imagen_f);\n }\n // si tiene firma válida, la añadimos\n if (row.nombre_firma && row.nombre_firma !== \"No Habilitada\") {\n filesToDelete.push(flow.get(\"path_firmas\") + row.nombre_firma);\n }\n // guardamos el uuid para luego borrar el registro en BD\n uuids.push(row.uuid);\n }\n });\n\n // 2) Construimos el DELETE SQL (solo si hay uuids)\n let deleteSQL = \"\";\n if (uuids.length > 0) {\n // Formamos una lista: 'uuid1','uuid2',...\n const inList = uuids.map(u => `'${u}'`).join(\",\"); // el map nos transforma el uuid agregandole dos ' simple y luego el join hace una cadena con los items separados por \",\"\n deleteSQL = `DELETE FROM movimientos_cerrados WHERE uuid IN (${inList});`; //este es el sql para borrar los records que contengan esos uuid \n\n\n // 3) Preparamos el mensaje para la SALIDA 1 (SQLite)\n const msgDelete = {}\n msgDelete.payload = \"\"\n msgDelete.topic = deleteSQL\n\n // 4) Preparamos el mensaje para la SALIDA 2 (Exec) para borra los archivos de imagenes y de firmas\n\n let msgExec = {};\n if (filesToDelete.length > 0) {\n\n // Genera la cadena de comando\n msgExec.payload = `rm -f ${filesToDelete.map(f => `\"${f}\"`).join(\" \")}`;\n return [msgDelete, msgExec, null, null];\n }\n else {\n return [msgDelete, null, null, null];\n }\n\n\n }\n}\nelse if (msg.tipo == \"api\") { // caso de envio de movimientos a cliente de nuestra API local\n return [null, null, msg, null];\n}\nelse if (msg.tipo == \"sincro\") { // caso de envio del movimiento actual, Ya fue enviado y sincronizado. No hay mas que hacer\n return [null, null, null];\n}\nreturn\n",
"outputs": 3,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 3120,
"y": 640,
"wires": [
[
"1bdaeab49913082e",
"6a2b1526103aa3b0"
],
[
"8778197111d2ab0d",
"b8029f9a251a232e"
],
[
"49bed5a59768fbef"
]
]
},
{
"id": "d19507c7bbe4bb32",
"type": "function",
"z": "744bdf3a0e08d754",
"g": "75dd57faa07f33b9",
"name": "UPDATE BD y Files",
"func": "const dias = Number(flow.get(\"dias_en_BD\")) || 2\nmsg.topic = `SELECT uuid, nombre_imagen_i, nombre_imagen_f, nombre_firma, sincro FROM movimientos_cerrados WHERE sincro = 1 AND ts_cierre <= UNIX_TIMESTAMP(NOW() - INTERVAL ${dias} DAY);`\nmsg.tipo = \"check\";\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 2710,
"y": 920,
"wires": [
[
"10affb7590fa95f1",
"65b220fc46d55c96"
]
]
},
{
"id": "e2fca1c2499902fa",
"type": "inject",
"z": "744bdf3a0e08d754",
"g": "75dd57faa07f33b9",
"name": "",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "1",
"payloadType": "num",
"x": 2510,
"y": 920,
"wires": [
[
"d19507c7bbe4bb32"
]
]
},
{
"id": "6a2b1526103aa3b0",
"type": "link out",
"z": "744bdf3a0e08d754",
"g": "75dd57faa07f33b9",
"name": "delete items",
"mode": "link",
"links": [
"aa870f8d73e4f504"
],
"x": 3345,
"y": 580,
"wires": []
},
{
"id": "26f04e07495817eb",
"type": "function",
"z": "744bdf3a0e08d754",
"g": "75dd57faa07f33b9",
"name": "read records",
"func": "/*\nCon esta funcion seleccionamos los records que van a ser enviados via el nodo http response\n*/\n\nlet tipo = msg.req.query.tipo || \"todos\";\n\n// Construimos SQL dinámico\nlet sql = \"\";\nswitch (tipo) {\n case \"no_sincro\":\n sql = \"SELECT * FROM movimientos_cerrados WHERE sincro = 0\";\n break;\n case \"sincro\":\n sql = \"SELECT * FROM movimientos_cerrados WHERE sincro = 1\";\n break;\n case \"todos\":\n default:\n sql = \"SELECT * FROM movimientos_cerrados\";\n break;\n}\nmsg.tipo = \"api\";\nmsg.topic = sql;\nreturn msg;\n",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 2730,
"y": 980,
"wires": [
[
"10affb7590fa95f1"
]
]
},
{
"id": "d5ce4cb6b3abd807",
"type": "http in",
"z": "744bdf3a0e08d754",
"g": "75dd57faa07f33b9",
"name": "API movimientos",
"url": "/api/movimientos",
"method": "get",
"upload": false,
"swaggerDoc": "",
"x": 2480,
"y": 980,
"wires": [
[
"26f04e07495817eb"
]
]
},
{
"id": "49bed5a59768fbef",
"type": "http response",
"z": "744bdf3a0e08d754",
"g": "75dd57faa07f33b9",
"name": "respuesta API",
"statusCode": "",
"headers": {},
"x": 3360,
"y": 740,
"wires": []
},
{
"id": "a58ecf0f246aff6a",
"type": "link out",
"z": "744bdf3a0e08d754",
"g": "75dd57faa07f33b9",
"name": "link out 1",
"mode": "link",
"links": [
"34bf8ec19b0e78a7"
],
"x": 2855,
"y": 520,
"wires": []
},
{
"id": "4e6d5242d6531d79",
"type": "comment",
"z": "744bdf3a0e08d754",
"g": "75dd57faa07f33b9",
"name": "Envio de movimiento y actualizacion BD",
"info": "Este grupo de nodos efectua fundamentalmente tres funciones:\n1. Envia el movimiento al servidor al salir el camion de la bascula cambiando el bit de sincronizacion\n de ese record en la BD en caso de exito en el envio. Eso lo hacen los nodos: \"envia API\", \"Enviar a API\", \"UPDATE SINCRO\" y \"SQLITE_TOTEM\",\n y los nodos MQTT involucrados con rx ACK.\n2. Diariamente a una hora determinada extrae un array de la BD con los records que tienen en la BD mas de una cierta cantidad\n de dias (configurable) y los elimina de la BD si estos ya han sido sincronizados. Adicionalmente borra tambien \n del sistema de archivos las imagenes correspondientes a las matriculas y firmas asociadas a dichos records.\n Esta funcion la hacen los nodos \"UPDATE BD y Files\", \"SQLITE_TOTEM\", \"send data\" y \"delete files\".\n3. Sirve un API para proporcionar a un cliente http un json con los records que ya han sido sincronizados pero que todavia\n permanecen en BD, un json con los records no sincronizados o bien un json con todos los records en la tabla de movimientos.\n http://direccion-deltotem:1880/api/movimientos?tipo=no_sincro\n http://direccion-deltotem:1880/api/movimientos?tipo=sincro\n http://direccion-deltotem:1880/api/movimientos?tipo=todos\n Los nodos que realizan esta funcion son \"API movimientos\", \"read records\", \"SQLITE_TOTEM\", \"send data\" y \"respuesta API\"\n ",
"x": 2190,
"y": 980,
"wires": []
},
{
"id": "732068c66d8e0a25",
"type": "debug",
"z": "744bdf3a0e08d754",
"g": "75dd57faa07f33b9",
"name": "debug 23",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 2920,
"y": 600,
"wires": []
},
{
"id": "32e555ac8f46a0f1",
"type": "mqtt in",
"z": "744bdf3a0e08d754",
"g": "e716da8ff5c458c9",
"name": "ack",
"topic": "totemautonomo/movimientos/movimiento_ok",
"qos": "1",
"datatype": "auto-detect",
"broker": "3966bb218232f6b3",
"nl": false,
"rap": true,
"rh": 0,
"inputs": 0,
"x": 2190,
"y": 800,
"wires": [
[
"6b1ae2b4cdb257fb",
"aba034491220fa28"
]
]
},
{
"id": "e36bb58515b89053",
"type": "mqtt out",
"z": "744bdf3a0e08d754",
"g": "e716da8ff5c458c9",
"name": "send data",
"topic": "totemautonomo/movimientos/movimiento",
"qos": "2",
"retain": "",
"respTopic": "",
"contentType": "",
"userProps": "",
"correl": "",
"expiry": "",
"broker": "3966bb218232f6b3",
"x": 2340,
"y": 640,
"wires": []
},
{
"id": "6b1ae2b4cdb257fb",
"type": "function",
"z": "744bdf3a0e08d754",
"g": "e716da8ff5c458c9",
"name": "rx ACK-1",
"func": "/*\nAqui recibimos el ack enviado por el programa del servidor que recibe y almacena los movimientos. Este programa (el del servidor) \ndebe enviar el topic:totemautonomo/movimientos/movimiento_ok ; payload: true / false ; uuid: el uuid del movimiento\n*/\n\nif (msg.payload.uuid === flow.get(\"ultimo_movimiento_id\") && msg.payload.movimiento_ok && !flow.get(\"ack_old_movimientos\")){\n // evaluamos el \"ack_old_movimientos\" para estar seguros que el ack recibido es para este nodo y no para un nodo\n // semejante (rx ACK) que tenemos en el grupo que se encarga de sincronizar los movimientos antiguos que no han podido ser \n // sincronizados en su momento.\n flow.set(\"ultimo_movimiento_ok\", true) // indicamos al nodo de UPDATE SINCRO que el ultimo envio ha sido recibido\n flow.set(\"ultimo_movimiento_id\", \"\")\n msg.reset = true // esto es para parar el timeout counter\n return msg\n}\n \nreturn ;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 2340,
"y": 800,
"wires": [
[
"d47200e5f67c772a",
"ac94ab2b132fab52"
]
]
},
{
"id": "7ef59a8ac4d8a7f3",
"type": "debug",
"z": "744bdf3a0e08d754",
"g": "e716da8ff5c458c9",
"name": "tx mqtt-1",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 2360,
"y": 700,
"wires": []
},
{
"id": "aba034491220fa28",
"type": "debug",
"z": "744bdf3a0e08d754",
"g": "e716da8ff5c458c9",
"name": "rx ack-1",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 2360,
"y": 840,
"wires": []
},
{
"id": "ac94ab2b132fab52",
"type": "trigger",
"z": "744bdf3a0e08d754",
"g": "e716da8ff5c458c9",
"name": "timeout",
"op1": "",
"op2": "1",
"op1type": "nul",
"op2type": "num",
"duration": "5",
"extend": false,
"overrideDelay": false,
"units": "s",
"reset": "",
"bytopic": "all",
"topic": "topic",
"outputs": 1,
"x": 2360,
"y": 740,
"wires": [
[
"774b2334aae0188c",
"d47200e5f67c772a",
"9a5e603ea67d2991"
]
]
},
{
"id": "774b2334aae0188c",
"type": "change",
"z": "744bdf3a0e08d754",
"g": "e716da8ff5c458c9",
"name": "init uuid",
"rules": [
{
"t": "set",
"p": "ultimo_movimiento_id",
"pt": "flow",
"to": "",
"tot": "str"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 2540,
"y": 740,
"wires": [
[]
]
},
{
"id": "9a5e603ea67d2991",
"type": "debug",
"z": "744bdf3a0e08d754",
"g": "e716da8ff5c458c9",
"name": "timeout",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 2520,
"y": 780,
"wires": []
},
{
"id": "9606dbbee4d79399",
"type": "comment",
"z": "744bdf3a0e08d754",
"g": "e716da8ff5c458c9",
"name": "",
"info": "Este grupo se encarga de enviar via mqtt el json correspondiente al contenido del movimiento\ndel vehiculo en la bascula. \nAdicionalmente espera un ACK con un timeout de 5 segundos. Este timeout se consigue con el uso de un nodo\ntrigger configurado en 5 segundos. Este nodo al cabo de 5 segundos envia un mensaje {payload: 1} al nodo\nde procesamiento para que continue el proceso con el proximo record.\nEn caso de recibir el acknowledge por parte del servidor, se envia un mensaje de reset al nodo trigger\ncon la finalidad de parar el contador.\nCuando se recibe el ack se borra tambien la referencia con el ultimo uuid para evitar recepciones de ack falsas\n\n\nNota sobre el funcionamiento del QoS.\nEl factor QoS de MQTT define la forma en que se gestiona el envio de una publicacion hacia el broker. (cliente - broker)\nMQTT define 3 niveles de QoS (Quality of Service) que determinan la garantía de entrega del mensaje entre el cliente y el broker:\n\n1. QoS 0 \"At most once\" (como máximo una vez)\nNo se garantiza la entrega. El mensaje se envía una única vez y no hay reintento ni confirmación.\nNo hay acuse de recibo (ACK).\nUsos comunes: datos de sensores donde la pérdida ocasional no importa (ej. temperatura cada 60 segundos).\nImplica solo un paquete TCP: PUBLISH \n\n2. QoS 1 \"At least once\" (al menos una vez)\nEl mensaje se entrega una o más veces. Se garantiza que llegará al broker, pero puede haber duplicados.\nSe requiere acuse de recibo: el cliente envía PUBLISH, el broker responde con PUBACK una vez recibido.\nEl cliente debe reintentar si no recibe el PUBACK. (Aqui es donde puede haber duplicados del mensaje)\nImplica al menos dos paquetes TCP: PUBLISH → PUBACK\n\n3. QoS 2 \"Exactly once\" (exactamente una vez)\nEl más seguro: garantiza que el mensaje se entrega una sola vez y sin duplicados.\nUsa un handshake de 4 pasos entre cliente y broker:\n 1. Cliente → Broker: PUBLISH (mensaje)\n 2. Broker → Cliente: PUBREC (recibido)\n 3. Cliente → Broker: PUBREL (libera) (el mensaje puede ser entregado al usuario)\n 4. Broker → Cliente: PUBCOMP (completado) (ciclo completado)\nImplica cuatro paquetes TCP para confirmar entrega precisa\n\n¿Qué pasa con TCP?\nMQTT siempre usa TCP como transporte (puerto 1883 por defecto o 8883 para MQTT sobre TLS), lo que significa que:\n - Hay verificación de integridad de paquetes.\n - Se garantiza el orden y la entrega de paquetes TCP.\n - Pero TCP por sí solo no garantiza que la aplicación MQTT haya recibido o procesado el mensaje.\n - Por eso, MQTT usa sus propios niveles de QoS por encima de TCP, para controlar la lógica de entrega a nivel de aplicación.",
"x": 2160,
"y": 720,
"wires": []
},
{
"id": "10affb7590fa95f1",
"type": "mysql",
"z": "744bdf3a0e08d754",
"g": "75dd57faa07f33b9",
"mydb": "1c8613764b7f685e",
"name": "Actualiza tabla",
"x": 2920,
"y": 640,
"wires": [
[
"fd9524c0921c0990",
"b4caaec341e453e0"
]
]
},
{
"id": "ee2f281460418d2a",
"type": "function",
"z": "744bdf3a0e08d754",
"d": true,
"g": "75dd57faa07f33b9",
"name": "UPDATE SINCRO",
"func": "\n\nlet sincro = 1\nif (flow.get(\"movimientosxapi\")) {\n let lastmsg = JSON.parse(msg.payload) // enviado por el servidor como respuesta. Debe enviar el IdMovimiento que hemos sincronizado para procesarlo\n msg = {}\n msg.tipo = \"sincro\";\n if (lastmsg.statusCode === 200) {\n //marcamos este record como enviado al API. Ponemos 1 ya que en sqlite no existe el tipo booleano true o false \n msg.topic = `UPDATE movimientos_cerrados SET sincro = ${sincro} WHERE uuid = '${lastmsg.IdMovimiento}'; `.trim(); // trim para eliminar espacios en blanco en los extremos\n msg.payload = \"\";\n return [{ payload: 1 }, msg];\n } \n else {\n node.warn(`Error al enviar UUID ${lastmsg.uuid}, código HTTP: ${msg.msg.statusCode}`);\n return [{ payload: 1 }, null];\n }\n}\nif (flow.get(\"movimientosxmqtt\")) {\n if (flow.get(\"ultimo_movimiento_ok\")) {\n msg.topic = `UPDATE movimientos_cerrados SET sincro = ${sincro} WHERE uuid = '${msg.payload.uuid}'`;\n msg.payload = \"\";\n return [{ payload: 1 }, msg];\n }\n else {\n node.warn(`Error al enviar UUID ${msg.payload.uuid} via MQTT`);\n return [{ payload: 1 }, null];\n }\n}\n\n\nreturn \n",
"outputs": 2,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 2990,
"y": 500,
"wires": [
[],
[]
]
},
{
"id": "6455539b9fac2063",
"type": "https-node",
"z": "744bdf3a0e08d754",
"g": "75dd57faa07f33b9",
"name": "Enviar a API",
"method": "POST",
"ret": "txt",
"paytoqs": "ignore",
"url": "",
"tls": "",
"persist": false,
"proxy": "",
"authType": "",
"senderr": false,
"x": 2350,
"y": 540,
"wires": [
[
"7ff54b99b5b933b2"
]
]
},
{
"id": "7ff54b99b5b933b2",
"type": "json",
"z": "744bdf3a0e08d754",
"g": "75dd57faa07f33b9",
"name": "",
"property": "payload",
"action": "",
"pretty": false,
"x": 2510,
"y": 540,
"wires": [
[
"d47200e5f67c772a",
"e192c564f2050e94"
]
]
},
{
"id": "08bf0c9897d6e815",
"type": "debug",
"z": "744bdf3a0e08d754",
"g": "75dd57faa07f33b9",
"name": "debug 41",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 2340,
"y": 500,
"wires": []
},
{
"id": "65b220fc46d55c96",
"type": "debug",
"z": "744bdf3a0e08d754",
"g": "75dd57faa07f33b9",
"name": "debug 43",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 2940,
"y": 820,
"wires": []
},
{
"id": "1279bc86f850f792",
"type": "exec",
"z": "744bdf3a0e08d754",
"g": "800484a81d9dc5ee",
"command": " ./remove-add-host.sh ",
"addpay": "payload",
"append": "",
"useSpawn": "false",
"timer": "",
"winHide": false,
"oldrc": false,
"name": "change hosts",
"x": 1330,
"y": 1120,
"wires": [
[
"c18ea33c86d877db"
],
[],
[]
]
},
{
"id": "c18ea33c86d877db",
"type": "debug",
"z": "744bdf3a0e08d754",
"g": "800484a81d9dc5ee",
"name": "",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 1490,
"y": 1060,
"wires": []
},
{
"id": "9b375376ea8f2a66",
"type": "inject",
"z": "744bdf3a0e08d754",
"g": "800484a81d9dc5ee",
"name": "",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": "1.5",
"topic": "",
"payload": "192.168.11.67 raspeso.com",
"payloadType": "str",
"x": 1090,
"y": 1040,
"wires": [
[
"1279bc86f850f792"
]
]
},
{
"id": "844805b92ed8fc5c",
"type": "exec",
"z": "744bdf3a0e08d754",
"g": "800484a81d9dc5ee",
"command": "",
"addpay": "payload",
"append": "",
"useSpawn": "false",
"timer": "",
"winHide": false,
"oldrc": false,
"name": "change IP",
"x": 1320,
"y": 1180,
"wires": [
[
"b1eb9bccca87f0f9"
],
[],
[]
]
},
{
"id": "b1eb9bccca87f0f9",
"type": "debug",
"z": "744bdf3a0e08d754",
"g": "800484a81d9dc5ee",
"name": "change IP",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 1500,
"y": 1160,
"wires": []
},
{
"id": "29b2dd9b01235f99",
"type": "file in",
"z": "744bdf3a0e08d754",
"g": "800484a81d9dc5ee",
"name": "read-file",
"filename": "/home/trypton/.node-red/projects/config_comun.txt",
"filenameType": "str",
"format": "utf8",
"chunk": false,
"sendError": false,
"encoding": "none",
"allProps": false,
"x": 860,
"y": 1180,
"wires": [
[
"8435634f3b8cf017"
]
]
},
{
"id": "0471bc0a0beb44fd",
"type": "file in",
"z": "744bdf3a0e08d754",
"g": "800484a81d9dc5ee",
"name": "read-file",
"filename": "/home/trypton/.node-red/projects/config_supervisor.txt",
"filenameType": "str",
"format": "utf8",
"chunk": false,
"sendError": false,
"encoding": "none",
"allProps": false,
"x": 860,
"y": 1120,
"wires": [
[
"f372e14a6921bc3a"
]
]
},
{
"id": "c5a3e80cc446b610",
"type": "link in",
"z": "744bdf3a0e08d754",
"g": "800484a81d9dc5ee",
"name": "read_config_in",
"links": [
"17547c6cf2cc8bdf"
],
"x": 685,
"y": 1140,
"wires": [
[
"0471bc0a0beb44fd",
"29b2dd9b01235f99"
]
]
},
{
"id": "323c2ee052338182",
"type": "inject",
"z": "744bdf3a0e08d754",
"g": "800484a81d9dc5ee",
"name": "",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": true,
"onceDelay": "2",
"topic": "",
"payload": "1",
"payloadType": "num",
"x": 710,
"y": 1080,
"wires": [
[
"0471bc0a0beb44fd",
"29b2dd9b01235f99"
]
]
},
{
"id": "a8218646952839ba",
"type": "function",
"z": "744bdf3a0e08d754",
"g": "800484a81d9dc5ee",
"name": "config comun",
"func": "var config = JSON.parse(msg.payload);\nmsg ={}; // clear msg object\nvar read;\n\nflow.set(\"vehiculo_activo_flag\", false);\nflow.set(\"last_QR\", \"\");\n\n//read = config.peso;\n//flow.set(\"peso\", read); // crea variable para recibir peso del supervisor\nread = config.peso_estable_flag;\nflow.set(\"peso_estable_flag\", read);\nread = config.peso_estable; // valor del peso estabilizado\nflow.set(\"peso_estable\", read);\nread = config.fin_pesada_flag;\nflow.set(\"fin_pesada_flag\", read);\nread = config.salida_vehiculo_flag;\nflow.set(\"salida_vehiculo_flag\", read);\nread = config.vehiculo_presente_flag;\nflow.set(\"vehiculo_presente_flag\", read);\nread = config.remover_firma;\nflow.set(\"remover_firma\", read);\n\nread = config.matricula_ok_flag;\nflow.set(\"matricula_ok_flag\", read);\nread = config.QR_ok_flag;\nflow.set(\"QR_ok_flag\", read);\nread = config.RFID\nflow.set(\"RFID\", read);\nread = config.RFID_ok_flag;\nflow.set(\"RFID_ok_flag\", read);\nread = config.QR_flag;\nflow.set(\"QR_flag\", read);\n\n\nread = config.auth_pin;\nflow.set(\"auth_pin\", read)\nread = config.pin_timeout; // timeout de espera del pin\nflow.set(\"pin_timeout\", read)\nread = config.mante_timeout; // timeout de la pantalla de mantenimiento\nflow.set(\"mante_timeout\", read)\n\nread = config.sw_bkgnd_on // color del background ON de los botones de seleccion de parametro \nflow.set(\"sw_bkgnd_on\", read);\nread = config.sw_bkgnd_off // color del background OFF de los botones de seleccion de parametro \nflow.set(\"sw_bkgnd_off\", read);\nread = config.titulo_param_color // color del nombre de los parametros\nflow.set(\"titulo_param_color\", read);\nflow.set(\"value_param_color\", read);\nread = config.namefixedparams_color // color del nombre de los parametros fijos (ip, serial, puertos remotos)\nflow.set(\"namefixedparams_color\", read);\nread = config.semaforo_green_color // color del semaforo en verde\nflow.set(\"semaforo_green_color\", read);\nread = config.semaforo_red_color // color del semaforo en rojo\nflow.set(\"semaforo_red_color\", read);\nread = config.titulo_matricula_color // color del titulo de la matricula y del ID\nflow.set(\"titulo_matricula_color\", read)\nread = config.valor_matricula_color // color del titulo de la matricula y del ID\nflow.set(\"valor_matricula_color\", read)\n\nread = config.totemid_max // maximo numero de identificacion asignado al totem\nflow.set(\"totemid_max\", read);\nread = config.vial_max // maximo numero de viales admitidos para asignar al dispositico\nflow.set(\"vial_max\", read);\n\n\n\n\nreturn",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1110,
"y": 1180,
"wires": [
[]
]
},
{
"id": "8435634f3b8cf017",
"type": "debug",
"z": "744bdf3a0e08d754",
"g": "800484a81d9dc5ee",
"name": "debug 11",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 1040,
"y": 1080,
"wires": []
},
{
"id": "f372e14a6921bc3a",
"type": "function",
"z": "744bdf3a0e08d754",
"g": "800484a81d9dc5ee",
"name": "config edit",
"func": "const config = JSON.parse(msg.payload);\nmsg ={}; // clear msg object\nlet read;\nlet config_general = []\nlet config_tiempos = []\nlet config_mensajes = []\nlet config_sensor = []\n//let config_camara = []\nlet config_conex = []\n//let config_tipos = []\n\nconfig.general = toObject(config.general);\n//config.camara = toObject(config.camara);\nconfig.conexion = toObject(config.conexion);\nconfig.sensores = toObject(config.sensores);\nconfig.mensajes = toObject(config.mensajes);\nconfig.tiempos = toObject(config.tiempos);\n\n\n\n/*\n// Aseguramos que cada tipo esté como array vacío si no tiene nada\nfor (let i = 1; i <= 6; i++) {\n if (!Array.isArray(tiposOriginales[i])) {\n tiposOriginales[i] = [];\n }\n}\nflow.set(\"tiposDatosGlobal\", tiposOriginales);\n\nconfig.tipos = tiposOriginales;\n*/\n\nread = config.general.formatoPais; // peso minimo para utilizado para indicar cuando hay vehiculo en la balanza o no\nconfig_general.push({ \"parametro\": \"formatoPais\", \"valor\": read })\nflow.set(\"formatoPais\", read);\n\n//---------------------------------------------------------------------------------------------\nread = config.tiempos.timer1;\nconfig_tiempos.push({ \"parametro\": \"timer1\", \"valor\": read })\nflow.set(\"timer1\", read);\nread = config.general.audio_level;\nconfig_general.push({ \"parametro\": \"audio_level\", \"valor\": read })\nflow.set(\"audio_level\", read);\n\n//-------------------------------------------------------\nread = config.general.mante_code_RFID; //codigo RFID para pasar a pantalla de mantenimiento\nconfig_general.push({ \"parametro\": \"mante_code_RFID\", \"valor\": read })\nflow.set(\"mante_code_RFID\", read)\nread = config.general.mante_code_QR; //codigo QR para pasar a pantalla de mantenimiento\nconfig_general.push({ \"parametro\": \"mante_code_QR\", \"valor\": read })\nflow.set(\"mante_code_QR\", read)\n\n//------------------------------- MENSAJES -------------------------\nread = config.tiempos.tiempo_mensaje;\nconfig_tiempos.push({ \"parametro\": \"tiempo_mensaje\", \"valor\": read })\nflow.set(\"tiempo_mensaje\", read);\nread = config.tiempos.relay2_pulse // duración del pulso generado en el relay 2 al terminar el pesaje\nconfig_tiempos.push({ \"parametro\": \"relay2_pulse\", \"valor\": read })\nflow.set(\"relay2_pulse\", read);\nread = config.mensajes.mensaje2\nconfig_mensajes.push({ \"parametro\": \"mensaje2\", \"valor\": read })\nflow.set(\"mensaje2\", read);\nread = config.mensajes.mensaje1\nconfig_mensajes.push({ \"parametro\": \"mensaje1\", \"valor\": read })\nflow.set(\"mensaje1\", read);\nread = config.general.init_screen\nconfig_general.push({ \"parametro\": \"init_screen\", \"valor\": read })\nflow.set(\"init_screen\", read);\n\n\n\n//-------------------------------------------------------\nread = config.general.totem_serial // serial asignado a este dispositivo\nconfig_general.push({ \"parametro\": \"totem_serial\", \"valor\": read })\nflow.set(\"totem_serial\", read);\n\nread = config.general.dias_en_BD // numero de dias maximo de un record en base de datos\nconfig_general.push({ \"parametro\": \"dias_en_BD\", \"valor\": read })\nflow.set(\"dias_en_BD\", read);\n\n\n//----------------------------------------- Textos Predefinidos -------------------------------------------------------------------------\nread = config.mensajes.men_pesaje_listo;\nconfig_mensajes.push({ \"parametro\": \"men_pesaje_listo\", \"valor\": read })\nflow.set(\"men_pesaje_listo\", read);\nread = config.mensajes.men_peso_estable;\nconfig_mensajes.push({ \"parametro\": \"men_peso_estable\", \"valor\": read })\nflow.set(\"men_peso_estable\", read);\nread = config.mensajes.men_identificacion;\nconfig_mensajes.push({ \"parametro\": \"men_identificacion\", \"valor\": read })\nflow.set(\"men_identificacion\", read);\nread = config.mensajes.men_salida;\nconfig_mensajes.push({ \"parametro\": \"men_salida\", \"valor\": read })\nflow.set(\"men_salida\", read);\nread = config.mensajes.id_titulo_matricula;\nconfig_mensajes.push({ \"parametro\": \"id_titulo_matricula\", \"valor\": read })\nflow.set(\"id_titulo_matricula\", read);\nread = config.mensajes.id_titulo_id;\nconfig_mensajes.push({ \"parametro\": \"id_titulo_id\", \"valor\": read })\nflow.set(\"id_titulo_id\", read);\nread = config.mensajes.id_leido;\nconfig_mensajes.push({ \"parametro\": \"id_leido\", \"valor\": read })\nflow.set(\"id_leido\", read);\nread = config.mensajes.id_no_leido;\nconfig_mensajes.push({ \"parametro\": \"id_no_leido\", \"valor\": read })\nflow.set(\"id_no_leido\", read);\n\n//===================================================CONEXION / CAMARA / SENSOR =====================================================\n//====================================================================================================================================\n\n\n//-----------------------------Gestion de IP fija / IP DHCP------------------------------------------------------\nvar ipdevice_flag = false // variables temporales para saber si hay cambios en las ip\nvar iprouter_flag = false\nread = config.conexion.ipdevice // direccion IP del dispositivo\nconfig_conex.push({ \"parametro\": \"ipdevice\", \"valor\": read })\nif (flow.get(\"ipdevice\") != read) {\n flow.set(\"ipdevice\", read);\n ipdevice_flag = true;\n}\nread = config.conexion.iprouter // direccion IP del router\nconfig_conex.push({ \"parametro\": \"iprouter\", \"valor\": read })\nif (flow.get(\"iprouter\") != read) {\n flow.set(\"iprouter\", read);\n iprouter_flag = true;\n}\nread = config.conexion.dhcp_flag // flag del DHCP\nconfig_conex.push({ \"parametro\": \"dhcp_flag\", \"valor\": read })\nif (flow.get(\"dhcp_flag\") != read) {\n flow.set(\"dhcp_flag\", read); // el dhcp_flag ha cambiado\n if (flow.get(\"dhcp_flag\")) {\n node.send([null, { payload: \". /home/trypton/scripts/enable_dhcp.sh\" }])\n }\n else {\n node.send([null, { payload: \". /home/trypton/scripts/enable_ipstatic.sh \" + flow.get(\"ipdevice\") + \"/24 \" + flow.get(\"iprouter\") }])\n }\n}\nelse { // el dhcp_flag no ha cambiado\n if (!flow.get(\"dhcp_flag\")) {\n if (ipdevice_flag || iprouter_flag) {\n node.send([null, { payload: \". /home/trypton/scripts/enable_ipstatic.sh \" + flow.get(\"ipdevice\") + \"/24 \" + flow.get(\"iprouter\") }])\n }\n }\n else {\n node.send([null, { payload: \". /home/trypton/scripts/enable_dhcp.sh\" }])\n }\n}\nflow.set(\"dhcpname_on\", \"IP Fija\") // texto que sale en el boton de la pantalla de mantenimiento\nflow.set(\"dhcpname_off\", \"DHCP\")\n\n//----------------------------------- ENVIO DE MOVIMIENTOS --------------------------------------------------------------------------------------\nread = config.conexion.movimientosxmqtt;\nconfig_conex.push({ \"parametro\": \"movimientosxmqtt\", \"valor\": read })\nflow.set(\"movimientosxmqtt\", read); // Modo de envio de movimientos\n\nread = config.conexion.movimientosxapi;\nconfig_conex.push({ \"parametro\": \"movimientosxapi\", \"valor\": read })\nflow.set(\"movimientosxapi\", read); // Modo de envio de movimientos\n\nread = config.conexion.path_images;\nconfig_conex.push({ \"parametro\": \"path_images\", \"valor\": read })\nflow.set(\"path_images\", read); // raiz de la carpeta de guardar imagenes\n\nread = config.conexion.path_firmas;\nconfig_conex.push({ \"parametro\": \"path_firmas\", \"valor\": read })\nflow.set(\"path_firmas\", read); // raiz de la carpeta de guardar imagenes de las firmas\n\n//-------------------------------------------------- URL de la API de URBASER(Trypton) ------------------------------------------------------------------------------\nread = config.conexion.url_API;\nconfig_conex.push({ \"parametro\": \"url_API\", \"valor\": read })\nflow.set(\"url_API\", read); // url de llamado a la api para leer tablas de bd\nflow.set(\"semaforo\", \"green\")\n\nread = config.conexion.tokenapi;\nconfig_conex.push({ \"parametro\": \"tokenapi\", \"valor\": read })\nflow.set(\"tokenapi\", read); // token del totem utilizado durante la lectura de tablas de la bd\n\nread = config.conexion.IdCentroProveedor;\nconfig_conex.push({ \"parametro\": \"IdCentroProveedor\", \"valor\": read })\nflow.set(\"IdCentroProveedor\", read); // token del proveedor utilizado durante la lectura de tablas de la bd y el envio de movimientos\n\n//----------------------------------- DEFINICION DEL BROKER DE MQTT --------------------------------------------------------------------------------------\n/*\nread = config.conexion.broker_url;\nconfig_conex.push({ \"parametro\": \"broker_url\", \"valor\": read })\nflow.set(\"broker_url\", read); // direccion del broker\n\nread = config.conexion.broker_port;\nconfig_conex.push({ \"parametro\": \"broker_port\", \"valor\": read })\nflow.set(\"broker_port\", read); // puerto del broker\n\nread = config.conexion.broker_username;\nconfig_conex.push({ \"parametro\": \"broker_username\", \"valor\": read })\nflow.set(\"broker_username\", read); // username del broker\n\nread = config.conexion.broker_pass;\nconfig_conex.push({ \"parametro\": \"broker_pass\", \"valor\": read })\nflow.set(\"broker_pass\", read); // password del broker\n*/\n\n\n//------------------------------------Lector de QR / RF----------------------------------------------------------\nread = config.sensores.tipo_de_lector\nconfig_sensor.push({ \"parametro\": \"tipo_de_lector\", \"valor\": read })\nflow.set(\"tipo_de_lector\", read); //tipo de lector QR / RF\nread = config.sensores.rfid_invertido\nconfig_sensor.push({ \"parametro\": \"rfid_invertido\", \"valor\": read })\nflow.set(\"rfid_invertido\", read);\nflow.set(\"lector_activo\", true) // activamos el lector\nflow.set(\"ID_enviado\", false) // variable para controlar un solo envio de RFID / QR\n\n\n//---------------------------------------Totems y Camara------------------------------------------------------------------------------\nread = config.conexion.cantidad_viales;\nconfig_conex.push({ \"parametro\": \"cantidad_viales\", \"valor\": read })\nflow.set(\"cantidad_viales\", read);\n\n\n\n//---------------------------------------Lectura de Matricula-----------------------------------------------------------------------\nread = config.tiempos.time_max_2_bascula\nconfig_tiempos.push({ \"parametro\": \"time_max_2_bascula\", \"valor\": read })\nflow.set(\"time_max_2_bascula\", read);\nread = config.tiempos.wait_time_to_send_plate\nconfig_tiempos.push({ \"parametro\": \"wait_time_to_send_plate\", \"valor\": read })\nflow.set(\"wait_time_to_send_plate\", read);\nread = config.tiempos.max_wait_time_to_send_plate\nconfig_tiempos.push({ \"parametro\": \"max_wait_time_to_send_plate\", \"valor\": read })\nflow.set(\"max_wait_time_to_send_plate\", read);\nread = config.tiempos.max_time_bascula_sensor\nconfig_tiempos.push({ \"parametro\": \"max_time_bascula_sensor\", \"valor\": read })\nflow.set(\"max_time_bascula_sensor\", read);\nread = config.tiempos.time_to_snapshot\nconfig_tiempos.push({ \"parametro\": \"time_to_snapshot\", \"valor\": read })\nflow.set(\"time_to_snapshot\", read);\nflow.set(\"plate_sent\", false);\nflow.set(\"anpr0_flag\", false);\n\n\n//---------------------------------------- Flags edicion de matricula ---------------------------------------------------------------------------------------------\nflow.set(\"badplate\", false) // inicializa flag de solicitu de modificación de matricula\nflow.set(\"prechar\", \"*\") // inicializa variable con caracter de prefijo para el $watch del nodo display de edicion de matricula\n\nflow.set(\"config_general\", config_general)\nflow.set(\"config_tiempos\", config_tiempos)\nflow.set(\"config_mensajes\", config_mensajes)\nflow.set(\"config_conex\", config_conex)\n//flow.set(\"config_camara\", config_camara)\nflow.set(\"config_sensor\", config_sensor)\n\nreturn;\n\n\n//*********************************************************** FUNCIONES **************************************************/\nfunction toObject(array) {\n \n const obj = {};\n array.forEach(el => {\n obj[el.parametro] = el.valor;\n\n });\n return obj;\n}\n",
"outputs": 2,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1110,
"y": 1120,
"wires": [
[
"cc890d832924b5d5",
"1279bc86f850f792"
],
[
"844805b92ed8fc5c"
]
]
},
{
"id": "cc890d832924b5d5",
"type": "debug",
"z": "744bdf3a0e08d754",
"g": "800484a81d9dc5ee",
"name": "debug 28",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 1260,
"y": 1040,
"wires": []
},
{
"id": "73752ba2ae415953",
"type": "debug",
"z": "744bdf3a0e08d754",
"g": "8aa65df7a757f075",
"name": "debug 2",
"active": false,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 1080,
"y": 1360,
"wires": []
},
{
"id": "182712df6587926f",
"type": "json",
"z": "744bdf3a0e08d754",
"g": "8aa65df7a757f075",
"name": "",
"property": "payload",
"action": "",
"pretty": false,
"x": 830,
"y": 1440,
"wires": [
[
"73752ba2ae415953",
"de78dc5fb92df251"
]
]
},
{
"id": "de78dc5fb92df251",
"type": "function",
"z": "744bdf3a0e08d754",
"g": "8aa65df7a757f075",
"name": "comandos \\n via \\n websocket",
"func": "if (msg.payload.device == \"supervisor\") {\n msg.numero_vial = msg.payload.numero_vial\n msg.totem_id = msg.payload.totem_id\n \n let msg_ack = {payload:{ack: true}}\n switch (msg.payload.accion) {\n case \"check\": // hace check de la matricula en la BD.\n msg.matricula = msg.payload.matricula\n \n /*Solicitamos a la BD si existe la matricula en la tabla Matriculas_Identificadores. Si esa matricula solo esta una vez\n entonces recogemos el id correspondiente y con ese id buscamosla descripcion en la tabla \"Identificadores\"\n Si el campo descripcion contiene una coma \",\" entonces quiere decir que no hay menu y que esa entrada corresponde a un QR / RFID\n por lo tanto en el mensaje que enviamos al totem indicamos que hay que mostrar una identificacion.\n en caso que no este registrada la matricula, la BD nos entrega un mensaje con el parametro \"registrada\" = 0.\n */\n msg.topic = `\n WITH input AS (SELECT TRIM('${msg.payload.matricula}') AS MatriculaInput)\n SELECT\n CASE WHEN ma.matricula IS NOT NULL THEN 1 ELSE 0 END AS registrada,\n CAST(EXISTS(SELECT 1 FROM movimientos m JOIN input i ON UPPER(TRIM(m.matricula)) = UPPER(i.MatriculaInput) LIMIT 1) AS INTEGER) AS transito,\n ma.tara, ma.pma, (SELECT CASE WHEN COUNT(*)=1 THEN MAX(idf.descripcion) ELSE NULL END FROM Matriculas_Identificadores mi JOIN input i ON UPPER(TRIM(mi.matricula)) = UPPER(i.MatriculaInput) LEFT JOIN Identificadores idf ON idf.id = mi.id) AS descripcion,\n (SELECT COUNT(*) FROM Matriculas_Identificadores mi JOIN input i ON UPPER(TRIM(mi.matricula)) = UPPER(i.MatriculaInput)) AS repeticion_matricula\n FROM input i\n LEFT JOIN Matriculas ma ON UPPER(TRIM(ma.matricula)) = UPPER(i.MatriculaInput)\n LIMIT 1;\n `;\n msg.payload = \"\"\n node.send([msg, null, null, null, null, null, null])\n break;\n\n case \"qr/rfid\":\n msg.tipo = msg.payload.tipo\n msg.id = msg.payload.id\n msg.payload = msg.payload.id\n node.send([null, msg, null, null, null, null, null])\n break;\n\n case \"movimiento\": // tenemos que enviar un ACK para asegurar que el supervisor recibio el comando\n let new_register = msg.payload.movimiento\n flow.set(\"new_register\", new_register) // guardamos ultimo registro en memoria local para uso durante el analisis\n let imagen_matricula = new_register.imagen\n let nombre_imagen = new_register.nombre_imagen\n let imagen_firma = new_register.firma\n let nombre_firma = new_register.nombre_firma\n let destareautomatico = new_register.destareautomatico\n let tara = new_register.tara\n if (tara > 0){\n //enviamos este movimiento como el cierre\n msg = {}\n msg.topic = \"movimiento_tarado\"\n node.send([null, null, null, msg, null, null, null])\n\n }\n else {\n if (msg.payload.tipomovimiento == \"inicial\") {\n new_register.tipomovimiento = \"inicial\" // indicamos que este registro corresponde a un movimiento de inicio\n //guardamos el movimiento en la tabla de movimientos para que posteriormente el supervisor los analice\n let msg_plate = { payload: imagen_matricula, filename: flow.get(\"path_images\") + nombre_imagen } // guardamos archivo con imagen en disco local\n if (nombre_firma != \"No Habilitada\"){\n let msg_firma = { payload: imagen_firma, filename: flow.get(\"path_firmas\") + nombre_firma } // guardamos archivo con imagen en disco local\n node.send([null, null, null, null, null, msg_firma, null])\n }\n let registro = `INSERT INTO movimientos (uuid, horaentrada, matricula, matriculaeditada, nombre_imagen, pesoestable, qr, rfid, vial, totem, horasalida,\n sentido, modo, botonvalidacion, nombre_firma, firmaobligatoria, timestamp, id_seleccionado, tipo) \n VALUES ('${new_register.uuid}', '${new_register.horaentrada}', '${new_register.matricula}', ${new_register.matriculaeditada}, '${new_register.nombre_imagen}', ${new_register.pesoestable}, \n '${new_register.qr}', '${new_register.rfid}', '${new_register.vial}', '${new_register.totem}', \n '${new_register.horasalida}', '${new_register.sentido}', '${new_register.modo}', ${new_register.botonvalidacion}, '${new_register.nombre_firma}',\n ${new_register.firmaobligatoria}, '${new_register.timestamp}', '${new_register.id_seleccionado}', '${new_register.tipomovimiento}')`;\n\n // Hacemos un json especial para enviar al Servidor con el movimiento inicial\n let msg_inicial = {}\n msg_inicial.payload = {}\n msg_inicial.payload.IdMovimiento = new_register.uuid\n msg_inicial.payload.matricula = new_register.matricula\n msg_inicial.payload.horaentrada_i = new_register.horaentrada\n msg_inicial.payload.horasalida_i = new_register.horasalida\n msg_inicial.payload.nombre_imagen_i = new_register.nombre_imagen\n msg_inicial.payload.nombre_firma = new_register.nombre_firma\n msg_inicial.payload.pesoEntrada = new_register.pesoestable\n msg_inicial.payload.rfid_i = new_register.rfid || \" \"\n msg_inicial.payload.qr_i = new_register.qr || \" \"\n msg_inicial.payload.IdCentroServicio = new_register.id_seleccionado\n msg_inicial.payload.vial = new_register.vial\n msg_inicial.payload.totem = new_register.totem\n msg_inicial.payload.ts_cierre = new_register.timestamp\n msg_inicial.payload.IdCentroProveedor = flow.get(\"IdCentroProveedor\") \n msg_inicial.payload.imagen_entrada = new_register.imagen || \" \"\n msg_inicial.payload.imagen_firma = new_register.firma || \" \"\n \n \n msg_inicial.url = flow.get(\"url_API\") + `api/Totem/InsertarMovimientos?token=${flow.get(\"tokenapi\")}`\n //msg_inicial.requestTimeout = \"20000\"\n \n node.send([null, null, null, null, msg_plate, null,{ topic: registro }, msg_inicial])\n }\n else if (msg.payload.tipomovimiento == \"final\") {\n new_register.tipomovimiento = \"final\" // indicamos que este registro corresponde a un movimiento final para cierre\n flow.set(\"matricula_movimiento\", new_register.matricula)\n let matricula = flow.get(\"new_register\").matricula\n msg.topic = `SELECT * FROM movimientos WHERE matricula = '${matricula}' AND tipo = 'inicial'`\n msg.payload = \"\"\n node.send([null, null, msg, null, null, null, null]) // inicia el analisis\n /*El objetivo del analisis es revisar si la matricula recibida en el movimiento de salida\n tiene un movimiento de entrada registrado en la tabla de movimientos de la BD.\n */\n\n }\n }\n break;\n }\n\n}\nreturn;",
"outputs": 8,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1090,
"y": 1460,
"wires": [
[
"6703fb916aef8f4b"
],
[
"4e1f0ff54eef4e7b"
],
[
"2dc5c2942966b097"
],
[
"8a7201a76e53897f"
],
[
"3d7afdefd9a5d353"
],
[
"9fa72afe7d97828e"
],
[
"5c4679ed20761208"
],
[
"138317044e960cfa",
"dc3fe6bd2494cab0"
]
]
},
{
"id": "6703fb916aef8f4b",
"type": "link out",
"z": "744bdf3a0e08d754",
"g": "8aa65df7a757f075",
"name": "check",
"mode": "link",
"links": [
"a1ac4bf3b8c14b21"
],
"x": 1315,
"y": 1400,
"wires": []
},
{
"id": "b264dc44d4344d72",
"type": "inject",
"z": "744bdf3a0e08d754",
"g": "8aa65df7a757f075",
"name": "",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "{\"device\":\"supervisor\",\"totem_id\":\"totem_103\",\"accion\":\"check\",\"matricula\":\"6234FLH\"}",
"payloadType": "json",
"x": 830,
"y": 1500,
"wires": [
[
"de78dc5fb92df251"
]
]
},
{
"id": "4e1f0ff54eef4e7b",
"type": "link out",
"z": "744bdf3a0e08d754",
"g": "8aa65df7a757f075",
"name": "QR/RFID",
"mode": "link",
"links": [
"8e56735bbb37a598"
],
"x": 1315,
"y": 1440,
"wires": []
},
{
"id": "2dc5c2942966b097",
"type": "link out",
"z": "744bdf3a0e08d754",
"g": "8aa65df7a757f075",
"name": "mov",
"mode": "link",
"links": [
"c674aac5bef7594a"
],
"x": 1315,
"y": 1480,
"wires": []
},
{
"id": "3d7afdefd9a5d353",
"type": "file",
"z": "744bdf3a0e08d754",
"g": "8aa65df7a757f075",
"name": "guarda imagenes",
"filename": "filename",
"filenameType": "msg",
"appendNewline": false,
"createDir": true,
"overwriteFile": "true",
"encoding": "none",
"x": 1410,
"y": 1560,
"wires": [
[]
]
},
{
"id": "138317044e960cfa",
"type": "debug",
"z": "744bdf3a0e08d754",
"g": "8aa65df7a757f075",
"name": "debug 24",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "false",
"statusVal": "",
"statusType": "auto",
"x": 1400,
"y": 1680,
"wires": []
},
{
"id": "4f30b9755ed381bb",
"type": "debug",
"z": "744bdf3a0e08d754",
"g": "8aa65df7a757f075",
"name": "debug 25",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 1560,
"y": 1720,
"wires": []
},
{
"id": "66630f04db34f483",
"type": "inject",
"z": "744bdf3a0e08d754",
"g": "8aa65df7a757f075",
"name": "salida",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "{\"device\":\"supervisor\",\"totem_id\":\"totem_103\",\"accion\":\"movimiento\",\"tipomovimiento\":\"final\",\"movimiento\":{\"uuid\":\"ae0f5823-2d12-4492-9e80-23c7990afee5\",\"horaentrada\":\"27/10/2025,21:16:29\",\"vial\":1,\"totem\":\"totem_103\",\"modo\":\"A\",\"identificacion_esp\":true,\"firmaobligatoria\":true,\"id_seleccionado\":\"083853B1-E17F-4833-B87E-6317D93B54FB\",\"nombre_firma\":\"firma-6234FLH-20251027211632.jpg\",\"QR\":\"-\",\"RFID\":\"-\",\"nombre_imagen\":\"6234FLH-20251027211632.jpg\",\"matricula\":\"6234FLH\",\"matriculaeditada\":false,\"botonvalidacion\":false,\"horasalida\":\"27/10/2025,21:17:02\",\"pesoestable\":14570,\"timestamp\":1761596222}}",
"payloadType": "json",
"x": 830,
"y": 1620,
"wires": [
[
"de78dc5fb92df251"
]
]
},
{
"id": "2d857220b76dda21",
"type": "inject",
"z": "744bdf3a0e08d754",
"g": "8aa65df7a757f075",
"name": "entrada",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "{\"device\":\"supervisor\",\"totem_id\":\"totem_103\",\"accion\":\"movimiento\",\"tipomovimiento\":\"inicial\",\"movimiento\":{\"uuid\":\"ae0f5823-2d12-4492-9e80-23c7990afee5\",\"horaentrada\":\"27/10/2025,21:16:29\",\"vial\":1,\"totem\":\"totem_103\",\"modo\":\"A\",\"identificacion_esp\":true,\"firmaobligatoria\":true,\"id_seleccionado\":\"083853B1-E17F-4833-B87E-6317D93B54FB\",\"nombre_firma\":\"firma-8305LSM-20251027211632.jpg\",\"QR\":\"-\",\"RFID\":\"-\",\"nombre_imagen\":\"8305LSM-20251027211632.jpg\",\"matricula\":\"8305LSM\",\"matriculaeditada\":false,\"botonvalidacion\":false,\"horasalida\":\"27/10/2025,21:17:02\",\"pesoestable\":14570,\"timestamp\":1761596222}}",
"payloadType": "json",
"x": 830,
"y": 1660,
"wires": [
[
"de78dc5fb92df251"
]
]
},
{
"id": "8a7201a76e53897f",
"type": "link out",
"z": "744bdf3a0e08d754",
"g": "8aa65df7a757f075",
"name": "movcerrar",
"mode": "link",
"links": [
"0ba07aef43c4a226"
],
"x": 1315,
"y": 1520,
"wires": []
},
{
"id": "1b2d9af6c1e13e5f",
"type": "inject",
"z": "744bdf3a0e08d754",
"g": "8aa65df7a757f075",
"name": "1234ABC",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "{\"device\":\"supervisor\",\"totem_id\":\"totem_103\",\"accion\":\"check\",\"matricula\":\"1234ABC\"}",
"payloadType": "json",
"x": 840,
"y": 1540,
"wires": [
[
"de78dc5fb92df251"
]
]
},
{
"id": "5c4679ed20761208",
"type": "mysql",
"z": "744bdf3a0e08d754",
"g": "8aa65df7a757f075",
"mydb": "1c8613764b7f685e",
"name": "guarda movimiento",
"x": 1410,
"y": 1640,
"wires": [
[]
]
},
{
"id": "c674aac5bef7594a",
"type": "link in",
"z": "744bdf3a0e08d754",
"g": "a57600b79a898710",
"name": "analisis",
"links": [
"2dc5c2942966b097"
],
"x": 655,
"y": 1880,
"wires": [
[
"cc229c10e3f4e423"
]
]
},
{
"id": "405f569c2efbf22d",
"type": "debug",
"z": "744bdf3a0e08d754",
"g": "a57600b79a898710",
"name": "debug 22",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 920,
"y": 1820,
"wires": []
},
{
"id": "417e71d002f16583",
"type": "function",
"z": "744bdf3a0e08d754",
"g": "a57600b79a898710",
"name": "movimiento cerrado",
"func": "msg.payload.tipoenvio = \"api\"\nif (msg.topic != \"movimiento tarado\") {\n if (msg.payload.length > 0) {\n //existe al menos un movimiento de entrada asociado a esta matricula y tomamos el utltimo\n let movinicial = msg.payload[0]\n let movfinal = flow.get(\"new_register\") // almacenado durante el ultimo movimiento recibido de salida\n \n // a continuacion tenemos que configurar el json para enviar a la api como movimiento cerrado\n msg.payload = {}\n msg.payload.IdMovimiento = movinicial.uuid\n msg.payload.matricula = movinicial.matricula\n msg.payload.horaentrada_i = movinicial.horaentrada\n msg.payload.horasalida_i = movinicial.horasalida\n msg.payload.horaentrada_f = movfinal.horaentrada\n msg.payload.horasalida_f = movfinal.horasalida\n msg.payload.nombre_imagen_i = movinicial.nombre_imagen\n msg.payload.nombre_imagen_f = movfinal.nombre_imagen\n msg.payload.nombre_firma = movinicial.nombre_firma\n msg.payload.pesoEntrada = movinicial.pesoestable\n msg.payload.pesoSalida = movfinal.pesoestable\n msg.payload.rfid_i = movinicial.rfid || \" \"\n msg.payload.qr_i = movinicial.qr || \" \"\n msg.payload.qr_f = movfinal.qr || \" \"\n msg.payload.rfid_f = movfinal.rfid || \" \"\n msg.payload.IdCentroServicio = movinicial.id_seleccionado\n msg.payload.vial = movinicial.vial\n msg.payload.totem = movinicial.totem\n msg.payload.ts_cierre = movfinal.timestamp\n msg.payload.IdCentroProveedor = flow.get(\"IdCentroProveedor\") \n msg.payload.imagen_entrada = movinicial.imagen || \" \"\n msg.payload.imagen_salida = movfinal.imagen || \" \"\n msg.payload.imagen_firma = movinicial.firma || \" \"\n flow.set(\"movimiento_cerrado_temp\", msg.payload)\n \n let msg_imagen = { topic: \"imagen_matricula\", filename: flow.get(\"path_images\") + movinicial.nombre_imagen }\n flow.set(\"imagen_matricula_rx\", false)\n node.send([msg_imagen, null, null])\n let msg_firma = {}\n node.warn(\"nombre firma = \" + movinicial.nombre_firma)\n flow.set(\"movinicial_firma\", false) // lo colacamos para saber en el proximo nodo si colocamos imagen en el envio hacia api\n if (movinicial.nombre_firma != \"No Habilitada\"){\n node.warn(\"2-nombre firma = \" + movinicial.nombre_firma)\n msg_firma = { topic: \"imagen_firma\", filename: flow.get(\"path_firmas\") + movinicial.nombre_firma }\n flow.set(\"imagen_firma_rx\", false)\n flow.set(\"movinicial_firma\", true) // utilizado en el proximo nodo para saber si existe firma en el movimiento inicial \n node.send([null, msg_firma, null])\n }\n else{\n flow.set(\"imagen_firma_rx\", true)\n }\n \n //flow.set(\"record_test\", msg.payload)\n \n let msg_final = {}\n // guardamos el movimiento cerrado en la tabla \"movimientos_cerrados\" y borramos el movimiento inicial de la tabla de \"movimientos\"\n\n const esc = s => (s ?? '').toString().replace(/'/g, \"''\"); // escapar comillas simples\n\n msg_final.topic = `\nINSERT INTO movimientos_cerrados(\n uuid, matricula, nombre_imagen_i, nombre_imagen_f, nombre_firma,\n horaentrada_i, horasalida_i, horaentrada_f, horasalida_f,\n pesoentrada, pesosalida, qr_i, rfid_i, qr_f, rfid_f, vial, totem, idseleccionado, sincro, ts_cierre\n) VALUES(\n '${esc(movinicial.uuid)}',\n '${esc(movinicial.matricula)}',\n '${esc(movinicial.nombre_imagen)}',\n '${esc(movfinal.nombre_imagen)}',\n '${esc(movinicial.nombre_firma)}',\n '${esc(movinicial.horaentrada)}',\n '${esc(movinicial.horasalida)}',\n '${esc(movfinal.horaentrada)}',\n '${esc(movfinal.horasalida)}',\n ${Number(movinicial.pesoestable) || 0},\n ${Number(movfinal.pesoestable) || 0},\n '${esc(movinicial.qr)}',\n '${esc(movinicial.rfid)}',\n '${esc(movfinal.qr)}',\n '${esc(movfinal.rfid)}', \n ${Number(movinicial.vial) || 0},\n '${esc(movinicial.totem)}',\n '${esc(movinicial.id_seleccionado)}',\n 0,\n ${Number(movfinal.timestamp) || 0});`;\n\n node.send([null, null, msg_final])\n \n flow.set(\"matriculafordelete\", movinicial.matricula)\n setTimeout(function () { // borramos el movimiento inicial en tabla de movimientos\n let msg_delete = {}\n msg_delete.topic = `DELETE FROM movimientos WHERE matricula = '${esc(flow.get(\"matriculafordelete\"))}'`;\n //node.send([null, null, msg_delete]) \n }, 1000)\n \n }\n else {\n //no hay movimiento de entrada asociado a esta matricula\n }\n}\nelse { // Aqui llegamos si tenemos que enviar el movimiento inicial como cerrado porque es un movimiento tarado\n msg.payload = flow.get(\"new_register\") // new register contiene el ultimo movimiento recibido por el supervisor (ver nodo comandos via websocket en el case movimientos)\n msg.payload.tarado = true\n}\nreturn;",
"outputs": 3,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1010,
"y": 1880,
"wires": [
[
"a1c2fb59c438c1c5"
],
[
"2e4cf9271d9a78d8"
],
[
"65bfea0283dfab05"
]
]
},
{
"id": "b1b9ac8e8b543dbb",
"type": "link out",
"z": "744bdf3a0e08d754",
"g": "a57600b79a898710",
"name": "link out 2",
"mode": "link",
"links": [
"c858b37cb0eeba4f"
],
"x": 1655,
"y": 1820,
"wires": []
},
{
"id": "0ba07aef43c4a226",
"type": "link in",
"z": "744bdf3a0e08d754",
"g": "a57600b79a898710",
"name": "cerrar movimiento",
"links": [
"8a7201a76e53897f"
],
"x": 855,
"y": 1980,
"wires": [
[
"417e71d002f16583"
]
]
},
{
"id": "cc229c10e3f4e423",
"type": "mysql",
"z": "744bdf3a0e08d754",
"g": "a57600b79a898710",
"mydb": "1c8613764b7f685e",
"name": "analisis",
"x": 780,
"y": 1880,
"wires": [
[
"405f569c2efbf22d",
"417e71d002f16583"
]
]
},
{
"id": "65bfea0283dfab05",
"type": "mysql",
"z": "744bdf3a0e08d754",
"g": "a57600b79a898710",
"mydb": "1c8613764b7f685e",
"name": "guarda movimiento_c",
"x": 1260,
"y": 1980,
"wires": [
[]
]
},
{
"id": "81f447b69bd2c603",
"type": "function",
"z": "744bdf3a0e08d754",
"g": "6fd6ce44bb173af7",
"name": "function 1",
"func": "let mat = \"0131HLN\"\nmsg.topic = `\nWITH\nseeds AS (\n SELECT mi.id AS id_root\n FROM Matriculas_Identificadores mi\n WHERE UPPER(TRIM(mi.matricula)) = UPPER(TRIM('${mat}'))\n)\nSELECT\n 0 AS nivel,\n i.descripcion,\n i.id,\n NULL AS id_padre\nFROM Identificadores i\nJOIN seeds s ON i.id = s.id_root\n\nUNION ALL\n\nSELECT\n 1 AS nivel,\n c.descripcion,\n c.id,\n c.id_padre\nFROM Identificadores c\nJOIN seeds s ON c.id_padre = s.id_root\n\nORDER BY nivel, id;\n`;\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 960,
"y": 2220,
"wires": [
[
"e22e7f0f1cea9cb5"
]
]
},
{
"id": "d2082aabd7021a2e",
"type": "inject",
"z": "744bdf3a0e08d754",
"g": "6fd6ce44bb173af7",
"name": "",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "",
"payloadType": "date",
"x": 730,
"y": 2220,
"wires": [
[
"81f447b69bd2c603"
]
]
},
{
"id": "1d44274163bba745",
"type": "debug",
"z": "744bdf3a0e08d754",
"g": "6fd6ce44bb173af7",
"name": "debug 34",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 1300,
"y": 2220,
"wires": []
},
{
"id": "17b55fb17b52609b",
"type": "function",
"z": "744bdf3a0e08d754",
"g": "6fd6ce44bb173af7",
"name": "seed",
"func": "let mat = \"0131HLN\"\nmsg.topic = `\nSELECT mi.id AS id_root\nFROM Matriculas_Identificadores mi\nWHERE UPPER(TRIM(mi.matricula)) = UPPER(TRIM('${mat}')) ;\n`;\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 970,
"y": 2300,
"wires": [
[
"e22e7f0f1cea9cb5"
]
]
},
{
"id": "f769af64083450ed",
"type": "inject",
"z": "744bdf3a0e08d754",
"g": "6fd6ce44bb173af7",
"name": "",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "",
"payloadType": "date",
"x": 730,
"y": 2300,
"wires": [
[
"17b55fb17b52609b"
]
]
},
{
"id": "a550322a7e6cafd2",
"type": "function",
"z": "744bdf3a0e08d754",
"g": "6fd6ce44bb173af7",
"name": "function 4",
"func": "let qr = msg.payload\nmsg.topic = `WITH RECURSIVE jerarquia AS (\n -- Primer nivel: comenzar con el id que recibimos\n SELECT id, id_padre, nivel, descripcion\n FROM Identificadores\n WHERE id = '${qr}'\n\n UNION ALL\n\n -- Subir en la jerarquía buscando el nivel 0\n SELECT i.id, i.id_padre, i.nivel, i.descripcion\n FROM Identificadores i\n JOIN jerarquia j ON i.id = j.id_padre\n WHERE i.nivel >= 0\n)\nSELECT\n -- La descripción del id original\n (SELECT descripcion FROM jerarquia WHERE id = '${qr}') AS descripcion_inicial,\n -- La descripción del primer registro con nivel 0 en la jerarquía ascendente\n (SELECT descripcion FROM jerarquia WHERE nivel = 0 LIMIT 1) AS descripcion_nivel_0;`;\n\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 960,
"y": 2380,
"wires": [
[
"e22e7f0f1cea9cb5"
]
]
},
{
"id": "9e4bc4c1c4148e95",
"type": "inject",
"z": "744bdf3a0e08d754",
"g": "6fd6ce44bb173af7",
"name": "id",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "D3220E78-2338-4738-B0D2-499D0223EFFF",
"payloadType": "str",
"x": 730,
"y": 2380,
"wires": [
[
"a550322a7e6cafd2"
]
]
},
{
"id": "e22e7f0f1cea9cb5",
"type": "mysql",
"z": "744bdf3a0e08d754",
"g": "6fd6ce44bb173af7",
"mydb": "1c8613764b7f685e",
"name": "analisis",
"x": 1140,
"y": 2220,
"wires": [
[
"1d44274163bba745"
]
]
},
{
"id": "6a066066436f56ef",
"type": "exec",
"z": "744bdf3a0e08d754",
"g": "e03fd8b52121427a",
"command": " ",
"addpay": "payload",
"append": "",
"useSpawn": "false",
"timer": "",
"oldrc": false,
"name": "Screen ON/OFF",
"x": 940,
"y": 2600,
"wires": [
[],
[],
[]
]
},
{
"id": "245c965f41b35b43",
"type": "inject",
"z": "744bdf3a0e08d754",
"g": "e03fd8b52121427a",
"name": "screen off",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "echo 1 | sudo tee /sys/class/backlight/0-0045/bl_power",
"payloadType": "str",
"x": 720,
"y": 2580,
"wires": [
[
"6a066066436f56ef"
]
]
},
{
"id": "bad546e70128edf8",
"type": "inject",
"z": "744bdf3a0e08d754",
"g": "e03fd8b52121427a",
"name": "screen on",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "echo 0 | sudo tee /sys/class/backlight/0-0045/bl_power",
"payloadType": "str",
"x": 720,
"y": 2540,
"wires": [
[
"6a066066436f56ef"
]
]
},
{
"id": "ff7604277bbc1810",
"type": "inject",
"z": "744bdf3a0e08d754",
"g": "e03fd8b52121427a",
"name": "reboot",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "sudo shutdown -r now",
"payloadType": "str",
"x": 710,
"y": 2620,
"wires": [
[
"6a066066436f56ef"
]
]
},
{
"id": "0a768660b1756f9a",
"type": "inject",
"z": "744bdf3a0e08d754",
"g": "e03fd8b52121427a",
"name": "poweroff",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "sudo poweroff",
"payloadType": "str",
"x": 720,
"y": 2660,
"wires": [
[
"6a066066436f56ef"
]
]
},
{
"id": "1ca5e534fa4962de",
"type": "link in",
"z": "744bdf3a0e08d754",
"g": "e03fd8b52121427a",
"name": "reboot",
"links": [
"e4f5cd90a27363a0"
],
"x": 815,
"y": 2520,
"wires": [
[
"6a066066436f56ef"
]
]
},
{
"id": "c61c445897350722",
"type": "link in",
"z": "744bdf3a0e08d754",
"g": "8aa65df7a757f075",
"name": "entrada",
"links": [
"63dfdbc95bd896d0"
],
"x": 655,
"y": 1440,
"wires": [
[
"182712df6587926f"
]
]
},
{
"id": "7e099ddf6bfcb9fb",
"type": "link in",
"z": "744bdf3a0e08d754",
"g": "610fa44452654078",
"name": "link in 2",
"links": [],
"x": 3775,
"y": 720,
"wires": [
[
"0f3cd31839136f10"
]
]
},
{
"id": "dd15174f0db1d58c",
"type": "link in",
"z": "744bdf3a0e08d754",
"g": "610fa44452654078",
"name": "entrada uibuilder-1",
"links": [
"58bb0cd40f89ba0d"
],
"x": 3775,
"y": 640,
"wires": [
[
"0f3cd31839136f10"
]
]
},
{
"id": "a1c2fb59c438c1c5",
"type": "file in",
"z": "744bdf3a0e08d754",
"g": "a57600b79a898710",
"name": "imagen matricula",
"filename": "filename",
"filenameType": "msg",
"format": "utf8",
"chunk": false,
"sendError": false,
"encoding": "none",
"allProps": false,
"x": 1250,
"y": 1860,
"wires": [
[
"dd4decb988977915"
]
]
},
{
"id": "2e4cf9271d9a78d8",
"type": "file in",
"z": "744bdf3a0e08d754",
"g": "a57600b79a898710",
"name": "imagen firma",
"filename": "filename",
"filenameType": "msg",
"format": "utf8",
"chunk": false,
"sendError": false,
"encoding": "none",
"allProps": false,
"x": 1230,
"y": 1900,
"wires": [
[
"dd4decb988977915"
]
]
},
{
"id": "dd4decb988977915",
"type": "function",
"z": "744bdf3a0e08d754",
"g": "a57600b79a898710",
"name": "mensaje para API",
"func": "if (msg.topic == \"imagen_matricula\"){\n flow.set(\"imagen_matricula_rx\", true)\n flow.set(\"imagen_leida_i\", msg.payload)\n}\nif (msg.topic == \"imagen_firma\"){\n flow.set(\"imagen_firma_rx\", true)\n flow.set(\"imagen_leida_firma\", msg.payload)\n}\nif (flow.get(\"imagen_matricula_rx\") && flow.get(\"imagen_firma_rx\") ){\n \n msg.payload = flow.get(\"movimiento_cerrado_temp\")\n msg.payload.imagen_entrada = flow.get(\"imagen_leida_i\")\n if (flow.get(\"movinicial_firma\")){\n msg.payload.imagen_firma = flow.get(\"imagen_leida_firma\")\n }\n node.send (msg)\n}\nreturn ;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1510,
"y": 1880,
"wires": [
[
"44c64f7eb7bdf346",
"b1b9ac8e8b543dbb"
]
]
},
{
"id": "9fa72afe7d97828e",
"type": "file",
"z": "744bdf3a0e08d754",
"g": "8aa65df7a757f075",
"name": "guarda firmas",
"filename": "filename",
"filenameType": "msg",
"appendNewline": false,
"createDir": true,
"overwriteFile": "true",
"encoding": "none",
"x": 1400,
"y": 1600,
"wires": [
[]
]
},
{
"id": "dc3fe6bd2494cab0",
"type": "https-node",
"z": "744bdf3a0e08d754",
"g": "8aa65df7a757f075",
"name": "Enviar a API",
"method": "POST",
"ret": "txt",
"paytoqs": "ignore",
"url": "",
"tls": "",
"persist": false,
"proxy": "",
"authType": "",
"senderr": false,
"x": 1390,
"y": 1720,
"wires": [
[
"4f30b9755ed381bb"
]
]
},
{
"id": "1dae889e8697bf15",
"type": "function",
"z": "744bdf3a0e08d754",
"g": "f0815d4d5c1e13e0",
"name": "UbicacionesInternas",
"func": "// Verifica que payload sea un array\nif (!Array.isArray(msg.payload.datos) || msg.payload.datos.length === 0) {\n node.warn(\"msg.payload.datos no es un array o está vacío.\");\n return null;\n}\n\n// Función para escapar comillas simples\nconst esc = s => (s ?? \"\").toString().replace(/'/g, \"''\");\n\n// Construimos las tuplas\nflow.set(\"fosos_size\", msg.payload.datos.length)\nconst valuesArray = msg.payload.datos.map(obj => {\n const id = esc(obj.id || \"\").trim().toUpperCase();\n const descripcion = obj.descripcion || \"\";\n \n return `('${id}', '${descripcion}')`;\n});\nflow.set(\"ubicacionesinternas_counter\", valuesArray.length )\n// Query final\n//Borra la tabla actual y si la tabla recibida es diferente de 0 records guarda la nueva tabla, si no pone \"\"\n// finalmente hace un commit\nmsg.topic = `\nSTART TRANSACTION;\nDELETE FROM UbicacionesInternas;\n${valuesArray.length ? `\nINSERT INTO UbicacionesInternas (id, descripcion)\nVALUES ${valuesArray.join(\",\")};\n` : \"\"}\nCOMMIT;\n`;\nmsg.payload = \"\"\nreturn msg;\n\n\n\n\n",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 2840,
"y": 1780,
"wires": [
[
"7aee3c3ea10fd9a5",
"afd34cb6f61a6475"
]
]
},
{
"id": "eca4e2e077d64f16",
"type": "function",
"z": "744bdf3a0e08d754",
"g": "0a78f1a176dd7ad5",
"name": "UbicacionesInternas",
"func": "\nmsg.url = `api/Totem/SeleccionarUbicacionesInternas?token=${flow.get(\"tokenapi\")}&idCentroProveedor=${flow.get(\"IdCentroProveedor\") }`\nmsg.url = flow.get(\"url_API\") + msg.url \nmsg.tabla = \"UbicacionesInternas\"\nreturn msg;\n\n\n//https://pre-urbgesbas.urbaser.com/WebService/api/Totem/SeleccionarUbicacionesInternas?token=FDEB4C00-5F58-4C2D-AD80-9E7064D8FC4E&idCentroProveedor=02C9381D-D4A0-4503-B9A4-890BFABD9608\n\n//https://pre-urbgesbas.urbaser.com/WebService/api/Totem/SeleccionarMatriculasTotem?token=FDEB4C00-5F58-4C2D-AD80-9E7064D8FC4E&idCentroProveedor=A1ADAC83-AEFA-4E1F-8944-56722AC43F4F",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 2420,
"y": 2720,
"wires": [
[
"e87018791b56046f"
]
]
},
{
"id": "cc2d5ea437442925",
"type": "inject",
"z": "744bdf3a0e08d754",
"name": "UbicacionesInternas",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "UbicacionesInternas",
"payloadType": "str",
"x": 1850,
"y": 2800,
"wires": [
[
"c95d9408ac6b1595"
]
]
},
{
"id": "a4b79d82d1b9e587",
"type": "inject",
"z": "576f3807f61f5098",
"g": "967aada409fe59f3",
"name": "Manual",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "MODE",
"payload": "MANUAL",
"payloadType": "str",
"x": 1760,
"y": 1020,
"wires": [
[
"06c0e45f61e0d364"
]
]
},
{
"id": "b5cb8dc6dff15a6f",
"type": "inject",
"z": "576f3807f61f5098",
"g": "967aada409fe59f3",
"name": "Auto",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "MODE",
"payload": "AUTO",
"payloadType": "str",
"x": 1760,
"y": 1060,
"wires": [
[
"06c0e45f61e0d364"
]
]
},
{
"id": "63ef47eeab45cd59",
"type": "inject",
"z": "576f3807f61f5098",
"g": "967aada409fe59f3",
"name": "S1 Green",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "MAN/SET_SEMS",
"payload": "{\"S1\":\"GREEN\", \"S2\":\"RED\", \"S3\":\"RED\"}",
"payloadType": "json",
"x": 1770,
"y": 1100,
"wires": [
[
"49a1dfc2d343bdbb"
]
]
},
{
"id": "406737da0cea85cd",
"type": "inject",
"z": "576f3807f61f5098",
"g": "967aada409fe59f3",
"name": "S2 Green",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "MAN/SET_SEMS",
"payload": "{\"S1\":\"RED\", \"S2\":\"GREEN\", \"S3\":\"RED\"}",
"payloadType": "json",
"x": 1770,
"y": 1140,
"wires": [
[
"49a1dfc2d343bdbb"
]
]
},
{
"id": "0626ad89ffb60829",
"type": "inject",
"z": "576f3807f61f5098",
"g": "967aada409fe59f3",
"name": "S3 Green",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "MAN/SET_SEMS",
"payload": "{\"S1\":\"RED\", \"S2\":\"RED\", \"S3\":\"GREEN\"}",
"payloadType": "json",
"x": 1770,
"y": 1180,
"wires": [
[
"49a1dfc2d343bdbb"
]
]
},
{
"id": "18202cf1d5012bd6",
"type": "inject",
"z": "576f3807f61f5098",
"g": "967aada409fe59f3",
"name": "Todo RED",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "MAN/SET_SEMS",
"payload": "{\"S1\":\"RED\", \"S2\":\"RED\", \"S3\":\"RED\"}",
"payloadType": "json",
"x": 1770,
"y": 1220,
"wires": [
[
"49a1dfc2d343bdbb"
]
]
},
{
"id": "e4e47d1f029304cb",
"type": "inject",
"z": "576f3807f61f5098",
"g": "3276a28141ee1362",
"name": "Sim L1 ON",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": "",
"topic": "lazo1",
"payload": "1",
"payloadType": "num",
"x": 1000,
"y": 340,
"wires": [
[
"e4a843aa2e13ce4d"
]
]
},
{
"id": "20a7ce11f2e8216a",
"type": "inject",
"z": "576f3807f61f5098",
"g": "3276a28141ee1362",
"name": "Sim L1 OFF",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": "",
"topic": "lazo1",
"payload": "0",
"payloadType": "num",
"x": 1000,
"y": 380,
"wires": [
[
"e4a843aa2e13ce4d"
]
]
},
{
"id": "e4a843aa2e13ce4d",
"type": "rbe",
"z": "576f3807f61f5098",
"g": "3276a28141ee1362",
"name": "RBE L1",
"func": "rbe",
"gap": "",
"start": "",
"inout": "out",
"septopics": true,
"property": "payload",
"topi": "topic",
"x": 1160,
"y": 360,
"wires": [
[
"46f02d9f8d41ee03",
"c79d32e49f4cf252"
]
]
},
{
"id": "46f02d9f8d41ee03",
"type": "function",
"z": "576f3807f61f5098",
"g": "3276a28141ee1362",
"name": "L1 -> EV",
"func": "const v = Number(msg.payload)?1:0;\nflow.set('L1',v);\nmsg.topic = v ? 'EV/L1_ON' : 'EV/L1_OFF';\nmsg.payload = {src:'L1',value:v,ts:Date.now()};\nif(v == 0){\n node.status({fill:\"green\",shape:\"dot\",text:\"LIBRE\"});\n}\nelse{\n node.status({fill:\"red\",shape:\"dot\",text:\"OCUPADO\"});\n}\n\nreturn msg;",
"outputs": 1,
"timeout": "",
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1340,
"y": 360,
"wires": [
[
"c4159a812594fc62"
]
]
},
{
"id": "d4986d948f5f65a7",
"type": "inject",
"z": "576f3807f61f5098",
"g": "3276a28141ee1362",
"name": "Sim L2 ON",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": "",
"topic": "lazo2",
"payload": "1",
"payloadType": "num",
"x": 1000,
"y": 440,
"wires": [
[
"69f5087384c48122"
]
]
},
{
"id": "15c2a5fd76e103ac",
"type": "inject",
"z": "576f3807f61f5098",
"g": "3276a28141ee1362",
"name": "Sim L2 OFF",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": "",
"topic": "lazo2",
"payload": "0",
"payloadType": "num",
"x": 1000,
"y": 480,
"wires": [
[
"69f5087384c48122"
]
]
},
{
"id": "69f5087384c48122",
"type": "rbe",
"z": "576f3807f61f5098",
"g": "3276a28141ee1362",
"name": "RBE L2",
"func": "rbe",
"gap": "",
"start": "",
"inout": "out",
"septopics": true,
"property": "payload",
"topi": "topic",
"x": 1160,
"y": 460,
"wires": [
[
"f38d6d27851d0e2b"
]
]
},
{
"id": "f38d6d27851d0e2b",
"type": "function",
"z": "576f3807f61f5098",
"g": "3276a28141ee1362",
"name": "L2 -> EV",
"func": "const v = Number(msg.payload)?1:0;\nflow.set('L2',v);\nmsg.topic = v ? 'EV/L2_ON' : 'EV/L2_OFF';\nmsg.payload = {src:'L2',value:v,ts:Date.now()};\nif(v == 0){\n node.status({fill:\"green\",shape:\"dot\",text:\"LIBRE\"});\n}\nelse{\n node.status({fill:\"red\",shape:\"dot\",text:\"OCUPADO\"});\n}\n\nreturn msg;",
"outputs": 1,
"timeout": "",
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1340,
"y": 460,
"wires": [
[
"c4159a812594fc62"
]
]
},
{
"id": "be21a62a431574d0",
"type": "inject",
"z": "576f3807f61f5098",
"g": "3276a28141ee1362",
"name": "Sim L3 ON",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": "",
"topic": "lazo3",
"payload": "1",
"payloadType": "num",
"x": 1000,
"y": 540,
"wires": [
[
"fb6d5180204e6bce"
]
]
},
{
"id": "0dde5e928354be6c",
"type": "inject",
"z": "576f3807f61f5098",
"g": "3276a28141ee1362",
"name": "Sim L3 OFF",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": "",
"topic": "lazo3",
"payload": "0",
"payloadType": "num",
"x": 1000,
"y": 580,
"wires": [
[
"fb6d5180204e6bce"
]
]
},
{
"id": "fb6d5180204e6bce",
"type": "rbe",
"z": "576f3807f61f5098",
"g": "3276a28141ee1362",
"name": "RBE L3",
"func": "rbe",
"gap": "",
"start": "",
"inout": "out",
"septopics": true,
"property": "payload",
"topi": "topic",
"x": 1160,
"y": 560,
"wires": [
[
"918df2f63a69c6c9"
]
]
},
{
"id": "918df2f63a69c6c9",
"type": "function",
"z": "576f3807f61f5098",
"g": "3276a28141ee1362",
"name": "L3 -> EV",
"func": "const v = Number(msg.payload)?1:0;\nflow.set('L3',v);\nmsg.topic = v ? 'EV/L3_ON' : 'EV/L3_OFF';\nmsg.payload = {src:'L3',value:v,ts:Date.now()};\nif(v == 0){\n node.status({fill:\"green\",shape:\"dot\",text:\"LIBRE\"});\n}\nelse{\n node.status({fill:\"red\",shape:\"dot\",text:\"OCUPADO\"});\n}\n\nreturn msg;",
"outputs": 1,
"timeout": "",
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1340,
"y": 560,
"wires": [
[
"c4159a812594fc62"
]
]
},
{
"id": "c4159a812594fc62",
"type": "link out",
"z": "576f3807f61f5098",
"g": "3276a28141ee1362",
"name": "EV BUS (desde LAZOS)",
"mode": "link",
"links": [
"e7754eeae28a6256",
"d95551b3e1091e71"
],
"x": 1525,
"y": 460,
"wires": []
},
{
"id": "a7c59fef860efa52",
"type": "inject",
"z": "576f3807f61f5098",
"g": "f4f4ca47869551e3",
"name": "Sim Peso 0",
"props": [
{
"p": "payload.peso",
"v": "",
"vt": "num"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": "",
"topic": "",
"x": 1010,
"y": 720,
"wires": [
[
"29994ee556e17a85"
]
]
},
{
"id": "e0b22a42652448d3",
"type": "inject",
"z": "576f3807f61f5098",
"g": "f4f4ca47869551e3",
"name": "Sim Peso 8000",
"props": [
{
"p": "payload.peso",
"v": "8000",
"vt": "num"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": "",
"topic": "",
"x": 1020,
"y": 760,
"wires": [
[
"29994ee556e17a85"
]
]
},
{
"id": "29994ee556e17a85",
"type": "function",
"z": "576f3807f61f5098",
"g": "f4f4ca47869551e3",
"name": "Peso -> EV/B_OCC (histeresis + confirm)",
"func": "const P_on = flow.get('P_on') ?? 2000;\nconst P_off = flow.get('P_off') ?? 1200;\nconst T = flow.get('T_confirm_ms') ?? 400;\n\nconst W = Number(msg.payload.peso); // lectura del peso\nflow.set('lastW', W);\nlet B = flow.get('B_OCC') ?? 0;\n\nfunction clearPending(){\n const p = flow.get('B_pending');\n if (p && p.tid) clearTimeout(p.tid);\n flow.set('B_pending', null);\n}\nfunction arm(kind){\n clearPending();\n const tid = setTimeout(() => {\n const ww = Number(flow.get('lastW') ?? W);\n let bb = flow.get('B_OCC') ?? 0;\n if (kind==='on' && bb===0 && ww>=P_on){\n flow.set('B_OCC',1);\n node.send({topic:'EV/B_OCC_ON', payload:{src:'W',W:ww,ts:Date.now()}});\n node.status({fill:\"red\",shape:\"dot\",text:\"OCUPADA\"});\n }\n if (kind==='off' && bb===1 && ww<=P_off){\n flow.set('B_OCC',0);\n node.send({topic:'EV/B_OCC_OFF', payload:{src:'W',W:ww,ts:Date.now()}});\n node.status({fill:\"green\",shape:\"dot\",text:\"LIBRE\"});\n }\n flow.set('B_pending', null);\n }, T);\n flow.set('B_pending', {kind, tid});\n}\n\nif (B===0){\n if (W>=P_on){\n const p=flow.get('B_pending');\n if (!p || p.kind!=='on') arm('on');\n } else {\n const p=flow.get('B_pending');\n if (p && p.kind==='on') clearPending();\n }\n} else {\n if (W<=P_off){\n const p=flow.get('B_pending');\n if (!p || p.kind!=='off') arm('off');\n } else {\n const p=flow.get('B_pending');\n if (p && p.kind==='off') clearPending();\n }\n}\nreturn null;",
"outputs": 1,
"timeout": "",
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1320,
"y": 740,
"wires": [
[
"74ea72530de212a4"
]
]
},
{
"id": "74ea72530de212a4",
"type": "link out",
"z": "576f3807f61f5098",
"g": "f4f4ca47869551e3",
"name": "EV BUS (desde PESO)",
"mode": "link",
"links": [
"e7754eeae28a6256",
"d95551b3e1091e71"
],
"x": 1535,
"y": 740,
"wires": []
},
{
"id": "04796f17dd930bad",
"type": "inject",
"z": "576f3807f61f5098",
"d": true,
"g": "6457918bac63ba59",
"name": "INIT",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.2,
"topic": "INIT",
"payload": "1",
"payloadType": "num",
"x": 1730,
"y": 340,
"wires": [
[
"c9d524190cbe7f96"
]
]
},
{
"id": "c9d524190cbe7f96",
"type": "function",
"z": "576f3807f61f5098",
"d": true,
"g": "6457918bac63ba59",
"name": "Init params (ajusta aquí)",
"func": "flow.set('P_on', 2000);\nflow.set('P_off', 1200);\nflow.set('T_confirm_ms', 400);\n\nflow.set('N2', 5); flow.set('N3', 3); flow.set('N1', 2);\n// Prioridad / orden de servicio (WRR)\nflow.set('order', [3,1,2]) // Taller = 3; Entrada = 1: Fosos = 2\n// ventana tiempos de transito\nflow.set('t1_ms', 8000);\nflow.set('t2_ms', 8000);\nflow.set('t3_ms', 8000);\n// ventana tiempos de semaforo verde\nflow.set('g1_ms', 10000);\nflow.set('g2_ms', 10000);\nflow.set('g3_ms', 10000);\n\nflow.set('state','IDLE');\nflow.set('B_OCC',0);\nflow.set('L1',0); flow.set('L2',0); flow.set('L3',0);\n\n// Accesos bloqueados por timeout de verde (Gi)\n// Si blocked[1] = true, el acceso 1 NO puede recibir verde hasta que L1 haga OFF.\nflow.set('blocked', { 1:false, 2:false, 3:false });\n\nflow.set('mode', \"AUTO\")\n\nreturn {topic:'CMD/SET_SEMS', payload:{S1:'RED',S2:'RED',S3:'RED',ts:Date.now()}};",
"outputs": 1,
"timeout": "",
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1950,
"y": 340,
"wires": [
[]
]
},
{
"id": "e7754eeae28a6256",
"type": "link in",
"z": "576f3807f61f5098",
"g": "6457918bac63ba59",
"name": "EV IN (FSM)",
"links": [
"c4159a812594fc62",
"74ea72530de212a4",
"15131de35b7d0425",
"2dc673e9f13e0f0b"
],
"x": 1715,
"y": 440,
"wires": [
[
"0e30fb1a5ee58746"
]
]
},
{
"id": "0e30fb1a5ee58746",
"type": "function",
"z": "576f3807f61f5098",
"g": "6457918bac63ba59",
"name": "FSM Dispatcher",
"func": "/*\n Nodo Function: FSM Dispatcher (Despachador de estados)\n\n OBJETIVO\n --------\n Este nodo es el “cerebro” del sistema. Recibe eventos (EV/*) y decisiones del scheduler (RSP/PICK),\n mantiene el estado global de la máquina (FSM) y emite comandos (CMD/*) hacia:\n - Semáforos (CMD/SET_SEMS)\n - Timers (CMD/START_* y CMD/STOP_*)\n - Scheduler (CMD/PICK_NEXT y CMD/SERVED)\n\n IDEA GENERAL (cómo fluye todo)\n ------------------------------\n 1) Sensores (lazos y peso) generan eventos EV/*:\n EV/L1_ON, EV/L1_OFF, ..., EV/B_OCC_ON, EV/B_OCC_OFF\n 2) Timers generan EV/*:\n EV/Ti_TIMEOUT, EV/Gi_TIMEOUT\n 3) El scheduler responde con:\n RSP/PICK { i: 1|2|3 }\n 4) Este Dispatcher decide transiciones de estado y manda CMD/*:\n - A semáforos: CMD/SET_SEMS {S1,S2,S3}\n - A timers: CMD/START_Ti, CMD/STOP_Ti, CMD/START_Gi, CMD/STOP_Gi\n - A scheduler: CMD/PICK_NEXT {demands} y CMD/SERVED {i}\n\n SOBRE \"??\" (Nullish Coalescing)\n -------------------------------\n a ?? b => usa a si NO es null/undefined; si lo es, usa b.\n Se usa para inicializar variables de contexto sin pisar valores válidos como 0.\n\n VARIABLES EN flow CONTEXT (memoria global del flow)\n ---------------------------------------------------\n - flow.state : estado actual de la FSM (string)\n Estados usados:\n 'IDLE' : sistema libre para decidir a quién dar paso\n 'DANDO_1' : semáforo 1 en verde, esperando L1_OFF\n 'DANDO_2' : semáforo 2 en verde, esperando L2_OFF\n 'DANDO_3' : semáforo 3 en verde, esperando L3_OFF\n 'TRANS_1' : camión del acceso 1 en tránsito (timer T1 corriendo)\n 'TRANS_2' : camión del acceso 2 en tránsito (timer T2 corriendo)\n 'TRANS_3' : camión del acceso 3 en tránsito (timer T3 corriendo)\n 'OCC' : báscula ocupada (B_OCC=1), todos los semáforos en rojo\n\n - flow.B_OCC : 0/1 estado de ocupación de la báscula (lo mantiene el nodo de peso)\n - flow.L1, flow.L2, flow.L3 : 0/1 demanda actual (hay camión en el lazo)\n (los actualizan los nodos de lazo)\n\n NOTA: flow.cur se puede usar para depuración, pero aquí se guarda de forma implícita en state\n (cuando state es DANDO_2 o TRANS_2, el \"cur\" es 2).\n\n ENTRADAS (msg.topic)\n --------------------\n - EV/B_OCC_ON, EV/B_OCC_OFF\n - EV/L1_ON, EV/L1_OFF ... EV/L3_OFF\n - EV/T1_TIMEOUT ... EV/T3_TIMEOUT\n - EV/G1_TIMEOUT ... EV/G3_TIMEOUT\n - RSP/PICK (respuesta del scheduler)\n\n SALIDAS (3 puertos)\n -------------------\n Output 1: comandos a semáforos (CMD/SET_SEMS)\n Output 2: comandos a timers (CMD/START_*, CMD/STOP_*)\n (se emite como ARRAY de mensajes, para mandar varios START/STOP en un ciclo)\n Output 3: comandos a scheduler (CMD/PICK_NEXT, CMD/SERVED)\n (también puede ser ARRAY de mensajes)\n\n IMPORTANTE: filosofía del diseño\n -------------------------------\n - “Event-driven”: este nodo reacciona a eventos, no hace polling.\n - “Exclusión”: durante TRANS_i no se permite dar otro verde.\n - “Seguridad”: si B_OCC pasa a 1 en cualquier momento, se fuerza estado OCC.\n - “Competencia”: el scheduler decide a quién dar paso cuando estamos en IDLE y hay demanda.\n*/\n\n// ------------------------------\n// Helpers de lectura de estado\n// ------------------------------\nconst topic = msg.topic || '';\nconst now = Date.now();\n\n// --- Gestión de bloqueos ---\nlet blocked = flow.get('blocked') ?? {1:false,2:false,3:false};\n\n// Si un lazo pasa a OFF, lo desbloqueamos (se resetea el fallo)\nif (topic === 'EV/L1_OFF') blocked[1] = false;\nif (topic === 'EV/L2_OFF') blocked[2] = false;\nif (topic === 'EV/L3_OFF') blocked[3] = false;\n\nflow.set('blocked', blocked);\n\n// Devuelve demanda actual (hay camión esperando) según los lazos guardados en flow\nfunction getDemands() {\n const blocked = flow.get('blocked') ?? {1:false,2:false,3:false};\n\n return {\n 1: (flow.get('L1') ? 1 : 0) && !blocked[1] ? 1 : 0,\n 2: (flow.get('L2') ? 1 : 0) && !blocked[2] ? 1 : 0,\n 3: (flow.get('L3') ? 1 : 0) && !blocked[3] ? 1 : 0\n };\n}\n\n// ¿Hay algún lazo ocupado?\nfunction anyDemand(d) {\n return (d[1] || d[2] || d[3]) ? 1 : 0;\n}\n\n// Guardar estado de la FSM en flow\nfunction setState(st) {\n flow.set('state', st);\n}\n\n// ------------------------------\n// Helpers para construir comandos\n// ------------------------------\n\n// Comando a semáforos: un solo mensaje con los 3 estados\nfunction cmdSetSems(s1, s2, s3) {\n // Guardar también aquí el \"estado ordenado\" para que el status del dispatcher sea coherente\n flow.set('S1', s1);\n flow.set('S2', s2);\n flow.set('S3', s3);\n return { topic: 'CMD/SET_SEMS', payload: { S1: s1, S2: s2, S3: s3, ts: now } };\n}\n\n// Comando a timers: START/STOP_Ti o START/STOP_Gi\nfunction cmdTimer(topic) {\n return { topic, payload: { ts: now } };\n}\n\n// Comando al scheduler: pedir elección o notificar servido\nfunction cmdSched(topic, payload = {}) {\n return { topic, payload: { ...payload, ts: now } };\n}\n\n// Si es posible (báscula libre y hay demanda), pedimos al scheduler que elija el siguiente acceso.\nfunction requestPickIfPossible() {\n const B = flow.get('B_OCC') ? 1 : 0;\n const d = getDemands();\n if (!B && anyDemand(d)) {\n return cmdSched('CMD/PICK_NEXT', { demands: d });\n }\n return null;\n}\n\n// ------------------------------\n// Estado actual (con defaults)\n// ------------------------------\nlet state = flow.get('state') ?? 'IDLE';\nlet B_OCC = flow.get('B_OCC') ? 1 : 0;\n\n// Mantener B_OCC actualizado si entran eventos de ocupación (por robustez)\nif (topic === 'EV/B_OCC_ON') { flow.set('B_OCC', 1); B_OCC = 1; }\nif (topic === 'EV/B_OCC_OFF') { flow.set('B_OCC', 0); B_OCC = 0; }\n\n// Buffers de salida: podemos emitir varios comandos en una misma transición\nlet outSems = null; // 1 mensaje (o null)\nlet outTimers = []; // array de mensajes (o vacío)\nlet outSched = []; // array de mensajes (o vacío)\n\n// =========================================================\n// REGLA DE SEGURIDAD GLOBAL:\n// Si la báscula está ocupada, forzamos OCC y todos en ROJO.\n// =========================================================\nif (B_OCC && state !== 'OCC') {\n // Nota: aquí NO paramos timers explícitamente en esta versión (se podría).\n // La filosofía es: en OCC no se da paso a nadie, y cuando se libere se re-arbitra.\n outSems = cmdSetSems('RED', 'RED', 'RED');\n setState('OCC');\n state = 'OCC';\n}\n\n// =========================================================\n// ESTADO: OCC (báscula ocupada)\n// - mientras esté ocupada: nadie en verde\n// - cuando se libera: pasamos a IDLE y pedimos pick si hay demanda\n// =========================================================\nif (state === 'OCC') {\n if (topic === 'EV/B_OCC_OFF') {\n setState('IDLE');\n const pick = requestPickIfPossible();\n if (pick) outSched.push(pick);\n }\n updateStatus();\n return [\n outSems,\n outTimers.length ? outTimers : null,\n outSched.length ? outSched : null\n ];\n}\n\n// =========================================================\n// ESTADO: IDLE (sistema libre para decidir)\n// - si llega cualquier EV/*, intentamos pedir PICK_NEXT si procede\n// - si llega RSP/PICK, encendemos el verde del acceso elegido y arrancamos su G_i\n// =========================================================\nif (state === 'IDLE') {\n if (!B_OCC) {\n // Respuesta del scheduler: ya sabemos a quién dar paso\n if (topic === 'RSP/PICK' && msg.payload && msg.payload.i) {\n const i = Number(msg.payload.i);\n\n // Poner solo un semáforo en verde, el resto en rojo\n outSems = cmdSetSems(\n i === 1 ? 'GREEN' : 'RED',\n i === 2 ? 'GREEN' : 'RED',\n i === 3 ? 'GREEN' : 'RED'\n );\n\n // Arrancar timer de verde máximo del acceso elegido (Gi)\n outTimers.push(cmdTimer(`CMD/START_G${i}`));\n\n // Cambiar estado a DANDO_i\n setState(`DANDO_${i}`);\n }\n // Cualquier evento puede disparar un nuevo arbitraje\n else if (topic.startsWith('EV/')) {\n const pick = requestPickIfPossible();\n if (pick) outSched.push(pick);\n }\n }\n updateStatus();\n return [\n outSems,\n outTimers.length ? outTimers : null,\n outSched.length ? outSched : null\n ];\n}\n\n// =========================================================\n// ESTADO: DANDO_i\n// Semáforo i en VERDE, esperando que el camión SALGA del lazo (Li_OFF)\n// Eventos relevantes:\n// - EV/Li_OFF => cerrar verde, parar Gi, arrancar Ti, notificar served, pasar a TRANS_i\n// - EV/Gi_TIMEOUT => timeout verde (camión no se movió), forzar rojo, volver a IDLE y re-arbitrar\n// - EV/B_OCC_ON => (robustez) si se ocupa báscula, forzar OCC\n// =========================================================\n\nif (state.startsWith('DANDO_')) {\n const i = Number(state.split('_')[1]); // extrae i de \"DANDO_2\"\n\n if (topic === `EV/L${i}_OFF`) {\n // El camión salió del lazo -> cerramos verde inmediatamente\n outSems = cmdSetSems('RED', 'RED', 'RED');\n\n // Parar el timer de verde máximo (Gi)\n outTimers.push(cmdTimer(`CMD/STOP_G${i}`));\n\n // Arrancar el timer de tránsito (Ti)\n outTimers.push(cmdTimer(`CMD/START_T${i}`));\n\n // Notificar al scheduler que se sirvió un camión de i\n // (para contar cupos WRR cuando hay competencia)\n outSched.push(cmdSched('CMD/SERVED', { i }));\n\n // Pasar a estado de tránsito\n setState(`TRANS_${i}`);\n updateStatus();\n return [outSems, outTimers, outSched];\n }\n\n if (topic === `EV/G${i}_TIMEOUT`) {\n // Timeout de verde: el camión no salió del lazo\n // Por seguridad ponemos todo en rojo y reintentamos arbitraje\n outSems = cmdSetSems('RED', 'RED', 'RED');\n outTimers.push(cmdTimer(`CMD/STOP_G${i}`)); // por limpieza, aunque ya venció\n setState('IDLE');\n\n // Bloqueo del acceso i hasta que Li haga OFF\n let blocked = flow.get('blocked') ?? {1:false,2:false,3:false};\n blocked[i] = true;\n flow.set('blocked', blocked);\n \n const pick = requestPickIfPossible();\n if (pick) outSched.push(pick);\n updateStatus();\n return [\n outSems,\n outTimers.length ? outTimers : null,\n outSched.length ? outSched : null\n ];\n }\n\n if (topic === 'EV/B_OCC_ON') {\n // Si se detecta ocupación, forzamos OCC\n outSems = cmdSetSems('RED', 'RED', 'RED');\n outTimers.push(cmdTimer(`CMD/STOP_G${i}`));\n setState('OCC');\n updateStatus();\n return [outSems, outTimers, outSched.length ? outSched : null];\n }\n\n return [null, null, null];\n}\n\n// =========================================================\n// ESTADO: TRANS_i\n// Durante el tránsito no se da paso a otros accesos.\n// Eventos relevantes:\n// - EV/B_OCC_ON => la báscula se ocupó antes de expirar Ti -> parar Ti y pasar a OCC\n// - EV/Ti_TIMEOUT => expiró Ti; si sigue libre, volver a IDLE para re-arbitrar\n// =========================================================\nif (state.startsWith('TRANS_')) {\n const i = Number(state.split('_')[1]);\n\n if (topic === 'EV/B_OCC_ON') {\n // La báscula se ocupó: detener timer de tránsito y pasar a OCC\n outTimers.push(cmdTimer(`CMD/STOP_T${i}`));\n outSems = cmdSetSems('RED', 'RED', 'RED');\n setState('OCC');\n updateStatus();\n return [outSems, outTimers, null];\n }\n\n if (topic === `EV/T${i}_TIMEOUT`) {\n // Transito agotado: si báscula sigue libre, volver a IDLE para dar paso al siguiente\n if (!B_OCC) {\n setState('IDLE');\n const pick = requestPickIfPossible();\n if (pick) outSched.push(pick);\n updateStatus();\n return [null, null, outSched.length ? outSched : null];\n }\n }\n updateStatus();\n return [null, null, null];\n}\n\n// Si por alguna razón llegamos aquí, no hacemos nada\nreturn [null, null, null];\n\n\n//***************************** FUNCION PARA VER STATUS ***********************************************************\n\nfunction updateStatus() {\n // --- StatusText para el Dispatcher (incluye bloqueos) ---\n\nconst state = flow.get('state') ?? 'IDLE';\nconst B = flow.get('B_OCC') ? 1 : 0;\n\nconst L1 = flow.get('L1') ? 1 : 0;\nconst L2 = flow.get('L2') ? 1 : 0;\nconst L3 = flow.get('L3') ? 1 : 0;\n\nconst blocked = flow.get('blocked') ?? {1:false,2:false,3:false};\nconst BLK = `${blocked[1]?1:0}${blocked[2]?1:0}${blocked[3]?1:0}`;\n\n// Si guardas estados de semáforos en flow desde el handler\nconst S1 = flow.get('S1') ?? 'RED';\nconst S2 = flow.get('S2') ?? 'RED';\nconst S3 = flow.get('S3') ?? 'RED';\n\n// Emojis (🟥 suele verse rojo consistente en node.status)\nconst dot = (c) => (c === 'GREEN' ? '🟢' : '🟥');\n\nlet statusText =\n ` st:${state} ` +\n ` B:${B} ` +\n ` L:${L1}${L2}${L3} ` +\n ` BLK:${BLK} ` +\n ` S:${dot(S1)}${dot(S2)}${dot(S3)}`;\n\n// Opcional: último peso si lo guardas como lastW\nconst lastW = flow.get('lastW');\nif (lastW !== undefined && lastW !== null) statusText += ` W:${lastW}`;\n\nnode.status({\n fill: B ? 'red' : 'green',\n shape: 'dot',\n text: statusText\n});\n\n}\n",
"outputs": 3,
"timeout": "",
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1960,
"y": 440,
"wires": [
[
"b017ceea4285eb76"
],
[
"798cfaee0987cdca"
],
[
"67e977418231666d"
]
]
},
{
"id": "67e977418231666d",
"type": "link out",
"z": "576f3807f61f5098",
"g": "6457918bac63ba59",
"name": "CMD -> SCHED",
"mode": "link",
"links": [
"606dc6f9453f5e0d"
],
"x": 2195,
"y": 440,
"wires": []
},
{
"id": "798cfaee0987cdca",
"type": "link out",
"z": "576f3807f61f5098",
"g": "6457918bac63ba59",
"name": "CMD -> TIMERS",
"mode": "link",
"links": [
"00ce5615a6247bec"
],
"x": 2195,
"y": 400,
"wires": []
},
{
"id": "b017ceea4285eb76",
"type": "link out",
"z": "576f3807f61f5098",
"g": "6457918bac63ba59",
"name": "CMD -> SEMS",
"mode": "link",
"links": [
"8da927c794b40546"
],
"x": 2195,
"y": 360,
"wires": []
},
{
"id": "606dc6f9453f5e0d",
"type": "link in",
"z": "576f3807f61f5098",
"g": "e51291e8f6dc7282",
"name": "CMD IN (SCHED)",
"links": [
"67e977418231666d"
],
"x": 1685,
"y": 580,
"wires": [
[
"891cfa9c8d6e464d"
]
]
},
{
"id": "891cfa9c8d6e464d",
"type": "function",
"z": "576f3807f61f5098",
"g": "e51291e8f6dc7282",
"name": "Scheduler WRR 2>3>1",
"func": "const t=msg.topic||'';\nconst order = flow.get('order') ?? [2,3,1];\nconst quota={2:(flow.get('N2')??5),3:(flow.get('N3')??3),1:(flow.get('N1')??2)};\nlet turn=context.get('turn')??0;\nlet served=context.get('served')??0;\nlet competitive=context.get('competitive')??false;\n\nfunction save(){context.set('turn',turn);context.set('served',served);context.set('competitive',competitive);}\n\nif (t==='CMD/SERVED'){\n const i=Number(msg.payload?.i);\n if (competitive && i===order[turn]) served++;\n save();\n return null;\n}\n\nif (t!=='CMD/PICK_NEXT') return null;\nconst d=msg.payload?.demands || {1:flow.get('L1')?1:0,2:flow.get('L2')?1:0,3:flow.get('L3')?1:0};\nconst act=[1,2,3].filter(i=>d[i]);\ncompetitive = act.length>1;\n\nif (act.length===0){save();return null;}\nif (!competitive){save();return {topic:'RSP/PICK',payload:{i:act[0]}};}\n\nfor (let k=0;k<3;k++){\n const i=order[turn];\n if (d[i] && served,\n \"G2\": ,\n ...\n }\n\n NOTAS IMPORTANTES\n -----------------\n 1) START \"reinicia\" el timer:\n si el timer ya existía, se cancela y se vuelve a arrancar.\n 2) STOP cancela el timer si estaba activo.\n 3) Al expirar, el timer se auto-limpia del objeto context.timers\n 4) node.send() emite el evento de timeout hacia el BUS (link out)\n\n ENTRADA / SALIDA\n ----------------\n Entrada: msg.topic con alguno de los CMD/START_* o CMD/STOP_*\n Salida: no retorna msg (return null). Emite eventos con node.send() al expirar.\n*/\n\nconst topic = msg.topic || \"\";\n\n/* -------------------------\n Leer configuración (ms)\n -------------------------\n Si no existen en flow, se usan defaults.\n Ajusta estos defaults si quieres.\n*/\nconst cfg = {\n T1: flow.get(\"t1_ms\") ?? 8000,\n T2: flow.get(\"t2_ms\") ?? 6000,\n T3: flow.get(\"t3_ms\") ?? 7000,\n G1: flow.get(\"g1_ms\") ?? 30000,\n G2: flow.get(\"g2_ms\") ?? 30000,\n G3: flow.get(\"g3_ms\") ?? 30000\n};\n\n/* -------------------------\n Parsear comando\n -------------------------\n topic esperado: CMD/START_T1, CMD/STOP_G3, etc.\n Regex:\n ^CMD/(START|STOP)_([TG])(\\d)$\n - Grupo1: START o STOP\n - Grupo2: 'T' o 'G'\n - Grupo3: '1' '2' '3'\n*/\nconst m = topic.match(/^CMD\\/(START|STOP)_([TG])(\\d)$/);\nif (!m) {\n // No es un comando de timer, lo ignoramos\n return null;\n}\n\nconst action = m[1]; // \"START\" | \"STOP\"\nconst family = m[2]; // \"T\" | \"G\"\nconst index = m[3]; // \"1\" | \"2\" | \"3\"\nconst key = family + index; // \"T1\"...\"T3\" o \"G1\"...\"G3\"\n\n/* -------------------------\n Almacén de timers\n -------------------------\n Usamos context (no flow) porque:\n - context se asocia al nodo y vive mientras el runtime está activo\n - evita colisiones con otros nodos\n*/\nlet timers = context.get(\"timers\") || {};\n\n/* -------------------------\n Helpers: cancelar / arrancar\n ------------------------- */\n\n// Cancela un timer si existe y lo borra del mapa\nfunction clearTimer(k) {\n if (timers[k]) {\n clearTimeout(timers[k]);\n delete timers[k];\n }\n}\n\n// Arranca un timer (reinicia si ya existía)\n// Cuando expira: emite EV/Ti_TIMEOUT o EV/Gi_TIMEOUT\nfunction armTimer(k, ms) {\n clearTimer(k);\n\n timers[k] = setTimeout(() => {\n // Construcción del topic de evento:\n // k=\"T1\" -> EV/T1_TIMEOUT\n // k=\"G3\" -> EV/G3_TIMEOUT\n const evTopic = `EV/${k}_TIMEOUT`;\n\n node.send({\n topic: evTopic,\n payload: {\n src: \"TIMER\",\n key: k, // \"T1\"...\"T3\" / \"G1\"...\"G3\"\n ms: ms, // duración programada\n ts: Date.now() // timestamp de expiración\n }\n });\n\n // Limpieza: el timer ya expiró\n clearTimer(k);\n context.set(\"timers\", timers);\n }, ms);\n}\n\n/* -------------------------\n Ejecutar acción\n ------------------------- */\n\nif (action === \"STOP\") {\n clearTimer(key);\n context.set(\"timers\", timers);\n return null;\n}\n\nif (action === \"START\") {\n const ms = cfg[key];\n\n // Defensa: si ms no es número o es <=0, evitamos armar un timer inválido\n if (!Number.isFinite(ms) || ms <= 0) {\n node.warn(`Timer ${key} no arrancado: duración inválida (${ms})`);\n return null;\n }\n\n armTimer(key, ms);\n context.set(\"timers\", timers);\n return null;\n}\n\nreturn null;\n",
"outputs": 1,
"timeout": "",
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1880,
"y": 720,
"wires": [
[
"2dc673e9f13e0f0b"
]
]
},
{
"id": "2dc673e9f13e0f0b",
"type": "link out",
"z": "576f3807f61f5098",
"g": "fdde9120f66c6c06",
"name": "EV BUS (desde TIMERS)",
"mode": "link",
"links": [
"e7754eeae28a6256",
"d95551b3e1091e71"
],
"x": 2045,
"y": 720,
"wires": []
},
{
"id": "8da927c794b40546",
"type": "link in",
"z": "576f3807f61f5098",
"g": "e928e581c722fe25",
"name": "CMD IN (SEMS)",
"links": [
"b017ceea4285eb76",
"7c6babaed4914e41"
],
"x": 1715,
"y": 920,
"wires": [
[
"49a1dfc2d343bdbb"
]
]
},
{
"id": "bc4418d271c32af0",
"type": "function",
"z": "576f3807f61f5098",
"g": "e928e581c722fe25",
"name": "Semaforos",
"func": "if (msg.topic!=='CMD/SET_SEMS') return null;\nflow.set('S1',msg.payload.S1);\nflow.set('S2',msg.payload.S2);\nflow.set('S3',msg.payload.S3);\n// Mapeo de datos a Modbus.\n// Estados (verde=true, rojo=false)\nlet s1 = (msg.payload.S1 === \"GREEN\"); // true si \"GREEN\" false si es diferente\nlet s2 = (msg.payload.S2 === \"GREEN\");\nlet s3 = (msg.payload.S3 === \"GREEN\");\n// Canales (0..5)\nlet ch1 = Number(flow.get(\"DO_semaforo1\")); // aseguramos que contienen numeros\nlet ch2 = Number(flow.get(\"DO_semaforo2\"));\nlet ch3 = Number(flow.get(\"DO_semaforo3\"));\n\nfunction validCh(ch) {\n return Number.isInteger(ch) && ch >= 0 && ch <= 5; \n}\n// chequeamos que los canales estan dentro del rango del dispositivo \nif (!validCh(ch1) || !validCh(ch2) || !validCh(ch3)) {\n node.warn(`Canal inválido: ch1=${ch1}, ch2=${ch2}, ch3=${ch3} (debe ser 0..5)`);\n return null;\n}\n\n// Detectar canales repetidos\nlet used = new Map();\nfor (const [name, ch] of [[\"S1\", ch1], [\"S2\", ch2], [\"S3\", ch3]]) {\n if (used.has(ch)) node.warn(`Conflicto: ${name} y ${used.get(ch)} usan el mismo canal ${ch}`);\n else used.set(ch, name);\n}\n\nconst unitid = flow.get(\"adam_id\");\nconst baseAddr = 16;\n\nlet m1 = { payload: { fc: 5, unitid: unitid, address: baseAddr + ch1, value: s1, quantity: 1} };\nlet m2 = { payload: { fc: 5, unitid: unitid, address: baseAddr + ch2, value: s2, quantity: 1 } };\nlet m3 = { payload: { fc: 5, unitid: unitid, address: baseAddr + ch3, value: s3, quantity: 1 } };\n\n// Devuelve 3 mensajes (un output): se enviarán en orden\nreturn [[m1, m2, m3]];\n\n\n",
"outputs": 1,
"timeout": "",
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 2140,
"y": 940,
"wires": [
[
"e6e4d33e9470fbca",
"2f0ca87e8afc0d73",
"d8112ca72e0a342d"
]
]
},
{
"id": "e6e4d33e9470fbca",
"type": "debug",
"z": "576f3807f61f5098",
"g": "e928e581c722fe25",
"name": "DBG Semaforos",
"active": true,
"tosidebar": true,
"complete": "true",
"targetType": "full",
"x": 2370,
"y": 940,
"wires": []
},
{
"id": "2f0ca87e8afc0d73",
"type": "function",
"z": "576f3807f61f5098",
"g": "e928e581c722fe25",
"name": "Status",
"func": "\nlet flag1 = flow.get('S1') || \"RED\";\nlet flag2 = flow.get('S2') || \"RED\";\nlet flag3 = flow.get(\"S3\") || \"RED\";\n\n// Creamos el texto con un dot verde o rojo por flag\nlet statusText = `S1:${flag1 === \"GREEN\" ? '🟢' : '🔴'} S2:${flag2 === \"GREEN\" ? '🟢' : '🔴'} S3:${flag3 === \"GREEN\" ? '🟢' : '🔴'} `;\n\nnode.status({\n fill: \"grey\", \n shape: \"dot\", // Mantén el shape como \"dot\" para no romper estilo\n text: statusText\n});\n\nreturn msg;\n",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 2340,
"y": 880,
"wires": [
[]
]
},
{
"id": "06c0e45f61e0d364",
"type": "function",
"z": "576f3807f61f5098",
"g": "e928e581c722fe25",
"name": "Manual / Auto",
"func": "// Espera: msg.payload = \"AUTO\" o \"MANUAL\"\nconst m = (String(msg.payload || '')).toUpperCase();\nif (m !== 'AUTO' && m !== 'MANUAL') return null;\n\nflow.set('mode', m);\nlet statusText = `Modo: ${m} `\nlet color = \"red\"\nif (m === \"AUTO\") color = \"green\";\nnode.status({\n fill: color, \n shape: \"dot\", // Mantén el shape como \"dot\" para no romper estilo\n text: statusText\n});\n\n// Opcional: al entrar en MANUAL forzar rojo por seguridad\nif (m === 'MANUAL') {\n return { topic: 'MAN/SET_SEMS', payload: { S1:'RED', S2:'RED', S3:'RED', reason:'enter_manual', ts:Date.now() } };\n}\n\n// Al volver a AUTO, puedes forzar un re-sync:\n// (a) mandar el último autoSems conocido\nconst autoS = flow.get('autoSems');\nif (autoS) return { topic:'CMD/SET_SEMS', payload:{...autoS, reason:'back_to_auto', ts:Date.now()} };\n\n// (b) o simplemente no mandar nada y dejar que el dispatcher lo haga en el siguiente evento\n\nreturn null;\n",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1930,
"y": 880,
"wires": [
[
"49a1dfc2d343bdbb"
]
]
},
{
"id": "49a1dfc2d343bdbb",
"type": "function",
"z": "576f3807f61f5098",
"g": "e928e581c722fe25",
"name": "Selector",
"func": "const mode = flow.get('mode') ?? 'AUTO';\nconst t = msg.topic || '';\n\nconst B = flow.get('B_OCC') ? 1 : 0;\n\n// Guardar “lo último pedido” por cada canal (útil para debug)\nif (t === 'CMD/SET_SEMS') flow.set('autoSems', msg.payload);\nif (t === 'MAN/SET_SEMS') flow.set('manualSems', msg.payload);\n\n// Seguridad opcional (muy recomendada):\n// Si báscula ocupada, no permitas ningún verde (ni en manual)\nfunction forceNoGreen(p){\n return { S1:'RED', S2:'RED', S3:'RED', ts:Date.now(), reason:'B_OCC_safety' };\n}\n\n// ---- MODO AUTO ----\nif (mode === 'AUTO') {\n if (t === 'CMD/SET_SEMS') {\n // En auto, pasan comandos automáticos\n // (si quieres seguridad dura:)\n if (B) msg.payload = forceNoGreen(msg.payload);\n return msg;\n }\n // En auto, ignoramos comandos manuales\n return null;\n}\n\n// ---- MODO MANUAL ----\nif (mode === 'MANUAL') {\n if (t === 'MAN/SET_SEMS') {\n // convertimos MAN/SET_SEMS a CMD/SET_SEMS para que el handler lo acepte\n msg.topic = 'CMD/SET_SEMS';\n if (B) msg.payload = forceNoGreen(msg.payload);\n return msg;\n }\n // En manual, ignoramos CMD/SET_SEMS del dispatcher\n return null;\n}\n\nreturn null;\n",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1970,
"y": 940,
"wires": [
[
"bc4418d271c32af0",
"4878484f33b6ea38"
]
]
},
{
"id": "4878484f33b6ea38",
"type": "debug",
"z": "576f3807f61f5098",
"g": "e928e581c722fe25",
"name": "debug 2",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 2150,
"y": 880,
"wires": []
},
{
"id": "d95551b3e1091e71",
"type": "link in",
"z": "576f3807f61f5098",
"g": "4a6a83419193ac56",
"name": "EV IN (DEBUG)",
"links": [
"c4159a812594fc62",
"74ea72530de212a4",
"15131de35b7d0425",
"2dc673e9f13e0f0b"
],
"x": 2215,
"y": 720,
"wires": [
[
"3e36b8139450735b"
]
]
},
{
"id": "3e36b8139450735b",
"type": "debug",
"z": "576f3807f61f5098",
"g": "4a6a83419193ac56",
"name": "DBG Eventos",
"active": false,
"tosidebar": true,
"complete": "true",
"targetType": "full",
"x": 2435,
"y": 720,
"wires": []
},
{
"id": "8dec1861e45c773b",
"type": "modbus-flex-getter",
"z": "576f3807f61f5098",
"name": "",
"showStatusActivities": false,
"showErrors": false,
"showWarnings": true,
"logIOActivities": false,
"server": "a00b83865450b74d",
"useIOFile": false,
"ioFile": "",
"useIOForPayload": false,
"emptyMsgOnFail": false,
"keepMsgProperties": false,
"delayOnStart": false,
"startDelayTime": "",
"x": 310,
"y": 460,
"wires": [
[
"130015e21da95201"
],
[
"c47982766953c4b0"
]
]
},
{
"id": "e19c952702b3de9b",
"type": "inject",
"z": "576f3807f61f5098",
"name": "",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "{ \"fc\": 2, \"unitid\": 1, \"address\": \"0\", \"quantity\": 3 }",
"payloadType": "json",
"x": 130,
"y": 460,
"wires": [
[
"8dec1861e45c773b"
]
]
},
{
"id": "c47982766953c4b0",
"type": "modbus-response",
"z": "576f3807f61f5098",
"name": "",
"registerShowMax": 20,
"x": 510,
"y": 540,
"wires": []
},
{
"id": "3845edd7f91846ec",
"type": "debug",
"z": "576f3807f61f5098",
"name": "debug 31",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 780,
"y": 260,
"wires": []
},
{
"id": "d8112ca72e0a342d",
"type": "modbus-flex-write",
"z": "576f3807f61f5098",
"g": "e928e581c722fe25",
"name": "",
"showStatusActivities": false,
"showErrors": false,
"showWarnings": true,
"server": "a00b83865450b74d",
"emptyMsgOnFail": false,
"keepMsgProperties": false,
"delayOnStart": false,
"startDelayTime": "",
"x": 2380,
"y": 1000,
"wires": [
[],
[
"b6317c2d3d6d070c"
]
]
},
{
"id": "dd43a77de63f2443",
"type": "inject",
"z": "576f3807f61f5098",
"g": "e928e581c722fe25",
"name": "",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "{ \"value\":[false,true,true], \"fc\": 15, \"unitid\": 1, \"address\": 16, \"quantity\": 3}",
"payloadType": "json",
"x": 2190,
"y": 1000,
"wires": [
[
"d8112ca72e0a342d"
]
]
},
{
"id": "c42bdd023acc8dc3",
"type": "uibuilder",
"z": "576f3807f61f5098",
"g": "350484a2e764ef67",
"name": "",
"topic": "",
"url": "semaforos",
"okToGo": true,
"fwdInMessages": false,
"allowScripts": false,
"allowStyles": false,
"copyIndex": true,
"templateFolder": "blank",
"extTemplate": "",
"showfolder": false,
"reload": true,
"sourceFolder": "src",
"deployedVersion": "7.4.3",
"showMsgUib": false,
"title": "",
"descr": "",
"editurl": "vscode://file/home/trypton/.node-red/projects/raspipeso20_10_25/uibuilder/semaforos/?windowId=_blank",
"x": 3050,
"y": 500,
"wires": [
[
"4333f08dfc27e390",
"ad67876e3fd4b4bb"
],
[]
]
},
{
"id": "85b34266c740174d",
"type": "inject",
"z": "576f3807f61f5098",
"g": "350484a2e764ef67",
"name": "",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "{\"payload\":{\"pantalla\":\"menu-principal\"}}",
"payloadType": "json",
"x": 2860,
"y": 380,
"wires": [
[
"c42bdd023acc8dc3"
]
]
},
{
"id": "4333f08dfc27e390",
"type": "function",
"z": "576f3807f61f5098",
"g": "350484a2e764ef67",
"name": "buttons \\n decoder",
"func": "\nif (msg.topic === \"tipoconfig\"){\n let tiposDatosGlobal = flow.get(\"tiposDatosGlobal\") || {\"1\": [], \"2\": [], \"3\": [], \"4\": [], \"5\": [], \"6\": []};\n\nconst tipo = msg.payload.tipo; // ej: 2\nconst datos = msg.payload.datos; // array con los parámetros\n\ntiposDatosGlobal[tipo] = datos;\n\nflow.set(\"tiposDatosGlobal\", tiposDatosGlobal);\n\nreturn ;\n}\nelse if (msg.payload.hasOwnProperty(\"seccion\")) {\n\n\n /* Los diferentes \"case\" corresponden a la pulsacion de botones de los diferentes menus de configuracion\n Cada menu solicitado mediante el click de un boton devuelve el nombre del \"div\" en el archivo html que debemos habilitar\n para que se muestre en pantalla.\n Si la entrada no consigue ningun case que se ajuste entonces caemos en el \"default\" y ahi buscamos primero si hay\n algun mensaje en \"origen\", lo que corresponde a la actualizacion de alguna de las tablas dinamicas que se generan\n para modificar cada tipo de parametro. Luego se revisan los \"modalCallback\" los cuales no indican que la respuesta\n de la notificacion de alguna accion importante ha sido positiva (\"SI\").\n \n */\n\n switch (msg.payload.seccion) {\n\n case \"menu-principal\": // boton de configuracion en pantalla principal para acceder al menu principal \n flow.set(\"snapshot_cam\", false)\n flow.set(\"escuchar_cam\", false)\n return { payload: { pantalla: \"menu-principal\" } }\n\n case \"editar\": // boton para acceder al menu de edicion de la configuracion\n return { payload: { pantalla: \"editar\" } }\n\n case \"pruebas\": // boton para acceder al menu de pruebas del sistema\n return { payload: { pantalla: \"pruebas\" } }\n\n case \"semaforo\": // boton para acceder al menu prueba del semaforo\n return { payload: { pantalla: \"semaforo\" } }\n\n case \"otras\": // boton para acceder al menu de otras acciones\n return { payload: { pantalla: \"otras\" } }\n\n case \"regresar\": // boton para regresar al menu principal desde otras acciones y desde menu de pruebas del sistema\n return { payload: { pantalla: \"menu-principal\" } }\n\n case \"salvar\": // boton para almacenar los cambios en el archivo de configuracion en el menu de edicion de la configuracion\n return ([{ payload: { pantalla: \"editar\" }, modalText: \"Desea MODIFICAR el archivo de CONFIGURACION ?\", modalCallback: \"modificar\" }, null, null, null])\n\n case \"cancelar\": // boton para cancelar y salir del menu de editar configuracion\n return ([{ payload: { pantalla: \"menu-principal\" } }, null])\n\n case \"semaforo_on\": // boton para poner el semaforo en rojo\n flow.set(\"semaforo\", \"red\")\n return ([{ payload: { pantalla: \"semaforo\" }, semaforo: \"firebrick\" }, null, { payload: 0 }])\n\n case \"semaforo_off\": // boton para poner el semaforo en verde\n flow.set(\"semaforo\", \"green\")\n return ([{ payload: { pantalla: \"semaforo\" }, semaforo: \"green\" }, null, { payload: 1 }])\n\n case \"salir-semaforo\": // boton de salida de la pantalla de prueba del semaforo\n return ({ payload: { pantalla: \"pruebas\" } })\n\n case \"barrera\": // boton de activar barrera\n return ({ payload: { pantalla: \"barrera\" }, barrera: false })\n\n case \"general\": // boton para ver / modificar tabla con los parametros generales\n return ([{ payload: { pantalla: \"tabla-general\" }, topic: \"general\", tabla: flow.get(\"config_general\") }, null, null])\n\n case \"camara\": // boton para ver / modificar tabla con los parametros de la camara\n return ([{ payload: { pantalla: \"tabla-general\" }, topic: \"camara\", tabla: flow.get(\"config_camara\") }, null, null])\n\n case \"conexion\": // boton para ver / modificar tabla con los parametros de conexion\n return ([{ payload: { pantalla: \"tabla-general\" }, topic: \"conexion\", tabla: flow.get(\"config_conex\") }, null, null])\n\n case \"sensores\": // boton para ver / modificar tabla con los parametros de sensores\n return ([{ payload: { pantalla: \"tabla-general\" }, topic: \"sensores\", tabla: flow.get(\"config_sensor\") }, null, null])\n\n case \"mensajes\": // boton para ver / modificar tabla con los parametros de los mensajes\n return ([{ payload: { pantalla: \"tabla-general\" }, topic: \"mensajes\", tabla: flow.get(\"config_mensajes\") }, null, null])\n\n case \"tiempos\": // boton para ver / modificar tabla con los parametros del tiempo\n return ([{ payload: { pantalla: \"tabla-general\" }, topic: \"tiempos\", tabla: flow.get(\"config_tiempos\") }, null, null])\n\n case \"aplicar\": // boton para aplicar la configuracion al RPi, en el menu-principal\n return ([{ payload: { pantalla: \"menu-principal\" }, topic: \"menu-principal\", modalText: \"Desea Recargar la CONFIGURACION ?\", modalCallback: \"recargar\" }, null, null])\n\n case \"reboot\": // boton para resetear el RPi en el menu de otras acciones\n return ([{ payload: { pantalla: \"otras\" }, topic: \"otras\", modalText: \"Desea Reiniciar el TOTEM ?\", modalCallback: \"reiniciar\" }, null, null])\n\n case \"apagar\": // boton para apagar el RPi en el menu de otras acciones\n return ([{ payload: { pantalla: \"otras\" }, topic: \"otras\", modalText: \"Desea APAGAR el TOTEM ?\", modalCallback: \"apagar\" }, null, null])\n\n case \"snapshot\": // boton para pedir snapshot\n flow.set(\"snapshot_cam\", true)\n flow.set(\"escuchar_cam\", false)\n let msgrqt = {}\n msgrqt.payload = {}\n msgrqt.url = \"http://\" + flow.get(\"camara_IP\") + \"/cgi-bin/trafficSnap.cgi?action=manSnap&channel=1\"\n msgrqt.topic = \"snapshot_rqtd\"\n return ([{ payload: { pantalla: \"snapshot\" }, topic: \"snapshot\" }, null, null, null, null, msgrqt])\n\n case \"escuchar\": // boton para escuchar camara ( recibir todas la imagenes que envie)\n flow.set(\"snapshot_cam\", false)\n flow.set(\"escuchar_cam\", true)\n return ([{ payload: { pantalla: \"escuchar\" }, topic: \"escuchar\" }, null, null])\n\n case \"left\": // Boton para ir a la imagen anterior en escuchar camara\n flow.set(\"getfromarray\", \"left\")\n return ([{ payload: { pantalla: \"escuchar\" }, topic: \"escuchar\" }, null, null])\n\n case \"right\": // Boton para ir a la siguiente imagen en escuchar camara\n flow.set(\"getfromarray\", \"right\")\n return ([{ payload: { pantalla: \"escuchar\" }, topic: \"escuchar\" }, null, null])\n\n case \"salir-snapshot\": // boton de salida de la pantalla de ver snapshot\n flow.set(\"snapshot_cam\", false)\n flow.set(\"escuchar_cam\", false)\n return ({ payload: { pantalla: \"pruebas\" } })\n\n case \"salir-barrera\": // boton de salida de la barrera\n\n return ({ payload: { pantalla: \"pruebas\" }, barrera: false })\n\n case \"salir-escuchar\": // boton de salida de la pantalla de escuchar la camara\n flow.set(\"snapshot_cam\", false)\n flow.set(\"escuchar_cam\", false)\n flow.set(\"snapshot_array\", [])\n flow.set(\"snapshot_readindex\", 0)\n flow.set(\"snapshot_saveindex\", 0)\n flow.set(\"snapshot_indexmax\", 0)\n flow.set(\"getfromarray\", \"\")\n return ({ payload: { pantalla: \"pruebas\" } })\n\n case \"activar-barrera\": // boton de activar barrera\n node.send([{ payload: { pantalla: \"barrera\" }, barrera: true }, null, null, null, null, null, null, { payload: 0 }])\n setTimeout(function () {\n node.send([null, null, null, null, null, null, null, { payload: 1 }])\n }, flow.get(\"relay2_pulse\"));\n return\n\n case \"cancel-table\": // boton de CANCEL de las tablas de configuracion de parametros\n return ({ payload: { pantalla: \"editar\" } })\n\n case \"salir\": // boton de salida del menu principal\n return ({ payload: { pantalla: \"menu-principal\" } })\n\n case \"tipos\": // boton de salida al menu editar\n msg.payload = {pantalla: \"tipos\", tiposDatos: flow.get(\"tiposDatosGlobal\") || {\"1\": [], \"2\": [], \"3\": [], \"4\": [], \"5\": [], \"6\": []}};\n return (msg)\n\n case \"volver_tipos\": // boton de salida al menu editar\n return ({ payload: { pantalla: \"editar\" } })\n\n case \"ayuda\": // boton de salida al menu editar\n return ({ payload: { pantalla: \"ayuda\" } })\n\n case \"salir-ayuda\": // boton de salida al menu editar\n return ({ payload: { pantalla: \"editar\" } })\n\n default: // chequeamos si tenemos acciones a realizar para lo cual revisamos el \"origen\" (actualizar en memoria sin preguntar usuario) y/o el \"modalCallback\" con esto sabemos de que tabla es el mensaje enviado por el MODAL\n node.send([null, null, null, null, null, null, msg])\n if (msg.origen == \"actualizar\") {// salvar en memoria la configuracion que ha sido modificada (el Topic nos dice cual configuracion)\n let myconfig = msg.payload.table // table contiene el resultado de leer la tabla con todos los valores normalizados en uibuilder\n \n switch (msg.topic) { // guardamos cambios en el tipo de configuracion correspondiente\n case \"general\":\n flow.set(\"config_general\", myconfig)\n break;\n case \"conexion\":\n flow.set(\"config_conex\", myconfig)\n break;\n case \"tiempos\":\n flow.set(\"config_tiempos\", myconfig)\n break;\n }\n return { payload: { pantalla: \"editar\" } }\n }\n\n \n msg.hello = \"hello 3\"\n node.send([null, null, null, null, null, null, msg])\n return null\n\n }\n} // final del SWITCH\nelse if (msg.modalCallback == \"modificar\") {\n /*\n let config_totem = []\n config_totem = flow.get(\"config_general\").concat(flow.get(\"config_camara\"), flow.get(\"config_conex\"), flow.get(\"config_sensor\"), flow.get(\"config_mensajes\"), flow.get(\"config_tiempos\"), flow.get(\"tiposDatosGlobal\"))\n flow.set(\"config_totem\", config_totem)\n let jsontest = toJson(flow.get(\"config_totem\"));\n */\n let config_totem = {\n general: flow.get(\"config_general\") || [],\n camara: flow.get(\"config_camara\") || [],\n conexion: flow.get(\"config_conex\") || [],\n sensores: flow.get(\"config_sensor\") || [],\n mensajes: flow.get(\"config_mensajes\") || [],\n tiempos: flow.get(\"config_tiempos\") || [],\n tipos: flow.get(\"tiposDatosGlobal\") || {}\n };\n\n flow.set(\"config_totem\", config_totem);\n\n msg.payload = JSON.stringify(config_totem, null, 2) + \"\\n\\r\";\n //msg.payload = JSON.stringify(jsontest, null, 2) + \"\\n\\r\"; // el factor 2 agrega 2 espacio al inicio de cada elemento\n //y agrega tambien una linea lo cual es interesante para la legibilidad del formato y para que los clientes SFTP puedan\n //leer bien el archivo, como es el caso de Mobaxterm.\n\n return ([{ payload: { pantalla: \"menu-principal\" } }, msg, null])\n}\nelse if (msg.modalCallback == \"recargar\") {\n return ([null, null, null, { payload: 1 }])\n}\n\nelse if (msg.modalCallback == \"reiniciar\") {\n return ([null, null, null, null, { payload: \"sudo shutdown -r now\" }])\n}\n\nelse if (msg.modalCallback == \"apagar\") {\n return ([null, null, null, null, { payload: \"sudo poweroff\" }])\n}\n\n//============================================== FUNCIONES ==============================================================\n//esta funcion convierte un array con elementos [parametro, valor] en un objeto Json respetando los tipos de parametro (numerico, booleano, texto)\nfunction toJson(data) {\n let newJson = {}\n /*\n data.forEach(item => {\n let param = item.parametro;\n let value = item.valor;\n */\n for (let i = 0; i < data.length; i++) {\n let param = data[i].parametro;\n let value = data[i].valor;\n\n // Parseo de tipos igual que antes\n if (typeof value === 'string') {\n if (value.toLowerCase() === \"true\") {\n newJson[param] = true;\n } else if (value.toLowerCase() === \"false\") {\n newJson[param] = false;\n } else if (!isNaN(value) && value.trim() !== \"\") {\n newJson[param] = Number(value);\n } else {\n newJson[param] = value;\n }\n } else {\n // Por si el value ya viene como booleano o número real\n newJson[param] = value;\n }\n }\n return (newJson)\n}\n\n//=======================================================================================================================================\n\nfunction validarIP(ip) {\n const regex = /^(25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]?\\d)\\.(25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]?\\d)\\.(25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]?\\d)\\.(25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]?\\d)$/;\n return regex.test(ip);\n}",
"outputs": 8,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 3270,
"y": 480,
"wires": [
[
"1e52a0895d377969",
"2f80166bb47a752f"
],
[
"c398ee4ceb30937f",
"136e09e5233e209b"
],
[
"adbe70916e20904f"
],
[
"6a6d080c74f2da43"
],
[
"081628af33270569"
],
[
"4847a19f86d704fc"
],
[
"711b20eac053af7f"
],
[
"7aa2adada5ca928e",
"77eb0df85abf7f16"
]
]
},
{
"id": "ad67876e3fd4b4bb",
"type": "debug",
"z": "576f3807f61f5098",
"g": "350484a2e764ef67",
"name": "debug 33",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 3250,
"y": 380,
"wires": []
},
{
"id": "adbe70916e20904f",
"type": "link out",
"z": "576f3807f61f5098",
"g": "350484a2e764ef67",
"name": "set semaforo",
"mode": "link",
"links": [],
"x": 3485,
"y": 500,
"wires": []
},
{
"id": "1e52a0895d377969",
"type": "link out",
"z": "576f3807f61f5098",
"g": "350484a2e764ef67",
"name": "feedback",
"mode": "link",
"links": [
"764c5454d21fc52b"
],
"x": 3435,
"y": 420,
"wires": []
},
{
"id": "2f80166bb47a752f",
"type": "debug",
"z": "576f3807f61f5098",
"g": "350484a2e764ef67",
"name": "debug 34",
"active": false,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 3470,
"y": 340,
"wires": []
},
{
"id": "6a6d080c74f2da43",
"type": "link out",
"z": "576f3807f61f5098",
"g": "350484a2e764ef67",
"name": "aplicar_config",
"mode": "link",
"links": [
"53019ab29a07f8f5"
],
"x": 3485,
"y": 540,
"wires": []
},
{
"id": "081628af33270569",
"type": "link out",
"z": "576f3807f61f5098",
"g": "350484a2e764ef67",
"name": "reiniciar",
"mode": "link",
"links": [],
"x": 3485,
"y": 580,
"wires": []
},
{
"id": "49e2b8c24d0ee245",
"type": "function",
"z": "576f3807f61f5098",
"g": "350484a2e764ef67",
"name": "datos de\\n pesada y \\n snapshot",
"func": "if(msg.payload.hasOwnProperty(\"tiposDatos\")){\n return(msg)\n\n}\nelse {\n let msgmix = {}\n if (flow.get(\"snapshot_cam\") && msg.payload.hasOwnProperty(\"Picture\")) { // si recibimos snapshot extraemos informacion necesaria para mostrar en la pagina de snapshot\n msgmix.plate = msg.payload.Picture.Plate.PlateNumber // guardamos el numero de matricula\n msgmix.picture = \"data:image/png;base64,\" + msg.payload.Picture.NormalPic.Content\n msgmix.plate_time = msg.payload.Picture.SnapInfo.SnapTime\n msgmix.camera_id = msg.payload.Picture.SnapInfo.DeviceID\n }\n\n\n else if (flow.get(\"escuchar_cam\")) { // Si estamos esperando snapshots de la camara o acciones del usuario en botones left o right\n let snapshot_array = flow.get('snapshot_array') || [];\n let snapshot_readindex = flow.get('snapshot_readindex') || 0;\n let snapshot_saveindex = flow.get('snapshot_saveindex') || 0;\n let snapshot_indexmax = flow.get('snapshot_indexmax') || 0;\n\n\n if (flow.get(\"getfromarray\") == \"left\") {\n msgmix.buttons = true // indica al index.js (del uibuilder) que no tiene que inicializar la pantalla de escuchar camara\n if (snapshot_readindex > 0) {\n snapshot_readindex = snapshot_readindex - 1\n flow.set(\"snapshot_readindex\", snapshot_readindex)\n msgmix = snapshot_array[snapshot_readindex]\n msgmix.snapshot_readindex = snapshot_readindex\n msgmix.snapshot_indexmax = snapshot_indexmax\n }\n }\n else if (flow.get(\"getfromarray\") == \"right\") {\n msgmix.buttons = true // indica al index.js (del uibuilder) que no tiene que inicializar la pantalla de escuchar camara\n if (snapshot_readindex < flow.get(\"snapshot_indexmax\") - 1) {\n snapshot_readindex = snapshot_readindex + 1\n flow.set(\"snapshot_readindex\", snapshot_readindex)\n msgmix = snapshot_array[snapshot_readindex]\n msgmix.snapshot_readindex = snapshot_readindex\n msgmix.snapshot_indexmax = snapshot_indexmax\n\n }\n }\n else {\n if (msg.payload.hasOwnProperty(\"Picture\")) { // si recibimos snapshot extraemos informacion necesaria para mostrar en la pagina de escuchar camara\n msgmix.plate = msg.payload.Picture.Plate.PlateNumber // guardamos el numero de matricula\n msgmix.picture = \"data:image/png;base64,\" + msg.payload.Picture.NormalPic.Content\n msgmix.plate_time = msg.payload.Picture.SnapInfo.SnapTime\n msgmix.camera_id = msg.payload.Picture.SnapInfo.DeviceID\n msgmix.buttons = true\n\n\n snapshot_array[snapshot_saveindex] = RED.util.cloneMessage(msgmix); // Clonamos para evitar referencias\n flow.set(\"snapshot_array\", snapshot_array)\n flow.set(\"snapshot_saveindex\", snapshot_saveindex + 1)\n flow.set(\"snapshot_indexmax\", snapshot_indexmax + 1)\n\n if (snapshot_readindex == snapshot_saveindex - 1) {\n flow.set(\"snapshot_readindex\", snapshot_readindex + 1) // en caso que el index de lectura este al final, se incrementa al llegar una nueva imagen\n }\n msgmix.snapshot_indexmax = flow.get(\"snapshot_indexmax\")\n msgmix.snapshot_readindex = flow.get(\"snapshot_readindex\")\n }\n }\n }\n\n else {\n\n let msgmix = {}\n\n }\n flow.set(\"getfromarray\", \"\")\n\n //*****************************************Agrega datos de la pesada para PAGINA PRINCIPAL************************************************************ */\n if (!msg.payload.hasOwnProperty(\"Picture\")) {\n msgmix = Object.assign({}, msg, msgmix); // msg sobrescribe valores si hay conflictos\n }\n msgmix.peso = flow.get(\"peso\")\n msgmix.semaforo_color = flow.get(\"semaforo\")\n msgmix.matricula = flow.get(\"matricula\")\n msgmix.peso_estable_flag = flow.get(\"peso_estable_flag\")\n msgmix.vial = flow.get(\"numero_vial\")\n msgmix.totem_id = flow.get(\"totem_id\")\n msgmix.iplan = flow.get(\"myip\")\n msgmix.ipsupervisor = flow.get(\"websocket_url\")\n msgmix.snapshot_cam = flow.get(\"snapshot_cam\")\n msgmix.escuchar_cam = flow.get(\"escuchar_cam\")\n return msgmix;\n}",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 2830,
"y": 500,
"wires": [
[
"c42bdd023acc8dc3",
"a4992bd24a1acc12"
]
]
},
{
"id": "a4992bd24a1acc12",
"type": "debug",
"z": "576f3807f61f5098",
"g": "350484a2e764ef67",
"name": "debug 35",
"active": false,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 3050,
"y": 400,
"wires": []
},
{
"id": "4847a19f86d704fc",
"type": "link out",
"z": "576f3807f61f5098",
"g": "350484a2e764ef67",
"name": "snapshot_rqst",
"mode": "link",
"links": [],
"x": 3485,
"y": 620,
"wires": []
},
{
"id": "711b20eac053af7f",
"type": "debug",
"z": "576f3807f61f5098",
"g": "350484a2e764ef67",
"name": "debug 36",
"active": false,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 3530,
"y": 660,
"wires": []
},
{
"id": "37b8b001f2c455d2",
"type": "comment",
"z": "576f3807f61f5098",
"g": "350484a2e764ef67",
"name": "CONFIGURACION DE PARAMETROS Y PRUEBAS ",
"info": "Este grupo de nodos permiten servir una pagina web para que el usuario desde un movil pueda realizar la gestion de configuracion\nde los principales parametros del Totem asi como realizar algunas pruebas en la camara y en el semaforo.\nEl punto central de este grupo es el paquete UIBUILDER para nodered el cual convive sin inconvenientes con el DASHBOARD de nodered.\nEl uibuilder posee un nodo para cada path de acceso que deseemos habilitar. Cada nodo es independiente del otro en el sentido que \ncada uno dispone de un index.html, un index.js y un index.css ue permiten configurar la pagina que sirve el nodo. \nRecordemos que estos archivos se ejecutan en el cliente, por lo tanto para que el index.js tenga acceso a los datos de nodered y nodered \na los datos generados por el cliente, el nodo uibuilder establece una comunicacion websocket transparente para el usuario de manera\na poder intercambiar informacion entre nodered y el browser. Los elementos fundamentales en index.js para llevar a cabo esta comunicacion\nson las funciones \"uibuilder.onChange()\" para recibir los mensajes de nodered y \"uibuilder.send()\" para enviar a nodered los datos generados \npor el cliente.\nEl nodo \"buttons decoder\" fundamentalmente se dedica a interpretar los comandos del cliente (botones que ha pulsado el usuario) de manera a recopilar\nla informacion adecuada para enviar al uibuilder nuevamente de manera que cambie de pagina o muestre ciertos datos, o tambien a ejecutar\nlos comandos solicitados como reboot, apagar dispositivo, encender / apagar semaforo, ...\nEl nodo de entrada \"datos de pesada y snapshot\" recibe por un lado los snapshots enviados por la camara y extrae la informacion necesaria \npara enviar al cliente, y por otro lado siempre que haya alguna recepcion de datos del Totem se preparan los datos correspondientes para \ncolocarlos en la pantalla principal del uibuilder o pantalla de monitoreo.\nCuando mostramos un array de numeros, la tabla dinamica lo muestra o bien como numero o bien como texto de manera que luego cuando\nlo leemos cometemos un error ya que en el archivo nos va a quedar un texto o unos numeros sueltos y no un array. Para solucionar eso \nhemos hecho una funcion formatForCell(v) que asegura que se muestre en pantalla el arry ejemplo [3,1,2].\nPara la lectura de la tabla dinamica hemos puesto en la funcion SendRow (osea antes de enviar el contenido de la tabla que se recoge de la pantalla)\nun codigo para asegurar que se escriben correctamente tanto los booleano, enteros, flotantes y arrays.",
"x": 2960,
"y": 640,
"wires": []
},
{
"id": "7aa2adada5ca928e",
"type": "link out",
"z": "576f3807f61f5098",
"g": "350484a2e764ef67",
"name": "activa-barrera",
"mode": "link",
"links": [],
"x": 3485,
"y": 700,
"wires": []
},
{
"id": "77eb0df85abf7f16",
"type": "debug",
"z": "576f3807f61f5098",
"g": "350484a2e764ef67",
"name": "debug 37",
"active": false,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 3530,
"y": 740,
"wires": []
},
{
"id": "c398ee4ceb30937f",
"type": "debug",
"z": "576f3807f61f5098",
"g": "350484a2e764ef67",
"name": "debug 38",
"active": false,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 3590,
"y": 540,
"wires": []
},
{
"id": "136e09e5233e209b",
"type": "file",
"z": "576f3807f61f5098",
"g": "350484a2e764ef67",
"name": "guardar config",
"filename": "/home/trypton/.node-red/projects/config_semaforos.txt",
"filenameType": "str",
"appendNewline": true,
"createDir": true,
"overwriteFile": "true",
"encoding": "none",
"x": 3570,
"y": 460,
"wires": [
[]
]
},
{
"id": "d6c429592c6cfdab",
"type": "link in",
"z": "576f3807f61f5098",
"g": "350484a2e764ef67",
"name": "link in 1",
"links": [],
"x": 2725,
"y": 540,
"wires": [
[
"49e2b8c24d0ee245"
]
]
},
{
"id": "764c5454d21fc52b",
"type": "link in",
"z": "576f3807f61f5098",
"g": "350484a2e764ef67",
"name": "entrada uibuilder-1",
"links": [
"1e52a0895d377969"
],
"x": 2725,
"y": 460,
"wires": [
[
"49e2b8c24d0ee245"
]
]
},
{
"id": "a2fcbe68dc4e70bd",
"type": "inject",
"z": "576f3807f61f5098",
"g": "6f7ca09a49c974d3",
"name": "",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": true,
"onceDelay": "2",
"topic": "",
"payload": "1",
"payloadType": "num",
"x": 2790,
"y": 880,
"wires": [
[
"6b89fc341280ad29"
]
]
},
{
"id": "6b89fc341280ad29",
"type": "file in",
"z": "576f3807f61f5098",
"g": "6f7ca09a49c974d3",
"name": "read-file",
"filename": "/home/trypton/.node-red/projects/config_semaforos.txt",
"filenameType": "str",
"format": "utf8",
"chunk": false,
"sendError": false,
"encoding": "none",
"allProps": false,
"x": 2960,
"y": 880,
"wires": [
[
"e02d2079825430db"
]
]
},
{
"id": "e02d2079825430db",
"type": "function",
"z": "576f3807f61f5098",
"g": "6f7ca09a49c974d3",
"name": "config edit",
"func": "const config = JSON.parse(msg.payload);\nmsg ={}; // clear msg object\nlet read;\nlet config_general = []\nlet config_tiempos = []\n//let config_mensajes = []\n//let config_sensor = []\n//let config_camara = []\nlet config_conex = []\n//let config_tipos = []\n\nconfig.general = toObject(config.general);\n//config.camara = toObject(config.camara);\nconfig.conexion = toObject(config.conexion);\n//config.sensores = toObject(config.sensores);\n//config.mensajes = toObject(config.mensajes);\nconfig.tiempos = toObject(config.tiempos);\n\n\n\n/*\n// Aseguramos que cada tipo esté como array vacío si no tiene nada\nfor (let i = 1; i <= 6; i++) {\n if (!Array.isArray(tiposOriginales[i])) {\n tiposOriginales[i] = [];\n }\n}\nflow.set(\"tiposDatosGlobal\", tiposOriginales);\n\nconfig.tipos = tiposOriginales;\n*/\n//=================================================== GENERAL =====================================================\nread = config.general.peso_min_on; // peso minimo para definir estado ocupado\nconfig_general.push({ \"parametro\": \"peso_min_on\", \"valor\": read })\nflow.set(\"P_on\", read);\n\nread = config.general.peso_min_off; // peso minimo para definir estado desocupado\nconfig_general.push({ \"parametro\": \"peso_min_off\", \"valor\": read })\nflow.set(\"P_off\", read);\n\nread = config.general.prioridades; \nconfig_general.push({ \"parametro\": \"prioridades\", \"valor\": read })\nflow.set(\"order\", read)\n\nread = config.general.vehiculos_acceso1; \nconfig_general.push({ \"parametro\": \"vehiculos_acceso1\", \"valor\": read })\nflow.set(\"N1\", read)\n\nread = config.general.vehiculos_acceso2; \nconfig_general.push({ \"parametro\": \"vehiculos_acceso2\", \"valor\": read })\nflow.set(\"N2\", read)\n\nread = config.general.vehiculos_acceso3; \nconfig_general.push({ \"parametro\": \"vehiculos_acceso3\", \"valor\": read })\nflow.set(\"N3\", read)\n\nread = config.general.modo; \nconfig_general.push({ \"parametro\": \"modo\", \"valor\": read })\nflow.set(\"mode\", read)\n\n\n//===================================================CONEXION =====================================================\n\nread = config.conexion.DO_semaforo1;\nconfig_conex.push({ \"parametro\": \"DO_semaforo1\", \"valor\": read })\nflow.set(\"DO_semaforo1\", read); \n\nread = config.conexion.DO_semaforo2;\nconfig_conex.push({ \"parametro\": \"DO_semaforo2\", \"valor\": read })\nflow.set(\"DO_semaforo2\", read); \n\nread = config.conexion.DO_semaforo3;\nconfig_conex.push({ \"parametro\": \"DO_semaforo3\", \"valor\": read })\nflow.set(\"DO_semaforo3\", read); \n\nread = config.conexion.DI_Lazo1;\nconfig_conex.push({ \"parametro\": \"DI_Lazo1\", \"valor\": read })\nflow.set(\"DI_Lazo1\", read); \n\nread = config.conexion.DI_Lazo2;\nconfig_conex.push({ \"parametro\": \"DI_Lazo2\", \"valor\": read })\nflow.set(\"DI_Lazo2\", read);\n\nread = config.conexion.DI_Lazo3;\nconfig_conex.push({ \"parametro\": \"DI_Lazo3\", \"valor\": read })\nflow.set(\"DI_Lazo3\", read);\n\nread = config.conexion.nombre_lazo1; \nconfig_conex.push({ \"parametro\": \"nombre_lazo1\", \"valor\": read })\nflow.set(\"nombre_lazo1\", read)\n\nread = config.conexion.nombre_lazo2; \nconfig_conex.push({ \"parametro\": \"nombre_lazo2\", \"valor\": read })\nflow.set(\"nombre_lazo2\", read)\n\nread = config.conexion.nombre_lazo3; \nconfig_conex.push({ \"parametro\": \"nombre_lazo3\", \"valor\": read })\nflow.set(\"nombre_lazo3\", read)\n\nread = config.conexion.adam_id;\nconfig_conex.push({ \"parametro\": \"adam_id\", \"valor\": read })\nflow.set(\"adam_id\", read);\n\nread = config.conexion.base_address;\nconfig_conex.push({ \"parametro\": \"base_address\", \"valor\": read })\nflow.set(\"base_address\", read);\n\nread = config.conexion.numero_vial;\nconfig_conex.push({ \"parametro\": \"numero_vial\", \"valor\": read })\nflow.set(\"numero_vial\", read);\n\n//=================================================== TIEMPOS =====================================================\n\nread = config.tiempos.transito_acceso1\nconfig_tiempos.push({ \"parametro\": \"transito_acceso1\", \"valor\": read })\nflow.set(\"t1_ms\", read);\n\nread = config.tiempos.transito_acceso2\nconfig_tiempos.push({ \"parametro\": \"transito_acceso2\", \"valor\": read })\nflow.set(\"t2_ms\", read);\n\nread = config.tiempos.transito_acceso3\nconfig_tiempos.push({ \"parametro\": \"transito_acceso3\", \"valor\": read })\nflow.set(\"t3_ms\", read);\n\nread = config.tiempos.t_confirm_bascula\nconfig_tiempos.push({ \"parametro\": \"t_confirm_bascula\", \"valor\": read })\nflow.set(\"T_confirm_ms\", read);\n\nread = config.tiempos.t_semaforo1\nconfig_tiempos.push({ \"parametro\": \"t_semaforo1\", \"valor\": read })\nflow.set(\"g1_ms\", read);\n\nread = config.tiempos.t_semaforo2\nconfig_tiempos.push({ \"parametro\": \"t_semaforo2\", \"valor\": read })\nflow.set(\"g2_ms\", read);\n\nread = config.tiempos.t_semaforo3\nconfig_tiempos.push({ \"parametro\": \"t_semaforo3\", \"valor\": read })\nflow.set(\"g3_ms\", read);\n\n\n// Inicializa algunas variables en el sistema interno\n\nflow.set('state','IDLE');\nflow.set('B_OCC',0);\nflow.set('L1',0); flow.set('L2',0); flow.set('L3',0);\n\n// Accesos bloqueados por timeout de verde (Gi)\n// Si blocked[1] = true, el acceso 1 NO puede recibir verde hasta que L1 haga OFF.\nflow.set('blocked', { 1:false, 2:false, 3:false });\n\n\n\nflow.set(\"config_general\", config_general)\nflow.set(\"config_tiempos\", config_tiempos)\nflow.set(\"config_conex\", config_conex)\n//flow.set(\"config_camara\", config_camara)\n//flow.set(\"config_sensor\", config_sensor)\n\nreturn [{topic:'CMD/SET_SEMS', payload:{S1:'RED',S2:'RED',S3:'RED',ts:Date.now()}}, {payload: flow.get(\"mode\")}];\n\n\n//*********************************************************** FUNCIONES **************************************************/\nfunction toObject(array) {\n \n const obj = {};\n array.forEach(el => {\n obj[el.parametro] = el.valor;\n\n });\n return obj;\n}\n",
"outputs": 2,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 3150,
"y": 880,
"wires": [
[
"7c6babaed4914e41"
],
[
"4232fc1ec89537af"
]
]
},
{
"id": "53019ab29a07f8f5",
"type": "link in",
"z": "576f3807f61f5098",
"g": "6f7ca09a49c974d3",
"name": "init semaforos",
"links": [
"6a6d080c74f2da43"
],
"x": 2805,
"y": 940,
"wires": [
[
"6b89fc341280ad29"
]
]
},
{
"id": "7c6babaed4914e41",
"type": "link out",
"z": "576f3807f61f5098",
"g": "6f7ca09a49c974d3",
"name": "salida config",
"mode": "link",
"links": [
"8da927c794b40546"
],
"x": 3285,
"y": 880,
"wires": []
},
{
"id": "130015e21da95201",
"type": "function",
"z": "576f3807f61f5098",
"name": "Extrae Lazos",
"func": "// Espera: msg.payload = [DI0, DI1, DI2, ... DI7]\n// Salidas: [Lazo1] [Lazo2] [Lazo3]\n\n// 1) Validaciones básicas\nif (!Array.isArray(msg.payload)) {\n node.error(\"msg.payload no es un array\", msg);\n return [null, null, null];\n}\n\nconst di = msg.payload;\n\n// 2) Leer la configuración de canal desde contexto\n// Cambia global.get por flow.get si las tienes en flow\nlet ch1 = flow.get(\"DI_Lazo1\");\nlet ch2 = flow.get(\"DI_Lazo2\");\nlet ch3 = flow.get(\"DI_Lazo3\");\n\n// Si vienen como string, convertir a número\nch1 = (ch1 !== undefined && ch1 !== null) ? Number(ch1) : ch1;\nch2 = (ch2 !== undefined && ch2 !== null) ? Number(ch2) : ch2;\nch3 = (ch3 !== undefined && ch3 !== null) ? Number(ch3) : ch3;\n\n// 3) Función helper: extraer valor de canal de forma segura\nfunction readChannel(ch) {\n if (!Number.isInteger(ch) || ch < 0 || ch >= di.length) return undefined;\n const v = di[ch]; // true/false (o a veces 0/1)\n return (v === undefined) ? undefined : (v ? 1 : 0);\n}\n\n// 4) Leer valores según mapeo\nconst v1 = readChannel(ch1);\nconst v2 = readChannel(ch2);\nconst v3 = readChannel(ch3);\n\n// 5) Construir mensajes para cada salida\n// Si no hay valor (canal mal configurado), devolvemos null para no disparar ese RBE\nconst out1 = (v1 === undefined) ? null : { payload: v1, channel: ch1, lazo: 1, _src: \"adam_di\", topic:\"lazo1\" };\nconst out2 = (v2 === undefined) ? null : { payload: v2, channel: ch2, lazo: 2, _src: \"adam_di\", topic:\"lazo2\"};\nconst out3 = (v3 === undefined) ? null : { payload: v3, channel: ch3, lazo: 3, _src: \"adam_di\", topic:\"lazo3\" };\n\n// (Opcional) copiar propiedades útiles del msg original (por si necesitas timestamp, etc.)\nfunction inherit(base, m) {\n if (!m) return null;\n // Copia todo menos payload si quieres mantener estructura\n // Aquí lo dejo simple: hereda algunas claves comunes si existen\n //if (base.topic !== undefined) m.topic = base.topic;\n if (base.timestamp !== undefined) m.timestamp = base.timestamp;\n return m;\n}\n\nreturn [\n inherit(msg, out1),\n inherit(msg, out2),\n inherit(msg, out3)\n];\n",
"outputs": 3,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 590,
"y": 460,
"wires": [
[
"e4a843aa2e13ce4d",
"3845edd7f91846ec"
],
[
"69f5087384c48122",
"176fe4510822e556"
],
[
"fb6d5180204e6bce",
"94f2a26231e45789"
]
]
},
{
"id": "b6317c2d3d6d070c",
"type": "modbus-response",
"z": "576f3807f61f5098",
"g": "e928e581c722fe25",
"name": "",
"registerShowMax": 20,
"x": 2490,
"y": 1040,
"wires": []
},
{
"id": "217697ee4e884321",
"type": "link in",
"z": "576f3807f61f5098",
"g": "e928e581c722fe25",
"name": "modo",
"links": [
"4232fc1ec89537af"
],
"x": 1715,
"y": 880,
"wires": [
[
"06c0e45f61e0d364"
]
]
},
{
"id": "4232fc1ec89537af",
"type": "link out",
"z": "576f3807f61f5098",
"g": "6f7ca09a49c974d3",
"name": "link out 5",
"mode": "link",
"links": [
"217697ee4e884321"
],
"x": 3285,
"y": 940,
"wires": []
},
{
"id": "29b8ed91354ac1e2",
"type": "link in",
"z": "576f3807f61f5098",
"g": "f4f4ca47869551e3",
"name": "peso_in",
"links": [
"3e6f6384f2ec9846"
],
"x": 935,
"y": 820,
"wires": [
[
"a849cb3f53ceb581",
"e616701e86d299ad"
]
]
},
{
"id": "b0c185b2e9fb99d4",
"type": "switch",
"z": "576f3807f61f5098",
"g": "f4f4ca47869551e3",
"name": "Vial",
"property": "payload.numero_vial",
"propertyType": "msg",
"rules": [
{
"t": "eq",
"v": "numero_vial",
"vt": "flow"
}
],
"checkall": "true",
"repair": false,
"outputs": 1,
"x": 1150,
"y": 820,
"wires": [
[
"29994ee556e17a85",
"0af79df0c83f9717"
]
]
},
{
"id": "176fe4510822e556",
"type": "debug",
"z": "576f3807f61f5098",
"name": "debug 45",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 780,
"y": 300,
"wires": []
},
{
"id": "94f2a26231e45789",
"type": "debug",
"z": "576f3807f61f5098",
"name": "debug 46",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 780,
"y": 340,
"wires": []
},
{
"id": "0af79df0c83f9717",
"type": "debug",
"z": "576f3807f61f5098",
"name": "debug 47",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 1240,
"y": 920,
"wires": []
},
{
"id": "a849cb3f53ceb581",
"type": "debug",
"z": "576f3807f61f5098",
"name": "debug 48",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 1040,
"y": 920,
"wires": []
},
{
"id": "e616701e86d299ad",
"type": "json",
"z": "576f3807f61f5098",
"g": "f4f4ca47869551e3",
"name": "",
"property": "payload",
"action": "",
"pretty": false,
"x": 1030,
"y": 820,
"wires": [
[
"b0c185b2e9fb99d4"
]
]
},
{
"id": "c79d32e49f4cf252",
"type": "debug",
"z": "576f3807f61f5098",
"name": "debug 49",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 1360,
"y": 240,
"wires": []
},
{
"id": "110e757690de4198",
"type": "comment",
"z": "576f3807f61f5098",
"name": "Gestion de Semaforos V1.0",
"info": "Esta version 1.0 contiene las conexiones modbus con ADAM 6066 para el control de los lazos de induccion\ny de los semaforos. \nContiene una pagina de configuracion de parametros elementales de operacion del sistema de gestion \nde semaforos el cual esta en http://ip:1880/semaforos. El archivo de configuracion \"semaforos.txt\" esta \nen \"/home/trypton/node-red/projects\" al igual que los demas archivos de configuracion de los otros flujos.\nEsta version 1.0 aunque tiene todas las funcionalidades requeridas aun se encuentra en pruebas.\n",
"x": 1120,
"y": 260,
"wires": []
}
]