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.
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.
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.
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.
O medidor pode ser configurado, pelo teclado, para enviar os dados pela porta serial em dois formatos diferentes:
relatório com múltiplas linhas
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.
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.
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
.
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.
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”.
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.
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.
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.
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.
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 }
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.
As informações fornecidas pelo usuário na tela de cadastramento são armazenadas nas seguintes variáveis:
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.
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.
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:
Este procedimento tem por objetivo arquivar as informações do cadastro do experimento no banco de dados e consiste basicamente em 3 etapas:
Verifica se o banco de dados já existe e se não existir cria um novo.
Resgata os atributos do objeto da classe Amostra
e armazena no array exp
as informações fornecidas durante o cadastramento do experimento.
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 }
Este procedimento é chamado para encerrar um experimento e executa as seguintes ações:
Atribui à variável estado_experimento
o valor “0”
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
Reabilita os botões “Iniciar Experimento” e “Fechar” da tela de cadastramento do experimento
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 } } }
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
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)
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 }
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.
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 } }
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 ] } } } }
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 } }
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
.
Para a exibição e configuração dos gráficos, implementamos as seguintes rotinas:
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] } }
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" }
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 }
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 } }
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 }
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/
.
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)