21. Aquisição de dados de um medidor multiparâmetro (Multipar)

Nessa seção vou mostrar o uso da Programação Orientada a Objetos (POO) com o uso da linguagem Tcl/Tk para automatizar a aquisição de dados de um medidor multiparâmetro.

Este trabalho foi apresentado em um Congresso de Metrologia em 2009 com o título: POO com Tcl/Tk na Automação de Laboratório.

Este trabalho foi inspirado nos artigos: Object-oriented techniques for design and development of standard software solutions in automation and data management in analytical chemistry Urbano, Luque e Gómez-Nieto,2006 e Objects inTcl de Koen Van Damme.

Também estou disponibilizando o programa orion5star_thermo_03.tcl e da biblioteca objLab.tcl (Orion5Star).

Neste programa utilizei a biblioteca Metakit para facilitar o gerenciamento dos dados e a biblioteca Plotchart para exibição de gráficos.

21.1. Características do Equipamento

Neste trabalho descrevi o desenvolvimento de uma interface para aquisição das leituras de Oxigênio Dissolvido (OD), pH (ou Íon Seletivo) e Condutividade (Cond) feitas pelo medidor multiparâmetro ORION 5 STAR. (Download do Manual)

Figura 160. Medidor multiparâmetro ORION 5 STAR™ com destaque para o conector serial (P1) na parte traseira.

Medidor multiparâmetro ORION 5 STAR™ com destaque para o conector serial (P1) na parte traseira.


A maioria dos equipamentos utilizam conectores DB9 para comunicação serial mas neste caso o equipamento vem com um conector P1.

Para a montagem do cabo você tem duas opções, ou compra o cabo comercial ou monta o seu próprio cabo seguindo as orientações da figura seguinte:

Figura 161. Esquema de ligação do cabo de comunicação DB9(fêmea) <-> P1 (stéreo) para o medidor ORION 5 STAR™. O pino 2 do conector DB9 deve ser ligado na extremidade do conector P1, o pino 3 do conector DB9 é ligado na parte intermediária do conector P1 e o pino 5 do conector DB9 é ligado na base do conector P1.

Esquema de ligação do cabo de comunicação DB9(fêmea) <-> P1 (stéreo) para o medidor ORION 5 STAR™. O pino 2 do conector DB9 deve ser ligado na extremidade do conector P1, o pino 3 do conector DB9 é ligado na parte intermediária do conector P1 e o pino 5 do conector DB9 é ligado na base do conector P1.


Atenção

Use um multímetro para identificar os 3 pontos de soldagem do pino P1 e verifique, antes de montar o cabo, se o pino P1 que você comprar consegue entrar no conector do equipamento pois este apresenta uma borda de proteção que limita o espaço para o encaixe do pino.

Após a conexão do Equipamento com o PC é necessário configurar os parâmetros de comunicação serial (velocidade de transmissão, bits de dados, paridade, bit de parada) conforme as informações do manual do equipamento.

Este equipamento se comunica com os padrões: 8 bits de dados, sem paridade (No parity) e 1 bit de parada(8N1). Resta ao usuário apenas configurar a velocidade de transmissão (baud).

Para configurar a velocidade de transmissão você deve pressionar o botão para entrar no modo de configuração. Em seguida usar as setas até encontrar a opção r232 e selecionar a velocidade de 9600 com os botões e .

Não encontrei no manual deste equipamento qualquer informação sobre comandos para serem enviados pelo PC para o Equipamento (PC --> Equipamento) e portanto optei por configurar o medidor para enviar em intervalos regulares as informações do display pela porta serial. (PC <-- Equipamento).

Para configurar o medidor para envio de dados em intervalos regulares pressione o botão para entrar no modo de configuração. Em seguida use as setas até a opção rEAd e com as setas selecione a opção tImE e com o botão selecione os campos numéricos e ajuste o intervalo de tempo mais adequado. No nosso caso selecionamos o intervalo de 5 segundos para cada leitura.

21.1.1. Formato dos dados enviados pela porta serial.

O medidor pode ser configurado, pelo teclado, para enviar os dados pela porta serial em dois formatos diferentes:

  1. relatório com múltiplas linhas

  2. uma única linha com todas as informações separadas por ,

Para selecionar o formato de saída de dados pressionar o botão para entrar no modo de configuração. Em seguida usar as setas até encontrar a opção r232.

Em seguida pressionar o botão para entrar no submenu e com as teclas selecionar a opção 0UtF (Output Format) que possui duas opções: Prnt e C0mP.

Para conhecer o formato com que os dados são enviados pela porta serial, usei o programa femto600s.tcl cujo desenvolvimento mostramos na seção Aquisição de dados de um espectrofotômetro - I.

Na opção Prnt o medidor ORION 5 STAR™ de bancada transmite pela porta serial um relatório com o seguinte formato:


 5-Star BENCHTOP MULTI with ISE

Meter S/n                B05876

SW rev                   2.01

Method #                 1

09-01-2004 18:37:21

                         

Relative                 -1402.4 RmV

mV                       -1402.4 mV

Temperature              25.0 C

Calibration # 0

                         

Conductivity             0.00 uS/cm

Conductance              0.00 uS

Temperature              25.0 C

Temp. Coefficient        2.1 %/C

Temp. Reference          25.0 C

Cell Constant            0.475 /cm

Calibration # 0

                         

Concentration            9999 mg/L

Saturation               9999 % sat

Current                  2097.0 uA

Temp.Solution            25.0 C

Temp.Membrane            25.0 C

Barometric Pressure      9999 mmHg

Salinity Correction      0.0 ppt

Slope                    13.1 nA/%sat

Calibration # 56

                         

Operator ____________

Sample # ____________

O equipamento gera um relatório com muitos dados e neste caso precisamos extrair as linhas que contêm a informação que precisamos. Portanto precisamos usar Expressões Regulares para localizar e extrair sequências de caracteres de uma string.

Nota

Na opção COmP o medidor envia todas as informações em apenas uma linha usando , como delimitador de campo.

Na primeira vez que li o manual não havia percebido essa diferença e acabei optando pelo formato de relatório.

Algum tempo mais tarde percebi que havia uma forma mais simples de saída de dados, mas o programa já estava pronto. :^(

Moral da história: procure conhecer com detalhes os recursos do instrumento antes de iniciar o desenvolvimento do programa.

21.2. Modelando o programa segundo o paradigma da Programação Orientada a Objetos

Através da filosofia da POO um programa é concebido como uma coleção de objetos ao invés de uma lista de instruções, como na programação tradicional.

Esses objetos vêm associados com as próprias operações que eles realizam, chamadas de métodos, na terminologia da POO. Os métodos portanto, servem para fazer os objetos operarem algo.

Além dos métodos os objetos também possuem atributos que são propriedades caracterizando a estrutura de um dado objeto. Os objetos, nesse tipo de programação, são organizados em classes hierárquicas as quais descrevem um ou mais objetos similares. (O que é programação?, 1996)

C++ e Java são linguagens bem conhecidas que oferecem suporte nativo para POO. Isso não significa que POO não seja possível em outras linguagens, pois POO é uma maneira de pensar, de modelar o sistema. Tem mais a ver com a análise e modelagem do sistema do que com a sua implementação. A Tcl (até a versão 8.5)[14] não oferece tipos primitivos para POO, mas a sua flexibilidade permite a criação de primitivas para a criação de objetos.

O artigo Objetos em Tcl mostra de maneira muito didática como usar o conceito de Orientação a Objetos usando os recurso de criação dinâmica de procedimentos com Tcl.

Existem também algumas bibliotecas que facilitam o uso da POO com Tcl, tais como: IncrTcl, Stooop e OTcl. Ver mais informações no http://wiki.tcl.tk/970 e na seção Programação Orientada a Objetos em Tcl/Tk com o pacote TclOO.

É possível identificar duas classes típicas na Automação de Laboratório para serem modelados segundo a POO: Equipamentos e Instrumentos.

Um instrumento poderia ser definido como um dispositivo que permite realizar medições e portanto fornece algum tipo de informação (qualitativa ou quantitativa), enquanto um equipamento serve para realizar alguma tarefa sem o compromisso de gerar informações sobre o sistema que se deseja estudar. Assim, pHmetro é um instrumento, enquanto uma bomba peristáltica é um equipamento.

Esses objetos fazem parte de uma classe mais geral Hardware Analítico, conforme o diagrama de classes na figura seguinte.

Figura 162. Diagrama de classe para a especialização da classe Hardware Analítico nas subclasses Equipamento e Instrumento.

Diagrama de classe para a especialização da classe Hardware Analítico nas subclasses Equipamento e Instrumento.


Portanto a classe instrumento possui os seguintes atributos: unidade, estado e leitura.

O atributo unidade armazena a unidade de leitura (Ex: mg/L, mV), o estado identifica o estado atual do instrumento (MONITORANDO, PARADO, PAUSADO, CALIBRANDO ou CONDICIONANDO) e a leitura armazena a última leitura realizada como uma lista no formato {tempo, leitura}.

Os objetos da classe Instrumento podem representar um sensor de temperatura, um eletrodo íon-seletivo ou um sistema de análise completo.

A automação da aquisição de dados aumenta significativamente o volume de dados para serem analisados, ainda que o número de amostras ou parâmetros seja pequeno. Em um contexto de pesquisa, a análise do conjunto de dados de um projeto é um suporte importante para tomada de decisões, por isso foram modeladas a classe Amostra e Medida conforme o diagrama de classes da figura seguinte.

Figura 163. Diagrama das classes Amostra e Medida para o gerenciamento de dados experimentais.

Diagrama das classes Amostra e Medida para o gerenciamento de dados experimentais.


A multiplicidade 1 e * na associação entre Amostra e Medida, significa que um objeto da classe Amostra pode estar associado a vários objetos da classe Medida para diferentes parâmetros em momentos diferentes, mas um objeto da classe Medida deve estar associado a apenas um único objeto da classe Amostra.

Durante um projeto de pesquisa são realizados vários experimentos durante os quais, podem ser usadas amostras únicas (coleta manual em campo) ou várias amostras (coleta automática), por isso a classe Amostra possui os seguintes atributos: projeto, experimento (exp), coleta, responsável pelo experimento (resp_exp) e parâmetros monitorados (par_mon).

Os atributos dos objetos da classe Amostra guardam as seguintes informações:

  • projeto {codigo coordenador} -> lista contendo o código do projeto e o nome do coordenador do projeto

  • experimento {numero estado_atual} -> lista com o número do experimento e o estado atual do experimento

    A variável estado_atual guarda o estado do experimento que estiver em andamento e pode assumir os seguintes estados: INICIANDO, ANDAMENTO, PAUSADO, INTERROMPIDO ou REALIZADO

  • coleta {local data} -> lista contendo o local e a data da coleta. O local pode ser referir a um procedimento de coleta manual ou mesmo o ponto de amostragem em um sistema de amostragem automatizada.

  • responsavel_experimento -> o responsável pode ser o nome ou o login do usuário

  • parametros_monitorados {p1 lim_inf lim_sup ...} -> lista contendo o nome do parâmetro e os respectivos limites superior e inferior.

    A definição desses limites permite executar ações pré-definidas quando os parâmetros monitorados estiverem fora dos limites estabelecidos.

O atributo am_analisada (amostra analisada) da classe Medida permite discriminar diferentes locais de amostragem durante um experimento, por exemplo entrada e saída de um biorreator, simplificando a implantação de um sistema da amostragem automatizado para monitoramento contínuo de uma montagem experimental.

As classes Instrumento, Amostra e Medida estão associadas entre si e com um Banco de Dados no contexto de um Experimento conforme o diagrama da figura seguinte.

Figura 164. Diagrama das associações entre as classes Instrumento, Amostra e Medida com um Banco de Dados no contexto de um Experimento.

Diagrama das associações entre as classes Instrumento, Amostra e Medida com um Banco de Dados no contexto de um “Experimento”.


Um instrumento pode estar monitorando uma amostra de cada vez, gerando uma leitura e permitindo a criação de uma instância da classe Medida.

Uma única amostra pode ser monitorada por vários instrumentos gerando várias medidas, mas uma instância da classe Medida só poderá estar associada a um único instrumento e uma única amostra.

Quando um dado chegar pela porta serial é verificado se o objeto instrumento existe E se está no estado MONITORANDO para a criação de uma instância da classe Medida que irá consolidar todas as informações (dados e meta-dados) dos respectivos objetos instrumento e amostra para armazenamento no banco de dados.

No caso de existirem diversos locais de medida para uma mesma amostra, então o instrumento deve permanecer no estado CONDICIONANDO enquanto é feita a troca de amostras e neste caso, a leitura não seria armazenada no banco de dados.

21.3. Implementação do pacote objLab

Criamos um módulo chamado objLab.tcl dentro do qual implementamos os procedimentos para emular a criação de objetos. Esses procedimentos são disponibilizados para a rotina principal com o comando package require objLab.

Adotamos o critério de nomear os objetos da classe Instrumento com o nome eletrodo seguido do nome do parâmetro: eletrodo_pH, eletrodo_ORP e eletrodo_OD.

Os atributos são armazenados em variáveis do tipo array com escopo global, as quais são atualizadas pelos respectivos métodos para atualizar (def_...) e resgatar o valor atual (get_...).

Por exemplo, um objeto da classe Instrumento possui a unidade de medida como um dos atributos, o qual é um array global que será indexado pelo nome do objeto e armazenará a unidade de concentração.

Por exemplo, o método get_unidade é implementado em Tcl da seguinte forma:

proc get_unidade { nome_sensor } { 
    
    global unidade 
    
    if { [info exists unidade($nome_sensor)] } { 
	 
	return $unidade($nome_sensor) 
	 
    } else { 
	 
	puts "Atenção: $nome_sensor não tem unidade" 
	return "unidade_indefinida" 
	 
    } 
 }

E o método def_unidade:

proc def_unidade { nome_sensor valor_unidade }  { 

global unidade 

set unidade($nome_sensor) $valor_unidade 

} 

Foram implementados dois procedimentos para a criação de um objeto da classe Instrumento: cria_instrumento e instrumento.

proc cria_instrumento { nome_instrumento comando args } {
    if { $comando == "get_unidade" } {
	    return [get_unidade $nome_instrumento]
	} elseif { $comando == "def_unidade"} {
	    def_unidade $nome_instrumento [lindex $args 0]
	} elseif { $comando == "get_estado_instrumento" } {
	    return [get_estado_instrumento $nome_instrumento]
	} elseif { $comando == "def_estado_instrumento"} {
	    def_estado_instrumento $nome_instrumento [lindex $args 0]
	} elseif { $comando == "get_leitura" } {
	    return [get_leitura $nome_instrumento]
	} elseif { $comando == "def_leitura"} {
	    def_leitura $nome_instrumento [lindex $args 0]
	} else {
	    puts "Erro: $comando é um comando desconhecido"
	}
}

E o procedimento instrumento

proc instrumento {args} {
    foreach nome $args {
#	puts "Criando instrumento $nome"
	proc $nome {comando args} "return \[eval cria_instrumento $nome \$comando \$args\]"
    }
}

O argumento args é um nome especial que armazena todos os argumentos restantes em uma lista de tamanho variável.

Essas rotinas são usadas no programa principal com o comando:

instrumento eletrodo_$nome_par

A criação de objetos utiliza espaços de memória que precisam ser liberados quando não forem mais necessários, por isso a necessidade de uma rotina para liberar recursos chamada de remover_instrumento:

proc remover_instrumento { args } {
    
    global unidade
    global estado_instrumento
    global leitura
    
    foreach nome $args {
	if { [info exists unidade($nome)] } {
		unset unidade($nome)  ;#Deleta os dados do objeto
	    }
	    if { [info exists estado_instrumento($nome)] } {
		    unset estado_instrumento($nome)   ;#Deleta os dados do objeto
		}
		if { [info exists leitura($nome)] } {
			unset leitura($nome) ;#Deleta os dados do objeto
		    }
		    rename $nome {}       ;#Deleta o comando objeto
    }
}

Procedimentos análogos foram codificados para a criação de objetos das demais classes.

Aqui você pode baixar o arquivo objLab.tcl para ver todo o código.

21.4. Desenvolvimento da Interface Gráfica

A tela inicial de configuração permite ao usuário selecionar os eletrodos instalados, as unidades de medida e a porta serial em que está conectado o equipamento, conforme mostra a figura seguinte.

Figura 165. Tela inicial de configuração do programa de aquisição de dados para o medidor multiparâmetro.

Tela inicial de configuração do programa de aquisição de dados para o medidor multiparâmetro.


Atenção

Lembrar que a comunicação com o medidor é unidirecional, ou seja, o equipamento apenas envia um relatório pela porta serial mas não permite a configuração remota das unidades de medida.

Por isso é necessário escolher na tela inicial as mesmas unidades de medida que foi configurada no equipamento.

A tela inicial foi implementada com a seguinte rotina:

proc tela_config { janela_seguinte } {
    

    global par_monit fonte_media
    global pH_ISE COND OD
    global config    
    global unidade_ph_ise unidade_cond unidade_od

    global nome_porta_serial num_porta_serial
    
destroy .janela  
destroy .cfg

set config [frame .cfg]

set mensagem [label $config.msg -font $fonte_media -wraplength 7i -justify left -text "Programa para aquisição automática \
das leituras do potenciômetro \"Orion 5 STAR\" da \"Thermo\".\ 
Selecione os eletrodos instalados e as respectivas unidades. \n Bom Trabalho!"]

set config_ph_ise [labelframe $config.ph_ise -text "pH/ISE" ]
set config_cond   [labelframe $config.cond -text "Condutividade" ]
set config_od     [labelframe $config.od -text "Oxigênio Dissolvido" ]
set config_porta_serial     [labelframe $config.porta_serial -text "Porta Serial" ]

checkbutton $config_ph_ise.par_ph -text "pH/ISE" -font $fonte_media -variable pH_ISE 

pack $config_ph_ise.par_ph

foreach { unidade1 unidade2 } { "pH" {"pH" "pH"} "mV" {"mV" "mV"} "RmV" {"Relative" "RmV"} "ISE:ppb" {"Concentration" "ppb"} } {
    
    set nome_minusculo [string tolower $unidade1]
    
    radiobutton $config_ph_ise.$nome_minusculo -text "$unidade1" \
	-font $fonte_media -relief flat -anchor w  \
	-variable unidade_ph_ise -value $unidade2
    
    pack $config_ph_ise.$nome_minusculo -side top -anchor w
    
}


checkbutton $config_cond.par_cond -text "Cond" -font $fonte_media -variable COND 

pack $config_cond.par_cond

foreach { unidade1 unidade2 } { "uS/cm" {"Conductivity" "uS/cm"} "mg/L" {"TDS" "mg/L"} "ppt" {"Salinity" "ppt"} "Mohmxcm" {"Resistivity" "Mohmxcm"} } {
    
    set nome_minusculo [string tolower $unidade1]
    
    radiobutton $config_cond.$nome_minusculo -text "$unidade1" \
-font $fonte_media -relief flat -anchor w  -variable unidade_cond -value $unidade2
    
    pack $config_cond.$nome_minusculo -side top -anchor w
    
}

checkbutton $config_od.par_od -text "OD" -font $fonte_media -variable OD 

pack $config_od.par_od

foreach { unidade1 unidade2 } { "mg/L" {"Concentration" "mg/L"} "% sat" {"Saturation" "% sat"} } {
    
    set nome_minusculo [string tolower $unidade1]
    
    radiobutton $config_od.$nome_minusculo -text "$unidade1" \
-font $fonte_media -relief flat -anchor w  -variable unidade_od -value $unidade2 
    
    pack $config_od.$nome_minusculo -side top -anchor w
    
}


label $config_porta_serial.nome_porta_serial -font $fonte_media -text $nome_porta_serial
spinbox $config_porta_serial.numero_porta_serial -from 0 -to 10 -bg white -font $fonte_media \
-textvariable num_porta_serial -width 2

pack $config_porta_serial.nome_porta_serial  $config_porta_serial.numero_porta_serial -side left


set botao [frame $config.bt]

button $botao.continuar -text "Continuar" -font $fonte_media  -command continuar

button $botao.consultar_db -font $fonte_media \
	-text "Banco de Dados" -command consultar_cadastro

button $botao.sair -text "Sair" -font $fonte_media -command "exit"

pack $botao.continuar $botao.consultar_db $botao.sair -side left -expand yes 

pack $mensagem -side top

pack $botao -side bottom -expand yes -fill x -pady 8

pack $config_ph_ise $config_cond $config_od $config_porta_serial -side left -pady 8 -expand yes

pack $config

}

Cada eletrodo do equipamento será identificado por uma variável global associada a cada checkbutton (pH_ISE, COND e OD). E as respectivas unidades de medida ficam armazenadas nas variáveis: unidade_ph_ise, unidade_cond e unidade_od, cada qual associada a um radiobutton.

O número da porta serial fica armazenado na variável num_porta_serial e o nome da porta serial é definido em função do sistema operacional no início do programa pelos comandos:

global nome_porta_serial

if {$tcl_platform(platform) == "unix"} {
        set nome_porta_serial "/dev/ttyS"
    } else {
        set nome_porta_serial "com"
    }

Ao clicar no botão Continuar o programa abre a tela de monitoramento que permite visualizar seletivamente as leituras dos eletrodos conectados, conforme a figura seguinte.

Figura 166. Tela de monitoramento do programa de aquisição de dados para o potenciômetro multiparâmetros.

Tela de monitoramento do programa de aquisição de dados para o potenciômetro multiparâmetros.


Internamente o botão Continuar chama primeiramente a rotina continuar que cria uma variável par_monit, do tipo lista, contendo os parâmetros que serão monitorados.

proc continuar {} {
    
    global pH_ISE COND OD
    
    global unidade_ph_ise unidade_cond unidade_od
    
    global nome_porta_serial num_porta_serial porta_serial
    
    global config par_monit
    
    
    if { !($pH_ISE) && !($COND) && !($OD) } { 
	tk_messageBox -message "Por favor, selecione pelo menos \"1\" eletrodo!" 
	return
    }
        
    set par_monit {}
    
    if { $pH_ISE } {
	lappend par_monit pH_ISE
	lappend par_monit "Eletrodo de pH/ISE"
	lappend par_monit $unidade_ph_ise
    }
    
    if { $COND } {
	lappend par_monit COND
	lappend par_monit "Condutividade"
	lappend par_monit $unidade_cond
    } 
    
    if { $OD } {
	lappend par_monit OD
	lappend par_monit "Oxigênio Dissolvido"
	lappend par_monit $unidade_od
    }
    
    set porta_serial {}
    set nome $nome_porta_serial
    set numero $num_porta_serial

    if {[string equal $nome "/dev/ttyS"]} {
	set porta_serial [append nome $numero]
    } else {
	set porta_serial [append nome $numero ":"]
    }
    
    tela_monit  $config
    
    vincular_objetos $par_monit
    
    return
    
}

Nota

O aspecto das telas seguintes será determinado pelo conteúdo da variável par_monit. Isso significa que grande parte desse código pode ser reutilizado para outros equipamentos exigindo apenas a inclusão dos novos parâmetros nas rotinas tela_config e continuar.

No final da rotina continuar é feita a chamada da rotina tela_monit que exibe a tela da figura anterior e em seguida chama a rotina vincular_objetos passando como argumento a lista par_monit.

A rotina vincular_objetos utiliza o comando trace para vincular os check-buttons, da tela de monitoramento, com a rotina inst encarregada da criação e remoção dos objetos (ou instâncias) da classe Instrumento. Ou seja, toda vez que que as variáveis globais pH-ISE, COND e OD for modificada a rotina inst é executada.

Rotina vincular_objetos:

proc vincular_objetos lista {

global pH_ISE COND OD

set pH_ISE 0
set COND 0
set OD 0

foreach {p n u} $lista { trace add variable $p   write [list inst $p $u] }

return

}

A rotina inst cria, ou remove, os objetos da classe Instrumento conforme os check-buttons, da tela de monitoramento, são ativados ou desativados. Essa vinculação foi feita através das variáveis pH-ISE, COND e OD.

proc inst { args } {
    
    upvar \#0 [lindex $args 0] par
    set nome_par [lindex $args 0]
    set nome_unid_par [lindex $args 1]

    global frame_cb_$nome_par.cb
    
    if { $par } {
	
	puts "criando instrumento eletrodo_$nome_par e \$par : $par"
	puts "Unidade: [lindex $nome_unid_par 1]"
	instrumento eletrodo_$nome_par
	eletrodo_$nome_par def_unidade [lindex $nome_unid_par 1]
	
    } else {
	
	puts "removendo instrumento eletrodo_$nome_par e \$par : $par"
	remover_instrumento eletrodo_$nome_par
	
    }
}

O botão Iniciar Leitura da tela de monitoramento chama a rotina iniciar_leitura a qual inicia um contador de tempo, único para todas as leituras, e o registro dos pares de dados {tempo leitura} nos respectivos widgets text.

A rotina iniciar_leitura usa o comando fileevent para chamar a execução da rotina ler_porta sempre que houver dados para leitura na porta serial, cujo identificador está armazenado na variável id_porta.

Uso do comando fileevent dentro da rotina iniciar_leitura:

proc iniciar_leitura { porta_fisica } {
    
    global t_inicial
    global estado_experimento

    set estado_experimento 0
    
    cadastrar_experimento

    set t_inicial [clock clicks -milliseconds]
    
    set id_porta [abrir_porta $porta_fisica]

    if {$id_porta != "SERIAL_ERRO_OPEN"} {
	fileevent $id_porta readable [list ler_porta $id_porta]
    }
    
}

A rotina ler_porta fica encarregada de analisar os dados enviados pela porta serial e extrair as leituras com o uso do comando regexp (Ver o apêndice Expressões Regulares).

Implementação da rotina ler_porta:

proc ler_porta { canal } {
    
    global t_inicial   par_monit
    global estado_experimento    
    
    #Comando para calcular o tempo atual em milisegundos   
    set t_final [clock clicks -milliseconds]
    
    if { [eof $canal ] } {
	catch { close $canal }
	return 
    }
    
    
    set l [gets $canal]
    
    foreach {p n u} $par_monit {
	
	global $p leituras_dados_$p 
	
	if { [set $p] } {
	    
	    if {([regexp (^[lindex $u 0]) $l unidade0]) && \
([regexp ([lindex $u 1]$) $l unidade1])} { 

		regexp {(-)*[0-9]+(\.)*[0-9]+} $l l_$p	    
		
		#Comando para calcular o tempo decorrido desde t0 em minutos
		set t_final [format "%.2f" [expr ($t_final - $t_inicial)/60000.0]]
	
			
		#Comando regsub usado para substituir as ocorrências de . por , 
		#É possível usar \\. e \\, ou {\.} e {,} ou {,}
		set t_final_br [regsub {\.} $t_final \, ]
		set l_br_$p [regsub {\.} [set l_$p] {,} ]
	
		
		[set leituras_dados_$p] insert end \
"$t_final_br (min)  [set l_br_$p]  ($unidade1)\n"

		[set leituras_dados_$p] see end


#Se houver um experimento em andamento é chamado o procedimento que inicia 
#o uso do objeto da classe medida

if { $estado_experimento } {

iniciar_medida $p  min  [set l_$p]

}
		return
		
	    }
    
	}
    }
}

A rotina iniciar_leitura também chama a rotina cadastrar_experimento que abre a tela para cadastramento do experimento conforme a figura seguinte:

Figura 167. Tela de cadastramento de experimentos do programa de aquisição de dados para o potenciômetro multiparâmetros.

Tela de cadastramento de experimentos do programa de aquisição de dados para o potenciômetro multiparâmetros.


As informações fornecidas pelo usuário na tela de cadastramento são armazenadas nas seguintes variáveis:

  • cod_proj => código do projeto

  • coord_proj => coordenador do projeto

  • cod_exp => código do experimento

  • resp_exp => responsável pelo experimento

  • local_col => local da coleta

  • data_col => data da coleta (dia/mes/ano hora:min)

  • resp_col => responsável pela coleta

O botão Iniciar Experimento, habilita a criação dos objetos das classes Medida e Amostra para o gerenciamento e armazenagem dos dados enviados pelo potenciômetro.

Dica

Portanto, o botão Iniciar Leitura habilita o registro das leituras na tela de monitoramento, mas o armazenamento automático em banco de dados somente ocorre com o início de um experimento.

21.5. Armazenamento e Resgate dos Resultados Experimentais,

O gerenciamento do banco de dados foi implementado com o banco de dados Metakit que simplifica significativamente o armazenamento e resgate dos resultados experimentais, os quais são gravados em um arquivo binário que pode estar na máquina local ou na rede.

Veja na seção Instalação do banco de dados Metakit, os detalhes do processo de instalação do Metakit.

Para o armazenamento e resgate dos resultados experimentais, implementamos as seguintes rotinas:

21.5.1. Procedimento: arquivar_experimento.

Este procedimento tem por objetivo arquivar as informações do cadastro do experimento no banco de dados e consiste basicamente em 3 etapas:

  1. Verifica se o banco de dados já existe e se não existir cria um novo.

  2. Resgata os atributos do objeto da classe Amostra e armazena no array exp as informações fornecidas durante o cadastramento do experimento.

  3. Arquiva o conteúdo do array exp no banco de dados e armazena a localização do registro na variável reg_cad_exp que será usada pelo procedimento parar_experimento para armazenar o conteúdo do campo comentários

Ele recebe como argumento o nome de um objeto da classe Amostra, resgata as informações fornecidas no cadastramento, e armazena todas essas informações no arquivo medidor_multiparametro.db com o comando:

set reg_cad_exp [eval mk::row append db.cadastro [array get exp]]

O identificador para o arquivo medidor_multiparametro.db é aberto com o comando:

mk::file open db medidor_multiparametro.db

Abaixo a listagem da rotina arquivar_experimento:

proc arquivar_experimento { info_amostra } {
    
    global reg_cad_exp   
    global num_id_cad_exp
    
#Array no formato reg_cad_med(parametro) para armazenar o número do
#registro na tabela de resultados "db.medida" para agilizar o arquivamento
#das medidas

    global reg_cad_med 

	if {![file exists "medidor_multiparametro.db"]} {
		
		criar_db
	    }
	    
	    set exp(cod_proj) [lindex [$info_amostra get_projeto] 0]
	    
            set exp(coord_proj) [lindex [$info_amostra get_projeto] 1] 
	    
	    set exp(num_exp) [lindex [$info_amostra get_experimento] 0]
	    
	    set exp(resp_exp) [$info_amostra get_responsavel_experimento]
	    
	    set exp(local_col) [lindex [$info_amostra get_coleta] 0]
	    
            set exp(data_col)  [lindex [$info_amostra get_coleta] 1]
	    
            set exp(resp_col)  [lindex [$info_amostra get_coleta] 2]
	    	    
	    set exp(lst_par_monit_amostra) [$info_amostra get_parametros_monitorados] 
	    	    
	    mk::file open db medidor_multiparametro.db
	    
	    set exp(id_cad_exp) [getuniqueid db]
	    
            set num_id_cad_exp $exp(id_cad_exp)

	    set reg_cad_exp [eval mk::row append db.cadastro [array get exp]]
	    
	    mk::file commit db
	    
	    mk::file close db
}

21.5.2. Procedimento: parar_experimento.

Este procedimento é chamado para encerrar um experimento e executa as seguintes ações:

  1. Atribui à variável estado_experimento o valor 0

  2. Arquiva o conteúdo do campo comentários no banco de dados consultando o registro da tabela cadastro usando como chave o conteúdo da variável num_cad-exp

  3. Reabilita os botões Iniciar Experimento e Fechar da tela de cadastramento do experimento

  4. Remove o objeto da classe Amostra

Abaixo a listagem da rotina parar_experimento:

 proc parar_experimento {} {
	
	global estado_experimento
	global codigo_amostra
	global reg_cad_exp
	global num_exp num_exp_ant
	
if { $estado_experimento } {    

    if {[msg_alerta .id_exp "Deseja Realmente \"Parar\" o Experimento?"]} {
	    
	    set estado_experimento 0
	    
	    set coment_exp [.id_exp.coment.txt get 1.0 {end -1c}]
	    
	    mk::file open db medidor_multiparametro.db
	    
	    mk::set $reg_cad_exp coment_exp $coment_exp
	    
	    mk::file commit db
	    
	    mk::file close db
	    
	    .id_exp.b.continuar configure -state normal
	    .id_exp.b.cancelar configure -state normal
	    
	    remover_amostra $codigo_amostra 

	    set num_exp_ant $num_exp

	    mudar_simbolo
	}
	
    }

}

Nota

A variável global num_exp_ant serve para controlar o símbolo que deve ser usado na sobreposição de dados na rotina graficar_medida

21.5.3. Procedimento: criar_db.

Procedimento para criação do banco de dados no arquivo medidor_multiparametro.db, executado apenas quando o arquivo medidor_multiparametro.db não existe.

Essa rotina cria três tabelas: cadastro, medida e cad_control.

Abaixo a listagem da rotina criar_db:

proc criar_db {} {
    
    mk::file open db medidor_multiparametro.db

#Criação da tabela com campos para armazenar os dados cadastrais de um experimento
    
    mk::view layout db.cadastro {id_cad_exp:L cod_proj:S \
             coord_proj:S num_exp resp_exp:S local_col:S data_col:L resp_col:S \
	     lst_par_monit_amostra coment_exp}
    
#Criação da tabela para armazenar as leituras de um parâmetro durante
#um experimento onde o campo id_cad_med será correspondente ao
#id_cad_exp no qual a medida foi gerada

    
    mk::view layout db.medida {id_cad_med:L parametro:S tempo:F leitura:F unid_leitura}
  
    mk::view layout db.cad_control {nextid:L}
  
    mk::row append db.cad_control nextid 1

    mk::file close db

    
}

Os diferentes campos suportam diferentes tipos de dados:

  • S - Strings de qualquer tamanho, exceto bytes nulos.

  • I - Números Inteiros (32 bits)

  • L - Números Inteiros Longos (64 bits)

  • F - Números Reais com precisão simples (32 bits)

  • D - Números Reais com precisão dupla (64 bits)

  • B - Dados binários (incluindo bytes nulos)

21.5.4. Procedimento: getuniqueid.

Este procedimento gera uma chave primária para indexação dos registros, correspondentes a cada experimento.

proc getuniqueid { db } {
    
    set id [mk::get db.cad_control!0 nextid]
    
    mk::set db.cad_control!0 nextid [expr {$id + 1 }]
    
    mk::file commit db
    
    return $id
}

21.5.5. Procedimento: arquivar_medida.

Esta rotina recebe como argumento o objeto info_medida armazena as medidas de um experimento anexando novos registros na tabela db.medida com os pares {tempo leitura ...} no formato de ponto decimal para otimizar o armazenamento e o seu uso em cálculos posteriores.

proc arquivar_medida { info_medida } {
    
    global num_id_cad_exp

    set id_cad_med $num_id_cad_exp
    
    set parametro [$info_medida get_parametro_medido]
    
    set tempo  [lindex [lindex [$info_medida get_leituras] 0 ] 0 ]

    set leitura [lindex [lindex [$info_medida get_leituras] 0] 1 ]

    set unid_leitura [$info_medida get_parametro_unidade]

    mk::file open db medidor_multiparametro.db    
    
    mk::row append db.medida id_cad_med $id_cad_med parametro $parametro \
	tempo $tempo leitura $leitura unid_leitura $unid_leitura
    
    mk::file commit db
    
    mk::file close db

}

A rotina arquivar_medida é chamada dentro da rotina iniciar_mediada a qual, logo em seguida, chama a rotina graficar_medida para exibir a leitura no respectivo gráfico.

21.5.6. Procedimento: consultar_cadastro.

Procedimento responsável pela exibição do conteúdo do banco de dados em um listbox.

proc consultar_cadastro {} {
    
    global fonte_media

    set c_db .cdb

    catch {destroy $c_db}

    toplevel $c_db

    wm title $c_db "Banco de Dados dos Experimentos"

    wm iconname $c_db "Banco de Dados"
    
    frame $c_db.frame -borderwidth 10

    pack $c_db.frame -side top -expand yes -fill y
    
    scrollbar $c_db.frame.yscroll -command "$c_db.frame.lista yview"

    scrollbar $c_db.frame.xscroll -orient horizontal -command "$c_db.frame.lista xview"

    listbox $c_db.frame.lista -yscroll "$c_db.frame.yscroll set" \
	-xscroll "$c_db.frame.xscroll set" -width 130 -bg white \
	-height 25 -setgrid 1
    
    grid $c_db.frame.lista -row 0 -column 0 -rowspan 1 -columnspan 1 -sticky news

    grid $c_db.frame.yscroll -row 0 -column 1 -rowspan 1 -columnspan 1 -sticky news

    grid $c_db.frame.xscroll -row 1 -column 0 -rowspan 1 -columnspan 1 -sticky news
    
    frame $c_db.botoes

    pack $c_db.botoes -side bottom -fill x

    button $c_db.botoes.fechar -font $fonte_media -text " Fechar " -command "destroy $c_db"

    button $c_db.botoes.remover -font $fonte_media -text " Remover" -command [list remover_registro $c_db.frame.lista ]

    button $c_db.botoes.relatorio -font $fonte_media -text "Relatório" \
	-command [list gerar_relatorio $c_db.frame.lista]

#button $c_db.botoes.grafico -text "Gráfico" -command [list gerar_grafico $c_db.frame.lista ]

    button $c_db.botoes.exportar -font $fonte_media -text "Exportar" -command exportar_cadastro
    
    pack $c_db.botoes.relatorio $c_db.botoes.exportar $c_db.botoes.remover $c_db.botoes.fechar -side left -expand 1 -fill both
    
    
    if {[file exists "medidor_multiparametro.db"]} {
	    
	    mk::file open db medidor_multiparametro.db -readonly
	    
	    mk::loop i db.cadastro {
		
		array set registro [mk::get $i]
		
		set registro(data_col) [clock format $registro(data_col) \
		    -format {%d/%m/%Y %H:%M}]
		
		$c_db.frame.lista insert 0 "$registro(id_cad_exp) \
\{Projeto: $registro(cod_proj)\} \
\{Experimento: $registro(num_exp)\} \{Amostra: $registro(local_col)\} \
\{Coleta: $registro(data_col)\} \{Parâmetros:$registro(lst_par_monit_amostra)\}"   
	    }   
	    
	    mk::file close db
	}
}

21.5.7. Procedimento: gerar_relatorio

Procedimento responsável pela geração de um relatório em um text após o usuário selecionar uma opção no listbox.

proc gerar_relatorio { j_l } {
    
global fonte_media 

    if {[$j_l curselection] == ""} {
	    tk_messageBox -message "Por Favor!\n Selecione um Experimento."
	    raise .cdb
	    return
	} else {
	    destroy .relatorio
	    toplevel .relatorio
	    wm title .relatorio "Relatório de Experimento"
	    wm resizable .relatorio 0 0

	    set frame_1 [frame .relatorio.ctl]
	    pack $frame_1 -side top -expand yes -fill x

	    set frame_2 [frame .relatorio.rel]
	    pack $frame_2 -side bottom 

	    menubutton $frame_1.arquivo -font $fonte_media \
		-text "Relatório" -relief raised \
		-menu $frame_1.arquivo.menu
	    
	    menu $frame_1.arquivo.menu

	    $frame_1.arquivo.menu add command -font $fonte_media \
		-label "Gravar" \
		-command [list gravar_arquivo_txt $frame_2.exp]
	    $frame_1.arquivo.menu add command -font $fonte_media \
		-label "Abrir" \
		-command [list abrir_arquivo $frame_2.exp]
	    $frame_1.arquivo.menu add command -font $fonte_media \
		-label "Fechar" \
		-command { destroy .relatorio }

	    menubutton $frame_1.leituras -font $fonte_media \
		-text "Leituras" -relief raised \
		-menu $frame_1.leituras.menu
	    
	    menu $frame_1.leituras.menu

	    set j_t [text $frame_2.exp -width 60 \
		-yscrollcommand "$frame_2.rlg set" -bg white]

	    scrollbar $frame_2.rlg -command "$frame_2.exp yview"
	    
	    pack $frame_1.arquivo $frame_1.leituras -side left -anchor w
	    pack $frame_2.rlg -side right -fill y
	    pack $frame_2.exp -side left -expand yes -fill both
	    
#Curselection retorna a posição do item selecionado na listbox
#"get" retorna o item selecionado e "lindex" extrai apenas o primeiro campo
#que neste caso é o id do experimento no cadastro de experimentos
#Este id é usado mais abaixo no comando "select" o qual retorna
#o número da linha deste experimento
#Com esta informação o comando "get" filtra do banco de dados  todas
#as informações sobre o experimento armazena no array registro com o comando
#"array set"

	    set id_cad_exp [lindex [ $j_l get [ $j_l curselection ] ] 0]
	    puts $id_cad_exp
	    
	    if {[file exists "medidor_multiparametro.db"]} {
		    
		    mk::file open db medidor_multiparametro.db -readonly
		    
		    set linha [mk::select db.cadastro id_cad_exp  $id_cad_exp]
		    
		    puts "saída de linha => $linha"
		    
		    array set registro [mk::get db.cadastro!$linha]

		    mk::file close db		   
 
		    set registro(data_col) [clock format $registro(data_col) \
			-format {%d/%m/%Y %H:%M}]
		    
		    $j_t insert end "Projeto: $registro(cod_proj) \n\
Coordenador do Projeto: $registro(coord_proj) \n\
Número do Experimento:  $registro(num_exp) \n\
Responsável pelo Experimento: $registro(resp_exp) \n\
Local de Amostragem: $registro(local_col) \n\
Data de Amostragem: $registro(data_col) \n\
Responsável pela Amostragem: $registro(resp_col) \n\
Parâmetros Monitorados: $registro(lst_par_monit_amostra) \n\
Comentários: $registro(coment_exp)"  

#Este loop insere no menubutton "Leituras" os botões correspondentes às
#leituras realizadas e chama a rotina "exportar_leituras" passando
#como argumento o nome do parâmetro e o id do experimento na tabela
#cadastro

		    foreach { p lim_inf lim_sup } $registro(lst_par_monit_amostra) {
			
			$frame_1.leituras.menu add command \
			    -font $fonte_media \
			    -label "Exportar Leituras de $p" \
			    -command [list exportar_leituras \
			    $registro(cod_proj) $id_cad_exp $p ]
		    }	   
		}
	}
}

21.5.8. Procedimento: exportar_leituras.

Esta rotina recebe como argumentos o código do projeto (proj), o experimento (exp) e o parâmetro que foi monitorado neste experimento (par) e exporta os pares (Tempo(min) e Leitura()) para um arquivo com a extensão csv que pode ser aberto e editado por qualquer programa de planilhas Excel™ ou Impress™ do pacote LibreOffice™.

Esta rotina utiliza o procedimento tk_getSaveFile que faz parte da biblioteca Tk para o usuário selecionar o nome e o local do arquivo para o qual serão exportadas as leituras.

proc exportar_leituras { proj exp par } {
    
    if {[file exists "medidor_multiparametro.db"]} {
	    
	    set tipo_arquivo {
		{"Arquivos de Leituras"  { .csv } }
		{"Todos os arquivos" * }
	    }

	    set nome_arquivo [tk_getSaveFile -filetypes $tipo_arquivo -initialdir pwd]

	    if {[catch {set id_arquivo [open $nome_arquivo w]} result] != 0} {
		    return
		}
		
		puts $id_arquivo "Leituras de $par do experimento $exp \
do projeto $proj"

		puts $id_arquivo "Tempo(min) Leitura()"

#Acesso ao banco de dados no modo "readonly" (somente leitura)
	
		mk::file open db medidor_multiparametro.db -readonly
		
		foreach reg [mk::select db.medida id_cad_med $exp parametro $par] {
		    
		    set dados [mk::get db.medida!$reg tempo leitura unid_leitura]

		    set tempo [format "%.2f" [lindex $dados 0]]

		    set leitura [format "%.2f" [lindex $dados 1]]

		    set unid_leitura [lindex $dados 2]

#Converte o indicador decinal "." para vírgula ","

		    set tempo  [regsub {\.} $tempo {,} ]

		    set leitura [regsub {\.} $leitura {,} ]

		    puts $id_arquivo "$tempo $leitura $unid_leitura"
		    
		} 
		
		close $id_arquivo		

		mk::file close db
		
	}
	
}

21.6. Exibição de Gráficos.

Em seguida adicionamos rotinas para permitir a exibição das leituras em gráficos Leitura X Tempo durante um experimento, com o pacote gráfico Plotchart.

Figura 168. Gráficos Leitura X Tempo durante um experimento.

Gráficos Leitura X Tempo durante um experimento.


Para a exibição e configuração dos gráficos, implementamos as seguintes rotinas:

21.6.1. Procedimento: exibir_tela_grafico

Esta rotina cria os canvas para exibição do gráfico e os campos para configuração da área do gráfico ao longo de um experimento.

proc exibir_tela_grafico {} {
    
    global par_monit
    
    destroy .graf
    toplevel .graf
    
    wm title .graf "Gráficos"
    
    wm resizable .graf 0 0
    
    if {[llength $par_monit] >= 9} {
	    set altura_graf 150 
	} else {
	    set altura_graf 300
	}
	
	foreach { par nome unidade } $par_monit {

#Label frame para acomodar o gráfico e o frame para configuração	    
	    set grafico_$par [labelframe .graf.[string tolower $par] -text $nome]
#Canvas para o gráfico
	    set canvas_$par [canvas [set grafico_$par].c -bg white -width 700 \
		-height $altura_graf]
#Frame para configuração
	    set conf_graf_$par [frame [set grafico_$par].config]

#Botão para configuração
	    set botao_conf_graf_$par [button [set conf_graf_$par].b_conf -text "Configurar" \
		-command [list config_grafico $par]]
		

#Botão para apagar o conteúdo do gráfico e refazer os eixos
#com chamada para os procedimentos apagar_grafico e criar_grafico
	
            set botao_apagar_graf_$par [button [set conf_graf_$par].b_apagar -text "Reiniciar" \
		-command "[list apagar_grafico [set canvas_$par]]; \
		[list criar_grafico $par $unidade [set canvas_$par]]"]
	    
#Frames para as opções de configuração dos limites dos eixos X e Y
	    set frame_x_max_$par [frame [set conf_graf_$par].x_max]
	    set frame_x_min_$par [frame [set conf_graf_$par].x_min]
	    set frame_x_int_$par [frame [set conf_graf_$par].x_int]
	    set frame_y_max_$par [frame [set conf_graf_$par].y_max]
	    set frame_y_min_$par [frame [set conf_graf_$par].y_min]
	    set frame_y_int_$par [frame [set conf_graf_$par].y_int]

#Labels para identificar as entradas de configuração
	    set rot_x_max_$par [label [set frame_x_max_$par].rot -justify left \
		-text "Tempo final" -anchor w]
	    set rot_x_min_$par [label [set frame_x_min_$par].rot -justify left \
		-text "Tempo inicial" -anchor w]
	    set rot_x_int_$par [label [set frame_x_int_$par].rot -justify left \
		-text "Int. tempo" -anchor w]
	    set rot_y_max_$par [label [set frame_y_max_$par].rot -justify left \
		-text "[lindex $unidade 1] máx." -anchor w]
	    set rot_y_min_$par [label [set frame_y_min_$par].rot -justify left \
		-text "[lindex $unidade 1] mín." -anchor w]
	    set rot_y_int_$par [label [set frame_y_int_$par].rot -justify left \
		-text "Int. [lindex $unidade 1]" -anchor w]

#Entradas para configurar os limites dos eixos
	    set ent_x_min_$par [entry [set frame_x_min_$par].ent -width 5 -bg white \
		-textvariable x_min_$par]	    
	    set ent_x_max_$par [entry [set frame_x_max_$par].ent -width 5 -bg white \
		-textvariable x_max_$par]
	    set ent_x_int_$par [entry [set frame_x_int_$par].ent -width 5 -bg white \
		-textvariable x_int_$par]
	    set ent_y_min_$par [entry [set frame_y_min_$par].ent -width 5 -bg white \
		-textvariable y_min_$par]	    
	    set ent_y_max_$par [entry [set frame_y_max_$par].ent -width 5 -bg white \
		-textvariable y_max_$par]
	    set ent_y_int_$par [entry [set frame_y_int_$par].ent -width 5 -bg white \
		-textvariable y_int_$par]

	    pack [set rot_x_min_$par] [set ent_x_min_$par] -side right \
		-expand yes -fill x -anchor w
	    pack [set rot_x_max_$par] [set ent_x_max_$par] -side right \
		-expand yes -fill x -anchor w
	    pack [set rot_x_int_$par] [set ent_x_int_$par] -side right \
		-expand yes -fill x -anchor w
	    pack [set rot_y_min_$par] [set ent_y_min_$par] -side right \
		-expand yes -fill x -anchor w
	    pack [set rot_y_max_$par] [set ent_y_max_$par] -side right \
		-expand yes -fill x -anchor w
	    pack [set rot_y_int_$par] [set ent_y_int_$par] -side right \
		-expand yes -fill x -anchor w

	    pack [set frame_x_min_$par] -expand yes -fill both
	    pack [set frame_x_max_$par] -expand yes -fill both
	    pack [set frame_x_int_$par] -expand yes -fill both
	    pack [set frame_y_min_$par] -expand yes -fill both
	    pack [set frame_y_max_$par] -expand yes -fill both
	    pack [set frame_y_int_$par] -expand yes -fill both

	    pack [set botao_apagar_graf_$par] -side bottom -expand yes -fill x	   

	    pack [set botao_conf_graf_$par] -side bottom -expand yes -fill x
	   
	    pack [set conf_graf_$par] [set canvas_$par]  -side left -expand yes -fill both

	    pack [set grafico_$par]
	    
	    criar_grafico $par $unidade [set canvas_$par]
	    
	}
	
}

21.6.2. Procedimento: criar_grafico

Este procedimento cria os eixos do gráfico considerando os seguintes limites:

  • Cond (uS/cm) 100 <-> 10000

  • Resistividade (R = 1/C) (Mohmxcm) 0.0001 <-> 0.01

  • Cond (mg/L) TDS 100 <-> 1000 (Água potável portaria 518)

  • Cond (ppt) TDS 100000 <-> 1000000

  • ISE (mV) -1000 <-> +1000

  • ISE (RmV) -1000 <-> +1000

  • OD (mg/L) 0 <-> 10

  • OD (% sat) 0 <-> 100

Foram definidas as seguintes variáveis:

  • ymax_$parametro: armazena o valor máximo do eixo y para o respectivo parâmetro,

  • ymin_$parametro armazena o valor mínimo para o eixo y para o respectivo parâmetro,

  • int_$parametro: intervalo,

  • g_$parametro: é o índice do array global grafico que armazena o gráfico do respectivo parâmetro.

proc criar_grafico { parametro unidade tela } {
    
global grafico
global x_min_$parametro x_max_$parametro x_int_$parametro 
global y_min_$parametro y_max_$parametro y_int_$parametro

set x_min_$parametro 0
set x_max_$parametro 60
set x_int_$parametro 5

    switch [lindex $unidade 1] \
	"uS/cm" {set y_min_$parametro 100; \
	set y_max_$parametro 10000; \
	set y_int_$parametro 1000 } \
	"Mohmxcm" {set y_min_$parametro 0.0001; \
	set y_max_$parametro 0.01; \
	set y_int_$parametro 0.001 } \
	"mg/L" {if {$parametro == "COND"} { \
	set y_min_$parametro 100; \
	set y_max_$parametro 1000; \
	set y_int_$parametro 200 } \
	else \
	{if {$parametro == "OD"} { \
	set y_min_$parametro 0; \
	set y_max_$parametro 10; \
	set y_int_$parametro 2 } } } \
	"ppt" { puts  [set y_min_$parametro 100000]; \
	set y_max_$parametro 1000000; \
	set y_int_$parametro 5000 } \
	"pH" { set y_min_$parametro 0; \
	set y_max_$parametro 14; \
	set y_int_$parametro 2 } \
	"mV" { set y_min_$parametro -1000; \
	set y_max_$parametro 1000; \
	set y_int_$parametro 400 } \
	"RmV" { set y_min_$parametro -1000; \
	set y_max_$parametro 1000; \
	set y_int_$parametro 200 } \
	"ppb" { set y_min_$parametro 0; \
	set y_max_$parametro 1000; \
	set y_int_$parametro 100 } \
	"% sat" { set y_min_$parametro 0; \
	set y_max_$parametro 100; \
	set y_int_$parametro 20 } \
	"default" { set y_min_$parametro 0; set y_max_$parametro 10; set y_int_$parametro 2 }

#O comando eval foi usado pois o ::Plotchart::createXYPlot
#aceita apenas valores numéricos

set cria_g "set g_$parametro \[::Plotchart::createXYPlot $tela \
{[set x_min_$parametro] [set x_max_$parametro] [set x_int_$parametro]} \
{[set y_min_$parametro] [set y_max_$parametro] [set y_int_$parametro]}\]"

eval $cria_g

set grafico(g_$parametro) [set g_$parametro]

$grafico(g_$parametro) xtext "Tempo (min)"
$grafico(g_$parametro) ytext "[lindex $unidade 1]"

#$s yconfig -format "%12.2e"

}

21.6.3. Procedimento: config_grafico

Esta rotina recebe como o argumento a variável parametro para identificar qual gráfico será reconfigurado.

O comando rescale aceita somente valores numéricos, por isso a necessidade de montar o comando dentro da variável conf_g e em seguida chamar o interpretador para executar o conteúdo desta variável com o comando eval.

proc config_grafico { parametro } {

global grafico 
global x_min_$parametro x_max_$parametro x_int_$parametro 
global y_min_$parametro y_max_$parametro y_int_$parametro

set conf_g "\$grafico(g_\$parametro) rescale \
{ [set x_min_$parametro] [set x_max_$parametro] [set x_int_$parametro] } \
{ [set y_min_$parametro] [set y_max_$parametro] [set y_int_$parametro] }"

eval $conf_g

}

21.6.4. Procedimento: graficar_medida

O procedimento graficar_medida [15] é chamado para exibir no gráfico as medidas realizadas durante um experimento.

Este procedimento usa o objeto da classe Medida e é chamado pelo procedimento iniciar_medida.

O nome do gráfico fica armazenado no array global grafico e indexado por g_$parametro.

Usamos o conteúdo da variável global num_exp para permitir a sobreposição dos gráficos de diferentes experimentos em um mesmo gráfico.

proc graficar_medida { info_medida } {
    
    global grafico num_exp num_exp_ant simbolo exibir_legenda
    
    set parametro [$info_medida get_parametro_medido]
    
    set tempo  [lindex [lindex [$info_medida get_leituras] 0 ] 0 ]
    
    set leitura [lindex [lindex [$info_medida get_leituras] 0] 1 ]

#Esse primeiro teste verifica a existência da variável num_exp_ant
#(número do experimento anterior) que é definida dentro do procedimento
#parar_experimento.
#Na primeira execução de um experimento usa-se o símbolo plus mas
#a partir dos experimentos seguintes os símbolos são alternados entre 
#as 8 possíveis símbolos disponíveis pela biblioteca Plotchart:
#(plus cross circle up down dot upfilled downfilled)
    
    if {![info exists num_exp_ant]} {

	    set simbolo plus
	    
	    $grafico(g_$parametro) dataconfig $num_exp -type symbol
	    
	    $grafico(g_$parametro) dataconfig $num_exp -symbol $simbolo
	    
	    $grafico(g_$parametro) plot $num_exp $tempo $leitura
	    
	    if { ![info exists exibir_legenda] } {	    

		    $grafico(g_$parametro) legend $num_exp "Exp. $num_exp"

		    set exibir_legenda 0

		}
		
	    return	
	} 
	
	$grafico(g_$parametro) dataconfig $num_exp -type symbol
	
	$grafico(g_$parametro) dataconfig $num_exp -symbol $simbolo
	
	$grafico(g_$parametro) plot $num_exp $tempo $leitura
	
	if { $exibir_legenda } {

	$grafico(g_$parametro) legend $num_exp "Exp. $num_exp"

	set exibir_legenda 0

	}
}

21.6.5. Procedimento: mudar_simbolo.

Este procedimento chamado ao final de cada experimento para mudar o símbolo usado nos gráficos e permitir a sobreposição das leituras de diferentes experimentos em um mesmo gráfico.

proc mudar_simbolo {} {
    
    global simbolo exibir_legenda
    
    set lista_simbolos {plus cross circle up down dot upfilled downfilled}
    
    set indice [lsearch -exact $lista_simbolos $simbolo]
    
    if {$indice == 7} { set indice 0} else { incr indice }
	
	set simbolo [lindex $lista_simbolos $indice]
	
	set exibir_legenda 1
}

21.6.6. Instalação do Pacote Gráfico Plotchart

Para instalar o pacote Plotchart, instalei o tklib com o comando:

# apt-get install tklib

E após instalado o pacote pode ser usado nos scripts incluindo no início o comando package require Plotchart.

Rodando o tclsh no modo interativo você pode verificar se está funcionando:

$ tclsh
% package require Plotchart
1.5.1
% 

Posteriormente baixei o pacote Tklib 0.5 do site http://sourceforge.net/projects/tcllib/files/tklib/0.5/ para usar uma versão 1.6.1 do pacote Plotchart.

Para conhecer os comandos do Plotchart consulte a documentação disponível no link http://tcllib.sourceforge.net/doc/plotchart.html e os exemplos que são instalados junto com a biblioteca Tklib 0.5 no diretório tklib-0.5/examples/plotchart/.

21.7. Procedimento para criação do Starpack executável para Windows XP

Uma alternativa muito conveniente para rodar scripts Tcl no Windows™ sem a necessidade de instalar o interpretador ou qualquer biblioteca é o Starkit.

O Starkit é uma ferramenta muito útil pois permite empacotar o script principal e bibliotecas em um único arquivo que pode ser executado pelo interpretador pelo interpretador Tclkit.

O Starkit pode ser combinado com o Tclkit em um único pacote executável chamado Starpack, que pode ser usado sem a necessidade de descompactação ou instalação.

Para iniciar a criação de um pacote Starpack para Windows™ com o programa multipar00.tcl baixei a extensão Tklib 0.5, descompactei e extraí o diretório plotchart.

Também baixei os programas tclkit-win32.upx.exe baseado no Tk 8.5 para Windows™ 32 bits e o sdx.kit.

Criei um diretório no qual coloquei todos os arquivos e pastas necessários: multipar00.tcl, plotchart, objLab, tclkit-win32.upx.exe e sdx.kit.

Para criar o pacote multipar00.kit no Windows™ abrir o Prompt de comando e executar os comandos:

> tclkit-win32.upx.exe sdx.kit qwrap multipar00.tcl
> tclkit-win32.upx.exe sdx.kit unwrap multipar00.kit

Copiar para a pasta multipar00.vfs/lib os diretórios plotchart e objLab e recriar o multipar00.kit com o comando:

> tclkit-win32.upx.exe sdx.kit wrap multipar00.kit

Criar uma cópia do tclkit-win32.upx.exe chamada copia_tclkit.upx.exe (a escolha do nome é arbitrária) e criar finalmente o multipar00.exe com o comando:

> tclkit-win32.upx.exe sdx.kit wrap multipar00.exe -runtime copia_tclkit.upx.exe

E finalmente distribuir o executável para os usuários de Windows™.

Deixei cópias para download das versões 00 e 01 do Starpack do programa Multipar nos links: Multipar_00 e Multipar_01.

Na versão 01 foi incluído o monitoramento da temperatura aproveitando as leituras do sensor de temperatura do eletrodo de OD.



[14] A Tcl versão 8.6 incorpora suporte nativo a POO baseado no uso de TclOO.

[15] Pensei em qual termo usar para o nome deste procedimento e ao invés de usar o anglicismo plotar escolhi o hispanismo graficar. Pois o verbo graficar existe em espanhol e é, segundo o dicionário da Real Academia Espanhola, usado em Cuba, Argentina, Chile, Salvador e Uruguai; significa representar mediante figuras e símbolos (Fonte: http://www.ciberduvidas.pt/pergunta.php?id=28036)