Antes de começar a ler esta seção quero avisar que a estratégia utilizada na implementação do programa de controle se mostrou inconsistente com o modelo proposto.
Por isso esse desenvolvimento foi interrompido e substituido por uma estratégia de Máquina de Estados Orientada a Objeto, que está documentado na seção seguinte: Programa para Controle do Fotômetro com LEDs usando Máquina de Estados Orientada a Objeto.
Mas decidi deixar documentada essas atividades pois pode ser útil como material de consulta para futuros projetos.
Os sinais de corrente gerados pelos LEDs sensores são convertidos em tensão e amplificados pelo circuito com o Amplificador Operacional, digitalizados pela placa Arduino e posteriormente enviados para o computador.
E para permitir a interação do usuário com o fotômetro vamos implementar uma “Interface Gráfica de Controle” que deve oferecer recursos para a execução das seguintes tarefas:
execução das ações de: conexão, configuração, calibração e leitura
definição do tipo de leitura a ser executada: Absorção, Fluorescência ou Turbidez, em modo contínuo ou discreto
definição dos LEDs (Emissor e Sensor) que serão usados nas leituras
definição dos parâmetros a serem exibidos nos gráficos
definição dos nomes dos arquivos e locais onde serão gravados os arquivos de calibração para cada LED
definição dos nomes dos arquivos e locais onde serão gravados os arquivos com as leituras (discretas e contínuas) e os metadados (unidade e timestamp)
Identificamos a necessidade de estabelecer as seguintes procedimentos operacionais para o fotômetro:
Conexão - inicar e monitorar a conexão entre o PC e a placa Arduino
Configuração - definir os LEDs que serão usados e o tipo de análise (Absorção, Fluorescência e/ou Turbidez)
Calibração - definir uma equação para conversão das leituras feitas pelo Arduino (ADC) em unidade de absorbância, emissão (fluorescência) ou turbidez (espalhamento)
Leitura - iniciar a leitura no modo discreto ou contínuo
As leituras podem ser feitas com ou sem uma equação de calibração.
A comunicação entre a placa Arduino e o PC pode ser feita de duas formas:
Amostragem passiva ou orientada a eventos (event driven): o Arduino envia as leituras de Absorbância ou Emissão em intervalos regulares (Ex: 2 leituras por segundo - 2Hz) e o programa no PC fica “esperando passivamente” o envio das mensagens e interpreta as leituras quando elas são enviadas. (Fonte: Programação Orientada a Eventos).
Amostragem ativa (polling): o programa no PC envia comandos, em intervalos regulares, para o Arduino solicitando o envio das leituras (Fonte: Polling)
Para modelar o comportamento do MultiFotômetro e os possíveis estados decidi usar a técnica de Máquina de Estados.
O conceito de Estado abstrai todos os eventos irrelevantes e se concentra apenas nos eventos relevantes. Nos diagramas de estado os nós são os estados e os conectores representam as transições.
O Estado, em uma máquina de estados, especifica um “contexto de comportamento”, enquanto que um “bloco” em um diagrama de fluxo representa uma “etapa de processamento” em um fluxo. (Fonte: A Crash Course in UML State Machines)
Importante distinguir “máquina de estados” de “diagrama de fluxos” pois cada qual representa um paradigma de programação. Máquina de estados é uma “programação orientada a eventos” e o diagrama de fluxo é uma “programação transformacional”.
Uma Máquina de Estados está ligada ao conceito de evento enquanto que na programação em fluxo os eventos são elementos secundários.
Um evento, após gerado passa por basicamente 3 etapas:
após recebido entra em uma fila de execução
é enviado para a máquina de estados e processado
é consumido e retirado da fila de execução
As etapas operacionais do MultiFotômetro foram modeladas como estados e subestados em uma máquina de estados hierárquicos conforme a figura 255, seguindo o formalismo dos Diagramas de Harel (David Harel, 1987).
Ao ser iniciado o programa, o usuário poderia verificar a conexão com a placa Arduino enviando um comando solicitando uma mensagem para confirmar a conexão (Ex: ACK). E em caso afirmativo entraria no estado CONNECTED e automaticamente no subestado “default” UNCONFIGURED.
A necessidade de estar conectado para entrar em outras instâncias do programa impediria o uso do programa, para outras finalidades (Ex: análise de dados), no modo DISCONNECTED.
Isso pode ser modificado futuramente incluindo outro estado (Ex: DATA_ANALYSIS) a partir de DISCONNECTED, mas independente do estado CONNECTED.
Entrando no estado CONNECTED, o programa poderia carregar “automaticamente” o(s) arquivo(s) de configuração com informações sobre os LEDs Emissores e Sensores instalados fisicamente.
Essas informações seriam exibidas para o usuário permitino selecionar os LEDs a serem usados nas leituras de Absorbância, Fluorescência e/ou Turbidez, através dos respectivos eventos: start_configuration_abs, start_configuration_fluor e/ou start_configuration_turb. com consequente carregamento dos “módulos” (drivers) correspondentes.
Em cada um dos estados CONFIGURED (ABS, FLUOR ou TURB) o estado “default” é IDLE, ou seja, o instrumento está diponível para iniciar leituras contínuas (READING) sem uma equação de calibração ao receber o evento “start_reading”. Nesse estado as leituras retornam apenas os valores no intervalo de 0-1023 do conversor AD do Arduino.
Mas com o evento “start_calibration” o instrumento entra no estado CALIBRATING (Figura 256) no qual pode ser carregada uma equação de calibração já armazenada ou entrar em subestados de uma rotina de calibração.
E para implementar a “lógica” que está descrita nos diagramas 255 e 256 precisamos implementar uma “estrutura de dados” que seja capaz de “representar” a estrutura da “máquina de estados” e permita identificar quais transições são possíveis, e quais as ações correspondentes, conforme os diferentes eventos.
Para isso resolvemos implementar uma “Árvore de Comportamento” (Behaviour Tree - BT) em uma variável do tipo “dicionário” que chamaremos de state_photometer
, cuja estrutura está descrita na figura 257.
Figura 257. “Árvore de Comportamento” (Behaviour Tree - BT) representando o diagrama de estados hierárquicos das figuras 255 e 256.
Comandos de criação da variável state_photometer
para o mapeamento dos estados e eventos da “Árvore de Comportamento” da figura 257.
############################################################### #Behaviour Tree (BT) of a Photometer ############################################################### dict set photometer_state_tree \ CONNECTED disconnect DISCONNECTING end_disconnection UNCONNECTED dict set photometer_state_tree \ UNCONNECTED connect CONNECTING error_connection UNCONNECTED #Configuring photometer for Absorption dict set photometer_state_tree \ UNCONNECTED connect CONNECTING accepted CONNECTED \ UNCONFIGURED start_configuration_abs CONFIGURING_ABS end_configuration_abs CONFIGURED_ABS \ unconfigure_abs UNCONFIGURING_ABS end_unconfiguration_abs UNCONFIGURED dict set photometer_state_tree \ UNCONNECTED connect CONNECTING accepted CONNECTED \ UNCONFIGURED start_configuration_abs CONFIGURING_ABS end_configuration_abs CONFIGURED_ABS \ IDLE_ABS start_reading_abs READING_ABS stop_reading_abs IDLE_ABS dict set photometer_state_tree \ UNCONNECTED connect CONNECTING accepted CONNECTED \ UNCONFIGURED start_configuration_abs CONFIGURING_ABS end_configuration_abs CONFIGURED_ABS \ IDLE_ABS start_calibration_abs CALIBRATING_ABS stop_calibration_abs IDLE_ABS dict set photometer_state_tree \ UNCONNECTED connect CONNECTING accepted CONNECTED \ UNCONFIGURED start_configuration_abs CONFIGURING_ABS end_configuration_abs CONFIGURED_ABS \ IDLE_ABS start_calibration_abs CALIBRATING_ABS end_calibration_abs CALIBRATED_ABS \ IDLE_CALIBRATED_ABS start_calibration_abs CALIBRATING_ABS dict set photometer_state_tree \ UNCONNECTED connect CONNECTING accepted CONNECTED \ UNCONFIGURED start_configuration_abs CONFIGURING_ABS end_configuration_abs CONFIGURED_ABS \ IDLE_ABS start_calibration_abs CALIBRATING_ABS end_calibration_abs CALIBRATED_ABS \ IDLE_CALIBRATED_ABS starting_reading_calibrated_abs READING_CALIBRATED_ABS stop_reading_calibrated_abs \ IDLE_CALIBRATED_ABS #Configuring photometer for Fluorescence dict set photometer_state_tree \ UNCONNECTED connect CONNECTING accepted CONNECTED \ UNCONFIGURED start_configuration_fluor CONFIGURING_FLUOR end_configuration_fluor CONFIGURED_FLUOR \ unconfigure_fluor UNCONFIGURING_FLUOR end_unconfiguration_fluor UNCONFIGURED dict set photometer_state_tree \ UNCONNECTED connect CONNECTING accepted CONNECTED \ UNCONFIGURED start_configuration_fluor CONFIGURING_FLUOR end_configuration_fluor CONFIGURED_FLUOR \ IDLE_FLUOR start_reading_fluor READING_FLUOR stop_reading_fluor IDLE_FLUOR dict set photometer_state_tree \ UNCONNECTED connect CONNECTING accepted CONNECTED \ UNCONFIGURED start_configuration_fluor CONFIGURING_FLUOR end_configuration_fluor CONFIGURED_FLUOR \ IDLE_FLUOR start_calibration_fluor CALIBRATING_FLUOR stop_calibration_fluor IDLE_FLUOR dict set photometer_state_tree \ UNCONNECTED connect CONNECTING accepted CONNECTED \ UNCONFIGURED start_configuration_fluor CONFIGURING_FLUOR end_configuration_fluor CONFIGURED_FLUOR \ IDLE_FLUOR start_calibration_fluor CALIBRATING_FLUOR end_calibration_fluor CALIBRATED_FLUOR \ IDLE_CALIBRATED_FLUOR start_calibration_fluor CALIBRATING_FLUOR dict set photometer_state_tree \ UNCONNECTED connect CONNECTING accepted CONNECTED \ UNCONFIGURED start_configuration_fluor CONFIGURING_FLUOR end_configuration_fluor CONFIGURED_FLUOR \ IDLE_FLUOR start_calibration_fluor CALIBRATING_FLUOR end_calibration_fluor CALIBRATED_FLUOR \ IDLE_CALIBRATED_FLUOR starting_reading_calibrated_fluor READING_CALIBRATED_FLUOR stop_reading_calibrated_fluor \ IDLE_CALIBRATED_FLUOR #Configuring photometer for Turbidity dict set photometer_state_tree \ UNCONNECTED connect CONNECTING accepted CONNECTED \ UNCONFIGURED start_configuration_turb CONFIGURING_TURB end_configuration_turb CONFIGURED_TURB \ unconfigure_turb UNCONFIGURING_TURB end_unconfiguration_turb UNCONFIGURED dict set photometer_state_tree \ UNCONNECTED connect CONNECTING accepted CONNECTED \ UNCONFIGURED start_configuration_turb CONFIGURING_TURB end_configuration_turb CONFIGURED_TURB \ IDLE_TURB start_reading_turb READING_TURB stop_reading_turb IDLE_TURB dict set photometer_state_tree \ UNCONNECTED connect CONNECTING accepted CONNECTED \ UNCONFIGURED start_configuration_turb CONFIGURING_TURB end_configuration_turb CONFIGURED_TURB \ IDLE_TURB start_calibration_turb CALIBRATING_TURB stop_calibration_turb IDLE_TURB dict set photometer_state_tree \ UNCONNECTED connect CONNECTING accepted CONNECTED \ UNCONFIGURED start_configuration_turb CONFIGURING_TURB end_configuration_turb CONFIGURED_TURB \ IDLE_TURB start_calibration_turb CALIBRATING_TURB end_calibration_turb CALIBRATED_TURB \ IDLE_CALIBRATED_TURB start_calibration_turb CALIBRATING_TURB dict set photometer_state_tree \ UNCONNECTED connect CONNECTING accepted CONNECTED \ UNCONFIGURED start_configuration_turb CONFIGURING_TURB end_configuration_turb CONFIGURED_TURB \ IDLE_TURB start_calibration_turb CALIBRATING_TURB end_calibration_turb CALIBRATED_TURB \ IDLE_CALIBRATED_TURB starting_reading_calibrated_turb READING_CALIBRATED_TURB stop_reading_calibrated_turb \ IDLE_CALIBRATED_TURB
Cada “ramo” dessa “árvore” é mapeado na variável state_photometer
com o comando dict set state_photometer como mostram os diagramas das figuras 258, 259 e 260.
Figura 258. Mapeamento dos ramos da “Árvore de Comportamento” (Behaviour Tree - BT) na variável state_photometer
.
Figura 259. Mapeamento dos ramos da “Árvore de Comportamento” (Behaviour Tree - BT) na variável state_photometer
.
Figura 260. Mapeamento dos ramos da “Árvore de Comportamento” (Behaviour Tree - BT) na variável state_photometer
.
Os nós de state_photometer
podem representar “estados” ou “eventos” e para permitir a identificação do nó foi criada a variável node_info
contendo “atributos” dos nós contidos em state_photometer
com as seguintes informações:
type - estado ou evento
default - 0 ou 1
Indica se um subestado é default (1)
Quando houver vários subestados em um mesmo nível, haverá apenas um subestado default.
level - corresponde ao nível de “profundidade” da árvore e consequentemente indica o nível de prioridade do evento
Eventos com menor profundidade possuem maior prioridade em relação aos demais
entry_action - ações que devem ser executadas ao entrar em um estado
exit_action - ações que devem ser executadas ao sair de um estado
############################################################### #Info about the nodes of Behaviour Tree (BT) of a Photometer ############################################################### dict set node_info UNCONNECTED { type state substate_default 1 level 1 entry_action none exit_action none} dict set node_info connect { type event level 1 } dict set node_info CONNECTING { type state substate_default 0 level 1 entry_action none exit_action none} dict set node_info error_connection { type event level 1 } dict set node_info accepted { type event level 1 } dict set node_info CONNECTED { type state substate_default 0 level 1 entry_action none exit_action none} dict set node_info UNCONFIGURED { type state substate_default 1 level 2 entry_action none exit_action none} dict set node_info unconfigure_abs { type event level 2 } dict set node_info UNCONFIGURING_ABS { type state substate_default 0 level 2 entry_action none exit_action none} dict set node_info end_unconfiguration_abs { type event level 2 } dict set node_info unconfigure_fluor { type event level 2 } dict set node_info UNCONFIGURING_FLUOR { type state substate_default 0 level 2 entry_action none exit_action none} dict set node_info end_unconfiguration_fluor { type event level 2 } dict set node_info unconfigure_turb { type event level 2 } dict set node_info UNCONFIGURING_TURB { type state substate_default 0 level 2 entry_action none exit_action none} dict set node_info end_unconfiguration_turb { type event level 2 } #Configuring photometer for Absorption dict set node_info start_configuration_abs { type event level 2 } dict set node_info CONFIGURING_ABS { type state substate_default 1 level 2 entry_action none exit_action none } dict set node_info end_configuration_abs { type event level 2 } dict set node_info CONFIGURED_ABS { type state substate_default 0 level 3 entry_action none exit_action none } dict set node_info IDLE_ABS { type state substate_default 1 level 3 entry_action none exit_action none } dict set node_info start_reading_abs { type event level 3 } dict set node_info READING_ABS { type state substate_default 0 level 3 entry_action none exit_action none } dict set node_info stop_reading_abs { type event level 3 } #Calibrating photometer for Absorption dict set node_info start_calibration_abs { type event level 3 } dict set node_info CALIBRATING_ABS { type state substate_default 0 level 3 entry_action none exit_action none } dict set node_info stop_calibration_abs { type event level 3 } dict set node_info end_calibration_abs { type event level 3 } dict set node_info CALIBRATED_ABS { type state substate_default 0 level 4 entry_action none exit_action none } dict set node_info IDLE_CALIBRATED_ABS { type state substate_default 1 level 4 entry_action none exit_action none } dict set node_info start_reading_calibrated_abs { type event level 4 } dict set node_info READING_CALIBRATED_ABS { type state substate_default 0 level 4 entry_action none exit_action none } dict set node_info stop_reading_calibrated_abs { type event level 4 } ############################################################################################################ #Configuring photometer for Fluorescence dict set node_info start_configuration_fluor { type event level 2 } dict set node_info CONFIGURING_FLUOR { type state substate_default 1 level 2 entry_action none exit_action none } dict set node_info end_configuration_fluor { type event level 2 } dict set node_info CONFIGURED_FLUOR { type state substate_default 0 level 3 entry_action none exit_action none } dict set node_info IDLE_FLUOR { type state substate_default 1 level 3 entry_action none exit_action none } dict set node_info start_reading_fluor { type event level 3 } dict set node_info READING_FLUOR { type state substate_default 0 level 3 entry_action none exit_action none } dict set node_info stop_reading_fluor { type event level 3 } #Calibrating photometer for Fluorescence dict set node_info start_calibration_fluor { type event level 3 } dict set node_info CALIBRATING_FLUOR { type state substate_default 0 level 3 entry_action none exit_action none } dict set node_info stop_calibration_fluor { type event level 3 } dict set node_info end_calibration_fluor { type event level 3 } dict set node_info CALIBRATED_FLUOR { type state substate_default 0 level 4 entry_action none exit_action none } dict set node_info IDLE_CALIBRATED_FLUOR { type state substate_default 1 level 4 entry_action none exit_action none } dict set node_info start_reading_calibrated_fluor { type event level 4 } dict set node_info READING_CALIBRATED_FLUOR { type state substate_default 0 level 4 entry_action none exit_action none } dict set node_info stop_reading_calibrated_fluor { type event level 4 } ############################################################################################################ #Configuring photometer for Turbidity dict set node_info start_configuration_turb { type event level 2 } dict set node_info CONFIGURING_TURB { type state substate_default 1 level 2 entry_action none exit_action none } dict set node_info end_configuration_turb { type event level 2 } dict set node_info CONFIGURED_TURB { type state substate_default 0 level 3 entry_action none exit_action none } dict set node_info IDLE_TURB { type state substate_default 1 level 3 entry_action none exit_action none } dict set node_info start_reading_turb { type event level 3 } dict set node_info READING_TURB { type state substate_default 0 level 3 entry_action none exit_action none } dict set node_info stop_reading_turb { type event level 3 } #Calibrating photometer for Turbidity dict set node_info start_calibration_turb { type event level 3 } dict set node_info CALIBRATING_TURB { type state substate_default 0 level 3 entry_action none exit_action none } dict set node_info stop_calibration_turb { type event level 3 } dict set node_info end_calibration_turb { type event level 3 } dict set node_info CALIBRATED_TURB { type state substate_default 0 level 4 entry_action none exit_action none } dict set node_info IDLE_CALIBRATED_TURB { type state substate_default 1 level 4 entry_action none exit_action none } dict set node_info start_reading_calibrated_turb { type event level 4 } dict set node_info READING_CALIBRATED_TURB { type state substate_default 0 level 4 entry_action none exit_action none } dict set node_info stop_reading_calibrated_turb { type event level 4 }
O atributo “nível” de um “nó” permite identificar eventos com diferentes níveis de prioridade e dessa forma evitar a necessidade de mapear “todas” as possíveis combinações de eventos com estados.
Foram atribuídos níveis para as seguintes etapas:
Conexão - level 1
Configuração - level 2
Leitura sem calibração - level 3
Calibração - level 4
Leitura com calibração - level 5
Ou seja, um evento do nível 1 tem maior prioridade do que todos os demais e portanto o evento “disconnect” irá levar “qualquer” estado dos níveis 2, 3, 4 ou 5 para o estado “DISCONNECTING”.
Por exemplo, o estado “CONNECTED” é um estado do nível de prioridade “2” que contêm vários subestados no mesmo nível (UNCONFIGURED, UNCONFIGURING, CONFIGURING_ABS, CONFIGURING_FLUOR e CONFIGURING_TURB). Mas, dos 5 subestados, apenas o estado “UNCONFIGURED” é o default.
Deve haver apenas 1 estado default para cada nível.
Os demais estados: CONFIGURED_ABS, CONFIGURED_FLUOR e CONFIGURED_TURB, já possuem um nível de prioridade menor (3). Os quais terão seus respectivos subestados default.
Após a definição da estrutura básica da máquina de estados hierárquicos passamos a implementar as “unidades lógicas” para o “funcionamento da máquina” utilizando o paradigma da Programação Orientada a Objetos (POO).
Dentro do paradigma da Programação Orientada a Objetos (POO) um programa pode ser “modelado” (estruturado) com o uso de entidades chamadas “classes”. Essa abordagem visa facilitar a organização e a manutenção do código.
Uma classe permite a criação de uma coleção de objetos “semelhantes” que possuem características que definem a identidade de cada objeto (atributos), e as próprias operações que são capazes de realizar (métodos).
Os “objetos” podem representar “objetos físicos reais” (Ex: instrumentos, equipamentos etc), mas também podem representar “entidades abstratas ou virtuais” (Ex: equação de calibração, medida etc).
Por isso vamos criar três classes para dar início ao desenvolvimento do código: Photometer
, Sample
e Measure
.
Os objetos da classe Photometer
vão representar o fotômetro nos seus diferentes “modos de operação”: absorbância, fluorescência e turbidez.
Podemos incluir como atributos um identificador (id), o nome do instrumento (name), os LEDs (leds
) instalados e a Propriedade Óptica (optical_property
) medida (abs, fluor e/ou turb). (Figura 261)
Para os objetos da classe Photometer
podemos ter um método chamado processEvent que receberá como argumento os eventos externos ou internos e fará o devido processamento, consultando as informações armazenadas na variável global state_photometer
, para gerar as ações correspondentes.
O fluxo para mudança de estado para os correspondentes eventos pode ser visualizado no diagrama da figura 262.
Até agora consideramos apenas a “entidade física” MultiFotômetro, mas para elaborar um modelo para a realização de uma análise (contínua ou discreta) precisamos considerar outras entidades tais como: amostra (real) e medida (virtual).
E os objetos da classe Sample
e Measure
vão representar respectivamente as amostras analisadas com as respectivas medidas, conformo o diagrama da figura 263.
A multiplicidade “1” para “*” na associação entre Sample
e Measure
, significa que um objeto da classe Sample
pode estar associado a nenhum ou vários objetos da classe Measure
para diferentes parâmetros e em momentos diferentes, mas um objeto da classe Measure
deve estar associado a apenas um único objeto da classe Sample
.
Um fotômetro pode estar monitorando uma amostra de cada vez, gerando uma leitura e permitindo a criação de uma instância da classe Measure
.
E uma única amostra pode ser monitorada por vários fotômetros gerando várias medidas, mas uma instância da classe Measure
só poderá estar associada a um único fotômetro e uma única amostra como indica o diagrama da figura 264.
Figura 264. Diagrama das associações entre as classes Photometer
, Sample
e Measure
com as respectivas multiplicidades.
Mas qual a relação do diagrama de classes da figura 264 com o diagrama de estados da figura 255?
Antes de responder a essa pergunta, vamos esclarecer que esses diagramas são representações gráficas de uma “forma de estruturar” o programa, ou seja, uma “forma de pensar”. E essa é apenas uma dentre as inúmeras alternativas possíveis.
O diagrama de estados da figura 255 representa os possíveis estados que os objetos da classe Photometer
, na figura 264, podem assumir.
Portanto as informações sobre os estados e os eventos de transição de estados para os objetos da classe Photometer
serão armazenadas nos atributos: Estado Atual (current_state
), Próximo Estado (next_state
) e Eventos (event
).
Os eventos recebidos pelo objeto podem ou não gerar uma transição de estado conforme as regras estabelecidas na variável state_photometer
que faz o mapeamento do diagrama de estados em uma Árvore de Comportamento.
Podemos ainda, discriminar as ações que devem ser executadas por um objeto ao Entrar em um estado (entry_action
), e as ações que devem ser executadas por um objeto ao Sair de um estado (exit_action
).
Essas informações podem ficar armazenadas na variável, do tipo dicionário, node_info
.
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 coletadas automaticamente, por isso a classe Sample
possui os seguintes atributos: Projeto (project
), Experimento (experiment
), Informações sobre a amostra (sample_info
, e Parâmetros Monitorados (par_monit
).
Os atributos dos objetos da classe Sample
guardam as seguintes informações:
project -> um código que identifica o projeto
experiment -> um identificador para o experimento ao qual pertence a amostra
sample_info -> informações sobre o local e o instante (timestamp) 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.
par_monit -> parâmetros que estão sendo monitorados na amostra
E os objetos da classe Measure
possuem os seguintes atributos: Amostra (sample
), Propriedade Óptica (optical_property
), Leitura (readout
), Unidade de Medida (unity_readout
).
Os atributos dos objetos da classe Measure
guardam as seguintes informações:
sample -> identifica a amostra na qual foi feita a medida
optical_property -> a propriedade óptica que está sendo quantificada
wavelength_color -> comprimento de onda ou cor da leitura
readout -> o dado numérico de leitura
unity_readout -> unidade da leitura
time_readout -> timestamp da leitura
As classes Photometer
, Sample
e Measure
estão associadas entre si e com um Banco de Dados no contexto de um “Experimento” conforme o diagrama da figura 267.
Figura 267. Diagrama das associações entre as classes Photometer
, Sample
e Measure
com um Banco de Dados no contexto de um “Experimento”.
Implementamos também a classe Controller
como uma interface entre o usuário e os objetos que compõem o programa.
Inicialmente vamos implementar os métodos na classe Controller
correspondentes aos eventos de transição de estado do diagrama de estados da figura 255: connect, start_configuration_[abs, fluor or turb], unconfigure_[abs, fluor or turb], start_reading_[abs, fluor or turb], stop_reading_[abs, fluor or turb], start_calibration_[abs, fluor or turb] e stop_calibration_[abs, fluor or turb].(Figuras 268).
Para dar início à interface gráfica para interação com o usuário defini a primeira tela com a rotina:
############################################################### #Graphical User Interface ############################################################### wm title . "Multifotometro" #To prevent resizing of windows wm resizable . 0 0 global big_font medium_font set big_font {Times 22} set medium_font {Times 16} ############################################################### #Window 00 #Select Sample Analysis or Data Analysis ############################################################### proc window_00 {} { global big_font medium_font w_00 set w_00 [frame .f_w_00] set message_00 [label $w_00.msg_00 -font $big_font -text " Por favor, selecione a atividade: "] set activity_00 [radiobutton $w_00.act_00 -text "Análise de Amostras" -font $big_font -relief flat \ -variable activity -value sample_analysis] set activity_01 [radiobutton $w_00.act_01 -text "Análise de Dados" -font $big_font -relief flat \ -variable activity -value data_analysis] set button_cont [button $w_00.btn_cont -text "Continuar" -font $medium_font -command window_01] set button_exit [button $w_00.btn_exit -text " Sair " -font $medium_font -command exit] pack $message_00 pack $activity_00 -anchor w -pady 10 pack $activity_01 -anchor w -pady 10 pack $button_cont $button_exit -side right -expand yes -pady 10 pack $w_00 }
Nessa tela o usuário seleciona o tipo de atividade: Análise de Amostras ou Análise de Dados (para implantação futura), como mostra a figura 269.
Na tela seguinte o usuário seleciona os modos de operação do fotômetro e o arquivo XML contendo informações sobre os LEDs disponíveis, figura 270.
Figura 270. Tela para escolha do modo de operação do fotômetro e o arquivo contendo informações sobre os LEDs disponíveis
Comandos para implementação da segunda tela:
############################################################### #Window 01 #Select types of photometry: Absorbance, Fluorescence and/or #Turbidity ############################################################### proc window_01 {} { global big_font medium_font w_00 w_01 activity destroy $w_00 puts "Selected $activity" set w_01 [frame .f_w_01] set message_01 [label $w_01.msg_00 -font $big_font -text " Por favor, selecione a(s) técnicas(s): "] set check_abs [checkbutton $w_01.chk_abs -text "Absorbância" -font $big_font -relief flat \ -variable use_tech_abs \ -onvalue "abs" \ -offvalue 0 ] set check_fluor [checkbutton $w_01.chk_fluor -text "Fluorescência" -font $big_font -relief flat \ -variable use_tech_fluor \ -onvalue "fluor" \ -offvalue 0 ] set check_turb [checkbutton $w_01.chk_turb -text "Turbidez" -font $big_font -relief flat \ -variable use_tech_turb \ -onvalue "turb" \ -offvalue 0 ] #Entry and button to select the directory for leds set labelframe_dir [labelframe $w_01.dir -text "Seleção do arquivo de LEDs (leds.xml)" -font $medium_font] set entry_dir [entry $labelframe_dir.e -width 30 -textvariable led_file_name] set button_dir [button $labelframe_dir.b -pady 0 -padx 2m -text "Selecione o arquivo:" -font $medium_font \ -command "selectAndLoadDir"] set button_cont [button $w_01.btn_cont -text "Continuar" -font $medium_font -command window_02] set button_exit [button $w_01.btn_exit -text " Sair " -font $medium_font -command exit] pack $message_01 pack $check_abs -anchor w -pady 10 pack $check_fluor -anchor w -pady 10 pack $check_turb -anchor w -pady 10 pack $entry_dir $button_dir -side right -expand yes -padx 5 -pady 10 pack $labelframe_dir pack $button_cont $button_exit -side right -expand yes -pady 10 pack $w_01 } proc selectAndLoadDir {} { global led_file_name set file_types { { {XML} {.xml} } { {All Files} * } } set led_file_name [tk_getOpenFile -filetypes $file_types] }
E na tela seguinte o usuário pode selecionar quais leds serão usados nos diferentes “modos de operação” do fotômetro, figura 271.
Figura 271. Tela para escolha dos leds que serão usados nos diferentes “modos de operação” do fotômetro.
Nessa etapa são criadas as instâncias das subclasses da classe Photometer
que foram selecionadas na tela anterior e o arquivo XML com informações sobre os LEDs disponíveis é processado com auxílio da biblioteca tDOM:
proc window_02 {} { global big_font medium_font w_01 w_02 use_tech_abs use_tech_fluor use_tech_turb led_file_name list_inst list_leds_available doc_leds destroy $w_01 #Open the xml file with tDOM to get info about LEDs available set channel [open $led_file_name] fconfigure $channel -encoding utf-8 set doc_leds [dom parse [read $channel]] #set doc_leds [dom parse -channel $channel] close $channel set root [$doc_leds documentElement] puts "The document has the root: $root" puts "And the name of root: [$root nodeName]" set list_leds_available {} foreach root_node [$root childNodes] { foreach node_info [$root_node childNodes] { #puts "[$node_info nodeName] : [[$node_info firstChild] nodeValue]" if {[$node_info nodeName] == "id"} { lappend list_leds_available [[$node_info firstChild] nodeValue] } } } #Create the instances of photometers selected set list_inst {} if { $use_tech_abs != 0 } { absorbanceMeter create photoAbs 01 "Medidor de Absorção" abs lappend list_inst photoAbs } if { $use_tech_fluor != 0 } { fluorescenceMeter create photoFluor 02 "Medidor de Fluorescência" fluor lappend list_inst photoFluor } if { $use_tech_turb != 0 } { turbidityMeter create photoTurb 03 "Medidor de Turbidez" turb lappend list_inst photoTurb } puts "use_tech_abs: $use_tech_abs - use_tech_fluor: $use_tech_fluor - use_tech_turb: $use_tech_turb" puts $list_inst set w_02 [frame .f_w_02] set message_02 [label $w_02.msg_02 -font $medium_font -text " Por favor, selecione os leds que serão usados por cada instrumento: "] pack $message_02 #Create the labelframes and checkbuttons to select the LEDs that will be used by the insturments #The variable of checkbuttons are led_$id_led\_$op foreach inst $list_inst { puts "$inst -> [$inst getName]" #Variable id_inst store the id of the instrument #We may have two or more instruments for Absorbance, Fluorescence or Turbidity #But each one has an unique id set id_inst [$inst getId] #set op [$inst getOpticalProperty] set labelframe_$id_inst [labelframe $w_02.lf_$id_inst -text [$inst getName] -font $medium_font] pack [set labelframe_$id_inst] -expand yes -fill both -pady 1 set labelframe_$id_inst\_emitter [labelframe [set labelframe\_$id_inst].emitter -text "Emissor" -font $medium_font] set labelframe_$id_inst\_detector [labelframe [set labelframe\_$id_inst].detector -text "Detector" -font $medium_font] pack [set labelframe\_$id_inst\_emitter] [set labelframe\_$id_inst\_detector] -side left -expand yes -fill both -pady 1 -padx 1 foreach root_node [$root childNodes] { foreach node_info [$root_node childNodes] { puts "[$node_info nodeName] : [[$node_info firstChild] nodeValue]" if {[$node_info nodeName] == "id"} { set id_led [[$node_info firstChild] nodeValue] } if {[$node_info nodeName] == "function"} { set function_led [[$node_info firstChild] nodeValue] } if {[$node_info nodeName] == "color"} { set color_led [[$node_info firstChild] nodeValue] } } if { $function_led == "emitter" } { set check_led_$id_led\_$id_inst [checkbutton [set labelframe_$id_inst\_emitter].chk_$id_led\_$id_inst \ -text $color_led -font $medium_font \ -variable led_$id_led\_inst\_$id_inst \ -onvalue $id_led\_$id_inst \ -offvalue 0] pack [set check_led_$id_led\_$id_inst] -anchor w } elseif { $function_led == "detector" } { set check_led_$id_led\_$id_inst [checkbutton [set labelframe_$id_inst\_detector].chk_$id_led\_$id_inst \ -text $color_led -font $medium_font \ -variable led_$id_led\_inst\_$id_inst \ -onvalue $id_led\_$id_inst \ -offvalue 0] pack [set check_led_$id_led\_$id_inst] -anchor w } } } set button_cont [button $w_02.btn_cont -text "Continuar" -font $medium_font -command window_03] set button_exit [button $w_02.btn_exit -text " Sair " -font $medium_font -command exit] pack $button_exit $button_cont -side left -expand yes -pady 2 pack $w_02 }
Os LEDs operam aos pares, ou seja, precisamos de um LED emissor e outro LED operando como detector para o comprimento de onda de interesse.
Essa é a configuração usual nas medidas de absorbância, mas nas medidas de fluorescência podemos usar um único LED emissor e usarmos dois ou mais LEDs detectores para medir a intensidade de luz emitida por fluorescência em diferentes comprimentos de onda. Por exemplo, excitar no UV e detectar a fluorescência emitida na região do azul (usando o LED detector verde) e/ou emitida na região correspondente ao verde (usando o LED detector vermelho).
Portanto é necessário definir quais o tipo de vínculo entre os diferentes LEDs, ou seja, como serão formados os pares e quem vai ser o detector de quem?
Por isso criamos a tela da figura 272 para fazer essa configuração
Figura 272. Tela para configurar o “pareamento” dos leds que serão usados nos diferentes “modos de operação” do fotômetro.
Comandos para a criação da tela da figura 272 e rotinas complementares.
proc window_03 {} { global big_font medium_font w_02 w_03 list_inst doc_leds destroy $w_02 #Var list_inst - list of instances of class Photometer #Var list_leds_available - list of IDs os Leds available #Var doc_leds is a DOM document object puts "" puts "==============Window_03===============" puts "" set led_nodes [ $doc_leds getElementsByTagName leds ] set id_nodes [ $led_nodes getElementsByTagName id ] foreach id_node $id_nodes { lappend list_leds_available [[$id_node firstChild] nodeValue] } #puts "List of LEDs available -> $list_leds_available" foreach inst $list_inst { set id_inst [$inst getId] foreach id_led $list_leds_available { #puts "LED $id_led and Inst $id_inst -> led_$id_led\_inst\_$id_inst : [set ::led_$id_led\_inst\_$id_inst]" #Check if led was selected if { [set ::led_$id_led\_inst\_$id_inst] != 0} { foreach led_node [$led_nodes childNodes] { set led_node_id [ $led_node getElementsByTagName id ] #puts "ID of the node $led_node is [[$led_node_id firstChild] nodeValue]" if { [[$led_node_id firstChild] nodeValue] == $id_led } { foreach led_info [$led_node childNodes] { #if {[$led_info nodeName] == "id"} { set id_led [[$led_info firstChild] nodeValue] } if {[$led_info nodeName] == "function"} { set function_led [[$led_info firstChild] nodeValue] } if {[$led_info nodeName] == "color"} { set color_led [[$led_info firstChild] nodeValue] } if {[$led_info nodeName] == "pinout"} { set pinout_led [[$led_info firstChild] nodeValue] } } puts "SELECTED led $color_led as $function_led with id $id_led is connected to instrument $inst by pin $pinout_led" #Create the instance of class Led and install in instrument #But check if the Led object already exist before to create a new one set led_exist 0 foreach led_instance [info class instances led] { if { [$led_instance getId] == $id_led } { puts "Led $led_instance already exist" set led_exist 1 puts "Install $led_instance in instrument $inst" $inst useLed $led_instance puts "****************************************" puts "led $led_instance with id [$led_instance getId] exist" puts [$inst getLeds] puts "****************************************" } } if {!$led_exist} { led create obj_led_$id_led $id_led $color_led $function_led #Include the pin at Arduino Board connected to this LED obj_led_$id_led setPinout $pinout_led $inst useLed obj_led_$id_led #$inst useLed [led new $id_led $color_led $function_led] puts "****************************************" puts "obj_led_$id_led with id [obj_led_$id_led getId] don't exist" puts [$inst getLeds] puts "****************************************" } $inst getLeds puts "List os instances of class Led [info class instances led]" } } } } } set w_03 [frame .f_w_03] set f_m_03 [frame $w_03.f_m_03] set message_03 [label $w_03.msg_03 -font $big_font -text " Por favor, configure o \"pareamento\" dos leds em cada instrumento: "] pack $message_03 foreach inst $list_inst { set id_inst [$inst getId] set labelframe_$id_inst [labelframe $w_03.lf_$id_inst -text [$inst getName] -font $medium_font] pack [set labelframe_$id_inst] -pady 5 set list_inst_leds [$inst getLeds] set list_emitter_leds_$id_inst [labelframe [set labelframe_$id_inst].l_e -text "Emissor" -font $medium_font] set list_detector_leds_$id_inst [labelframe [set labelframe_$id_inst].l_d -text "Detector" -font $medium_font] puts "-------------------------------------------" puts "The leds of inst : $inst is [$inst getLeds]" puts "-------------------------------------------" puts "After create labelframe for inst $inst \[set list_emitter_leds_\$id_inst\] : [set list_emitter_leds_$id_inst]" puts "After create labelframe for inst $inst \[set list_detector_leds_\$id_inst\] : [set list_detector_leds_$id_inst]" #set list_emitter_leds_$id_inst [listbox [set labelframe_$id_inst].l_e] #set list_detector_leds_$id_inst [listbox [set labelframe_$id_inst].l_d] foreach led $list_inst_leds { puts "" puts "list_inst_leds : $list_inst_leds" puts "Loop foreach inst : $inst led : $led function [$led getFunction]" #To avoid the error: #can't set "radio_emitter_led_::obj_led_00_photoFluor": parent namespace doesn't exist #can't set "radio_emitter_led_::obj_led_00_photoFluor": parent namespace doesn't exist #while executing #"set radio_emitter_led_$led\_$inst [radiobutton [set list_emitter_leds_$id_inst].rb_$led #We included this command to remove the leading "::" in instances of Led set led [string trim $led ::] if {[$led getFunction] == "emitter"} { dict set dict_$inst\_emitter_leds [$led getColor] $led #[set list_emitter_leds_$id_inst] insert end [$led getColor] set radio_emitter_led_$led\_$inst [radiobutton [set list_emitter_leds_$id_inst].rb_$led \ -text "[$led getColor]" -variable emitter_led_color \ -value [$led getColor] ] puts "\[set list_emitter_leds_\$id_inst\] : [set list_emitter_leds_$id_inst]" puts "\[set radio_emitter_led_$led\_$inst\] : [set radio_emitter_led_$led\_$inst]" pack [set radio_emitter_led_$led\_$inst] -anchor w puts "" puts "Show led [$led getColor] in inst [$inst getName]" } elseif {[$led getFunction] == "detector"} { dict set list_$inst\_detector_leds [$led getColor] $led #[set list_detector_leds_$id_inst] insert end [$led getColor] set radio_detector_led_$led\_$inst [radiobutton [set list_detector_leds_$id_inst].rb_$led \ -text "[$led getColor]" -variable detector_led_color \ -value [$led getColor] ] pack [set radio_detector_led_$led\_$inst] -anchor w puts "" puts "Show led [$led getColor] in inst [$inst getName]" } puts "" } puts "END of loop foreach led ..." puts "" puts "For inst [$inst getName] list_inst_leds: $list_inst_leds" #puts "Listboxes [set list_emitter_leds_$id_inst] and [set list_detector_leds_$id_inst]" puts "" set list_pair_leds_$id_inst [listbox [set labelframe_$id_inst].pair_led] set frame_button [frame [set labelframe_$id_inst].f_b] set button_set_pair [ button $frame_button.set_pair -text " Incluir ->" -font $medium_font \ -command "insertLedPairListbox [set list_pair_leds_$id_inst]"] set button_unset_pair [ button $frame_button.unset_pair -text "<- Remover" -font $medium_font \ -command "removeLedPairListbox [set list_pair_leds_$id_inst]"] set button_install_pair [ button $frame_button.install_pair -text "Instalar" -font $medium_font] #To be able to include the name of button widget, just created before, as an argument to command $button_install_pair configure -command "installLedPairInstrument [set list_pair_leds_$id_inst] $id_inst $button_install_pair" pack $button_set_pair -pady 5 pack $button_unset_pair -pady 5 pack $button_install_pair -pady 5 pack [set list_emitter_leds_$id_inst] [set list_detector_leds_$id_inst] $frame_button -side left -padx 5 pack [set list_pair_leds_$id_inst] -side right } #set button_cont [button $w_03.btn_cont -text "Continuar" -font $medium_font -command window_04] set button_cont [button $w_03.btn_cont -text "Continuar" -font $medium_font -command saveConfig] #set button_save_config [button $w_03.btn_save_config -text "Salvar Configuração" -font $medium_font -command saveConfig] set button_exit [button $w_03.btn_exit -text " Sair " -font $medium_font -command exit] pack $button_exit $button_cont -side left -expand yes -pady 2 pack $f_m_03 pack $w_03 } proc insertLedPairListbox { listbox_to_insert } { puts "-Command insertLedPair $::emitter_led_color $::detector_led_color $listbox_to_insert" $listbox_to_insert insert end "$::emitter_led_color:$::detector_led_color" } proc removeLedPairListbox { listbox_to_remove } { if {[$listbox_to_remove curselection] != ""} { $listbox_to_remove delete [$listbox_to_remove curselection] } } proc installLedPairInstrument { listbox_with_leds id_instrument button_install} { global list_inst puts "Command installLedPairInstrument install leds [$listbox_with_leds get 0 end] at $id_instrument with button_install:$button_install" puts "All instruments $list_inst" foreach inst $list_inst { if { [$inst getId] == $id_instrument } { foreach pair_led [$listbox_with_leds get 0 end] { puts "Install pair [split $pair_led :] at instrument $inst" set pair_emitter_detector [split $pair_led :] set led_emitter_color [lindex $pair_emitter_detector 0] set led_detector_color [lindex $pair_emitter_detector 1] foreach led_instance [info class instances led] { puts "led_instance:$led_instance color:[$led_instance getColor] function:[$led_instance getFunction]" if { [$led_instance getColor] == $led_emitter_color && [$led_instance getFunction] == "emitter" } { puts "And is selected as the emitter" set object_led_emitter $led_instance } elseif { [$led_instance getColor] == $led_detector_color && [$led_instance getFunction] == "detector" } { puts "And is selected as the detector" set object_led_detector $led_instance } else { puts "*The led $led_instance is NOT selected*"} } #$inst setPairLeds {*}$pair_emitter_detector puts "Install leds $object_led_emitter and $object_led_detector at inst $inst" $inst setPairLeds $object_led_emitter $object_led_detector } puts "Instrument $inst id:[$inst getId] name:[$inst getName] pair_leds:[$inst getPairLeds]" } } $listbox_with_leds delete 0 end #To disable the button $button_install configure -state disabled } #Procedure to save the all configuration in a XML file proc saveConfig {} { global list_inst puts "Method saveConfig: " set list_obj_leds [info class instances led] set doc_config [dom createDocument instruments] set root [$doc_config documentElement] foreach inst $list_inst { set instrument [$doc_config createElement instrument] set id_instrument [$doc_config createElement id_instrument] $id_instrument appendChild [$doc_config createTextNode [$inst getId]] set property [$doc_config createElement property] $property appendChild [$doc_config createTextNode [$inst getOpticalProperty]] set name [$doc_config createElement name] $name appendChild [$doc_config createTextNode [$inst getName]] set led_pairs [$doc_config createElement led_pairs] set list_pair_leds [$inst getPairLeds] puts "List of pair_leds $list_pair_leds" foreach pair $list_pair_leds { set pair_node [$doc_config createElement pair] puts "inst:$inst pair:$pair emitter:[lindex $pair 0] detector:[lindex $pair 1]" foreach led $pair { puts "Creating the element for led:$led" set led_node [$doc_config createElement led] $pair_node appendChild $led_node set id_led_node [$doc_config createElement id_led] #The variable led contains the id_led $id_led_node appendChild [$doc_config createTextNode $led] $led_node appendChild $id_led_node foreach obj_led $list_obj_leds { set id_led [$obj_led getId] if {$id_led == $led} { #Insert color set color [$obj_led getColor] set color_node [$doc_config createElement color] $color_node appendChild [$doc_config createTextNode $color] $led_node appendChild $color_node #Insert function set function [$obj_led getFunction] set function_node [$doc_config createElement function] $function_node appendChild [$doc_config createTextNode $function] $led_node appendChild $function_node #Insert pinout set pinout [$obj_led getPinout] set pinout_node [$doc_config createElement pinout] $pinout_node appendChild [$doc_config createTextNode $pinout] $led_node appendChild $pinout_node } } $pair_node appendChild $led_node } $led_pairs appendChild $pair_node } $instrument appendChild $id_instrument $instrument appendChild $property $instrument appendChild $name $instrument appendChild $led_pairs #Append a new instrument $root appendChild $instrument } set channel_config [open auto_save_config.xml w] fconfigure $channel_config -encoding utf-8 $doc_config asXML -channel $channel_config close $channel_config puts "doc_config: [$doc_config asXML]" set user_save_config [tk_getSaveFile] if { $user_save_config != ""} { set channel_config [open $user_save_config w] fconfigure $channel_config -encoding utf-8 $doc_config asXML -channel $channel_config close $channel_config window_04 $user_save_config } else { tk_messageBox -icon warning -type ok -title "Alerta" \ -message "Prezado(a) \n Por favor, salve antes um arquivo de configuração para poder continuar!" saveConfig } }
A criação das subclasses filhas da classe Photometer
com nomes definidos pelo usuário dificulta o resgate das instâncias dos diferentes tipos de fotômetro e impede o uso do comando [info class instances photometer] e por isso foi criada a variável global list_inst
contendo todas as instâncias dos instrumentos.
Mas a limitação mais crítica da estratégia que foi utilizada, é que os diferentes tipos de fotômetros podem assumir, simultâneamente, diferentes estados. E isso viola o modelo concebido inicialmente gerando uma “inconsistência” entre o que foi proposto e o que foi implementado.
Afinal, existe apenas um fotômetro físico com um conjunto fixo de LEDs emissores e detectores. E os diferentes modos de operação não podem ser simultâneos podem compartilham os mesmos LEDs.
Ou seja, não pode operar como fluorímetro enquanto está operando no modo de Absorbância.
Mas só percebi isso agora. :-(
Ao invés de continuar com essa “inconsistência”, decidi interromper a estratégia que foi usada para implementar o modelo de Máquina de Estados utilizando variáveis do tipo dicionário e reescrever essa parte do código utilizando Orientação a Objeto para implementar a estrutura de estados hierárquicos.