{"id":635,"date":"2010-11-12T00:52:21","date_gmt":"2010-11-11T23:52:21","guid":{"rendered":"http:\/\/www.zigbe.net\/?p=635"},"modified":"2010-11-12T00:52:21","modified_gmt":"2010-11-11T23:52:21","slug":"midiendo-la-energia-26-otro-post-despues-del-ultimo","status":"publish","type":"post","link":"https:\/\/blog.whatsbee.net\/?p=635","title":{"rendered":"Midiendo la energ\u00eda: 26 \u00bfOtro post despues del \u00faltimo?"},"content":{"rendered":"<div class=\"mceTemp mceIEcenter\">\u00a0<\/div>\n<p>Pues s\u00ed&#8230;.<\/p>\n<p>Tal como coment\u00e9 en el \u00faltimo art\u00edculo nuestro producto ya env\u00eda de forma peri\u00f3dica al gateway los valores de corriente rms, tensi\u00f3n rms, potencia aparente, potencia activa, potencia reactiva, frecuencia, etc. Adem\u00e1s se identifica, env\u00eda el n\u00famero de serie, el identificador del HW y el del SW.\u00a0 Como primer prototipo alfa ya nos vale, aunque hay que pulir algunas cosas.<\/p>\n<p>He hecho una nueva versi\u00f3n del firmware (la 10) que incluye un par de novedades, las comento y aprovecho para publicarla.<\/p>\n<p>Para compensar desviaciones de los componentes el sketch tiene una serie de par\u00e1metros de calibraci\u00f3n, de tensi\u00f3n, de corriente para cada puerto y de fase para cada puerto. Los par\u00e1metros de calibraci\u00f3n se definen como variables al principio del sketch. Pero \u00bfc\u00f3mo los puede cambiar el usuario si necesidad de reescribir el firmware y cargarlo?, tenemos que darle opci\u00f3n.<\/p>\n<p>No tiene ninguna l\u00f3gica preguntarlos en un interfaz de usuario cada vez que se enchufa\u00a0la PDU, deber\u00edamos de establecer una calibraci\u00f3n por defecto y dar al usuario la posibilidad de cambiarla. Evidentemente no podemos permitirnos que se carguen los par\u00e1metros por defecto cada vez que se vaya la corriente, deber\u00edamos de almacenarlos en un \u00e1rea de memoria que no se borre aunque la PDU no est\u00e9 alimentada, esta memoria es la EEPROM.\u00a0<\/p>\n<div id=\"attachment_636\" style=\"width: 235px\" class=\"wp-caption aligncenter\"><a href=\"http:\/\/blog.whatsbee.net\/wp-content\/uploads\/2010\/11\/ATMEGA328.jpg\"><img aria-describedby=\"caption-attachment-636\" loading=\"lazy\" class=\"size-full wp-image-636\" title=\"ATMEGA328\" src=\"http:\/\/blog.whatsbee.net\/wp-content\/uploads\/2010\/11\/ATMEGA328.jpg\" alt=\"\" width=\"225\" height=\"225\" srcset=\"https:\/\/blog.whatsbee.net\/wp-content\/uploads\/2010\/11\/ATMEGA328.jpg 225w, https:\/\/blog.whatsbee.net\/wp-content\/uploads\/2010\/11\/ATMEGA328-150x150.jpg 150w\" sizes=\"(max-width: 225px) 100vw, 225px\" \/><\/a><p id=\"caption-attachment-636\" class=\"wp-caption-text\">ATMEGA 328, contiene internamente una memoria EEPROM<\/p><\/div>\n<p>El ATMEGA328 contiene 4K de memoria EEPROM, los valores que conten\u00eda el sketch ahora son los valores por defecto, la primera vez que se arranca el producto se comprueba si est\u00e1n escritos en la EEPROM, y si no lo est\u00e1n se escriben, esto solo sucede la primera vez que se enchufa el producto a la corriente.<\/p>\n<p>Para poder cambiar estos valores he puesto una ventana de comando en el driver, ahora se pueden enviar comandos a la PDU, para encender y apagar rel\u00e9s, para cambiar los valores de calibraci\u00f3n y para cambiar los datos de la PDU: n\u00famero de serie, versi\u00f3n del HW, etc. Simplemente en esta inputbox tenemos que poner XXX:YYY=ZZZZZZ. Para modificar el estado de los rel\u00e9s:<\/p>\n<p>CMD:PX=Y -&gt; d\u00f3nde X es el puerto e Y es 0 \u00f3 1 en funci\u00f3n de si queremos encender o apagar el rel\u00e9.<\/p>\n<p>Para cambiar un par\u00e1metro de configuraci\u00f3n: CNF:XXX=YYYYYYYYY, d\u00f3nde XXX puede tomar los valores CV0 (calibraci\u00f3n de V), CIX (Calibraci\u00f3n de I para el puertoX), CFX (Calibraci\u00f3n de fase para el puertoX), PID (Identificador de producto), SNX(N\u00famero de serie), HWV (Versi\u00f3n del HW), SWV (Versi\u00f3n del SW), TLE (Segundos entre env\u00edos de lecturas) o TIN (Segundos entre env\u00edos de la identificaci\u00f3n del equipo). Lo que hay despues del signo igual es el valor que queremos asignar. Recuerdo que estos\u00a0valores se almacenan de forma permanente.<\/p>\n<p>En pr\u00f3ximas versiones a\u00f1adiremos nuevos par\u00e1metros para definir el estado por defecto de los\u00a0rel\u00e9s en el arranque de la PDU, para definir en que momento se quiere cortar la corriente, etc. Tambi\u00e9n utilizaremos la EEPROM para almacenar el consumo acumulado cada hora.<\/p>\n<p>Otra novedad es que ahora la PDU acepta el comando GET del gateway y env\u00eda de forma inmediata toda la info, de esta forma podemos alargar los tiempos en los que el gateway env\u00eda y controlarlo desde el driver pidiendo la info con un scheduler.<\/p>\n<p>Hay un mont\u00f3n de caracter\u00edsticas que podemos a\u00f1adir, pero de momento lo dejaremos en este punto, seguiremos trabajando en otros elementos del entorno y al final le daremos un repaso.<\/p>\n<p>Por si a alguien le interesa \u00e9l fuente de la versi\u00f3n 10 del c\u00f3digo es el siguiente (Mis disculpas por el aburrimiento ;-)):<\/p>\n<pre>\/\/**********************************************************************************\n\/\/PDU gestionable por carlos Rodr\u00edguez\n\/\/http:\/\/www.zigbe.net\n\/\/El Sketch mide voltaje y corriente y calcula los valores de consumo de potencia\n\/\/Basado en los algoritmos del la app note de Atmel AVR465 con inspiraci\u00f3n en el c\u00f3digo de openenergymonitor\n\/\/**********************************************************************************\n\/\/____________________LIBRERIA DE EEPROM PARA ALMACENAR LOS DATOS___________________\n#include &lt;EEPROM.h&gt;\n\/\/____________________NEWSOFTSERIAL (SOLO PARA DEPURAR)_____________________________\n#include &lt;NewSoftSerial.h&gt;\nuint8_t ssRX = 8; \/\/ Connect Arduino pin 8 to TX of usb-serial device\nuint8_t ssTX = 7;\/\/ Connect Arduino pin 7 to RX of usb-serial device\nNewSoftSerial nss(ssRX, ssTX);\nint cuentas =0;\nint cuentasZB =0;\n\/\/__________________________________XBEE____________________________________________\n#include &lt;XBee.h&gt; \/\/Librer\u00eda Xbeee\n#define MAX_PAYLOAD_SIZE 72\nXBee xbee = XBee(); \/\/Crea el objeto de Xbee\nuint8_t payload[MAX_PAYLOAD_SIZE]; \/\/Crea el puntero para el payload\nXBeeAddress64 addr64 = XBeeAddress64(0x00000000, 0x00000000);\nZBTxRequest zbTx = ZBTxRequest(addr64, payload, sizeof(payload));\nZBTxStatusResponse txStatus = ZBTxStatusResponse();\n\/\/ create reusable response objects for responses we expect to handle \nZBRxResponse rx = ZBRxResponse();\nModemStatusResponse msr = ModemStatusResponse();\nString pzb; \/\/Contiene la cadena que se va a enviar en el payload\n\/\/*************************Tiempo Entre Lecturas**************************<\/pre>\n<pre>struct Config {\nchar OK[3];\nchar ProductId[10];\nchar SerialNumber[10];\nchar HardwareVersion[10];\nchar SoftwareVersion[10];\ndouble tLecturas; \/\/Tiempo en segundos para leer cada puerto, lo lee de la EEPROM\ndouble tInfo; \/\/Tiempo en segundos para mandar la info del equipo, lo lee de la EEPROM\n};<\/pre>\n<pre>Config defConf = {\"OK\", \"bMotesPDU\", \"000000001\", \"\u00a0\u00a0 A01.03\", \"\u00a0\u00a0 V07.10\", 5, 100};\nConfig cf;<\/pre>\n<pre>double mlecturas=0; \/\/Contador del tiempo de lectura\ndouble tUlLectura=0; \/\/Tiempo transcurrido desde la \u00faltima lectura\ndouble tUlLecInfo=0; \/\/Contador del tiempo de info<\/pre>\n<pre>\n\/\/________________________VARIABLES DE CONFIGURACI\u00d3N DEL HARDWARE________________________\nconst int NumeroDeMuestras = 3000; \/\/N\u00famero de muestras para las medidas de energ\u00eda, \nconst int NumPorts =4; \/\/Define el n\u00famero de puertos de la PDU\nint Port=1; \/\/define el puerto incial\nint PinV = 1; \/\/Pin de medida de tensi\u00f3n\nint PinI[NumPorts+1] = {0, 2, 3, 4, 5}; \/\/Pines de medida de corriente (1 a 4)\nint PinRele[NumPorts+1] = {0, 6, 3, 4, 5}; \/\/Pines en los que est\u00e1n conectados los rel\u00e9s\nint EstadoRele[NumPorts+1] = {0, 1, 1, 1, 1}; \/\/Por defecto todos los rel\u00e9s est\u00e1n en on TODO: Esto deber\u00eda de poder ser configurable????\n\/\/________________________COEFICIENTES DE CALIBRACI\u00d3N____________________________________<\/pre>\n<pre>struct Calib {\nchar OK[3];\ndouble CalV; \/\/Coeficiente de calibraci\u00f3n de tensi\u00f3n\ndouble CalI [NumPorts+1]; \/\/Coeficientes de calibraci\u00f3n de corriente\ndouble CalFase[NumPorts+1]; \/\/Coeficientes de calibraci\u00f3n de fase\ndouble CalOfset;\ndouble DifOfset;\n};\nCalib defCal = {\"OK\", 1.62, {0, 0.043, 0.043, 0.043, 0.043}, {0, 0.7, 0.7, 0.7, 0.7}, 607, 3};\nCalib cl;<\/pre>\n<pre>\/*double CalV = 1.62; \/\/Coeficiente de calibraci\u00f3n de tensi\u00f3n\ndouble CalI [NumPorts+1] = {0, 0.043, 0.043, 0.043, 0.043}; \/\/Coeficientes de calibraci\u00f3n de corriente\ndouble CalFase[NumPorts+1] = {0, 0.7, 0.7, 0.7, 0.7}; \/\/Coeficientes de calibraci\u00f3n de fase\n*\/<\/pre>\n<pre>\/\/________________________VARIABLES PARA LAS MUESTRAS Y ACUMULADORES_____________________\nint UlMuestraV, UlMuestraI, MuestraV, MuestraI;\ndouble UlFiltradoV, UlFiltradoI, FiltradoV, FiltradoI, filterTemp, calibratedV;\ndouble sumI,sumV,sumP;\n\/\/_________________________VARIABLES PARA ALMACENAR LOS RESULTADOS DE SALIDA_____________\ndouble PotenciaReal[NumPorts+1];\ndouble PotenciaAparente[NumPorts+1];\ndouble FactorPotencia[NumPorts+1];\ndouble Vrms[NumPorts+1];\ndouble Irms[NumPorts+1];\n\/\/********************Variables de medida de la energ\u00eda********************\n\u00a0\u00a0\u00a0 \/\/Calculation of kwh\n\u00a0\u00a0\u00a0 \/\/time taken since last measurment timems = tmillis - ltmillis;\n\u00a0\u00a0\u00a0 unsigned long ltmillis, tmillis, timems;\n\u00a0\u00a0\u00a0 \/\/time when arduino is switched on... is it 0?\n\u00a0\u00a0\u00a0 unsigned long startmillis;\n\u00a0\u00a0\u00a0 \/\/kwhTotal is cumulative kwh today, the other 3 are historCalIX over the last 3 days for comparison.\n\u00a0\u00a0\u00a0 double kwhTotal =0.0;<\/pre>\n<pre>\/\/********************Variables de medida de la frecuencia********************\nunsigned long vLastZeroMsec;\/\/Tiempo en microsegundos desde el \u00faltimo paso del voltaje por cero\nunsigned long vPeriod;\nunsigned long vPeriodSum;\/\/Suma de vPeriod para obtener el promedio\nunsigned long vPeriodCount;\/\/N\u00famero de periodos\nfloat freq;\/\/Frequencia\nunsigned long expPeriod = 20000;\/\/Se utiliza para filtrar lecturas err\u00f3neas de vPeriod, 50Hz -&gt; 20000, 60 Hz -&gt; 16666 \nunsigned long filterWidth = 2000;<\/pre>\n<pre>int statusLed = 13;\nint errorLed=13;<\/pre>\n<pre>\/\/___________________________TEMPLATES PARA LA GRABACI\u00d3N EN LA EEPROM_______________________________________\ntemplate &lt;class T&gt; int EEPROM_writeAnything(int ee, const T&amp; value)\n{\n\u00a0\u00a0\u00a0 const byte* p = (const byte*)(const void*)&amp;value;\n\u00a0\u00a0\u00a0 int i;\n\u00a0\u00a0\u00a0 for (i = 0; i &lt; sizeof(value); i++)\n\u00a0\u00a0 EEPROM.write(ee++, *p++);\n\u00a0\u00a0\u00a0 return i;\n}<\/pre>\n<pre>template &lt;class T&gt; int EEPROM_readAnything(int ee, T&amp; value)\n{\n\u00a0\u00a0\u00a0 byte* p = (byte*)(void*)&amp;value;\n\u00a0\u00a0\u00a0 int i;\n\u00a0\u00a0\u00a0 for (i = 0; i &lt; sizeof(value); i++)\n\u00a0\u00a0 *p++ = EEPROM.read(ee++);\n\u00a0\u00a0\u00a0 return i;\n}<\/pre>\n<pre>\u00a0<\/pre>\n<pre>void setup()\n{\n\u00a0 xbee.begin(9600);\n\/\/*******************Setup de la medida de energ\u00eda*******************\ntmillis = millis();\nstartmillis=tmillis;\nfor (int n=1; n&lt;=NumPorts; n++){pinMode (PinRele[n], OUTPUT);} \/\/Inicializa todos los puertos de los rel\u00e9s como salidas \npinMode (13, OUTPUT); \/\/Inicializa el puerto del LED como salida\nEEPROM_readAnything (0, cf); \/\/carga las variables de la EEPROM en cf\n\u00a0 if (defConf.OK == cf.OK){ \/\/suponemos que la EEPROM tiene algo grabado\n\u00a0 \/\/No hace nada\n\u00a0 } \n\u00a0 else{EEPROM_writeAnything (0 , defConf);\n\u00a0 delay(50); \/\/se espera porque en la EEPROM se tarda en escribir\n\u00a0 EEPROM_readAnything (0, cf); \/\/Carga los datos de la EEPROM en la variable cf\n\u00a0 }<\/pre>\n<pre>EEPROM_readAnything (100, cl); \/\/carga las variables de la EEPROM en cf\n\u00a0 if (defCal.OK == cl.OK){ \/\/suponemos que la EEPROM tiene algo grabado\n\u00a0 \/\/No hace nada\n\u00a0 } \n\u00a0 else{EEPROM_writeAnything (100 , defCal);\n\u00a0 delay(50); \/\/se espera porque en la EEPROM se tarda en escribir\n\u00a0 EEPROM_readAnything (100, cl); \/\/Carga los datos de la EEPROM en la variable cf\n\u00a0 }<\/pre>\n<pre>\n\/\/ Solo depuraci\u00f3n\n\u00a0 nss.begin(9600);\n\u00a0 nss.println(\"Se ha reiniciado el micro!!\");\n\u00a0 nss.println (cf.ProductId);<\/pre>\n<pre>\n\/\/TODO: Definir el valor de arranque de los puertos por defecto\n\/\/TODO: Hacer una funci\u00f3n para escribir directamente en la EEPROM desde la red Zigbee\n}<\/pre>\n<pre>\/\/_________________________________BUCLE PRINCIPAL DEL PROGRAMA________________________________________\nvoid loop()\n{ \n\u00a0 if (millis()-tUlLectura &gt;cf.tLecturas*1000){ \/\/Si ha pasado un tiempo superior al de la \u00faltima lectura\n\u00a0\u00a0\u00a0 tUlLectura=millis();\/\/Asigna el tiempo de la \u00faltima lectura\n\u00a0\u00a0\u00a0 if (Port&gt;NumPorts){Port=1;}\/\/si el n\u00famero de puerto es mayor que el n\u00famero de puertos vuelve al uno\n\u00a0\u00a0\u00a0 flashLed(13, Port, 100); \/\/Activa el Led un n\u00famero de veces igual al del puerto que est\u00e1 escaneando\n\u00a0\u00a0\u00a0 \/\/ debug\n\u00a0\u00a0\u00a0 nss.println (\"\u00a0 \");\n\u00a0\u00a0\u00a0 nss.print (\"Calculando energia, Puerto-&gt;\");\n\u00a0\u00a0\u00a0 nss.println (Port);\n\u00a0\u00a0\u00a0 \n\u00a0\u00a0\u00a0\u00a0\u00a0 if (CalcularEnergia(Port)){ \/\/Calcula la energ\u00eda devuelve true si no se cancela por una entrada serie\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 nss.println (\"ha vuelto de la funcion de calcular FINALIZADA\");\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 enviarPaquetePort (Port);\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Port += 1; \/\/Incrementa el n\u00famero de puerto\u00a0\u00a0\u00a0 \n\u00a0\u00a0\u00a0\u00a0\u00a0 }\n\u00a0\u00a0\u00a0\u00a0\u00a0 else {\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 nss.println (\"ha vuelto de la funcion de calcular CANCELADA\");\n\u00a0\u00a0\u00a0\u00a0\u00a0 }\n\u00a0\u00a0 }\n\u00a0 if (millis()-tUlLecInfo &gt;cf.tInfo*1000){ \/\/Si ha pasado un tiempo superior al de la \u00faltima lectura\n\u00a0\u00a0\u00a0 nss.println (\"Enviando datos del HW\");\n\u00a0\u00a0\u00a0 tUlLecInfo=millis();\/\/Asigna el tiempo de la \u00faltima lectura\n\u00a0\u00a0\u00a0 enviarPaqueteHW();\n\u00a0 }\n\u00a0leerZigbee();\n} \/\/Fin del loop\u00a0<\/pre>\n<pre>\n\/\/_______________________________________LEE LOS PAQUETES DE LA RED ZIGBEE________________________________________\n\u00a0void leerZigbee()\n\u00a0{\n\u00a0\u00a0 xbee.readPacket(); \/\/lee el paquete\n\u00a0\u00a0 cuentasZB += 1;\n\u00a0\u00a0 if (cuentasZB&gt;200){\n\u00a0\u00a0\u00a0\u00a0 cuentasZB=0;\n\u00a0\u00a0 nss.print (\"#\");\n\u00a0 }\n\u00a0 \n\u00a0\u00a0\u00a0 if (xbee.getResponse().isAvailable()) {\/\/Si se ha recibido.....\n\u00a0\u00a0\u00a0 nss.println (\"Se ha recibido un paquete\");<\/pre>\n<pre>\u00a0\u00a0\u00a0\u00a0\u00a0 if (xbee.getResponse().getApiId() == ZB_RX_RESPONSE) { \/\/Comprueba que se trate de un paquete ZB RX\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 xbee.getResponse().getZBRxResponse(rx); \/\/rellena la clase ZB RX<\/pre>\n<pre>\u00a0\u00a0\u00a0 borrarPayload();\n\u00a0\u00a0\u00a0 String comando;\n\u00a0\u00a0\u00a0 for (int n=0; n&lt;=rx.getDataLength(); n++){\n\u00a0\u00a0\u00a0 payload[n] = rx.getData()[n];\n\u00a0\u00a0\u00a0 comando += rx.getData()[n];\n\u00a0\u00a0\u00a0 }<\/pre>\n<pre>\u00a0\u00a0\u00a0\u00a0 if (comando.substring (0, 4)==\"CMD:\"){ \/\/Se ha enviado un comando a los rel\u00e9s\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 nss.println (\"Se ha recibido un comando\");\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 procesarRele (atoi(&amp;comando[5]), atoi(&amp;comando[7]));\n\u00a0\u00a0\u00a0\u00a0 } else if(comando.substring (0, 4)==\"CNF:\"){ \/\/Se ha enviado un cambio en la configuraci\u00f3n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 nss.println (\"Se ha recibido un comando de CONFIGURACION\");\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 procesarConfig (comando.substring (4, 7), comando.substring (8, 17));\n\u00a0\u00a0\u00a0\u00a0 } else if(comando.substring (0, 3)==\"get\"){ \/\/El gateway ha pedido que le manden la configuraci\u00f3n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 nss.println (\"El Gateway ha pedido informaci\u00f3n de estado\");\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 for (int i=1; i&lt;=NumPorts; i++){\/\/Manda un paquete de cada puerto\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 enviarPaquetePort(i);}\n\u00a0\u00a0\u00a0\u00a0 }\u00a0\u00a0\u00a0 enviarPaqueteHW ();\/\/Manda la info del HW\n\u00a0\u00a0\u00a0\u00a0\u00a0 } else if (xbee.getResponse().getApiId() == MODEM_STATUS_RESPONSE) {\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 xbee.getResponse().getModemStatusResponse(msr); \/\/El Xbee local env\u00eda estas respuestas ante ciertos eventos, por ejemplo asociaci\u00f3n desasociaci\u00f3n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 if (msr.getStatus() == ASSOCIATED) {flashLed(statusLed, 10, 10);} \/\/ Enciende el LED 10 veces para indicar que est\u00e1 asociado\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 else if (msr.getStatus() == DISASSOCIATED) {flashLed(errorLed, 5, 10);}\/\/ Enciende el LED 5 veces para indicar que est\u00e1 desasociado\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 else {flashLed(statusLed, 3, 10);}\/\/ Otro statos\n\u00a0\u00a0\u00a0\u00a0\u00a0 } else {flashLed(errorLed, 1, 25);} \/\/ Error inesperado\n\u00a0\u00a0\u00a0 }\n\u00a0}<\/pre>\n<pre>\/\/_______________________PROCESAR COMANDO RELE____________________________________________________<\/pre>\n<pre>void procesarRele (int Pto, int Est)\n{\n\u00a0 digitalWrite (PinRele[Pto], !Est); \/\/Cambia el estado del puerto\n\u00a0 EstadoRele[Pto]=Est; \/\/Cambia la variable que almacena el estado\n\u00a0 enviarPaquetePort (Pto); \/\/Envia un paquete con la informaci\u00f3n del puerto actualizada\n\/\/DEBUG\n\u00a0 nss.print (\"Dentro de la funci\u00f3n de los reles. Puerto-&gt;\");\n\u00a0 nss.print (Pto);\n\u00a0 nss.print (\" \/ Estado-&gt;\");\n\u00a0 nss.println (Est);\n\/\/DEBUG\u00a0 \n}\n\/\/_______________________PROCESAR CONFIGURACION____________________________________________________\nvoid procesarConfig (String cmd, String val){\nif (cmd==\"CV0\"){<\/pre>\n<pre>} else if (cmd==\"CI1\"){<\/pre>\n<pre>} else if (cmd==\"CI2\"){<\/pre>\n<pre>} else if (cmd==\"CI3\"){<\/pre>\n<pre>} else if (cmd==\"CI4\"){<\/pre>\n<pre>} else if (cmd==\"CF1\"){<\/pre>\n<pre>} else if (cmd==\"CF2\"){<\/pre>\n<pre>} else if (cmd==\"CF3\"){<\/pre>\n<pre>} else if (cmd==\"CF4\"){<\/pre>\n<pre>} else if (cmd==\"PID\"){\n\u00a0 for (int i=0; i&lt;10; i++){cf.ProductId[i]=val[i];}\n} else if (cmd==\"SNX\"){\n\u00a0 for (int i=0; i&lt;10; i++){cf.SerialNumber[i]=val[i];}\n} else if (cmd==\"HWV\"){\n\u00a0 for (int i=0; i&lt;10; i++){cf.HardwareVersion[i]=val[i];}\n} else if (cmd==\"SWV\"){\n\u00a0 for (int i=0; i&lt;10; i++){cf.SoftwareVersion[i]=val[i];}\n} else if (cmd==\"TLE\"){<\/pre>\n<pre>} else if (cmd==\"TIN\"){\n}\n\/\/DEBUG\nnss.print (\"Se ha recibido el comando \");\nnss.print (cmd);\nnss.print (\"=\");\nnss.println (val);\n\/\/DEBUG\nEEPROM_writeAnything (0, cf);\nEEPROM_writeAnything (100, cl);\ndelay(50);\nenviarPaqueteHW();\n}<\/pre>\n<pre>\n\/\/______________________CALCULA LOS VALORES DE LA ENERGIA__________________________________________\nboolean CalcularEnergia(int Port)\n{\n\u00a0 UlMuestraV=cl.CalOfset; UlMuestraI=cl.CalOfset;\n\u00a0 MuestraV=cl.CalOfset; MuestraI=cl.CalOfset;\n\u00a0 UlFiltradoV=2.5; UlFiltradoI=2.5;\n\u00a0 FiltradoV=2.5; FiltradoI=2.5;\n\u00a0 double MaxOfset=cl.CalOfset + cl.DifOfset;\n\u00a0 double MinOfset=cl.CalOfset - cl.DifOfset;\n\u00a0 \n\u00a0 for (int n=0; n&lt;NumeroDeMuestras; n++)\n\u00a0 {\n\u00a0\u00a0\u00a0\u00a0 if (digitalRead (0)==LOW) {return false;} \/\/Si se ha recobodo alg\u00fan dato por la UART cancela inmediatamente el bucle\n\u00a0\u00a0\u00a0\u00a0 UlMuestraV=MuestraV;\u00a0\u00a0 \/\/Variables utilizadas para eliminar el ofset de tensi\u00f3n\n\u00a0\u00a0\u00a0\u00a0 UlMuestraI=MuestraI;\u00a0\u00a0 \/\/Variables utilizadas para eliminar el ofset de corriente\n\u00a0\u00a0\u00a0\u00a0 MuestraV = analogRead(PinV); \/\/Lee las muestras de voltaje\n\u00a0\u00a0\u00a0\u00a0 MuestraI = analogRead(PinI[Port]); \/\/Lee las muestras de corriente\n\u00a0\u00a0\u00a0\u00a0 if (MuestraI &lt;=MaxOfset and MuestraI &gt;=MinOfset){MuestraI=cl.CalOfset;} \/\/Filtro de hist\u00e9resis para eliminar el ruido en el sensor de corriente\n\u00a0\u00a0\u00a0\u00a0 UlFiltradoV = FiltradoV;\u00a0\u00a0 \/\/Variables utilizadas para eliminar el ofset de tensi\u00f3n\n\u00a0\u00a0\u00a0\u00a0 UlFiltradoI = FiltradoI;\u00a0\u00a0 \/\/Variables utilizadas para eliminar el ofset de corriente\n\u00a0\u00a0\u00a0\u00a0 FiltradoV = 0.996*(UlFiltradoV+MuestraV-UlMuestraV);\u00a0\u00a0 \/\/Filtro digital de paso alto para eliminar el Ofset de 2,5V en la entrada de tensi\u00f3n\n\u00a0\u00a0\u00a0\u00a0 FiltradoI = 0.996*(UlFiltradoI+MuestraI-UlMuestraI);\u00a0\u00a0 \/\/Filtro digital de paso alto para eliminar el Ofset de 2,5V en las entradas de corriente\n\u00a0\u00a0\u00a0\u00a0 calibratedV = UlFiltradoV + cl.CalFase[Port] * (FiltradoV - UlFiltradoV);\u00a0\u00a0 \/\/Calibraci\u00f3n de fase (para compensar el retraso en el conversor AD)\n\u00a0\u00a0\u00a0\u00a0 sumV += calibratedV * calibratedV; \/\/A\u00f1ade al acumulador el cuadrado del voltaje\u00a0 \n\u00a0\u00a0\u00a0\u00a0 sumI += FiltradoI * FiltradoI; \/\/Eleva al cuadrado y a\u00f1ade al acumulador\n\u00a0\u00a0\u00a0\u00a0 sumP +=calibratedV * FiltradoI;\u00a0 \/\/C\u00e1lculo de la potencia instantanea Multiplica tensi\u00f3n por corriente y A\u00f1ade al acumulador\n\u00a0\u00a0\u00a0\u00a0 \/\/Medici\u00f3n de la frecuencia\n\u00a0\u00a0\u00a0\u00a0 if (n==0) vLastZeroMsec = micros();\n\u00a0\u00a0\u00a0\u00a0 if (UlFiltradoV &lt; 0 &amp;&amp; FiltradoV &gt;= 0 &amp;&amp; n&gt;1){ \/\/Chequea el paso por cero\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 vPeriod = micros() - vLastZeroMsec; \/\/Periodo de la forma de onda del voltaje\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 if (vPeriod &gt; (expPeriod-filterWidth) &amp;&amp; vPeriod&lt;(expPeriod+filterWidth)){\/\/Filtra las medidas de periodo err\u00f3neas\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 vPeriodSum += vPeriod;\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 vPeriodCount++;}\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 vLastZeroMsec = micros();\n\u00a0\u00a0\u00a0\u00a0 }\n\u00a0 }\nVrms[Port] = cl.CalV*sqrt(sumV \/ NumeroDeMuestras); \/\/C\u00e1lculo de la raiz cuadrada del promedio del voltaje elevado al cuadrado (RMS) \/ Aplica los coeficientes de calibraci\u00f3n\nIrms[Port] = cl.CalI[Port]*sqrt(sumI \/ NumeroDeMuestras); \/\/C\u00e1lculo de la raiz cuadrada del promedio de la corriente elevada al cuadrado (RMS) \/ Aplica los coeficientes de calibraci\u00f3n \nPotenciaReal[Port] = cl.CalV*cl.CalI[Port]*sumP \/ NumeroDeMuestras;\/\/calcula las potencias\nPotenciaAparente[Port] = double(int((Vrms[Port] * Irms[Port] -4)*10))\/10;\/\/El cuatro es una calibraci\u00f3n para compensar el algoritmo de reducci\u00f3n de ruido\nif (PotenciaAparente[Port]&lt;PotenciaReal[Port]){PotenciaAparente[Port]=PotenciaReal[Port];} \/\/En alguna ocasi\u00f3n \"rara\" Ha dado alg\u00fan valor inferior que da factor de potencia negativo\nif (PotenciaReal[Port]&lt;4){PotenciaReal[Port]=0;}\/\/ No se detecta una potencia menor de 4W, para evitar los errores que quedan del filtro pasa bajos\nif (PotenciaAparente[Port]&lt;4){PotenciaAparente[Port]=0;}\/\/ No se detecta una potenciao menor de 4VA, para evitar los errores que quedan del filtro pasa bajos\nFactorPotencia[Port] = PotenciaReal[Port] \/ PotenciaAparente[Port];\nfreq = (1000000.0 * vPeriodCount) \/ vPeriodSum; \/\/divido por 1000 crc \/\/C\u00e1lculo de la frecuencia\nvPeriodSum=0; \/\/resetea los acumuladores\nvPeriodCount=0; \/\/resetea los acumuladores\nsumV = 0;\/\/resetea los acumuladores\nsumI = 0;\/\/resetea los acumuladores\nsumP = 0;\/\/resetea los acumuladores\nreturn true;\n}<\/pre>\n<pre>\/\/____________________________ENVIAR PAQUETE DE UN PUERTO_______________________________________\nvoid enviarPaquetePort (int Port)\n{\n\u00a0\u00a0\u00a0 pzb= \"PT=\";\n\u00a0\u00a0\u00a0 pzb += floatToString(Port, 0);\n\u00a0\u00a0\u00a0 pzb += \";P=\";\n\u00a0\u00a0\u00a0 pzb += floatToString(PotenciaReal[Port], 1);\n\u00a0\u00a0\u00a0 pzb += \"W;S=\";\n\u00a0\u00a0\u00a0 pzb += floatToString(PotenciaAparente[Port], 1);\n\u00a0\u00a0\u00a0 pzb += \"VA;V=\";\n\u00a0\u00a0\u00a0 pzb += floatToString(Vrms[Port], 1);\n\u00a0\u00a0\u00a0 pzb += \"V;I=\";\n\u00a0\u00a0\u00a0 pzb += floatToString(Irms[Port], 1);\n\u00a0\u00a0\u00a0 pzb += \"A;F=\";\n\u00a0\u00a0\u00a0 pzb += floatToString(freq, 1);\n\u00a0\u00a0\u00a0 pzb += \"Hz;ST=\";\n\u00a0\u00a0\u00a0 pzb += floatToString(EstadoRele[Port], 0);\n\u00a0\u00a0\u00a0 pzb += \"st;\";\n\u00a0\u00a0\u00a0 borrarPayload();\n\u00a0\u00a0\u00a0 for (int i=0; i&lt;=pzb.length(); i++){payload[i] = pzb[i];} \/\/Mete el valor de la string pzb en el payload \n\u00a0\u00a0\u00a0 xbee.send(zbTx); \/\/Env\u00eda el paquete a la red Zigbee<\/pre>\n<pre>\u00a0\u00a0\u00a0 \/\/DEBUG\n\u00a0\u00a0\u00a0 nss.println (\"enviarPaquetePort-&gt;ha enviado el paquete\");\n\u00a0\u00a0\u00a0 nss.println(pzb);\n\u00a0\u00a0\u00a0 \/\/\n}<\/pre>\n<pre>\/\/____________________________ENVIAR PAQUETE DEL HARDWARE_______________________________________\nvoid enviarPaqueteHW ()\n{\n\u00a0\u00a0\u00a0 pzb= \"PID=\";\n\u00a0\u00a0\u00a0 pzb += cf.ProductId;\n\u00a0\u00a0\u00a0 pzb += \";HW=\";\n\u00a0\u00a0\u00a0 pzb += cf.HardwareVersion;\n\u00a0\u00a0\u00a0 pzb += \";SW=\";\n\u00a0\u00a0\u00a0 pzb += cf.SoftwareVersion;\n\u00a0\u00a0\u00a0 pzb += \";SN=\";\n\u00a0\u00a0\u00a0 pzb += cf.SerialNumber;\n\u00a0\u00a0\u00a0 pzb += \";\";\n\u00a0\u00a0\u00a0 borrarPayload();\n\u00a0\u00a0\u00a0 for (int i=0; i&lt;=pzb.length(); i++){payload[i] = pzb[i];} \/\/Copia la String en el Payload\n\u00a0\u00a0\u00a0 xbee.send(zbTx);\n\u00a0\u00a0\u00a0 \/\/DEBUG\n\u00a0\u00a0\u00a0 nss.println (\"enviarPaqueteHW-&gt;ha enviado el paquete\");\n\u00a0\u00a0\u00a0 nss.println(pzb);\n\u00a0\u00a0\u00a0 \/\/<\/pre>\n<pre>}<\/pre>\n<pre>\n\/\/____________________________BORRAR PAYLOAD_______________________________________\nvoid borrarPayload()\n{\n\u00a0\u00a0\u00a0\u00a0\u00a0 for ( int n=0; n&lt;=MAX_PAYLOAD_SIZE; n++){payload[n]= ' ';} \/\/borra el contenido de la valiable del payload\n}<\/pre>\n<pre>\/\/_____________CONVERTIR LAS VARIABLES DOUBLE EN TEXTO______________________________\nString floatToString(double number, uint8_t digits) \n{ \n\u00a0 String resultString = \"\";\n\u00a0 if (number &lt; 0.0){\u00a0 \/\/ Handle negative numbers\n\u00a0\u00a0\u00a0\u00a0 resultString += \"-\";\n\u00a0\u00a0\u00a0\u00a0 number = -number;\n\u00a0 }\n\u00a0 double rounding = 0.5;\/\/ Redondea correctamente (1.999, 2) se imprimem como \"2.00\"\n\u00a0 for (uint8_t i=0; i&lt;digits; ++i)\n\u00a0\u00a0\u00a0 rounding \/= 10.0;\n\u00a0\u00a0\u00a0 number += rounding;\n\u00a0 unsigned long int_part = (unsigned long)number;\/\/ Extrae la parte entera del n\u00famero y la imprime\n\u00a0 double remainder = number - (double)int_part;\n\u00a0 resultString += int_part;\n\u00a0 if (digits &gt; 0) {resultString += \".\";}\u00a0\u00a0 \/\/ Imprime el punto digital si hay decimales\n\u00a0 while (digits-- &gt; 0){\u00a0 \/\/ Estrae los d\u00edgitos que quedan de uno en uno\n\u00a0\u00a0\u00a0 remainder *= 10.0;\n\u00a0\u00a0\u00a0 int toPrint = int(remainder);\n\u00a0\u00a0\u00a0 resultString += toPrint;\n\u00a0\u00a0\u00a0 remainder -= toPrint; \n\u00a0 } \n\u00a0 return resultString;\n}<\/pre>\n<pre>\/\/_________________MUESTRA LOS C\u00d3DIGOS DE ERROR EN EL LED____________________________\nvoid flashLed(int pin, int times, int wait) {\n\u00a0\u00a0\u00a0 for (int i = 0; i &lt; times; i++) {\n\u00a0\u00a0\u00a0\u00a0\u00a0 digitalWrite(pin, HIGH);\n\u00a0\u00a0\u00a0\u00a0\u00a0 delay(wait);\n\u00a0\u00a0\u00a0\u00a0\u00a0 digitalWrite(pin, LOW);\n\u00a0\u00a0\u00a0\u00a0\u00a0 if (i + 1 &lt; times) {delay(wait);}\n\u00a0\u00a0\u00a0 }\n}<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>\u00a0 Pues s\u00ed&#8230;. Tal como coment\u00e9 en el \u00faltimo art\u00edculo nuestro producto ya env\u00eda de forma peri\u00f3dica al gateway los valores de corriente rms, tensi\u00f3n rms, potencia aparente, potencia activa, potencia reactiva, frecuencia, etc. Adem\u00e1s se identifica, env\u00eda el n\u00famero&hellip; <br \/><a class=\"read-more-button\" href=\"https:\/\/blog.whatsbee.net\/?p=635\">Leer m\u00e1s<\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_bbp_topic_count":0,"_bbp_reply_count":0,"_bbp_total_topic_count":0,"_bbp_total_reply_count":0,"_bbp_voice_count":0,"_bbp_anonymous_reply_count":0,"_bbp_topic_count_hidden":0,"_bbp_reply_count_hidden":0,"_bbp_forum_subforum_count":0},"categories":[14,17,19,2,9,10],"tags":[28,35,37,56,76,102,109,110,111,173,175,197],"_links":{"self":[{"href":"https:\/\/blog.whatsbee.net\/index.php?rest_route=\/wp\/v2\/posts\/635"}],"collection":[{"href":"https:\/\/blog.whatsbee.net\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.whatsbee.net\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.whatsbee.net\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.whatsbee.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=635"}],"version-history":[{"count":0,"href":"https:\/\/blog.whatsbee.net\/index.php?rest_route=\/wp\/v2\/posts\/635\/revisions"}],"wp:attachment":[{"href":"https:\/\/blog.whatsbee.net\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=635"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.whatsbee.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=635"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.whatsbee.net\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=635"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}