unit BoardManager;

  interface
    uses
      AdsData       (* tAdsStringField *),
      AdsFunc       (* used by AdsTable *),
      AdsTable      (* tAdsQuery *),
      AdvGrid       (* glCustom *),
      AdvObj        (* TMS *),
      AdvUtil       (* TMS *),
      BaseGrid      (* TMS *),
      Classes       (**),
      ComCtrls      (* tPageControl, tTabSheet *),
      Controls      (**),
      DB            (* tBooleanField, tDataSet, tDataSource, tDateField,
                         tDateTimeField, tIntegerField, tStringField; also used
                         by AdsTable *),
      DBAdvGrid     (* tDBAdvGrid *),
      DBCtrls       (* tDBCheckBox, tDBText *),
      Forms         (* tForm *),
      Graphics      (* TMS *),
      Grids         (* TMS *),
      ObservedAndWM (* tBoardSlot, tSensorType, tWindowsToBase,
                         wtbBroadcastLocalTime, wtbEnableELWire *),
      StdCtrls      (* tButton, tCheckBox, tGroupBox, tLabel, tListBox *),
      Windows       (* tRect *);

    type             //     0              1         2          3
      tSoftwareLevel = (slZeroNotUsed, slDefault, slSetRTC, slSetSpeedup,
                     //        4            5             6
                          slSetSpeedup2, slEnableEL, slNewRelayCode,
                     //        7
                          slDebugging);

    type
      tfmBoardManager = class(tForm)
          pgctlBoards: tPageControl;
            tabBoards: tTabSheet;
              qBoards: tAdsQuery;
                qBoardsBoardComment:  tAdsStringField;
                qBoardsBoardKind:     tAdsStringField;
                qBoardsBoardLabel:    tAdsStringField;
                qBoardsBoardLocation: tAdsStringField;
                qBoardsCPULabel:      tAdsStringField;
                qBoardsCPUModel:      tAdsStringField;
                qBoardsRelayKind:     tAdsStringField;
                qBoardsTag:           tAdsStringField;
                qBoardsXBeeNI:        tAdsStringField;
                qBoardsXBeeType:      tAdsStringField;
                qBoardsDebugging:          tBooleanField;
                qBoardsPondRelated:        tBooleanField;
                qBoardsSpeedupActive:      tBooleanField;
                qBoardsWantsPressureValue: tBooleanField;
                qBoardsLastReboot:      tDateField;
                qBoardsCPUSoftwareDate: tDateField;
                qBoardsLastData: tDateTimeField;
                qBoardsCPUUID:          tIntegerField;
                qBoardsBoardPN:         tIntegerField;
                qBoardsBoardUID:        tIntegerField;
                qBoardsSoftwareVersion: tIntegerField;
                qBoardsXBeeMY:          tIntegerField;
                qBoardsXBoardSpec:     tStringField;
                qBoardsXLabelAndTag:   tStringField;
                qBoardsXNodeAddress:   tStringField;
                qBoardsXSpeedupActive: tStringField;
                dataBoards: tDataSource;
                  dbadvgridBoards: tDBAdvGrid;
                  dbckbxUsesPressureValue: tDBCheckBox;
              qNewData:         tAdsQuery;
              qUpdateDebugging: tAdsQuery;
              qUpdateSpeedup:   tAdsQuery;
              btnLogReboot: tButton;
                qLogReboot: tAdsQuery;
              ckbxDebugging: tCheckBox;
              grpCarport: tGroupBox;
                btnDisableEL: tButton;
                btnEnableEL:  tButton;
              grpCPU: tGroupBox;
                dbtxCPULabel:     tDBText;
                dbtxCPUModel:     tDBText;
                dbtxCPUUID:       tDBText;
                dbtxLastReboot:   tDBText;
                dbtxSoftwareDate: tDBText;
                lbCPULabel:     tLabel;
                lbCPUUID:       tLabel;
                lbLastReboot:   tLabel;
                lbSoftwareDate: tLabel;
              grpSpeedup: tGroupBox;
                btnSpeedupOff: tButton;
                btnSpeedupOn:  tButton;
                ckbxPondFilling: tCheckBox;
              grpXBee: tGroupBox;
                dbtxXBeeMY:   tDBText;
                dbtxXBeeNI:   tDBText;
                dbtxXBeeType: tDBText;
                lbXBeeMY:             tLabel;
                lbXBeeNodeIdentifier: tLabel;
                qBoardsWantsOutsideTempF: tBooleanField;
              lbRelayInfo: tLabel;
            tabBoardSensors: tTabSheet;
              qBoardSensors: tAdsQuery;
                qBoardSensorsConnector:      tAdsStringField;
                qBoardSensorsObserved:       tAdsStringField;
                qBoardSensorsSensorLabel:    tAdsStringField;
                qBoardSensorsSensorTypeName: tAdsStringField;
                qBoardSensorsSensorUID: tIntegerField;
                dataBoardSensors: tDataSource;
                  dbadvgridBoardSensors: tDBAdvGrid;
                  qBoardConnectors: tAdsQuery;
                    dataBoardConnectors: tDataSource;
                      dbadvgridBoardConnectors: tDBAdvGrid;
                      qUpdateConnector: tAdsQuery;
              grpSetConnector: tGroupBox;
                btnSetConnector: tButton;
              lbBoardSensors: tLabel;
            tabTrouble: tTabSheet;
              qMissingTable: tAdsQuery;
                qMissingTableLabel: tAdsStringField;
                qMissingTableName:  tAdsStringField;
                dataMissingTable: tDataSource;
                  dbadvgridMissingTable: tDBAdvGrid;
              lbTrouble: tLabel;

          procedure qBoardsAfterScroll   (DataSet: tDataSet);
          procedure qBoardsCalcFields    (DataSet: tDataSet);

          procedure btnDisableELClick   (Sender: tObject);
          procedure btnEnableELClick    (Sender: tObject);
          procedure btnLogRebootClick   (Sender: tObject);
          procedure btnSetConnectorClick(Sender: tObject);
          procedure btnSpeedupOffClick  (Sender: tObject);
          procedure btnSpeedupOnClick   (Sender: tObject);
          procedure ckbxDebuggingClick  (Sender: tObject);
          procedure ckbxPondFillingClick(Sender: tObject);
          procedure FormDblClick        (Sender: tObject);
          procedure FormShow            (Sender: tObject);

          procedure FormCloseQuery(Sender: tObject; var CanClose: boolean);
        private
          BoardScanForward: boolean;
          DumpInProgress: boolean;
          KnownTags: set of char;
          LastBroadcastTempF: real;
          function  Broadcast(const What: string;
                              const FieldName: string;
                              const WTB: tWindowsToBase;
                              const Value: real): boolean;
          procedure SendToBoard(const WTB: tWindowsToBase;
                                const Value: integer;
                                const SoftwareLevelRequired: tSoftwareLevel);
          procedure UpdateDebugging(const DoDebugging: boolean);
        public
          function  BoardTagRecognized(const BoardTag: char): boolean;
          procedure BroadcastLocalTime;
          procedure BroadcastOutsideTempF(const TempF: real);
          procedure BroadcastPressureInches(const Pressure: real);
          function  GetMyID(const BoardTag: char): integer;
          procedure Initialize;
          procedure LogReboot(const BoardTag: char; const Crashed: boolean);
          procedure LogSpeedupChange(const NewValue: boolean);
          procedure NewData(const BoardTag: char; const LocalStamp: tDateTime);
          procedure NowReceivingDump(const NowReceiving: boolean);
        end (* tfmBoardManager *);

    var
      fmBoardManager: tfmBoardManager;

  implementation
    uses
      Dashboard      (* ConnectionMgr, fmDashboard *),
      DateUtils      (* DayOf, HourOf, MinuteOf, MonthOf, YearOf *),
      DBAdvGridUtils (* SetUpDBAdvGridReadOnly *),
      // Dialogs      (* ShowMessageFmt *),
      HC3DM          (* dmHC3, Global, gsReceivingDump, SillyRealValue *),
      HC4Main        (* fmHC4Main *),
      MyConsts       (* nullstring, TAB, UsualFormColor *),
      SysUtils       (* Format, Now *),
      table_Pond     (* SetPondFilling *);
      {$R *.dfm}

{**************** infrastructure **********************************************}
    procedure tfmBoardManager.FormCloseQuery(Sender: tObject; var CanClose: Boolean);
      (* avoid incomplete configuration *)
    
      begin (* tfmBoardManager.FormCloseQuery *)
        CanClose := false;
        with qMissingTable do
          begin (* qMissingTable *)
            Close;
            Open;
            if RecordCount = 0 then
              CanClose := true
            else
              pgctlBoards.ActivePage := tabTrouble
          end   (* qMissingTable *)
      end   (* tfmBoardManager.FormCloseQuery *);

    procedure tfmBoardManager.FormDblClick(Sender: tObject);
      (* show master form *)

      begin (* tfmBoardManager.FormDblClick *)
        fmHC4Main.Show
      end   (* tfmBoardManager.FormDblClick *);

    procedure tfmBoardManager.FormShow(Sender: tObject);
      (* initialize *)

      begin (* tfmBoardManager.FormShow *)
        self.Color := UsualFormColor;
        qBoards.Open;
          SetUpDBAdvGridReadOnly(dbadvgridBoards);

        qBoardSensors.Open;
          SetUpDBAdvGridReadOnly(dbadvgridBoardSensors);

        qBoardConnectors.Open;
          SetUpDBAdvGridReadOnly(dbadvgridBoardConnectors);

        SetUpDBAdvGridReadOnly(dbadvgridMissingTable);
        pgctlBoards.ActivePage := tabBoards
      end   (* tfmBoardManager.FormShow *);

    function tfmBoardManager.GetMyID(const BoardTag: char): integer;
      (* service for annunciators & eFrames *)
    
      begin (* tfmBoardManager.GetMyID(const BoardTag: char) *)
        with dmHC3.qBoards do
          begin (* get MYID *)
            Close;
            ParamByName('BoardTag').AsString := BoardTag;
            Open;
            Result := FieldByName('MYID').AsInteger;
            Close
          end   (* get MYID *)
      end   (* tfmBoardManager.GetMyID(const BoardTag: char) *);

    procedure tfmBoardManager.Initialize;
      (* necessary stuff, even if not shown *)

      var
        ThisTag: string;

      begin (* tfmBoardManager.Initialize *)
        KnownTags := [];
        with qBoards do
          begin (* qBoards *)
            Close;
            Open;
            while not EOF do
              begin (* one record *)
                ThisTag := FieldByName('Tag').AsString;
                if ThisTag <> nullstring then
                  KnownTags := KnownTags + [ThisTag[1]];
                Next
              end   (* one record *);
            Close
          end   (* qBoards *);

        LastBroadcastTempF := SillyRealValue;
        BoardScanForward := true
      end   (* tfmBoardManager.Initialize *);

    procedure tfmBoardManager.NowReceivingDump(const NowReceiving: boolean);
      (* avoid conflicts *)

      begin (* tfmBoardManager.NowReceivingDump *)
        DumpInProgress        := NowReceiving;
        btnSpeedupOff.Enabled := not NowReceiving;
        btnSpeedupOn.Enabled  := not NowReceiving
      end   (* tfmBoardManager.NowReceivingDump *);

    procedure tfmBoardManager.UpdateDebugging(const DoDebugging: boolean);
      (* from table or UI *)
    
      begin (* tfmBoardManager.UpdateDebugging *)
        if DoDebugging then
          SendToBoard(wtbDebugging, 1, slDebugging)
        else
          SendToBoard(wtbDebugging, 0, slDebugging)
      end   (* tfmBoardManager.UpdateDebugging *);

{**************** end infrastructure ******************************************}
{**************** boards tab **************************************************}
    function tfmBoardManager.BoardTagRecognized(const BoardTag: char): boolean;
      (* service for Dispatcher *)
    
      begin (* tfmBoardManager.BoardTagRecognized *)
        Result := (BoardTag in KnownTags)
      end   (* tfmBoardManager.BoardTagRecognized *);

    procedure tfmBoardManager.LogReboot(const BoardTag: char; const Crashed: boolean);
      (* called from Dispatcher.ProcessDataLine *)

      var
        BoardLabel, TheMessage: string;

      begin (* tfmBoardManager.LogReboot *)
        with qBoards do
          if Locate('Tag', BoardTag, []) then
            begin (* board selected *)
              BoardLabel := FieldByName('BoardLabel').AsString;
              btnLogRebootClick(nil);
              TheMessage := BoardLabel + ' board rebooted';
              if Crashed then
                TheMessage := TheMessage + ' after crash';
              fmDashBoard.ExtendChat('bm_279 ' + TheMessage);
              fmHC4Main.BoardRebooted(TheMessage)
            end   (* board selected *)
      end   (* tfmBoardManager.LogReboot *);

    procedure tfmBoardManager.LogSpeedupChange(const NewValue: boolean);
      (* called from Dashboard *)

      begin (* tfmBoardManager.LogSpeedupChange *)
        with qUpdateSpeedup do
          begin (* qUpdateSpeedup *)
            ParamByName('SpeedupActive').AsBoolean := NewValue;
            ExecSQL
          end   (* qUpdateSpeedup *);
        qBoards.Close;
        qBoards.Open
      end   (* tfmBoardManager.LogSpeedupChange *);

    procedure tfmBoardManager.qBoardsAfterScroll(DataSet: tDataSet);
      (* set up BoardSensors page *)

      const
        SpeedupSoftwareLevel = slSetSpeedup2;

      var
        Debugging, PondRelated: boolean;
        SoftwareLevel: tSoftwareLevel;
        BoardKind, BoardName, RelayKind: string;

      begin (* tfmBoardManager.qBoardsAfterScroll *)
        with DataSet do
          begin (* DataSet *)
            BoardKind     :=                FieldByName('BoardKind'      ).AsString;
            BoardName     :=                FieldByName('BoardLabel'     ).AsString;
            Debugging     :=                FieldByName('Debugging'      ).AsBoolean;
            PondRelated   :=                FieldByName('PondRelated'    ).AsBoolean;
            RelayKind     :=                FieldByName('RelayKind'      ).AsString;
            SoftwareLevel := tSoftwareLevel(FieldByName('SoftwareVersion').AsInteger);
          end   (* DataSet *);

         btnLogReboot.Caption   := 'log reboot of ' + BoardName + ' board';
         lbBoardSensors.Caption := 'sensors of '    + BoardName + ' board';

        // ShowMessageFmt('%s debugging %d SW level %d', [BoardName, ord(Debugging), ord(SoftwareLevel)]);
        ckbxDebugging.Checked   := Debugging;
        ckbxPondFilling.Visible := PondRelated;
        grpSpeedup.Visible      := (BoardKind = 'peripheral');
        grpCarport.Visible      := (BoardKind = 'carport');

        // if SoftwareLevel >= slDebugging then
        //   UpdateDebugging(Debugging);

        grpSpeedup.Caption      := Format('speedup %s board', [BoardName]);
        btnSpeedupOff.Enabled   := (SoftwareLevel >= SpeedupSoftwareLevel);
        btnSpeedupOn.Enabled    := btnSpeedupOff.Enabled;

        with lbRelayInfo do
          case RelayKind[1] of
            'H': Caption := 'relay used for heater control';
            'N': Caption := 'relay used for nightlight';
            'W': Caption := 'relay used for waterfall';
            else Caption := 'no relay connected'
            end (* case *)
      end   (* tfmBoardManager.qBoardsAfterScroll *);

    procedure tfmBoardManager.qBoardsCalcFields(DataSet: tDataSet);
      (* prettyprinting *)

      var
        SpeedupActive: boolean;
        BoardPN, XBeeMY: integer;
        BoardKind, BoardLabel, BoardTag, XBoardSpec, XLabelAndTag,
          XNodeAddress: string;

      begin (* tfmBoardManager.qBoardsCalcFields *)
        with DataSet do
          begin (* DataSet *)
            BoardKind     := FieldByName('BoardKind'    ).AsString;
            BoardLabel    := FieldByName('BoardLabel'   ).AsString;
            BoardPN       := FieldByName('BoardPN'      ).AsInteger;
            BoardTag      := FieldByName('Tag'          ).AsString;
            SpeedupActive := FieldByName('SpeedupActive').AsBoolean;
            XBeeMY        := FieldByName('XBeeMY'       ).AsInteger;

            XBoardSpec   := Format('%s [P/N %d]', [BoardKind, BoardPN]);
            XLabelAndTag := Format('%s (%s)', [BoardLabel, BoardTag]);
            XNodeAddress := Format('%d (0x%x)', [XBeeMY, XBeeMY]);

            FieldByName('XBoardSpec'  ).AsString := XBoardSpec;
            FieldByName('XLabelAndTag').AsString := XLabelAndTag;
            FieldByName('XNodeAddress').AsString := XNodeAddress;
            with FieldByName('XSpeedupActive') do
              if SpeedupActive then
                AsString := 'active'
              else
                Clear
          end   (* DataSet *)
      end   (* tfmBoardManager.qBoardsCalcFields *);


{**************** end boards tab **********************************************}
{**************** sensors tab *************************************************}

    procedure tfmBoardManager.btnSetConnectorClick(Sender: tObject);
      (* update connector field *)

      var
        OldSensorUID: integer;
        Connector: string;

      begin (* tfmBoardManager.btnSetConnectorClick *)
        Connector := qBoardConnectors.FieldByName('Connector').AsString;

        with qBoardSensors do
          begin (* qBoardSensors *)
            OldSensorUID := FieldByName('SensorUID').AsInteger;
            qUpdateConnector.ParamByName('Connector').AsString := Connector;
            qUpdateConnector.ExecSQL;
            Close;
            Open;
            Locate('SensorUID', OldSensorUID, []);
            Next
          end   (* qBoardSensors *)
      end   (* tfmBoardManager.btnSetConnectorClick *);

{**************** end sensors tab *********************************************}


{ ********** messages to stations ******************************************** }
    function tfmBoardManager.Broadcast(const What: string;
                                       const FieldName: string;
                                       const WTB: tWindowsToBase;
                                       const Value: real): boolean;
      (* run through boards, then send each needy board a line like
            wtbBroadcastPressure round(100*Pressure) or
            wtbBroadcastOutsideTempF round(100*TempF)
         by alternately scanning the boards forward & backward, the required
            number of destination-setting delays is reduced from N to N-1 per
            broadcast
      *)

      var
        intValue, TargetMY: integer;
        BoardKind, BoardLabel: string;

      begin (* tfmBoardManager.Broadcast *)
        Result := false;
        if not DumpInProgress then
          begin (* status OK *)
            intValue := round(100.0 * Value);
            with qBoards do
              begin (* qBoards *)
                Close;
                Open;
                if not BoardScanForward then
                  Last;
                while true do
                  begin (* scan forward or backward *)
                    BoardKind := FieldByName('BoardKind').AsString;
                    if (BoardKind = 'peripheral') or (BoardKind = 'carport') then
                      if FieldByName(FieldName).AsBoolean then
                        begin (* send to this target *)
                          BoardLabel := FieldByName('BoardLabel').AsString;
                          TargetMy   := FieldByName('XBeeMY'    ).AsInteger;
                          with fmDashboard.SerialManager do
                            IntMessageToBaseStation(wtbSendToXBee,
                                [TargetMY, ord(WTB), intValue]);
                          fmDashboard.ExtendChat(Format('BM_473 sending %s %d to %s (MY %d)',
                                                        [What, intValue, BoardLabel, TargetMY]));
                        end   (* send to this target *);

                    if BoardScanForward then
                      begin (* try to move forward *)
                        Next;
                        if EOF then
                          break
                      end   (* try to move forward *)

                    else
                      begin (* try to move backward *)
                        Prior;
                        if BOF then
                          break
                      end   (* try to move backward *)
                  end   (* scan forward or backward *);
              end   (* qBoards *);

            BoardScanForward := not BoardScanForward;
            Result := true
          end   (* status OK *)
      end   (* tfmBoardManager.Broadcast *);

    procedure tfmBoardManager.BroadcastLocalTime;
      (* service to Dashboard *)

      var
        TheTime: tDateTime;
        TargetMY: integer;
        SWVersion: tSoftwareLevel;
        BoardKind, BoardLabel: string;

      begin (* tfmBoardManager.BroadcastLocalTime *)
        if not DumpInProgress then
          begin (* status OK *)
            // fmDashBoard.ExtendChat('starting broadcast');
            with qBoards do
              begin (* qBoards *)
                Close;
                Open;
                while not EOF do
                  begin (* one board *)
                    BoardKind  :=                FieldByName('BoardKind'      ).AsString;
                    SWVersion  := tSoftwareLevel(FieldByName('SoftwareVersion').AsInteger);
                    TargetMy   :=                FieldByName('XBeeMY'         ).AsInteger;
                    BoardLabel :=                FieldByName('BoardLabel'     ).AsString;

                    with fmDashboard do
                      if (BoardKind <> 'peripheral') and (BoardKind <> 'carport') then
                        // fmDashBoard.ExtendChat('bm_419 ' + BoardLabel + ' board not peripheral or carport')
                      else if SWVersion < slSetRTC then
                        ExtendChat('bm_526 ' + BoardLabel + ' board has old software')
                      else
                        begin (* really send time *)
                          TheTime := Now;
                          ExtendChat(Format('bm_530 setting RTC at MY = %d (%s)',
                                                        [TargetMy, BoardLabel]));
                          SerialManager.MessageToBaseStation(
                            wtbSendToXBee,
                                 // MY  wtb  Y   M   D   H   M
                            Format('%d%s%d%s%d%s%d%s%d%s%d%s%d',
                                   [TargetMy,
                                    TAB, ord(wtbBroadcastLocalTime),
                                    TAB, YearOf(TheTime),
                                    TAB, MonthOf(TheTime),
                                    TAB, DayOf(TheTime),
                                    TAB, HourOf(TheTime),
                                    TAB, MinuteOf(TheTime)]),
                            Format('sent local time to %s board', [BoardLabel]) )
                        end   (* really send time *);

                    Next
                  end   (* one board *)
              end   (* qBoards *);
            // fmDashBoard.ExtendChat('broadcast done')
          end   (* status OK *)
      end   (* tfmBoardManager.BroadcastLocalTime *);

    procedure tfmBoardManager.BroadcastOutsideTempF(const TempF: real);
      (* Broadcast does all the work *)

      const
        MinTempChangeF = 0.1;

      begin (* tfmBoardManager.BroadcastOutsideTempF *)
        if abs(TempF - LastBroadcastTempF) > MinTempChangeF then
          if Broadcast('outside temp', 'WantsOutsideTempF', wtbBroadcastOutsideTempF, TempF) then
            LastBroadcastTempF := TempF
      end   (* tfmBoardManager.BroadcastOutsideTempF *);

    procedure tfmBoardManager.BroadcastPressureInches(const Pressure: real);

      begin (* tfmBoardManager.BroadcastPressureInches *)
        fmDashboard.ExtendChat('BM_568 broadcasting pressure');
        if not Broadcast('pressure', 'WantsPressureValue', wtbBroadcastPressure, Pressure) then
          (* ignore it *)
      end   (* tfmBoardManager.BroadcastPressureInches *);

    procedure tfmBoardManager.btnDisableELClick(Sender: tObject);
      (* preserve EL life *)
    
      begin (* tfmBoardManager.btnDisableELClick *)
        SendToBoard(wtbEnableELWire, 0, slEnableEL)
      end   (* tfmBoardManager.btnDisableELClick *);

    procedure tfmBoardManager.btnEnableELClick(Sender: tObject);
      (* EL may be used *)
    
      begin (* tfmBoardManager.btnEnableELClick *)
        SendToBoard(wtbEnableELWire, 1, slEnableEL)
      end   (* tfmBoardManager.btnEnableELClick *);

    procedure tfmBoardManager.btnLogRebootClick(Sender: tObject);
      (* query does the work *)

      var
        TheTag: string;

      begin (* tfmBoardManager.btnLogRebootClick *)
        with qBoards do
          begin (* qBoards *)
            TheTag := FieldByName('Tag').AsString;
            qLogReboot.ExecSQL;
            Close;
            Open;
            Locate('Tag', TheTag, [])
          end   (* qBoards *)
      end   (* tfmBoardManager.btnLogRebootClick *);

    procedure tfmBoardManager.btnSpeedupOffClick(Sender: tObject);
      (* this board shouldn't be sped up *)

      begin (* tfmBoardManager.btnSpeedupOffClick *)
        SendToBoard(wtbSpeedUpChanged, 0, slSetSpeedup2);
        LogSpeedupChange(false)
      end   (* tfmBoardManager.btnSpeedupOffClick *);

    procedure tfmBoardManager.btnSpeedupOnClick(Sender: tObject);
      (* this board should be speeded up *)

      begin (* tfmBoardManager.btnSpeedupOnClick *)
        SendToBoard(wtbSpeedUpChanged, 1, slSetSpeedup2);
        LogSpeedupChange(true)
      end   (* tfmBoardManager.btnSpeedupOnClick *);

    procedure tfmBoardManager.ckbxDebuggingClick(Sender: tObject);
      (* change board's debugging state  *)

      var
        DoIt: boolean;
        BoardUID: integer;
        SoftwareLevel: tSoftwareLevel;

      begin (* tfmBoardManager.ckbxDebuggingClick *)
        with qBoards do
          begin (* qBoards *)
            BoardUID      :=                FieldByName('BoardUID'       ).AsInteger;
            SoftwareLevel := tSoftwareLevel(FieldByName('SoftwareVersion').AsInteger);
          end   (* qBoards *);
        DoIt := ckbxDebugging.Checked;

        if SoftwareLevel >= slDebugging then
          UpdateDebugging(DoIt);
        with qUpdateDebugging do
          begin (* qUpdateDebugging *)
            ParamByName('Debugging').AsBoolean := DoIt;
            ParamByName('UID'      ).AsInteger := BoardUID;
            ExecSQL
          end   (* qUpdateDebugging *)
      end   (* tfmBoardManager.ckbxDebuggingClick *);

    procedure tfmBoardManager.ckbxPondFillingClick(Sender: tObject);
      (* change pond damping *)
    
      begin (* tfmBoardManager.ckbxPondFillingClick *)
        SetPondFilling(ckbxPondFilling.Checked)
      end   (* tfmBoardManager.ckbxPondFillingClick *);

    procedure tfmBoardManager.NewData(const BoardTag: char; const LocalStamp: tDateTime);
      (* recent activity *)

      var
        OldTag: string;

      begin (* tfmBoardManager.NewData *)
        with qBoards do
          begin (* qBoards *)
            OldTag := FieldByName('Tag').AsString;

            with qNewData do
              begin (* qNewData *)
                ParamByName('BoardTag'  ).AsString   := BoardTag;
                ParamByName('LocalStamp').AsDateTime := LocalStamp;
                ExecSQL;
              end   (* qNewData *);

            Close;
            Open;
            fmHC4Main.BoardQuiet(FieldByName('BoardLabel').AsString,
                                 FieldByName('LastData'  ).AsDateTime);
            Locate('Tag', OldTag, [])
          end   (* qBoards *);

      end   (* tfmBoardManager.NewData *);

    procedure tfmBoardManager.SendToBoard(const WTB: tWindowsToBase;
                                          const Value: integer;
                                          const SoftwareLevelRequired: tSoftwareLevel);
      (* send one-arg message to board.
         ASSUMED dump not in progress (buttons disabled then) *)

      var
        BoardUID, TargetMY: integer;
        SWVersion: tSoftwareLevel;
        BoardKind: string;

      begin (* tfmBoardManager.SendToBoard *)
        if Global.Status = gsConnected then
          with qBoards, fmDashBoard.SerialManager do
            begin (* qBoards *)
              BoardKind  :=                FieldByName('BoardKind'      ).AsString;
              BoardUID   :=                FieldByName('BoardUID'       ).AsInteger;
              SWVersion  := tSoftwareLevel(FieldByName('SoftwareVersion').AsInteger);
              TargetMy   :=                FieldByName('XBeeMY'         ).AsInteger;
              // ShowMessageFmt('will send %d (%d) to board %d; SW wanted %d, present %d)',
              //                [ord(WTB), Value, BoardUID,
              //                 ord(SoftwareLevelRequired), ord(SWVersion)]);
              if (BoardKind = 'peripheral') or (BoardKind = 'carport') then
                if SWVersion >= SoftwareLevelRequired then
                  begin (* tell board something *)
                    AfterScroll := nil;
                    IntMessageToBaseStation(wtbSendToXBee,
                                            [TargetMY, ord(WTB), Value]);
                    Close;
                    Open;
                    if not Locate('BoardUID', BoardUID, []) then
                      (* ignore it *);
                    AfterScroll := qBoardsAfterScroll
                  end   (* tell board something *)

                else
                  fmDashboard.ExtendChat('bm_536 software too old')
            end   (* qBoards *)
      end   (* tfmBoardManager.SendToBoard *);

{ ********** end messages to stations **************************************** }
{ ********** unclassified **************************************************** }



end.
