unit SensorManager;

  interface
    uses
      AdsData          (* tAdsStringField; also used by AdsFunc *),
      AdsFunc          (* used by AdsTable *),
      AdsTable         (* tAdsQuery *),
      AdvGrid          (* glCustom *),
      AdvObj           (* TMS *),
      AdvUtil          (* TMS *),
      BaseGrid         (* TMS *),
      Buttons          (* BitBtn *),
      Classes          (**),
      ComCtrls         (* tPageControl, tTabSheet *),
      Controls         (**),
      DB               (* tDataSource, tDataSet, tIntegerField; also used by
                            AdsTable *),
      DBAdvGrid        (* tDBAdvGrid *),
      DBCtrls          (* tDBNavigator, tDBText *),
      ExtCtrls         (* tLabeledEdit, tShape; also used by DBCtrls *),
      Forms            (* tForm *),
      Graphics         (* tBrush, clRed, tFont; TMS *),
      Grids            (* tGridDrawState; TMS *),
      IndexTab2        (* tRRFIndexTab2 *),
      LabeledSpinEdits (* tLabeledSpinInteger *),
      RRFSpinBase      (* used by LabeledSpinEdits *),
      StdCtrls         (* tButton, tCheckBox, tLabel, tMemo *),
      Windows          (* tRect *);

    type
      tfmSensorManager = class(tForm)
          lbNotConnected: tLabel;
          pgctlSensorManager: tPageControl;
            tabSensorTypes: tTabSheet;
              qSensorTypes: tAdsQuery;
                qSensorTypesComment:        tAdsStringField;
                qSensorTypesSensorTypeName: tAdsStringField;
                qSensorTypesSensorTypeUID: tIntegerField;
                dataSensorTypes: tDataSource;
                   dbadvgridSensorTypes: tDBAdvGrid;
                  navSensorTypes: tDBNavigator;
                  qSensorTypesWM: tAdsQuery;
                    dataSensorTypesWM: tDataSource;
                      dbadvgridSensorWM: tDBAdvGrid;
              btnNewSensor: tButton;

            tabSensors: tTabSheet;
              qDeleteSensor: tAdsQuery;
              qSensors:      tAdsQuery;
                qSensorsComment:      tAdsStringField;
                qSensorsLocation:     tAdsStringField;
                qSensorsSensorLabel:  tAdsStringField;
                qSensorsSensorType:   tAdsStringField;
                qSensorsSerialNumber: tAdsStringField;
                qSensorsObservedUID:   tIntegerField;
                qSensorsOrderNumber:   tIntegerField;
                qSensorsSensorUID:     tIntegerField;
                qSensorsSensorTypeUID: tIntegerField;
                dataSensors: tDataSource;
                  dbadvgridSensors: tDBAdvGrid;
                    idxSensors: tRRFIndexTab2;
                  navSensors: tDBNavigator;
                  qSensorConnections: tAdsQuery;
                    dataSensorConnections: tDataSource;
                      dbadvgridSensorConnections: tDBAdvGrid;
                  qSensorBoardInfo: tAdsQuery;
                    dataSensorBoardInfo: tDataSource;
                      dbtxBoard:            tDBText;
                      dbtxBoardInfoComment: tDBText;
                      dbtxBoardSlot:        tDBText;
              lbBoard: tLabel;
              lbSlot: tLabel;
              shapeDeleteAlert: tShape;
                btnDeleteSensor: tButton;

            tabAddOrEditSensor: TTabSheet;
              qInstallSensorTableLink: tAdsQuery;
              qNewBoardSensorLink:     tAdsQuery;
              qNewOrUpdateIntrinsic:   tAdsQuery;
              btnCancel: tBitBtn;
              btnOK:     tBitBtn;
              lbAddingOrEditing: tLabel;
              pgctlAddEdit: tPageControl;
                tabAddEditIntrinsic: tTabSheet;
                  ckbxConnectedToBoard: tCheckBox;
                  lbedLocation:      tLabeledEdit;
                  lbedSensorComment: tLabeledEdit;
                  lbedSensorLabel:   tLabeledEdit;
                  lbedSerialNumber:  tLabeledEdit;
                  lbspintOrderNumber: tLabeledSpinInteger;
                tabAddEditBoard:     tTabSheet;
                  qBoards: tAdsQuery;
                    dataBoards: tDataSource;
                      dbadvgridBoards: tDBAdvGrid;
                  qBoardSlots: tAdsQuery;
                    dataBoardSlots: tDataSource;
                      dbadvgridBoardSlots: tDBAdvGrid;
                  qObserved: tAdsQuery;
                    dataObserved: tDataSource;
                      dbadvgridObserved: tDBAdvGrid;
                  lbedBoardSensorComment: tLabeledEdit;
                tabAddEditTables:    tTabSheet;
                  qClearTempTable: tAdsQuery;
                  qLoadTempTable:  tAdsQuery;
                  qTempTable: tAdsQuery;
                    dataTempTable: tDataSource;
                      dbadvgridTempTable: tDBAdvGrid;
                  qTables: tAdsQuery;
                    qTablesTableName: tAdsStringField;
                    qTablesUID: tIntegerField;
                    dataTables: tDataSource;
                      dbadvgridTables: tDBAdvGrid;
                      qFieldNames: tAdsQuery;
                        dataFieldNames: tDataSource;
                          dbadvgridFieldNames: tDBAdvGrid;
                  qUpdateTemp: tAdsQuery;
                  btnDisassociate: tButton;
                    qDisassociate: tAdsQuery;
                  shapeUpdateAlert: tShape;
                    btnUpdate: tButton;
            tabAddOrEditSensorType: tTabSheet;
              qClearSensorTypeWM: tAdsQuery;
              qNewSensorTypeWM:   tAdsQuery;
              btnFillTypeWMTable: tButton;
              memoInstructions: tMemo;

          procedure LiveQueryParanoia          (DataSet: tDataSet);
          procedure qSensorConnectionsAfterOpen(DataSet: tDataSet);
          procedure qSensorsAfterScroll        (DataSet: tDataSet);
          procedure qSensorTypesAfterScroll    (DataSet: tDataSet);
          procedure qTempTableAfterScroll      (DataSet: tDataSet);

          procedure btnCancelClick           (Sender: tObject);
          procedure btnDeleteSensorClick     (Sender: tObject);
          procedure btnDisassociateClick     (Sender: tObject);
          procedure btnFillTypeWMTableClick  (Sender: tObject);
          procedure btnNewSensorClick        (Sender: tObject);
          procedure btnOKClick               (Sender: tObject);
          procedure btnUpdateClick           (Sender: tObject);
          procedure ckbxConnectedToBoardClick(Sender: tObject);
          procedure dbadvgridSensorsDblClick (Sender: tObject);
          procedure FormDblClick             (Sender: tObject);
          procedure FormShow                 (Sender: tObject);
          procedure MustValidate             (Sender: tObject);

          procedure dbadvgridSensorsCanSort(Sender: tObject;
                                            ACol: integer;
                                            var DoSort: boolean);
        private
          BoardUID: integer;
          TheSensorTypeUID, TheSensorUID: integer;
          procedure AddEditCommon(const NewCaption: string);
          procedure BuildSensorQuery;
          procedure EditSensor;
          procedure NewSensor;
          procedure Validate;
        end (* tfmSensorManager *);

    var
      fmSensorManager: tfmSensorManager;

  implementation
    uses
      DBAdvGridUtils (* FieldReadOnly, SetUpDBAdvGrid, SetUpDBAdvGridReadOnly,
                          SortOn, StretchField *),
      Dialogs        (* ShowMessageFmt *),
      HC3DM          (* UsualFormColor *),
      HC3Utilities   (* GetFieldWidth, LastUIDOfHomeControl *),
      HC4Main        (* fmHC4Main *),
      MyConsts       (* nullstring *),
      MyUtils        (* AskYesNo2 *),
      ObservedAndWM  (* tMeasurements2, tSensorType, stACDetect,
                          stAnemometer, stBMP085, stBroadcast, stDepthGauge,
                          stDHT20, stDHT22, stPhotocell, stRainGauge, stRelay,
                          stSCD30, stSCD40, stSPS30, stSwitch, stVC0706,
                          stVoltage, tWhatMeasured2, wm2CO2ppm, wm2DewPointF,
                          wm2EventCount, wm2Frequency, wm2FrequencyMax,
                          wm2FrequencyMin, wm2FrequencyRaw, wm2HumidexF,
                          wm2IsNowOn, wm2MassPM1, wm2MassPM10, wm2MassPM25,
                          wm2MassPM4, wm2NewEvents, wm2NumPM05, wm2NumPM1,
                          wm2NumPM10, wm2NumPM25, wm2NumPM4, wm2PartSize,
                          wm2PressureInches, wm2RainMMPerHour, wm2Resistance,
                          wm2RH, wm2SwitchClosed, wm2TargetF, wm2TempF,
                          wm2VC0706Manual, wm2VC0706Motion, wm2VC0706Snap,
                          wm2Voltage *),
      SQLUtils       (* qRequestLiveAfterOpen *),
      SysUtils       (* Format, Trim *);
      {$R *.dfm}

    type
      tSensorSort = (ssLocation, ssType, ssUID);

    var
      TheSensorSort: tSensorSort;

{********** infrastructure *************************************************** }
    procedure tfmSensorManager.FormDblClick(Sender: tObject);
      (* go home *)
    
      begin (* tfmSensorManager.FormDblClick *)
        fmHC4Main.Show
      end   (* tfmSensorManager.FormDblClick *);

    procedure tfmSensorManager.FormShow(Sender: tObject);
      (* initialization *)

      begin (* tfmSensorManager.FormShow *)
        { global }
            self.Color := UsualFormColor;
            TheSensorSort := ssType;

        { sensor types }
            qSensorTypes.Open;
            SetUpDBAdvGrid(dbadvgridSensorTypes);
              FieldReadOnly('SensorTypeUID');
              FieldReadOnly('SensorTypeName');


            qSensorTypesWM.Open;
            SetUpDBAdvGridReadOnly(dbadvgridSensorWM);

        { sensors }
            qSensors.Open;
            BuildSensorQuery;
            SetUpDBAdvGridReadOnly(dbadvgridSensors);
              SortOn('SensorUID');
              SortOn('SensorType');
              SortOn('Location');
              idxSensors.Initialize;
              StretchField('Comment');

            qSensorConnections.Open;
            SetUpDBAdvGridReadOnly(dbadvgridSensorConnections);

            qSensorBoardInfo.Open;

        { add/edit }
            SetUpDBAdvGridReadOnly(dbadvgridTempTable);
            qBoards.Open;
            SetUpDBAdvGridReadOnly(dbadvgridBoards);
            
            qBoardSlots.Open;
            SetUpDBAdvGridReadOnly(dbadvgridBoardSlots);

            qObserved.Open;
            SetUpDBAdvGridReadOnly(dbadvgridTables);

            qTables.Open;
            SetUpDBAdvGridReadOnly(dbadvgridObserved);

            qFieldNames.Open;
            SetUpDBAdvGridReadOnly(dbadvgridFieldNames);


            tabAddOrEditSensor.TabVisible    := false;
            lbedLocation.MaxLength           := GetFieldWidth('Sensors3',        'Location');
            lbedSensorComment.MaxLength      := GetFieldWidth(                   'Comment');
            lbedSensorLabel.MaxLength        := GetFieldWidth(                   'Label');
            lbedSerialNumber.MaxLength       := GetFieldWidth(                   'SerialNumber');
            lbedBoardSensorComment.MaxLength := GetFieldWidth('BoardSensorLink', 'Comment')
      end   (* tfmSensorManager.FormShow *);

{********** end infrastructure *********************************************** }
{********** sensor types ***************************************************** }
    procedure tfmSensorManager.btnNewSensorClick(Sender: tObject);
      (* other tab does all the work *)

      begin (* tfmSensorManager.btnNewSensorClick *)
        NewSensor
      end   (* tfmSensorManager.btnNewSensorClick *);

    procedure tfmSensorManager.LiveQueryParanoia(DataSet: tDataSet);
      (* usual paranoia *)

      begin (* tfmSensorManager.LiveQueryParanoia *)
        SQLUtils.qRequestLiveAfterOpen(tAdsQuery(DataSet))
      end   (* tfmSensorManager.LiveQueryParanoia *);

    procedure tfmSensorManager.qSensorTypesAfterScroll(DataSet: tDataSet);
      (* prettyprinting *)

      var
        SensorType: string;

      begin (* tfmSensorManager.qSensorTypesAfterScroll *)
        SensorType := qSensorTypes.FieldByName('SensorTypeName').AsString;

        btnNewSensor.Caption := Format('new %s sensor', [SensorType])
      end   (* tfmSensorManager.qSensorTypesAfterScroll *);

{********** end sensor types ************************************************* }
{********** one-time ********************************************************* }
    procedure tfmSensorManager.btnFillTypeWMTableClick(Sender: tObject);
      (* initialize the table *)

      var
        NewUID: integer;

      procedure OneType(const ST: tSensorType; const M: tMeasurements2);
        (* fill in table for one sensor type *)
      
        var
          WM: tWhatMeasured2;

        begin (* OneType *)
          qNewSensorTypeWM.ParamByName('SensorTypeUID').AsInteger := ord(ST);

          for WM := low(tWhatMeasured2) to high(tWhatMeasured2) do
            if WM in M then
              with qNewSensorTypeWM do
                begin (* qNewSensorTypeWM *)
                  inc(NewUID);
                  ParamByName('UID').AsInteger := NewUID;
                  ParamByName('WM' ).AsInteger := ord(WM);
                  ExecSQL
                end   (* qNewSensorTypeWM *)
        end   (* OneType *);

      begin (* tfmSensorManager.btnFillTypeWMTableClick *)
        qClearSensorTypeWM.ExecSQL;
        NewUID := 0;

        OneType(stACDetect,    [wm2IsNowOn, wm2OnOffIntent]);
        OneType(stAnemometer,  [wm2Frequency, wm2FrequencyMax, wm2FrequencyMin,
                                  wm2FrequencyRaw]);
        OneType(stBMP085,      [wm2PressureInches, wm2TempF]);
        OneType(stBroadcast,   [wm2PressureInches, wm2TempF]);
        OneType(stDepthGauge,  [wm2DepthInches]);
        OneType(stDHT20,       [wm2DewPointF, wm2HumidexF, wm2RH, wm2TempF]);
        OneType(stDHT22,       [wm2DewPointF, wm2HumidexF, wm2RH, wm2TempF]);
        // OneType(stLightning,   [wm2LightningDistance, wm2LightningDistPerHour,
        //                           wm2LightningEnergy, wm2LightningNoisePerHour,
        //                           wm2LightningFlashes]);
        OneType(stPhotocell,   [wm2Resistance]);
        OneType(stRainGauge,   [wm2EventCount, wm2RainMMPerHour]);
        OneType(stRelay,       [wm2IsNowOn, wm2TargetF]);
        OneType(stSCD30,       [wm2DewPointF, wm2RH, wm2TempF, wm2CO2ppm]);
        OneType(stSCD40,       [wm2DewPointF, wm2RH, wm2TempF, wm2CO2ppm]);
        OneType(stSHT45,       [wm2DewPointF, wm2HumidexF, wm2RH, wm2TempF]);
        OneType(stSPS30,       [wm2MassPM1, wm2MassPM25, wm2MassPM4, wm2MassPM10,
                                  wm2NumPM05, wm2NumPM1,wm2NumPM25, wm2NumPM4,
                                  wm2NumPM10, wm2PartSize]);
        OneType(stSwitch,      [wm2EventCount, wm2Frequency, wm2FrequencyMax,
                                  wm2FrequencyMin, wm2FrequencyRaw, wm2NewEvents,
                                  wm2SwitchClosed]);
        OneType(stVC0706,      [wm2VC0706Manual, wm2VC0706Motion, wm2VC0706Snap]);
        OneType(stVoltage,     [wm2Voltage]);
        OneType(stWeathervane, [wm2Resistance])
      end   (* tfmSensorManager.btnFillTypeWMTableClick *);

{********** end one-time ***************************************************** }
{********** sensors ********************************************************** }
    procedure tfmSensorManager.btnDeleteSensorClick(Sender: tObject);
      (* delete selected sensor *)

      var
        NextSensorUID, SensorUID: integer;

      begin (* tfmSensorManager.btnDeleteSensorClick *)
        with qSensors do
          begin (* qSensors *)
            SensorUID := FieldByName('SensorUID').AsInteger;
            Next;
            NextSensorUID := FieldByName('SensorUID').AsInteger;

            if AskYesNo2(Format('Really delete sensor #%d?', [SensorUID])) then
              with qDeleteSensor do
                begin (* really do it *)
                  ParamByName('SensorUID').AsInteger := SensorUID;
                  ExecSQL
                end   (* really do it *);
            Close;
            Open;
            Locate('SensorUID', NextSensorUID, [])
          end   (* qSensors *)
      end   (* tfmSensorManager.btnDeleteSensorClick *);

    procedure tfmSensorManager.BuildSensorQuery;
      (* allow sort options *)

      var
        OldUID: integer;

      function OrderBy: string;
        (* usual sorting code *)

        begin (* OrderBy *)
          case TheSensorSort of
            ssLocation: Result := 'Location, SensorType, SensorLabel, SensorUID';
                ssType: Result := '          SensorType, SensorLabel, SensorUID';
                 ssUID: Result := '                                   SensorUID';
            end (* case *)
        end   (* OrderBy *);

      begin (* tfmSensorManager.BuildSensorQuery *)
        with qSensors do
          begin (* qSensors *)
            OldUID := FieldByName('SensorUID').AsInteger;
            Close;
            SQL.Text := 'select                             ' +
                        '  s.Comment      as Comment,       ' +
                        '  s.Location     as Location,      ' +
                        '  s.ObservedUID  as ObservedUID,   ' +
                        '  s.OrderNumber  as OrderNumber,   ' +
                        '  s.Label        as SensorLabel,   ' +
                        '  st.Name        as SensorType,    ' +
                        '  s.TypeUID      as SensorTypeUID, ' +
                        '  s.UID          as SensorUID,     ' +
                        '  s.SerialNumber as SerialNumber   ' +
                        'from                      ' +
                        '  Sensors3 s,             ' +
                        '  SensorTypes3 st         ' +
                        'where                     ' +
                        '  st.UID = s.TypeUID      ' +
                        'order by                  ' +
                           OrderBy;
            Open;
            Locate('SensorUID', OldUID, [])
          end   (* qSensors *)
      end   (* tfmSensorManager.BuildSensorQuery *);

    procedure tfmSensorManager.dbadvgridSensorsDblClick(Sender: tObject);
      (* edit this sensor *)
    
      begin (* tfmSensorManager.dbadvgridSensorsDblClick *)
        EditSensor
      end   (* tfmSensorManager.dbadvgridSensorsDblClick *);

    procedure tfmSensorManager.dbadvgridSensorsCanSort(Sender: tObject;
                                                       ACol: Integer;
                                                       var DoSort: boolean);
      (* sorting *)

      var
        Header: string;
    
      begin (* tfmSensorManager.dbadvgridSensorsCanSort *)
        DoSort := false;
        Header := dbadvgridSensors.Columns[ACol].Header;

        if      Header =   'UID'    then TheSensorSort := ssUID
        else if Header =  'type'    then TheSensorSort := ssType
        else if Header = 'location' then TheSensorSort := ssLocation;

        BuildSensorQuery
      end   (* tfmSensorManager.dbadvgridSensorsCanSort *);

    procedure tfmSensorManager.qSensorConnectionsAfterOpen(DataSet: tDataSet);
      (* warning message *)
    
      begin (* tfmSensorManager.qSensorConnectionsAfterOpen *)
        lbNotConnected.Visible := (DataSet.RecordCount = 0)
      end   (* tfmSensorManager.qSensorConnectionsAfterOpen *);

    procedure tfmSensorManager.qSensorsAfterScroll(DataSet: tDataSet);
      (* improve UI *)

      begin (* tfmSensorManager.qSensorsAfterScroll *)
        with btnDeleteSensor do
          Caption := Format('delete sensor #%d',
                            [qSensors.FieldByName('SensorUID').AsInteger]);
         qSensorConnections.Close;
         qSensorConnections.Open
      end   (* tfmSensorManager.qSensorsAfterScroll *);


{********** end sensors ****************************************************** }
{********** add/edit sensor ************************************************** }
    procedure tfmSensorManager.AddEditCommon(const NewCaption: string);
      (* shared *)

      begin (* tfmSensorManager.AddEditCommon *)
        tabAddOrEditSensor.TabVisible := true;
        tabAddOrEditSensor.Caption    := NewCaption;
        lbAddingOrEditing.Caption     := NewCaption;
        pgctlSensorManager.ActivePage := tabAddOrEditSensor;
        pgctlAddEdit.ActivePage       := tabAddEditIntrinsic;
        qTempTable.Close;
        qTempTable.Open;
        Validate
      end   (* tfmSensorManager.AddEditCommon *);

    procedure tfmSensorManager.btnCancelClick(Sender: tObject);
      (* give up *)
    
      begin (* tfmSensorManager.btnCancelClick *)
        pgctlSensorManager.ActivePage := tabSensors;
        tabAddOrEditSensor.TabVisible := false
      end   (* tfmSensorManager.btnCancelClick *);

    procedure tfmSensorManager.btnDisassociateClick(Sender: tObject);
      (* disconnect this measurement *)

      begin (* tfmSensorManager.btnDisassociateClick *)
        qDisassociate.ExecSQL;
        qTempTable.Close;
        qTempTable.Open
      end   (* tfmSensorManager.btnDisassociateClick *);

    procedure tfmSensorManager.btnOKClick(Sender: tObject);
      (* update tables from UI *)

      procedure NewBoardConnection;
        (* new board connection, perhaps with table and fields *)

        var
          BoardUID, BoardSlotUID, NewUID, TableUID, WMUID: integer;
          BoardComment, FieldName: string;

        begin (* NewBoardConnection *)
          BoardComment := Trim(lbedBoardSensorComment.Text);
          BoardSlotUID := qBoardSlots.FieldByName('UID').AsInteger;
          BoardUID     := qBoards.FieldByName('UID').AsInteger;
          NewUID       := LastUIDOfHomeControl('BoardSensorLink') + 1;

          with qNewBoardSensorLink do
            begin (* qNewBoardSensorLink *)
              ParamByName('BoardUID'    ).AsInteger := BoardUID;
              ParamByName('BoardSlotUID').AsInteger := BoardSlotUID;
              with ParamByName('Comment') do
                if BoardComment <> nullstring then
                  AsString := BoardComment
                else
                  Clear;
              ParamByName('SensorUID').AsInteger := TheSensorUID;
              ParamByName('UID').AsInteger := NewUID;
              ExecSQL
            end   (* qNewBoardSensorLink *);

          NewUID := LastUIDOfHomeControl('SensorTableLink');
          with qTempTable do
            begin (* qTempTable *)
              First;
              while not EOF do
                begin (* one record *)
                  TableUID  := FieldByName('TableUID' ).AsInteger;
                  WMUID     := FieldByName('WMUID'    ).AsInteger;
                  FieldName := FieldByName('FieldName').AsString;
                  if TableUID >= 0 then
                    begin (* install *)
                      inc(NewUID);
                      with qInstallSensorTableLink do
                        begin (* qInstallSensorTableLink *)
                          ParamByName('FieldName').AsString  := FieldName;
                          ParamByName('SensorUID').AsInteger := TheSensorUID;
                          ParamByName('TableUID' ).AsInteger := TableUID;
                          ParamByName('UID'      ).AsInteger := NewUID;
                          ParamByName('WMUID'    ).AsInteger := WMUID;
                          ExecSQL
                        end   (* qInstallSensorTableLink *)
                    end   (* install *);
                  Next
                end   (* one record *)
            end   (* qTempTable *)
        end   (* NewBoardConnection *);

      procedure NewOrUpdateIntrinsic;
        (* change Sensors3 *)

        var
          OrderNumber: integer;
          Comment, SerialNumber, TheLabel: string;

        procedure SetOrClearInteger(const ParamName: string;
                                    const Condition: boolean;
                                    const Data: integer);
          (* set the parameter iff condition true *)
        
          begin (* SetOrClearInteger *)
            with qNewOrUpdateIntrinsic.ParamByName(ParamName) do
              if Condition then
                AsInteger := Data
              else
                Clear
          end   (* SetOrClearBoolean *);

        procedure SetOrClearString(const ParamName, Data: string);
          (* cliche *)
        
          begin (* SetOrClearString *)
            with qNewOrUpdateIntrinsic.ParamByName(ParamName) do
              if Data <> nullstring then
                AsString := Data
              else
                Clear
          end   (* SetOrClearString *);

        begin (* NewOrUpdateIntrinsic *)
          Comment      := Trim(lbedSensorComment.Text);
          SerialNumber := Trim(lbedSerialNumber.Text);
          TheLabel     := Trim(lbedSensorLabel.Text);
          OrderNumber  := lbspintOrderNumber.Value;

          with qNewOrUpdateIntrinsic do
            begin (* qNewOrUpdateIntrinsic *)
              SetOrClearString ('Comment'      , Comment);
              SetOrClearString ('Label'        , TheLabel);
              ParamByName      ('Location'     ).AsString := Trim(lbedLocation.Text);
              SetOrClearInteger('ObservedUID' , ckbxConnectedToBoard.Checked,
                                                  qObserved.FieldByName('UID').AsInteger);
              SetOrClearInteger('OrderNumber'  , OrderNumber > 0, OrderNumber);
              SetOrClearString ('SerialNumber' , SerialNumber);
              ParamByName      ('SensorTypeUID').AsInteger := TheSensorTypeUID;
              ParamByName      ('SensorUID'    ).AsInteger := TheSensorUID;
              ExecSQL
            end   (* qNewOrUpdateIntrinsic *)
        end   (* NewOrUpdateIntrinsic *);

      begin (* tfmSensorManager.btnOKClick *)
        qDeleteSensor.ParamByName('SensorUID').AsInteger := TheSensorUID;
        qDeleteSensor.ExecSQL;
        NewOrUpdateIntrinsic;
        if ckbxConnectedToBoard.Checked then
          NewBoardConnection;
        pgctlSensorManager.ActivePage := tabSensors;
        BuildSensorQuery
      end   (* tfmSensorManager.btnOKClick *);

    procedure tfmSensorManager.btnUpdateClick(Sender: tObject);
      (* update the temp table from the choices to the R *)

      var
        TableUID, WMUID: integer;
        FieldName: string;

      begin (* tfmSensorManager.btnUpdateClick *)
        FieldName := qFieldNames.FieldByName('FieldName').AsString;
        TableUID  := qTables.FieldByName('UID').AsInteger;
        with qTempTable do
          begin (* qTempTable *)
            WMUID := FieldByName('WMUID').AsInteger;

            with qUpdateTemp do
              begin (* qUpdateTemp *)
                ParamByName('FieldName').AsString := FieldName;
                ParamByName('TableUID').AsInteger := TableUID;
                ParamByName('WMUID').AsInteger := WMUID;
                ExecSQL
              end   (* qUpdateTemp *);

            Close;
            Open;
            Locate('WMUID', WMUID, []);
            Next
          end   (* qTempTable *)
      end   (* tfmSensorManager.btnUpdateClick *);

    procedure tfmSensorManager.ckbxConnectedToBoardClick(Sender: tObject);
      (* connect/disconnect *)
    
      begin (* tfmSensorManager.ckbxConnectedToBoardClick *)
        tabAddEditBoard.TabVisible := ckbxConnectedToBoard.Checked;
        tabAddEditTables.TabVisible := tabAddEditBoard.TabVisible
      end   (* tfmSensorManager.ckbxConnectedToBoardClick *);

    procedure tfmSensorManager.EditSensor;
      (* edit selected sensor *)

      var
        BoardSlot, ObservedUID, OrderNumber: integer;
        BoardComment, Comment, Location, SensorLabel, SensorType, SerialNumber: string;

      procedure LoadTempTable;
        (* read-only, but refreshable *)

        begin (* LoadTempTable *)
          qBoards.Locate     ('UID',           BoardUID,         []);
          qBoardSlots.Locate ('UID',           BoardSlot,        []);
          qObserved.Locate   ('UID',           ObservedUID,      []);
          qSensorTypes.Locate('SensorTypeUID', TheSensorTypeUID, []);
          qClearTempTable.ExecSQL;
          qLoadTempTable.ExecSQL
        end   (* LoadTempTable *);

      begin (* tfmSensorManager.EditSensor *)
        with qSensors do
          begin (* qSensors *)
            Comment          := FieldByName('Comment'      ).AsString;
            Location         := FieldByName('Location'     ).AsString;
            ObservedUID      := FieldByName('ObservedUID'  ).AsInteger;
            OrderNumber      := FieldByName('OrderNumber'  ).AsInteger;
            SensorLabel      := FieldByName('SensorLabel'  ).AsString;
            SensorType       := FieldByName('SensorType'   ).AsString;
            TheSensorTypeUID := FieldByName('SensorTypeUID').AsInteger;
            TheSensorUID     := FieldByName('SensorUID'    ).AsInteger;
            SerialNumber     := FieldByName('SerialNumber' ).AsString;
          end   (* qSensors *);

        with qSensorBoardInfo do
          begin (* qSensorBoardInfo *)
            BoardComment := FieldByName('Comment' ).AsString;
            BoardSlot    := FieldByName('SlotUID' ).AsInteger;
            BoardUID     := FieldByName('BoardUID').AsInteger
          end   (* qSensorBoardInfo *);

        lbedLocation.Text            := Location;
        lbedSensorLabel.Text         := SensorLabel;
        lbedSerialNumber.Text        := SerialNumber;
        lbedSensorComment.Text       := Comment;
        lbspintOrderNumber.Value     := OrderNumber;
        lbedBoardSensorComment.Text  := BoardComment;

        ckbxConnectedToBoard.Checked := (BoardUID > 0);
        ckbxConnectedToBoardClick(nil);
        if ckbxConnectedToBoard.Checked then
          LoadTempTable;

        AddEditCommon(Format('editing %s (sensor #%d)', [SensorType, TheSensorUID]))
      end   (* tfmSensorManager.EditSensor *);

    procedure tfmSensorManager.MustValidate(Sender: TObject);
      (* ?data acceptable *)

      begin (* tfmSensorManager.MustValidate *)
        Validate
      end   (* tfmSensorManager.MustValidate *);

    procedure tfmSensorManager.NewSensor;
      (* new sensor of selected type *)

      var
        SensorTypeName: string;

      begin (* tfmSensorManager.NewSensor *)
        TheSensorUID := LastUIDOfHomeControl('Sensors3') + 1;
        with qSensorTypes do
          begin (* qSensorTypes *)
            SensorTypeName   := FieldByName('SensorTypeName').AsString;
            TheSensorTypeUID := FieldByName('SensorTypeUID' ).AsInteger;
          end   (* qSensorTypes *);
        // ShowMessageFmt('SensorTypeUID = %d',
        //                [qSensorTypes.FieldByName('SensorTypeUID').AsInteger]);

        lbedLocation.Clear;
        lbedSensorLabel.Clear;
        lbedSerialNumber.Clear;
        lbedSensorComment.Clear;
        lbspintOrderNumber.Value := 0;
        ckbxConnectedToBoard.Checked := false;
        qClearTempTable.ExecSQL;

        AddEditCommon(Format('defining new %s sensor', [SensorTypeName]))
      end   (* tfmSensorManager.NewSensor *);

    procedure tfmSensorManager.qTempTableAfterScroll(DataSet: tDataSet);
      (* catch up the table & field tables *)

      var
        TableUID: integer;
        FieldName: string;

      begin (* tfmSensorManager.qTempTableAfterScroll *)
        TableUID  := qTempTable.FieldByName('TableUID').AsInteger;
        FieldName := qTempTable.FieldByName('FieldName').AsString;
        qTables.Locate    ('UID',       TableUID,  []);
        qFieldNames.Locate('FieldName', FieldName, [])
      end   (* tfmSensorManager.qTempTableAfterScroll *);

    procedure tfmSensorManager.Validate;
      (* enable/disable OK button *)

      begin (* tfmSensorManager.Validate *)
        btnOK.Enabled := (Trim(lbedLocation.Text) <> nullstring)
      end   (* tfmSensorManager.Validate *);

{********** end add/edit sensor ********************************************** }

end.
