21. Programa para Controle do Fotômetro com LEDs usando Máquina de Estados

Atenção

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:

  1. execução das ações de: conexão, configuração, calibração e leitura

  2. definição do tipo de leitura a ser executada: Absorção, Fluorescência ou Turbidez, em modo contínuo ou discreto

  3. definição dos LEDs (Emissor e Sensor) que serão usados nas leituras

  4. definição dos parâmetros a serem exibidos nos gráficos

  5. definição dos nomes dos arquivos e locais onde serão gravados os arquivos de calibração para cada LED

  6. 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:

A comunicação entre a placa Arduino e o PC pode ser feita de duas formas:

  1. 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).

  2. 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:

  1. após recebido entra em uma fila de execução

  2. é enviado para a máquina de estados e processado

  3. é consumido e retirado da fila de execução

21.1. Máquina de Estados do MultiFotômetro

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).

Figura 255. Diagrama de Estados Hierárquicos para o Fotômetro

Diagrama de Estados Hierárquicos para o Fotômetro

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.

Nota

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.

Figura 256. Diagrama de subestados do estado CONFIGURED (ABS, FLUOR e/ou TURB)

Diagrama de subestados do estado CONFIGURED (ABS, FLUOR e/ou TURB)

21.2. Árvore de Comportamento (Behaviour Tree - BT) correspondente ao Diagrama de Estados

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.

“Á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.

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.

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.

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 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:

  1. Conexão - level 1

  2. Configuração - level 2

  3. Leitura sem calibração - level 3

  4. Calibração - level 4

  5. 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.

Nota

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.

21.3. Definição das Classes Photometer, Led, Sample, Measure e Controller.

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)

Figura 261. Diagrama da classe Photometer com os respectivos atributos.

Diagrama da classe Photometer com os respectivos atributos.

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.

Figura 262. Diagrama de fluxo para mudança de estado.

Diagrama de fluxo para mudança de estado.

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.

Figura 263. Diagrama das classes Sample e Measure com a multiplicidade 1 para *.

Diagrama das classes Sample e Measure com a multiplicidade “1” para “*”.

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.

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?

Nota

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

Figura 265. Diagrama da classe Sample com os respectivos atributos.

Diagrama da classe Sample com os respectivos atributos.

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

Figura 266. Diagrama da classe Measure com os respectivos atributos.

Diagrama da classe Measure com os respectivos atributos.

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.

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).

Figura 268. Diagrama da classe Controller com os respectivos métodos iniciais

Diagrama da classe Controller com os respectivos métodos iniciais

21.4. Interface Gráfica

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.

Figura 269. Tela inicial para escolha do tipo de atividade

Tela inicial para escolha do tipo de atividade

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

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.

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.

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.

Atenção

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.

21.5. Alguns Links: