Superintendência de Distribuição DEPARTAMENTO DE MEDIÇÃO DA DISTRIBUIÇÃO - PROTOCOLO PARA COLETA DE SISTEMAS DE INFORMAÇÕES DE MEDIDORES maio/2016 CÓPIA NÃO CONTROLADA Verificar versão atualizada na Internet
HISTÓRICO DE REVISÃO Informações sobre qualquer modificação neste documento Data Nome/Tel. Itens alterados maio/2016 Marcio 41-3230-8500 Elaboração desta Especificação. CÓPIA NÃO CONTROLADA Verificar versão atualizada na Internet 2-21
SUMÁRIO 1 - PROTOCOLO 1.1 - Composição 1.2 - Conceitos 1.3 - Descrição do protocolo 1.4 - Exemplos do protocolo 2 Código JAVA de exemplo de receptor de protocolo 2.1 Main. Java 2.2 - Tarefa.java 2.3 - Protocolo.java DESCRIÇÃO Este documento especifica o protocolo de comunicação a ser utilizado nos sistemas automáticos de leitura de medidores de energia das unidades do Grupo B. 1 - PROTOCOLO 1.1 - Composição O protocolo é composto por 31 bytes expressos em pares de dígitos hexadecimais ou decimais, totalizando 62 dígitos ou caracteres. Por que 1 byte = 2 caracteres hexadecimais? 1 byte = 8 bits = 2^8 = 256 2 hex = 2 * 4 bits = 2^4 * 2^4 = 16 * 16 = 256 1.2 - Conceitos 1. MSB (Most Significant Byte): Referente à ordem dos bytes, onde neste caso o byte mais significativo é apresentado primeiro (da esquerda para direita), ou seja, na ordem direta; 2. LSB (Least Significant Byte): Neste caso o byte menos significativo é apresentado primeiro, ou seja, a sequência natural dos dados ficam da direita para a esquerda, na ordem inversa; 3. Hexadecimal: Número de base 16, apresentado utilizando notação com o prefixo 0x ; 4. Conversão do dado real para o protocolo: Se for necessário converter para hexadecimal, sempre realizar esta operação primeiro. Caso seja necessário inverter (LSB), realize esta operação somente após a conversão (se houver) para hexadecimal; 5. Conversão do protocolo para o dado real: Se for necessário inverter (LSB), sempre realizar esta operação primeiro. Caso seja necessário converter de hexadecimal para decimal, realize esta operação somente após a inversão (se houver). 6. NIO (número interno operacional) é o nome utilizado na Copel para o medidor. 7. Concentrador: Elemento da rede de comunicação responsável pela concentração das leituras dos medidores e envio ao MDM (banco de dados). CÓPIA NÃO CONTROLADA Verificar versão atualizada na Internet 3-21
1.3 - Descrição do protocolo O concentrador de dados será o responsável por fazer a ponte entre a rede ethernet e a rede de comunicação com os medidores. O concentrador funcionará como um repassador de informações para o banco de dados. Deve ser definido um endereço (IP/PORTA) para comunicação com o servidor (o concentrador deverá ter a capacidade de hospedar um servidor de páginas HTTP para esta configuração) que, por sua vez, deve escutar esta porta esperando os dados do concentrador. Toda vez que o concentrador tiver uma informação nova ela será encaminhada para o servidor que deverá descompactar/interpretar o pacote e salvar os dados. Função Informar Dados Este pacote é enviado pelo concentrador toda vez que o mesmo recebe a consumo de algum equipamento. Ele serve para informar ao servidor o status de um consumidor específico. Rede Sequência Comando Subcomando ID Leitura Status 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Longitude (inteiro) Longitude (decimal) Longitude (sinal) Latitude (inteiro) Latitude (decimal) Latitude (sinal) 16 17 18 19 20 21 22 23 24 25 Nº do Equipamento 26 27 28 29 30 Rede: descreve o número da rede em que o concentrador está trabalhando, como existe apenas um concentrador por rede este campo descreve de qual concentrador o pacote foi enviado; Sequência: Número que indica a "comunicação" à qual o pacote se refere. Caso o pacote seja a resposta do concentrador ao servidor este campo deve conter o mesmo número enviado pelo servidor na requisição do mesmo, caso contrário (pacote gerado de maneira ativa para o servidor) o campo deverá conter zero (0); Comando: 01 Dados de informação; Sub-comando: 01 Informação do equipamento com o ID do campo ID ; CÓPIA NÃO CONTROLADA Verificar versão atualizada na Internet 4-21
ID: descreve o número de identificação do equipamento que enviou o pacote, para sistemas com um equipamento por consumidor este campo descreve a quem pertence à leitura; Leitura: descreve o consumo ativo de cada consumidor. Os dados são enviados no padrão BCD, por exemplo: Campo Leitura = 12 34 56 78 09 Consumo = 0978563412 W ou 978563,412 KW Status: Indica o status do consumidor em questão (este campo está reservado para futuras aplicações onde o equipamento pode reportar faltas de energia, falhas na comunicação com o medidor e outros eventos a serem definidos) Em condições normais deve ser preenchido com zeros outras condições serão definidas futuramente; Longitude Inteiro: Parte inteira da Longitude; Longitude Decimal: Parte decimal da Longitude. Está no formato Little Endian e o valor está alinhado à esquerda, com 6 casas. Exemplo: Para uma longitude 120,1: Longitude decimal = 100000 = 0x0186A0(hexa) No pacote será: Byte 17 18 19 Valor A0 86 01 Longitude sinal: Indicação de positivo ou negativo do dado de Longitude 0 positivo, 1 negativo; Latitude Inteiro: Parte inteira da Latitude Latitude Decimal: Parte decimal da Latitude. Está no formato Little Endian e o valor está alinhado à esquerda, com 6 casas. Exemplo: Para uma latitude 50,0454: Latitude decimal = 045400 = 45400 = 0x00B158(hexa) No pacote será: Byte 22 23 24 Valor 58 B1 00 CÓPIA NÃO CONTROLADA Verificar versão atualizada na Internet 5-21
Latitude sinal: Indicação de positivo ou negativo do dado de Latitude 0 positivo, 1 negativo; Número do Equipamento: Descreve o número único que identifica o consumidor. Os dados são enviados no padrão BCD, por exemplo: Campo Número do Equipamento = 78 56 34 12 00 Equipamento = 0012345678 Fluxo da informação Neste caso a comunicação será iniciada ativamente pelo concentrador. Ele irá abrir uma comunicação em um IP/PORTA especifico (no caso o do servidor no qual a aplicação está rodando) segundo o protocolo TCP/IP e enviar o pacote de Informar Dados. Tendo como base a premissa de que o concentrador vai apenas informar os dados e não irá se preocupar com a recepção deles pela aplicação, já que o servidor não vai responder nada ao concentrador após receber os dados do mesmo e o próprio protocolo TCP/IP possui garantia de entrega, o campo Sequência não possui sentido e será preenchido com zeros (0). Possibilitando desta forma uma diferenciação (caso necessário) dos pacotes enviados de forma ativa do concentrador daqueles enviados como resposta à requisição do servidor (previstos para futuras aplicações). CÓPIA NÃO CONTROLADA Verificar versão atualizada na Internet 6-21
1.4 - Exemplos do protocolo Exemplos do protocolo (índice de 0 até 30, com 31 bytes no total) Caso 1 Rede Sequência Comando Subcomando ID Leitura Status 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 02 59 00 00 00 00 01 01 03 99 00 90 23 15 00 00 Longitude (inteiro) Longitude (decimal) Longitude (sinal) Latitude (inteiro) Latitude (decimal) Latitude (sinal) Nº do Equipamento 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 00 00 00 00 00 00 00 00 00 00 23 06 74 12 03 CÓPIA NÃO CONTROLADA Verificar versão atualizada na Internet 7-21
Detalhamento do protocolo caso 1 Tipo de dado Posição/índice Ordem Métrica Entrada Saída Rede 0 e 1 MSB (direta) Hexadecimal 0x0259 601 Sequência 2, 3, 4 e 5 MSB (direta) Hexadecimal 0x00000000 0 Comando 6 MSB (direta) Hexadecimal 0x01 1 Subcomando 7 MSB (direta) Hexadecimal 0x01 1 Id 8 e 9 MSB (direta) Hexadecimal 0x0399 921 Leitura 10, 11, 12, 13 e 14 LSB (inversa) Decimal 0090231500 0015239000 (15239 kwh) Status 15 MSB (direta) Hexadecimal 0x00 0 Longitude (inteiro) 16 MSB (direta) Hexadecimal 0x00 0 Longitude (decimal) 17, 18 e 19 LSB (inversa) Hexadecimal 0x000000 0 Longitude (sinal) 20 MSB (direta) Decimal 00 + (positivo) Latitude (inteiro) 21 MSB (direta) Hexadecimal 0x00 0 Latitude (decimal) 22, 23 e 24 LSB (inversa) Hexadecimal 0x000000 0 Latitude (sinal) 25 MSB (direta) Decimal 00 + (positivo) Nº do equipamento 26, 27, 28, 29 e 30 LSB (inversa Decimal 2306741203 312740623 CÓPIA NÃO CONTROLADA Verificar versão atualizada na Internet 8-21
Caso 2: Rede Sequência Comando Subcomando ID Leitura Status 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 02 58 00 00 00 00 01 01 00 2C 00 90 72 11 00 00 Longitude (inteiro) Longitude (decimal) Longitude (sinal) Latitude (inteiro) Latitude (decimal) Latitude (sinal) Nº do Equipamento 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 F4 2D 05 01 19 69 9A 06 01 03 02 83 10 03 CÓPIA NÃO CONTROLADA Verificar versão atualizada na Internet 9-21
Detalhamento do caso 2 Tipo de dado Posição/índice Ordem Métrica Entrada Saída Rede 0 e 1 MSB (direta) Hexadecimal 0x0258 600 Sequência 2, 3, 4 e 5 MSB (direta) Hexadecimal 0x00000000 0 Comando 6 MSB (direta) Hexadecimal 0x01 1 Subcomando 7 MSB (direta) Hexadecimal 0x01 1 Id 8 e 9 MSB (direta) Hexadecimal 0x002C 44 Leitura 10, 11, 12, 13 e 14 LSB (inversa) Decimal 0090721100 0011729000 (11729 kwh) Status 15 MSB (direta) Hexadecimal 0x00 0 Longitude (inteiro) 16 MSB (direta) Hexadecimal 0x31 49 Longitude (decimal) 17, 18 e 19 LSB (inversa) Hexadecimal 0xF42D05 339444 Longitude (sinal) 20 MSB (direta) Decimal 01 - (negativo) Latitude (inteiro) 21 MSB (direta) Hexadecimal 0x19 25 Latitude (decimal) 22, 23 e 24 LSB (inversa) Hexadecimal 0x699A06 432745 Latitude (sinal) 25 MSB (direta) Decimal 01 - (negativo) Nº do equipamento 26, 27, 28, 29 e 30 LSB (inversa Decimal 0302831003 310830203 CÓPIA NÃO CONTROLADA Verificar versão atualizada na Internet 10-21
2 - Código JAVA de exemplo de receptor de protocolo 2.1 - Main.java package com.copel.telemedicao; import java.io.ioexception; import java.net.bindexception; import java.net.serversocket; import java.net.socket; import java.util.concurrent.executorservice; import java.util.concurrent.executors; import org.apache.log4j.logger; /** * @author C047267 * */ public class Main { private static final Logger log = Logger.getLogger(Main.class); private static final int PORTA = 12345; public static void main(string[] args) { try { final ServerSocket serversocket = new ServerSocket(PORTA); new Thread("portListener") { public void run() { try { log.info("escutando na porta " + serversocket.getlocalport() + "..."); Socket socket = null; while ((socket = serversocket.accept())!= null) { log.info("conectado! - " + socket.tostring()); ExecutorService es = Executors.newSingleThreadExecutor(); es.submit(new Tarefa(socket)); CÓPIA NÃO CONTROLADA Verificar versão atualizada na Internet 11-21
catch (Exception e) { log.error(e); finally { if (!serversocket.isclosed()) { try { serversocket.close(); log.info("fechando porta: " + serversocket.getlocalport() + "..."); catch (IOException e) { log.error(e); ;.start(); catch (BindException be) { log.error("porta " + PORTA + " ja em uso! ", be); catch (Exception e) { log.error(e); CÓPIA NÃO CONTROLADA Verificar versão atualizada na Internet 12-21
2.2 - Tarefa.java package com.copel.telemedicao; import java.io.ioexception; import java.net.socket; import org.apache.log4j.logger; /** * @author C047267 * */ public class Tarefa implements Runnable { private final Socket socket; private final static Logger log = Logger.getLogger(Tarefa.class); private final static char[] hexarray = "0123456789ABCDEF".toCharArray(); public Tarefa(Socket socket) { this.socket = socket; @Override public void run() { log.info("entrou na tarefa! " + socket.tostring()); int lido = -1; byte[] buffer = new byte[31 * 256]; byte[] arraydados = null; try { while ((lido = socket.getinputstream().read(buffer)) > -1) { arraydados = new byte[lido]; System.arraycopy(buffer, 0, arraydados, 0, lido); /** * Neste ponto o protocolo pode ser utilizado de diversas * maneiras diferentes. O array de bytes foi transformado em uma * unica string, em array de pares de string (hexadecimal * literal), e depois montado um objeto. */ CÓPIA NÃO CONTROLADA Verificar versão atualizada na Internet 13-21
log.info("array de bytes (em decimal): " + printararraydebytes(arraydados)); String umastringso = bytestohex(arraydados); log.info("string unica: " + umastringso); if (umastringso.length() == 62) { try { String[] arraydeparesdestring = umastringso.split("(?<=\\g.{2)"); log.info("array de pares (hex): " + printararraydepares(arraydeparesdestring)); Protocolo protocolo = new Protocolo( arraydeparesdestring); log.info("objeto montado: " + protocolo.tostring()); catch (NumberFormatException nfe) { log.error("erro ao montar o protocolo, dados invalidos: " + umastringso + ": " + nfe.getmessage()); else { throw new Exception( "Protocolo com tamanho diferente de 31 bytes!"); catch (Exception e) { log.error("erro ao receber/enviar leitura: " + bytestohex(arraydados) + " " + e.getmessage()); finally { try { socket.close(); log.info("desconectado! " + socket.tostring()); catch (IOException e) { log.error("erro ao fechar o socket: " + e.getmessage()); private static String bytestohex(byte[] bytes) { char[] hexchars = new char[bytes.length * 2]; for (int j = 0; j < bytes.length; j++) { int v = bytes[j] & 0xFF; hexchars[j * 2] = hexarray[v >>> 4]; CÓPIA NÃO CONTROLADA Verificar versão atualizada na Internet 14-21
hexchars[j * 2 + 1] = hexarray[v & 0x0F]; return new String(hexChars); private static String printararraydebytes(byte[] arraydebytes) { StringBuilder sb = new StringBuilder(); for (byte b : arraydebytes) { int i = b; if (i < 0) { i = 256 + i; sb.append(i).append(" "); return sb.tostring().trim(); private static String printararraydepares(string[] arraydeparesdestring) { StringBuilder sb = new StringBuilder(); for (String s : arraydeparesdestring) { sb.append(s).append(" "); return sb.tostring().trim(); CÓPIA NÃO CONTROLADA Verificar versão atualizada na Internet 15-21
2.3 - Protocolo.java package com.copel.telemedicao; import java.io.serializable; /** * @author C047267 * */ public class Protocolo implements Serializable{ //serial random private static final long serialversionuid = -549593122530022285L; private int rede; private int sequencia; private int comando; private int subcomando; private int id; private int leitura; private int status; private double latitude; private double longitude; private long nio; public Protocolo() { public Protocolo(String[] protocolo) throws NumberFormatException { rede = Integer.parseInt(protocolo[0] + protocolo[1], 16); sequencia = Integer.parseInt(protocolo[2] + protocolo[3] + protocolo[4] + protocolo[5], 16); comando = Integer.parseInt(protocolo[6], 16); subcomando = Integer.parseInt(protocolo[7], 16); id = Integer.parseInt(protocolo[8] + protocolo[9], 16); //LSB -> Inverter a ordem dos bytes //Apos a inversao, foram removidas as 3 ultimas casas para converter Wh em kwh leitura = Integer.parseInt((protocolo[14] + protocolo[13] + protocolo[12] + protocolo[11] + protocolo[10]).substring(0, 7)); CÓPIA NÃO CONTROLADA Verificar versão atualizada na Internet 16-21
status = Integer.parseInt(protocolo[15], 16); //Longitude recebido por partes int longitudeinteiro = Integer.parseInt(protocolo[16], 16); //LSB -> Inverter a ordem dos bytes e depois converter o hexadecimal int longitudedecimal = Integer.parseInt(protocolo[19] + protocolo[18] + protocolo[17], 16); if ((longitudedecimal > 0) (longitudeinteiro > 0)) { longitude = (double) longitudeinteiro + (longitudedecimal * 0.000001); if (Integer.parseInt(protocolo[20]) == 1) { longitude = longitude * -1; else { longitude = 0d; //Para a latitude sao as mesmas regras da longitude int latitudeinteiro = Integer.parseInt(protocolo[21], 16); int latitudedecimal = Integer.parseInt(protocolo[24] + protocolo[23] + protocolo[22], 16); if ((latitudedecimal > 0) (latitudeinteiro > 0)) { latitude = (double) latitudeinteiro + (latitudedecimal * 0.000001); if (Integer.parseInt(protocolo[25]) == 1) { latitude = latitude * -1; else { latitude = 0d; nio = Long.parseLong(protocolo[30] + protocolo[29] + protocolo[28] + protocolo[27] + protocolo[26]); public int getrede() { return rede; public void setrede(int rede) { this.rede = rede; public int getsequencia() { return sequencia; CÓPIA NÃO CONTROLADA Verificar versão atualizada na Internet 17-21
public void setsequencia(int sequencia) { this.sequencia = sequencia; public int getcomando() { return comando; public void setcomando(int comando) { this.comando = comando; public int getsubcomando() { return subcomando; public void setsubcomando(int subcomando) { this.subcomando = subcomando; public int getid() { return id; public void setid(int id) { this.id = id; public int getleitura() { return leitura; public void setleitura(int leitura) { this.leitura = leitura; public int getstatus() { return status; public void setstatus(int status) { this.status = status; CÓPIA NÃO CONTROLADA Verificar versão atualizada na Internet 18-21
public double getlatitude() { return latitude; public void setlatitude(double latitude) { this.latitude = latitude; public double getlongitude() { return longitude; public void setlongitude(double longitude) { this.longitude = longitude; public long getnio() { return nio; public void setnio(long nio) { this.nio = nio; @Override public String tostring() { StringBuilder builder = new StringBuilder(); builder.append("protocolo [rede="); builder.append(rede); builder.append(", sequencia="); builder.append(sequencia); builder.append(", comando="); builder.append(comando); builder.append(", subcomando="); builder.append(subcomando); builder.append(", id="); builder.append(id); builder.append(", leitura="); builder.append(leitura); builder.append(", status="); builder.append(status); CÓPIA NÃO CONTROLADA Verificar versão atualizada na Internet 19-21
builder.append(", latitude="); builder.append(latitude); builder.append(", longitude="); builder.append(longitude); builder.append(", nio="); builder.append(nio); builder.append("]"); return builder.tostring(); CÓPIA NÃO CONTROLADA Verificar versão atualizada na Internet 20-21
2.4 - log4j.properties # Root logger option log4j.rootlogger=info, stdout, file # Redirect log messages to console log4j.appender.stdout=org.apache.log4j.consoleappender log4j.appender.stdout.target=system.out log4j.appender.stdout.layout=org.apache.log4j.patternlayout log4j.appender.stdout.layout.conversionpattern=%d{dd/mm/yyyy HH:mm:ss,SSS %-5p %c{1:%l - %m%n # Redirect log messages to a log file, support file rolling. log4j.appender.file=org.apache.log4j.rollingfileappender log4j.appender.file.file=c:/logs/telemedicao.log log4j.appender.file.immediateflush=true log4j.appender.file.maxfilesize=10mb log4j.appender.file.maxbackupindex=10 log4j.appender.file.layout=org.apache.log4j.patternlayout log4j.appender.file.layout.conversionpattern=%d{dd/mm/yyyy HH:mm:ss,SSS %-5p %c{1:%l - %m%n Teste Para utilizar a aplicação telemedicao.jar, via prompt, terminal ou similar, executar o comando: java -jar telemedicao.jar A aplicação irá reservar a porta 12345 para ela. Caso a porta esteja ocupada, ela não funcionará. Para simular o envio dos pacotes de protocolo, pode ser utilizado o aplicativo TCP Test Tool. CÓPIA NÃO CONTROLADA Verificar versão atualizada na Internet 21-21