Project decisions

In a computer science project, some decisions have to be taken, which will determine the further course of the project. Sometimes, these decisions seem to be logical to other scientists, sometimes these decisions are controversial. Many times several sensible alternatives exist, and a scientist should always be able to justify his personal decisions.
This post includes several important decisions for the seismometer project. All of the decisions will be explained in the hope that the gentle reader will be able to understand them.
Here are the four most important decisions:

  1. How to achieve data persistence?
  2. Shall the task be solved by one program or several independent programs?
  3. Which data structure shall be used for data persistence?
  4. How to handle meta data?

How to achieve data persistence?

The seismometer produces much data: 400 voltage measurements per second, which makes about one billion values per month. Since a scientist not only wants to see the current data of the last minutes, but wants to be able to compare data of different times with each other in order to find common characteristics and ways to predict future behavior, he needs to save the data in order to achieve data persistence. What kind of data needs to be saved? For each measurement there are two data, both of which need to be saved: the time including the date (a 64 bit value) and the ADC value (a 32 bit value). There are many different ways to save data. One simple approach is to save the raw data continuously in a file and to add new data to the end of the file. An improved approach would not use the raw data but structured data, like using an XML structure and save the XML data continuously in an XML text file. Both approaches have important disadvantages: It is very difficult to retrieve and search for data of a given time interval. Also these files would get very big and are difficult to handle with simple file operations. Of course the file size could be limited and a new file started, but still such a proprietary data structure will be difficult to understand and to handle.

Here a more sophisticated and general approach will be used instead. The data will be saved in a data base. The data base will take care of the internal data structure and offers simple commands for saving the data and for retrieving the data of a given time interval. For the data base the PostgreSQL server is used, which is open source, fast and very powerful.
The data will be saved in the table adc_values using the following table structure:

  • id bigint,
  • time_stamp timestamp,
  • adc_value integer.

The field id is a 64 bit integer value, which is set automatically and gets increased automatically when a new row is inserted.
The field time_stamp contains the time including the date of the measured value.
The field adc_value contains the measured value as a 32 bit integer, which is the output of the ADC.

Shall the task be solved by one program or several independent programs?

It is possible to write a program which reads the data from the ADC, saves the data in the data base, analyses the data and displays the data. Such a program would not only be very big but every time when some part of the analysing or displaying of the data should be improved, the old program needs to stop, the new program needs to compile and no data is recorded during that time.

Therefore, an old principle of the Romans is used: DIVIDE ET IMPERA. In computer science DIVIDE ET IMPERA means that the original problem is divided into several parts (subgoaling), each of which is easier to solve than the original problem.
For the present project the main problem is divided into two parts:
The fist part measures the ADC values and saves the values into the PostgreSQL data base. The second part retrieves the data from the data base, analysis and displays the data.
By this approach, the part which analysis and displays the data can be stopped, improved and compiled without disturbing the saving of the data as the program, which saves the data, is still running. Furthermore, the second program could run on a different computer.

Which data structure shall be used for data persistence?

In theory, it would be possible to simply add all new data to the data base as a new row of the table adc_values. Since the data base will be present at the ssd of the Raspberry Pi with 250 GB, the size of the data base is limited. For this reason, a special data structure is used, which only includes the data of a specific time frame of the past by overwriting data older than the time frame. Here the time frame has a length of one month, which makes about one billion table rows. Such a data structure is called a ring buffer (also called cyclic or circular buffer). It is possible to read the data of the data base in regular time intervals and save the data in a larger data base outside the Raspberry Pi, in case data older than one month should be saved too.

For the present project the id value, which is used to save the data is reset to 0, after a defined value, which is defined as 400 * 60 * 60 * 24 *31 (the amount of data in one month), is exceeded. This way, the data is saved in a ring, and data older than a month is overwritten.
It would also be possible, to always add new data and to delete data older than a month, but this would cause more calculations, as an index is maintained for the id value at the data base, which needs more overhead than the ring buffer. Also overwriting is faster than adding and deleting.
This way, the measuring and saving of the data is made independent from the anylsis and display of the data.

 

How to handle meta data?

By using the ring buffer, another program needs to know, at which point of the ring buffer the current data was written to. This meta information is saved in the table id_values, which only consists of one row with two fields:

  • id bigint,
  • adc_values_id bigint.

The field adc_values_id contains the id value of the table adc_values, where the current data was written to. The field id is set to 0 in order to read and write the data fast.

Summary

In summary, there are two programs: one measures and saves the data, the other program reads the data and displays the data on a form at the screen. The second program doesn’t have to run on the Raspberry Pi but could run on any computer in the world, which can connect over the internet to the Raspberry Pi. The display of the data uses already the velocity values and not the voltage values, by using a conversion factor of 20.9 V/(m/s), which is given by the manufacturer for a 1 kΩ termination shunt. With these two programs, real data of the movement of the ground can be displayed for the first time. The program for measuring and saving the data uses about 15% of the CPU resourses of the Raspberry Pi; this value includes the resources needed by the PostgreSQL server for saving the data. Both programs together use about 40% of the CPU resourses of the Raspberry Pi, including the work of the PostgreSQL server for saving and retrieving data. Not to bad for such a little computer, but stay tuned, the Raspberry Pi can achieve even more as to be seen in the next posts…

Software

Following the software is lested for the data saving part and for the retrieving, analysing and displaying part.

 

DB Transmitter Project

The files main_unit.pas, main_unit.frm and project_defines.inc contain the source code for measuring the data and saving the data to the data base. Furthermore project_defines.inc offer some options for trancating the table adc_values, to initialize the data base by creating the ring buffer in table adc_values and to add continuously new measured values to the table adc_values.

Please note that the user names and passwords need to be adapted to your choice.

The file go.sh will compile and run the program.

The software for the db transmitter project can be downloaded here and be copied to any directory. The choosen path of the used directory needs to be adjusted in go.sh.

 

main_unit.pas

{ ####################################################################################### }
{ ##                                                                                   ## }
{ ## Main_Unit                                                                         ## }
{ ##                                                                                   ## }
{ ## Main form for db_transmitter                                                      ## }
{ ##                                                                                   ## }
{ ## Copyright (C) 2018-2019  : Dr. Jürgen Abel                                        ## }
{ ## Email                    : juergen@mve.info                                       ## }
{ ## Internet                 : www.seismometer.info                                   ## }
{ ##                                                                                   ## }
{ ## This program is free software: you can redistribute it and/or modify              ## }
{ ## it under the terms of the GNU Lesser General Public License as published by       ## }
{ ## the Free Software Foundation, either version 3 of the License, or                 ## }
{ ## (at your option) any later version with the following modification:               ## }
{ ##                                                                                   ## }
{ ## As a special exception, the copyright holders of this library give you            ## }
{ ## permission to link this library with independent modules to produce an            ## }
{ ## executable, regardless of the license terms of these independent modules, and     ## }
{ ## to copy and distribute the resulting executable under terms of your choice,       ## }
{ ## provided that you also meet, for each linked independent module, the terms        ## }
{ ## and conditions of the license of that module. An independent module is a          ## }
{ ## module which is not derived from or based on this library. If you modify          ## }
{ ## this library, you may extend this exception to your version of the library,       ## }
{ ## but you are not obligated to do so. If you do not wish to do so, delete this      ## }
{ ## exception statement from your version.                                            ## }
{ ##                                                                                   ## }
{ ## This program is distributed in the hope that it will be useful,                   ## }
{ ## but WITHOUT ANY WARRANTY; without even the implied warranty of                    ## }
{ ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                     ## }
{ ## GNU General Public License for more details.                                      ## }
{ ##                                                                                   ## }
{ ## You should have received a copy of the GNU Lesser General Public License          ## }
{ ## COPYING.LGPL.txt along with this program.                                         ## }
{ ## If not, see <https://www.gnu.org/licenses/>.                                      ## }
{ ##                                                                                   ## }
{ ####################################################################################### }


Unit Main_Unit;

{$mode objfpc}{$H+}

{$INCLUDE project_defines.inc}

Interface

Uses
  CThreads,
  Classes,
  SysUtils,
  Forms,
  Controls,
  Graphics,
  Dialogs,
  StdCtrls,
  ExtCtrls,
  ComCtrls,
  TAGraph,
  TASeries,
  ADS1262_Unit,
  TACustomSeries,
  TATools,
  PQConnection,
  SQLDB;

Const
  {$IFDEF USE_INTERRUPT}
  {$ELSE}
  DATA_INTERVAL_MS                  = 2;
  {$ENDIF}
  LP_SPAN_INIT                      = 20;
  DATA_ARRY_SIZE                    = 1024 * 1024;
  CHART_EXTEND_Y                    = 0.0005;
  CHART_EXTEND_Y_V                  = 5;
  VALUES_PER_HOUR                   = 60 * 60;
  RMS_COLOR                         = $2222AA;
  RMS_BAR_WIDTH_PERCENT             = 100;
  DATA_COLOR                        = $FF4444;
  DATA_PIN_WIDTH                    = 3;
  POSITION_COLOR                    = $6666EE;
  POSITION_WIDTH                    = 11;
  IMAGE_FILE_NAME                   = 'images/daily_image.jpg';
  HOUR_CHART_N                      = 24;
  HOUR_CHART_FONT_SIZE              = 6;
  STATION_NAME                      = 'NORF_00';
  CURRENT_TIME_FORMAT_00_S          = 'Live Data  -  Station Name: ' + STATION_NAME + '  -  Date: ';
  CURRENT_TIME_FORMAT_01_S          = '  -  Time: ';
  SQR_2                             = sqrt (2);
  BIT_N_RANGE                       = 100;
  PROGRAM_START_CLEAR_DB_S          = 'Clear db';
  PROGRAM_START_CREATE_DB_S         = 'Create db';
  PROGRAM_START_S                   = 'Start program';
  {$IFDEF USE_DB_LOCALHOST}
  SQL_HOSTNAME                      = '127.0.0.1';
  {$ELSE}
  SQL_HOSTNAME                      = '192.168.0.99';
  {$ENDIF}
  SQL_DATABASENAME                  = 'seismometer_db';
  SQL_USERNAME                      = 'user';
  SQL_PASSWORD                      = '123456';
  SQL_POSTGRES_USERNAME             = 'postgres';
  SQL_POSTGRES_PASSWORD             = '123456';
  SQL_RESTART_SEQUENCE_TXT          = 'ALTER SEQUENCE adc_values_id_seq RESTART WITH 0;';
  SQL_END_TRANSATION_TXT            = 'END TRANSACTION;';
  SQL_BEGIN_TRANSATION_TXT          = 'BEGIN TRANSACTION;';
  SQL_VACUUM_FULL_TXT               = 'VACUUM FULL;';
  SQL_VACUUM_ANALYZE_TXT            = 'VACUUM ANALYZE;';
  SQL_ADC_VALUES_RECORD_N           = 400 * 60 * 60 * 24 * 31;
  SQL_ADC_VALUES_RECORD_AT_ONCE_N   = 1000;
  SQL_ADC_VALUES_TABLE_NAME_TXT     = 'adc_values';
  SQL_ADC_VALUES_ID_TXT             = 'id';
  SQL_ADC_VALUES_TIME_STAMP_TXT     = 'time_stamp';
  SQL_ADC_VALUES_ADC_VALUE_TXT      = 'adc_value';
  SQL_ADC_VALUES_TRUNCATE_TXT       = 'TRUNCATE ' + SQL_ADC_VALUES_TABLE_NAME_TXT + ';';
  SQL_ADC_VALUES_INSERT_DUMMIES_TXT = 'INSERT INTO ' + SQL_ADC_VALUES_TABLE_NAME_TXT + ' (' + SQL_ADC_VALUES_TIME_STAMP_TXT + ', ' + SQL_ADC_VALUES_ADC_VALUE_TXT + ') VALUES (CURRENT_TIMESTAMP , 0);';
  SQL_ADC_VALUES_UPDATE_TXT         = 'UPDATE ' + SQL_ADC_VALUES_TABLE_NAME_TXT + ' SET ' + SQL_ADC_VALUES_TIME_STAMP_TXT + ' = :' + SQL_ADC_VALUES_TIME_STAMP_TXT + ', ' + SQL_ADC_VALUES_ADC_VALUE_TXT + ' = :' + SQL_ADC_VALUES_ADC_VALUE_TXT + ' WHERE ' + SQL_ADC_VALUES_ID_TXT + ' = :' + SQL_ADC_VALUES_ID_TXT + ';';
  SQL_ID_VALUES_TABLE_NAME_TXT      = 'id_values';
  SQL_ID_VALUES_ID_TXT              = 'id';
  SQL_ID_VALUES_ID_VALUE_TXT        = 0;
  SQL_ID_VALUES_ADC_VALUES_ID_TXT   = 'adc_values_id';
  SQL_ID_VALUES_UPDATE_TXT          = 'UPDATE ' + SQL_ID_VALUES_TABLE_NAME_TXT + ' SET ' + SQL_ID_VALUES_ADC_VALUES_ID_TXT + ' = :' + SQL_ID_VALUES_ADC_VALUES_ID_TXT + ' WHERE ' + SQL_ADC_VALUES_ID_TXT + ' = :' + SQL_ADC_VALUES_ID_TXT + ';';

Type
  T_Data_X_A = Array [0 .. DATA_ARRY_SIZE - 1] Of TDateTime;
  T_Data_Y_A = Array [0 .. DATA_ARRY_SIZE - 1] Of Int_32;

  { ####################################################################################### }
  { ## T_Timer_Thread                                                                    ## }
  { ####################################################################################### }
  T_Timer_Thread = Class(TThread)
  Private
      M_ADS1262 :      T_ADS1262;
      M_Data_X_A :     T_Data_X_A;
      M_Data_Y_A :     T_Data_Y_A;
      M_Data_A_Index : Integer;
      M_Count :        Int_64;
  Public
      Property Data_X_A : T_Data_X_A Read M_Data_X_A;
      Property Data_Y_A : T_Data_Y_A Read M_Data_Y_A;
      Property Data_A_Index : Integer Read M_Data_A_Index;
      Property Count : Int_64 Read M_Count;
      Property ADS1262 : T_ADS1262 Read M_ADS1262;
      Constructor Create (F_Suspended : Boolean);
      Destructor Destroy; Override;
      Procedure Execute; Override;
  End; { T_Timer_Thread }

  { ####################################################################################### }
  { ## TMain_F                                                                           ## }
  { ####################################################################################### }

  { TMain_F }

  TMain_F = Class(TForm)
      ADC_Values_Q :           TSQLQuery;
      ID_Values_Q :            TSQLQuery;
      Close_B :                TButton;
      Input_Timer_Sec_E :      TEdit;
      Input_Timer_Sec_L :      TLabel;
      Postgres_C :             TPQConnection;
      Postgres_T :             TSQLTransaction;
      Input_OFCAL_Value_E :    TEdit;
      Input_OFCAL_Value_L :    TLabel;
      Output_1_P :             TPanel;
      Output_2_P :             TPanel;
      Output_Bits_E :          TEdit;
      Output_Bits_L :          TLabel;
      Output_Counter_E :       TEdit;
      Output_Counter_L :       TLabel;
      Output_Freq_E :          TEdit;
      Output_Freq_L :          TLabel;
      Output_NPS_E :           TEdit;
      Input_Sensivity_Bits_E : TEdit;
      Output_NPS_L :           TLabel;
      Input_Sensivity_Bits_L : TLabel;
      Start_Stop_B :           TButton;
      Output_0_P :             TPanel;
      Chart_T :                TTimer;
      Clear_DB_T :             TTimer;
      Status_P :               TPanel;
      Status_L :               TLabel;
      Procedure Chart_TTimer (Sender : TObject);
      Procedure Clear_DB_TTimer (Sender : TObject);
      Procedure Close_BClick (Sender : TObject);
      Procedure FormCreate (Sender : TObject);
      Procedure FormDestroy (Sender : TObject);
      Procedure FormShow (Sender : TObject);
      Procedure Input_Timer_Sec_EEditingDone (Sender : TObject);
      Procedure Input_OFCAL_Value_EEditingDone (Sender : TObject);
      Procedure Input_Sensivity_Bits_EEditingDone (Sender : TObject);
      Procedure Start_Stop_BClick (Sender : TObject);
  Protected
      M_Timer_Thread :      T_Timer_Thread;
      M_Data_A_Index_0 :    Integer;
      M_Data_A_Index_1 :    Integer;
      M_id :                Int_64;
      M_Start_Time :        TDateTime;
      M_Old_Data_Hour :     Integer;
      M_Old_Data_Minute :   Integer;
      M_Old_Data_Second :   Integer;
      M_Last_Saved_Minute : Integer;
      Procedure Show_Status (F_Status : String);
      Function Validate_Array_Index (F_Index : Integer; F_Array_Size : Integer) : Integer;
      Procedure Output_Current_Data (F_Measurement_N : Integer; F_Zero_Crossings_N : Integer; F_End_Time : TDateTime);
  Public
  End; { TMain_F }

Var
  Main_F : TMain_F;

Implementation

{$R *.frm}

Uses
  Math,
  DateUtils,
  TAChartUtils;

{ ####################################################################################### }
{ ## T_Timer_Thread                                                                    ## }
{ ####################################################################################### }

{ --------------------------------------------------------------------------------------- }
Constructor T_Timer_Thread.Create (F_Suspended : Boolean);
  { Create timer thread                                                                     }
  { --------------------------------------------------------------------------------------- }
{$IFDEF DEBUG_ERROR}
Var
  Register_Block : T_Register_Block;
{$ENDIF}
Begin { T_Timer_Thread.Create }
  Inherited Create (F_Suspended);

  FreeOnTerminate := FALSE;

  M_ADS1262 := T_ADS1262.Create ();
  M_ADS1262.Set_Continuous_Conversion ();
  M_ADS1262.Start_Conversion ();
  Sleep (ADS1262_WAIT_TIME_MS_COMMAND);

  {$IFDEF DEBUG_ERROR}
  M_ADS1262.Read_All_Registers (Register_Block);
  {$ENDIF}

  FillByte (M_Data_X_A [0], DATA_ARRY_SIZE * SizeOf (TDateTime), $00);
  FillByte (M_Data_Y_A [0], DATA_ARRY_SIZE * SizeOf (Int_32), $00);

  M_Data_A_Index := 0;
  M_Count        := 0;
End; { T_Timer_Thread.Create }


{ --------------------------------------------------------------------------------------- }
Destructor T_Timer_Thread.Destroy;
  { Free data                                                                               }
  { --------------------------------------------------------------------------------------- }
Begin { T_Timer_Thread.Destroy }
  M_ADS1262.Free;

  Inherited Destroy;
End; { T_Timer_Thread.Destroy }


{ --------------------------------------------------------------------------------------- }
Procedure T_Timer_Thread.Execute;
{ Execute thread                                                                          }
{ --------------------------------------------------------------------------------------- }
Begin { T_Timer_Thread.Execute }
  While (Terminated = FALSE) Do
    Begin { While }
      {$IFDEF USE_INTERRUPT}
      M_ADS1262.DRDY_Pin.Wait_For_Interrupt (GPIO_TIMEOUT_INTERRUPT_INFINITE);
      {$ELSE}
      Sleep (DATA_INTERVAL_MS);
      {$ENDIF}

      M_Data_X_A[M_Data_A_Index] := Now;
      M_Data_Y_A[M_Data_A_Index] := M_ADS1262.ADC1_Data;

      Inc (M_Count);
      Inc (M_Data_A_Index);
      If M_Data_A_Index >= DATA_ARRY_SIZE Then
        Begin { then }
          M_Data_A_Index := 0;
        End; { then }
    End;     { While }
End; { T_Timer_Thread.Execute }


{ ####################################################################################### }
{ ## TMain_F                                                                           ## }
{ ####################################################################################### }

{#####################################################################################}
{ Build form                                                                          }
{#####################################################################################}

{ --------------------------------------------------------------------------------------- }
Procedure TMain_F.FormCreate (Sender : TObject);
{ Create main from                                                                        }
{ --------------------------------------------------------------------------------------- }
Begin { TMain_F.FormCreate }
  M_Old_Data_Hour     := 0;
  M_Old_Data_Minute   := 0;
  M_Old_Data_Second   := 0;
  M_Last_Saved_Minute := 0;
  M_id                := 0;

  M_Timer_Thread := T_Timer_Thread.Create (TRUE);
  M_Timer_Thread.ADS1262.Set_OFCAL (StrToInt (Input_OFCAL_Value_E.Text));
End; { TMain_F.FormCreate }


{ --------------------------------------------------------------------------------------- }
Procedure TMain_F.FormDestroy (Sender : TObject);
{ Free data                                                                               }
{ --------------------------------------------------------------------------------------- }
Begin { TMain_F.FormDestroy }
  Chart_T.Enabled := FALSE;

  M_Timer_Thread.Terminate;
  Sleep (100);
  M_Timer_Thread.Free;

  Postgres_C.Close;
End; { TMain_F.FormDestroy }


{ --------------------------------------------------------------------------------------- }
Procedure TMain_F.FormShow (Sender : TObject);
{ Show main form                                                                          }
{ --------------------------------------------------------------------------------------- }
Begin { TMain_F.FormShow }
  {$IFDEF INIT_DB_AT_START}
  Clear_DB_T.Enabled   := TRUE;
  {$ELSE}
  ADC_Values_Q.Active  := FALSE;
  ID_Values_Q.Active   := FALSE;
  Postgres_T.Active    := FALSE;
  Postgres_C.Connected := FALSE;

  Postgres_C.Transaction  := Postgres_T;
  Postgres_C.HostName     := SQL_HOSTNAME;
  Postgres_C.DatabaseName := SQL_DATABASENAME;

  Postgres_C.UserName  := SQL_USERNAME;
  Postgres_C.Password  := SQL_PASSWORD;
  Postgres_C.Connected := TRUE;

  ADC_Values_Q.Close;
  ADC_Values_Q.SQL.Clear;
  ADC_Values_Q.SQL.Add (SQL_ADC_VALUES_UPDATE_TXT);

  ID_Values_Q.Close;
  ID_Values_Q.SQL.Clear;
  ID_Values_Q.SQL.Add (SQL_ID_VALUES_UPDATE_TXT);

  M_Data_A_Index_0 := 0;
  M_Data_A_Index_1 := 0;
  M_Start_Time     := Now;

  M_Timer_Thread.Start;

  Chart_T.Interval := StrToInt (Input_Timer_Sec_E.Text);
  Chart_T.Enabled  := TRUE;
  {$ENDIF}
End; { TMain_F.FormShow }


{#####################################################################################}
{ Buttons and edit components                                                         }
{#####################################################################################}

{ --------------------------------------------------------------------------------------- }
Procedure TMain_F.Show_Status (F_Status : String);
{ Show status and update form                                                             }
{ --------------------------------------------------------------------------------------- }
Begin { TMain_F.Show_Status }
  Status_L.Caption := F_Status;

  Update;
  Application.ProcessMessages;
End; { TMain_F.Show_Status }


{ --------------------------------------------------------------------------------------- }
Procedure TMain_F.Output_Current_Data (F_Measurement_N : Integer; F_Zero_Crossings_N : Integer; F_End_Time : TDateTime);
{ --------------------------------------------------------------------------------------- }
Var
  I :         Integer;
  Bit_N_Max : Double;

Begin { TMain_F.Output_Current_Data }
  Bit_N_Max := 0;
  For I := 0 To BIT_N_RANGE - 1 Do
    Begin { For }
      Bit_N_Max := Max (Bit_N_Max, Log2 (Max (Abs (M_Timer_Thread.Data_Y_A [(M_Data_A_Index_1 - 1 - I) mod DATA_ARRY_SIZE]), 1)));
    End; { For }

  Output_NPS_E.Text     := FormatFloat ('#,##0.0', Double (F_Measurement_N) / (Double (MilliSecondSpan (M_Start_Time, F_End_Time)) / 1000));
  Output_Counter_E.Text := FormatFloat ('#,##0', M_Timer_Thread.Data_A_Index);
  Output_Bits_E.Text    := FormatFloat ('#,##0.0', Bit_N_Max);
  Output_Freq_E.Text    := FormatFloat ('#,##0.0', F_Zero_Crossings_N / 2 / Double (MilliSecondSpan (M_Start_Time, F_End_Time)) * 1000);
  M_Start_Time          := F_End_Time;

  Update;
  Application.ProcessMessages;
End; { TMain_F.Output_Current_Data }


{ --------------------------------------------------------------------------------------- }
Procedure TMain_F.Close_BClick (Sender : TObject);
{ Close button pressed                                                                    }
{ --------------------------------------------------------------------------------------- }
Begin { TMain_F.Close_BClick }
  Close;
End; { TMain_F.Close_BClick }


{ --------------------------------------------------------------------------------------- }
Procedure TMain_F.Start_Stop_BClick (Sender : TObject);
{ Start/Stop button pressed                                                               }
{ --------------------------------------------------------------------------------------- }
Begin { TMain_F.Start_Stop_BClick }
  Chart_T.Enabled := not Chart_T.Enabled;
End; { TMain_F.Start_Stop_BClick }


{ --------------------------------------------------------------------------------------- }
Procedure TMain_F.Input_Sensivity_Bits_EEditingDone (Sender : TObject);
{ --------------------------------------------------------------------------------------- }
Begin { TMain_F.Input_Sensivity_Bits_EEditingDone }
    Try
      M_Timer_Thread.ADS1262.Sensivity := StrToInt (Input_Sensivity_Bits_E.Text);
    Except
      M_Timer_Thread.ADS1262.Sensivity := ADS1262_INITIAL_SENSIVITY;
    End; { Try }
End; { TMain_F.Input_Sensivity_Bits_EEditingDone }


{ --------------------------------------------------------------------------------------- }
Procedure TMain_F.Input_OFCAL_Value_EEditingDone (Sender : TObject);
{ --------------------------------------------------------------------------------------- }
Begin { TMain_F.Input_OFCAL_Value_EEditingDone }
    Try
      M_Timer_Thread.ADS1262.Set_OFCAL (StrToInt (Input_OFCAL_Value_E.Text));
    Except
      M_Timer_Thread.ADS1262.Set_OFCAL (ADS1262_INITIAL_OFCAL);
    End; { Try }
End; { TMain_F.Input_OFCAL_Value_EEditingDone }


{ --------------------------------------------------------------------------------------- }
Procedure TMain_F.Input_Timer_Sec_EEditingDone (Sender : TObject);
{ --------------------------------------------------------------------------------------- }
Begin { TMain_F.Input_Chart_Timer_Sec_EEditingDone }
  Chart_T.Interval := StrToInt (Input_Timer_Sec_E.Text);
End; { TMain_F.Input_Chart_Timer_Sec_EEditingDone }


{#####################################################################################}
{ Utilities                                                                           }
{#####################################################################################}

{ --------------------------------------------------------------------------------------- }
Function TMain_F.Validate_Array_Index (F_Index : Integer; F_Array_Size : Integer) : Integer;
  { Validate index into array                                                               }
  { --------------------------------------------------------------------------------------- }
Begin { TMain_F.Validate_Array_Index }
  If F_Index >= 0 Then
    Begin { then }
      Result := F_Index mod F_Array_Size;
    End { then }
  Else
    Begin { else  }
      Result := ((F_Index mod F_Array_Size) + F_Array_Size) mod F_Array_Size;
    End; { else  }
End; { TMain_F.Validate_Array_Index }


{#####################################################################################}
{ DB data                                                                             }
{#####################################################################################}

{ --------------------------------------------------------------------------------------- }
Procedure TMain_F.Clear_DB_TTimer (Sender : TObject);
{ Clear and initialize db                                                                 }
{ --------------------------------------------------------------------------------------- }
Var
  I :        Integer;
  {$IFDEF INSERT_DUMMIES_AT_START}
  End_Time : TDateTime;
  {$ENDIF}
  S :        String;

Begin { TMain_F.Clear_DB_TTimer }
  Clear_DB_T.Enabled := FALSE;

  ADC_Values_Q.Active     := FALSE;
  ID_Values_Q.Active      := FALSE;
  Postgres_T.Active       := FALSE;
  Postgres_C.Connected    := FALSE;
  Postgres_C.Transaction  := Postgres_T;
  Postgres_C.HostName     := SQL_HOSTNAME;
  Postgres_C.DatabaseName := SQL_DATABASENAME;

  {$IFDEF CLEAR_DB_AT_START}
  Show_Status (PROGRAM_START_CLEAR_DB_S);

  Postgres_C.UserName  := SQL_POSTGRES_USERNAME;
  Postgres_C.Password  := SQL_POSTGRES_PASSWORD;
  Postgres_C.Connected := TRUE;
  Postgres_C.ExecuteDirect (SQL_END_TRANSATION_TXT);
  Postgres_C.ExecuteDirect (SQL_ADC_VALUES_TRUNCATE_TXT);
  Postgres_C.ExecuteDirect (SQL_RESTART_SEQUENCE_TXT);
  Postgres_C.ExecuteDirect (SQL_VACUUM_FULL_TXT);
  Postgres_C.ExecuteDirect (SQL_VACUUM_ANALYZE_TXT);
  Postgres_T.Commit;
  Postgres_T.EndTransaction;
  Postgres_C.Connected := FALSE;
  {$ENDIF}

  Postgres_C.UserName  := SQL_USERNAME;
  Postgres_C.Password  := SQL_PASSWORD;
  Postgres_C.Connected := TRUE;

  ADC_Values_Q.Close;
  S := '';
  For I := 0 To SQL_ADC_VALUES_RECORD_AT_ONCE_N - 1 Do
    Begin { For }
      S := S + SQL_ADC_VALUES_INSERT_DUMMIES_TXT + Chr ($0D);
    End; { For }

  ID_Values_Q.Close;
  ID_Values_Q.SQL.Clear;
  ID_Values_Q.SQL.Add (SQL_ID_VALUES_UPDATE_TXT);

  {$IFDEF INSERT_DUMMIES_AT_START}
  Show_Status (PROGRAM_START_CREATE_DB_S);

  M_Start_Time := Now;
  Postgres_T.StartTransaction;
  For I := 0 To (SQL_ADC_VALUES_RECORD_N div SQL_ADC_VALUES_RECORD_AT_ONCE_N) - 1 Do
    Begin { For }
      { Add several rows at once }
      ADC_Values_Q.SQL.Clear;
      ADC_Values_Q.SQL.Add (S);
      ADC_Values_Q.Prepare;
      ADC_Values_Q.ExecSQL;

      End_Time := Now;
      Show_Status ('Record counter: ' + FormatFloat ('#,##0', I * SQL_ADC_VALUES_RECORD_AT_ONCE_N) + ' --- Record rate : ' + FormatFloat ('#,##0.0', Double (SQL_ADC_VALUES_RECORD_AT_ONCE_N) / (Double (MilliSecondSpan (M_Start_Time, End_Time)) / 1000)));
      M_Start_Time := End_Time;
    End; { For }

  { Save last id }
  ID_Values_Q.Prepare;
  ID_Values_Q.Params.ParamByName (SQL_ID_VALUES_ID_TXT).AsLargeInt            := SQL_ID_VALUES_ID_VALUE_TXT;
  ID_Values_Q.Params.ParamByName (SQL_ID_VALUES_ADC_VALUES_ID_TXT).AsLargeInt := 0;
  ID_Values_Q.ExecSQL;

  Postgres_T.Commit;
  {$ENDIF}

  ADC_Values_Q.Close;
  ADC_Values_Q.SQL.Clear;
  ADC_Values_Q.SQL.Add (SQL_ADC_VALUES_UPDATE_TXT);

  Show_Status ('');

  M_Data_A_Index_0 := 0;
  M_Data_A_Index_1 := 0;
  M_Start_Time     := Now;

  {$IFDEF UPDATE_VALUES_AFTER_START}
  M_Timer_Thread.Start;

  Chart_T.Interval := StrToInt (Input_Timer_Sec_E.Text);
  Chart_T.Enabled  := TRUE;
  {$ELSE}
  Close;
  {$ENDIF}
End; { TMain_F.Clear_DB_TTimer }


{ --------------------------------------------------------------------------------------- }
Procedure TMain_F.Chart_TTimer (Sender : TObject);
{ Repaint chart                                                                           }
{ --------------------------------------------------------------------------------------- }
Var
  I :                Integer;
  Start_Index :      Integer;
  End_Index :        Integer;
  Voltage :          Double;
  Old_Voltage :      Double;
  Zero_Crossings_C : Integer;
  Additional_Index : Integer;

Begin { TMain_F.Chart_TTimer }
  Postgres_T.StartTransaction;

  M_Data_A_Index_1 := M_Timer_Thread.Data_A_Index;
  Zero_Crossings_C := 0;
  Additional_Index := 0;

  If M_Data_A_Index_1 < M_Data_A_Index_0 Then
    Begin { then }
      Start_Index := M_Data_A_Index_0;
      End_Index   := DATA_ARRY_SIZE - 1;
      Voltage     := 0;
      Old_Voltage := Voltage;
      I           := Start_Index;
      While I <= End_Index Do
        Begin { While }
          Voltage := M_Timer_Thread.ADS1262.Calculate_Voltage (M_Timer_Thread.Data_Y_A [I]);

          ADC_Values_Q.Prepare;
          ADC_Values_Q.Params.ParamByName (SQL_ADC_VALUES_ID_TXT).AsLargeInt         := M_id;
          ADC_Values_Q.Params.ParamByName (SQL_ADC_VALUES_TIME_STAMP_TXT).AsDateTime := M_Timer_Thread.Data_X_A [I];
          ADC_Values_Q.Params.ParamByName (SQL_ADC_VALUES_ADC_VALUE_TXT).AsInteger   := M_Timer_Thread.Data_Y_A [I];
          ADC_Values_Q.ExecSQL;

          If (I > Start_Index) and ((Old_Voltage < 0) and (Voltage > 0) or (Old_Voltage > 0) and (Voltage < 0)) Then
            Begin { then }
              Inc (Zero_Crossings_C);
            End; { then }

          Old_Voltage := Voltage;
          M_id        := Validate_Array_Index (M_id + 1, SQL_ADC_VALUES_RECORD_N);
          Inc (I);
        End; { While }

      Additional_Index := End_Index - Start_Index;
      M_Data_A_Index_0 := 0;
    End; { then }

  Start_Index := M_Data_A_Index_0;
  End_Index   := M_Data_A_Index_1 - 1;
  Voltage     := 0;
  Old_Voltage := Voltage;
  I           := Start_Index;
  While I <= End_Index Do
    Begin { While }
      Voltage := M_Timer_Thread.ADS1262.Calculate_Voltage (M_Timer_Thread.Data_Y_A [I]);

      ADC_Values_Q.Prepare;
      ADC_Values_Q.Params.ParamByName (SQL_ADC_VALUES_ID_TXT).AsLargeInt         := M_id;
      ADC_Values_Q.Params.ParamByName (SQL_ADC_VALUES_TIME_STAMP_TXT).AsDateTime := M_Timer_Thread.Data_X_A [I];
      ADC_Values_Q.Params.ParamByName (SQL_ADC_VALUES_ADC_VALUE_TXT).AsInteger   := M_Timer_Thread.Data_Y_A [I];
      ADC_Values_Q.ExecSQL;

      If (I > Start_Index) and ((Old_Voltage < 0) and (Voltage > 0) or (Old_Voltage > 0) and (Voltage < 0)) Then
        Begin { then }
          Inc (Zero_Crossings_C);
        End; { then }

      Old_Voltage := Voltage;
      M_id        := Validate_Array_Index (M_id + 1, SQL_ADC_VALUES_RECORD_N);
      Inc (I);
    End; { While }

  ID_Values_Q.Prepare;
  ID_Values_Q.Params.ParamByName (SQL_ID_VALUES_ID_TXT).AsLargeInt            := SQL_ID_VALUES_ID_VALUE_TXT;
  ID_Values_Q.Params.ParamByName (SQL_ID_VALUES_ADC_VALUES_ID_TXT).AsLargeInt := M_id;
  ID_Values_Q.ExecSQL;

  Postgres_T.Commit;

  Output_Current_Data (End_Index - Start_Index + Additional_Index, Zero_Crossings_C, M_Timer_Thread.Data_X_A [I - 1]);

  M_Data_A_Index_0 := M_Data_A_Index_1;
End; { TMain_F.Chart_TTimer }


End.

Truncate and initialze data base

The file project_defines.inc as listed below will trancate the table adc_values and initialize the data base by creating 400 * 60 * 60 * 24 * 31 rows for one month at the start of the program, which will take about one week to run and build all the rows. These rows are the ring buffer.

{ ####################################################################################### }
{ ##                                                                                   ## }
{ ## Includes                                                                          ## }
{ ##                                                                                   ## }
{ ## Copyright (C) 2018-2019  : Dr. Jürgen Abel                                        ## }
{ ## Email                    : juergen@juergen-abel.info                              ## }
{ ## Internet                 : www.seismometer.info                                   ## }
{ ##                                                                                   ## }
{ ####################################################################################### }


{ ####################################################################################### }
{ ##                                                                                   ## }
{ ## Global defines                                                                    ## }
{ ##                                                                                   ## }
{ ####################################################################################### }

{$PACKRECORDS C}

{ ####################################################################################### }
{ ##                                                                                   ## }
{ ## Following Defines can be enabled or disabled                                      ## }
{ ##                                                                                   ## }
{ ####################################################################################### }

{   $DEFINE DEBUG_GPIO_OUTPUT}
{   $DEFINE DEBUG_SPI_OUTPUT}
{   $DEFINE DEBUG_ADS1262_OUTPUT}
{   $DEFINE DEBUG_MAIN_OUTPUT}
{   $DEFINE DEBUG_ERROR}
{   $DEFINE DEBUG_ADC1_DATA}
{   $DEFINE SELF_CALIBRATION_ON}
{$DEFINE USE_INTERRUPT}
{$DEFINE USE_DB_LOCALHOST}
{$DEFINE INIT_DB_AT_START}
{$DEFINE CLEAR_DB_AT_START}
{$DEFINE INSERT_DUMMIES_AT_START}
{   $DEFINE UPDATE_VALUES_AFTER_START}


Adding new measurements to the data base

The file project_defines.inc as listed below will insert the measured values into the ring buffer. This is the normal usage of the program. The program will run continuously and will not stop. It uses about 15% of the CPU resourses of the Raspberry Pi.

{ ####################################################################################### }
{ ##                                                                                   ## }
{ ## Includes                                                                          ## }
{ ##                                                                                   ## }
{ ## Copyright (C) 2018-2019  : Dr. Jürgen Abel                                        ## }
{ ## Email                    : juergen@juergen-abel.info                              ## }
{ ## Internet                 : www.seismometer.info                                   ## }
{ ##                                                                                   ## }
{ ####################################################################################### }

{ ####################################################################################### }
{ ##                                                                                   ## }
{ ## Global defines                                                                    ## }
{ ##                                                                                   ## }
{ ####################################################################################### }

{$PACKRECORDS C}

{ ####################################################################################### }
{ ##                                                                                   ## }
{ ## Following Defines can be enabled or disabled                                      ## }
{ ##                                                                                   ## }
{ ####################################################################################### }

{   $DEFINE DEBUG_GPIO_OUTPUT}
{   $DEFINE DEBUG_SPI_OUTPUT}
{   $DEFINE DEBUG_ADS1262_OUTPUT}
{   $DEFINE DEBUG_MAIN_OUTPUT}
{   $DEFINE DEBUG_ERROR}
{   $DEFINE DEBUG_ADC1_DATA}
{   $DEFINE SELF_CALIBRATION_ON}
{$DEFINE USE_INTERRUPT}
{$DEFINE USE_DB_LOCALHOST}
{ $DEFINE INIT_DB_AT_START}
{ $DEFINE CLEAR_DB_AT_START}
{ $DEFINE INSERT_DUMMIES_AT_START}
{$DEFINE UPDATE_VALUES_AFTER_START}

DB Receiver Simple Display Project

The files main_unit.pas, main_unit.frm and project_defines.inc contain the source code for retrieving, analysing and displaying the measured data.

Please note that the user names and passwords need to be adapted to your choice.

The file go.sh will compile and run the program.

The software for the db transmitter project can be downloaded here and be copied to any directory. The choosen path of the used directory needs to be adjusted in go.sh.

main_unit.pas

{ ####################################################################################### }
{ ##                                                                                   ## }
{ ## Main_Unit                                                                         ## }
{ ##                                                                                   ## }
{ ## Main form for db_receiver                                                         ## }
{ ##                                                                                   ## }
{ ## Copyright (C) 2018-2019  : Dr. Jürgen Abel                                        ## }
{ ## Email                    : juergen@juergen-abel.info                              ## }
{ ## Internet                 : www.seismometer.info                                   ## }
{ ##                                                                                   ## }
{ ## This program is free software: you can redistribute it and/or modify              ## }
{ ## it under the terms of the GNU Lesser General Public License as published by       ## }
{ ## the Free Software Foundation, either version 3 of the License, or                 ## }
{ ## (at your option) any later version with the following modification:               ## }
{ ##                                                                                   ## }
{ ## As a special exception, the copyright holders of this library give you            ## }
{ ## permission to link this library with independent modules to produce an            ## }
{ ## executable, regardless of the license terms of these independent modules, and     ## }
{ ## to copy and distribute the resulting executable under terms of your choice,       ## }
{ ## provided that you also meet, for each linked independent module, the terms        ## }
{ ## and conditions of the license of that module. An independent module is a          ## }
{ ## module which is not derived from or based on this library. If you modify          ## }
{ ## this library, you may extend this exception to your version of the library,       ## }
{ ## but you are not obligated to do so. If you do not wish to do so, delete this      ## }
{ ## exception statement from your version.                                            ## }
{ ##                                                                                   ## }
{ ## This program is distributed in the hope that it will be useful,                   ## }
{ ## but WITHOUT ANY WARRANTY; without even the implied warranty of                    ## }
{ ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                     ## }
{ ## GNU General Public License for more details.                                      ## }
{ ##                                                                                   ## }
{ ## You should have received a copy of the GNU Lesser General Public License          ## }
{ ## COPYING.LGPL.txt along with this program.                                         ## }
{ ## If not, see <https://www.gnu.org/licenses/>.                                      ## }
{ ##                                                                                   ## }
{ ####################################################################################### }


Unit Main_Unit;

{$mode objfpc}{$H+}

{$INCLUDE project_defines.inc}

Interface

Uses
  CThreads,
  Classes,
  SysUtils,
  Forms,
  Controls,
  Graphics,
  Dialogs,
  StdCtrls,
  ExtCtrls,
  ComCtrls,
  TAGraph,
  TASeries,
  ADS1262_Unit,
  TACustomSeries,
  TAIntervalSources,
  TATools,
  Types,
  TACustomSource,
  TAFuncSeries,
  TASources,
  TAChartExtentLink,
  SpinEx,
  PQConnection,
  SQLDB;

Const
  ADS1262_MODE2_GAIN_01_LIMIT       = 2.5;
  ADS1262_MODE2_GAIN_02_LIMIT       = ADS1262_MODE2_GAIN_01_LIMIT / 2;
  ADS1262_MODE2_GAIN_04_LIMIT       = ADS1262_MODE2_GAIN_01_LIMIT / 4;
  ADS1262_MODE2_GAIN_08_LIMIT       = ADS1262_MODE2_GAIN_01_LIMIT / 8;
  ADS1262_MODE2_GAIN_16_LIMIT       = ADS1262_MODE2_GAIN_01_LIMIT / 16;
  ADS1262_MODE2_GAIN_32_LIMIT       = ADS1262_MODE2_GAIN_01_LIMIT / 32;
  {$IFDEF USE_INTERRUPT}
  {$ELSE}
  DATA_INTERVAL_MS                  = 2;
  {$ENDIF}
  LP_SLOW_SPAN                      = 4000;
  LP_SLOW_SETTLE_FACTOR             = 4;
  LP_INTEGRAL_SPAN                  = 1200;
  LP_INTEGRAL_CORRECTION_DIVIDER    = 40000;
  DATA_ARRAY_SIZE                   = 1024 * 1024;
  DATA_CHART_EXTEND_Y               = 0.02;
  DATA_CHART_EXTEND_Y_MAX           = 2.5;
  DATA_CHART_LABEL_SIZE             = 120;
  DATA_CHART_UNIT_FORMAT            = '%0:.9g mm/s';
  TIME_UNIT_FORMAT                  = '%0:.9g mm/s';
  VALUES_PER_HOUR                   = 60 * 60;
  RMS_COLOR                         = $2222AA;
  RMS_BAR_WIDTH_PERCENT             = 100;
  DATA_COLOR                        = $FF4444;
  DATA_PIN_WIDTH                    = 3;
  POSITION_COLOR                    = $6666EE;
  POSITION_WIDTH                    = 11;
  IMAGE_FILE_NAME                   = 'images/daily_image.jpg';
  HOUR_CHART_N                      = 24;
  HOUR_CHART_FONT_SIZE              = 6;
  STATION_NAME                      = 'NORF_00';
  CURRENT_TIME_FORMAT_00_S          = 'Live Data  -  Station Name: ' + STATION_NAME + '  -  Date: ';
  CURRENT_TIME_FORMAT_01_S          = '  -  Time: ';
  SQR_2                             = sqrt (2);
  BIT_N_RANGE                       = 100;
  VOLTAGE_LIMIT                     = ADS1262_MODE2_GAIN_32_LIMIT;
  SM24_VELOCITY_FACTOR              = 20.9;
  {$IFDEF USE_DB_LOCALHOST}
  SQL_HOSTNAME                      = '127.0.0.1';
  {$ELSE}
  SQL_HOSTNAME                      = '192.168.0.99';
  {$ENDIF}
  SQL_DATABASENAME                  = 'seismometer_db';
  SQL_USERNAME                      = 'user';
  SQL_PASSWORD                      = '123456';
  SQL_POSTGRES_USERNAME             = 'postgres';
  SQL_POSTGRES_PASSWORD             = '123456';
  SQL_RESTART_SEQUENCE_TXT          = 'ALTER SEQUENCE adc_values_id_seq RESTART WITH 0;';
  SQL_END_TRANSATION_TXT            = 'END TRANSACTION;';
  SQL_BEGIN_TRANSATION_TXT          = 'BEGIN TRANSACTION;';
  SQL_VACUUM_FULL_TXT               = 'VACUUM FULL;';
  SQL_VACUUM_ANALYZE_TXT            = 'VACUUM ANALYZE;';
  SQL_ADC_VALUES_RECORD_N           = 400 * 60 * 60 * 24 * 31;
  SQL_ADC_VALUES_RECORD_AT_ONCE_N   = 1000;
  SQL_ADC_VALUES_TABLE_NAME_TXT     = 'adc_values';
  SQL_ADC_VALUES_ID_TXT             = 'id';
  SQL_ADC_VALUES_TIME_STAMP_TXT     = 'time_stamp';
  SQL_ADC_VALUES_ADC_VALUE_TXT      = 'adc_value';
  SQL_ADC_VALUES_TRUNCATE_TXT       = 'TRUNCATE ' + SQL_ADC_VALUES_TABLE_NAME_TXT + ';';
  SQL_ADC_VALUES_INSERT_DUMMIES_TXT = 'INSERT INTO ' + SQL_ADC_VALUES_TABLE_NAME_TXT + ' (' + SQL_ADC_VALUES_TIME_STAMP_TXT + ', ' + SQL_ADC_VALUES_ADC_VALUE_TXT + ') VALUES (CURRENT_TIMESTAMP , 0);';
  SQL_ADC_VALUES_UPDATE_TXT         = 'UPDATE ' + SQL_ADC_VALUES_TABLE_NAME_TXT + ' SET ' + SQL_ADC_VALUES_TIME_STAMP_TXT + ' = :' + SQL_ADC_VALUES_TIME_STAMP_TXT + ', ' + SQL_ADC_VALUES_ADC_VALUE_TXT + ' = :' + SQL_ADC_VALUES_ADC_VALUE_TXT + ' WHERE ' + SQL_ADC_VALUES_ID_TXT + ' = :' + SQL_ADC_VALUES_ID_TXT + ';';
  SQL_ID_VALUES_TABLE_NAME_TXT      = 'id_values';
  SQL_ID_VALUES_ID_TXT              = 'id';
  SQL_ID_VALUES_ID_VALUE_TXT        = 0;
  SQL_ID_VALUES_ADC_VALUES_ID_TXT   = 'adc_values_id';
  SQL_ID_VALUES_UPDATE_TXT          = 'UPDATE ' + SQL_ID_VALUES_TABLE_NAME_TXT + ' SET ' + SQL_ID_VALUES_ADC_VALUES_ID_TXT + ' = :' + SQL_ID_VALUES_ADC_VALUES_ID_TXT + ' WHERE ' + SQL_ADC_VALUES_ID_TXT + ' = :' + SQL_ADC_VALUES_ID_TXT + ';';
  SQL_READ_ID_TXT                   = 'SELECT ' + SQL_ID_VALUES_ADC_VALUES_ID_TXT + ' from ' + SQL_ID_VALUES_TABLE_NAME_TXT + ' LIMIT 1;';
  SQL_READ_DATA_TXT                 = 'SELECT id, time_stamp, adc_value FROM adc_values WHERE (id >= %d) AND (id < %d) ORDER BY id ASC;';

Type
  T_Data_X_A = Array [0 .. DATA_ARRAY_SIZE - 1] Of TDateTime;
  T_Data_Y_A = Array [0 .. DATA_ARRAY_SIZE - 1] Of Int_32;

  { ####################################################################################### }
  { ## TMain_F                                                                           ## }
  { ####################################################################################### }

  { TMain_F }

  TMain_F = Class(TForm)
      ADC_Values_Q :                TSQLQuery;
      ID_Values_Q :                 TSQLQuery;
      Data_LS :                     TLineSeries;
      Data_Chart_C :                TChart;
      Chart_TS :                    TChartToolset;
      Chart_TS_ZoomDragTool :       TZoomDragTool;
      Chart_TS_DataPointHintTool :  TDataPointHintTool;
      Chart_TS_PanDragTool :        TPanDragTool;
      Chart_TS_ZoomMouseWheelTool : TZoomMouseWheelTool;
      Close_B :                     TButton;
      Data_CL :                     TConstantLine;
      Label_LP :                    TLabel;
      Main_PC :                     TPageControl;
      Postgres_C :                  TPQConnection;
      Postgres_T :                  TSQLTransaction;
      Main_P :                      TPanel;
      Output_1_P :                  TPanel;
      Output_2_P :                  TPanel;
      Output_Bits_E :               TEdit;
      Output_Bits_L :               TLabel;
      Output_Counter_E :            TEdit;
      Output_Counter_L :            TLabel;
      Output_Freq_E :               TEdit;
      Output_Freq_L :               TLabel;
      Output_NPS_E :                TEdit;
      Output_NPS_L :                TLabel;
      Reset_B :                     TButton;
      Start_Stop_B :                TButton;
      Data_Chart_TS :               TTabSheet;
      Status_L :                    TLabel;
      Status_P :                    TPanel;
      Time_CS :                     TDateTimeIntervalChartSource;
      Chart_T :                     TTimer;
      Procedure Chart_TS_DataPointHintToolHint (ATool : TDataPointHintTool; Const APoint : TPoint; Var AHint : String);
      Procedure Chart_TTimer (Sender : TObject);
      Procedure Close_BClick (Sender : TObject);
      Procedure FormCreate (Sender : TObject);
      Procedure FormDestroy (Sender : TObject);
      Procedure FormShow (Sender : TObject);
      Procedure Reset_BClick (Sender : TObject);
      Procedure Start_Stop_BClick (Sender : TObject);
  Protected
      M_Data_X_A :         T_Data_X_A;
      M_Data_Y_A :         T_Data_Y_A;
      M_DB_id :            Int_64;
      M_DB_Start_id :      Int_64;
      M_DB_End_id :        Int_64;
      M_Data_Start_Index : Integer;
      M_Data_End_Index :   Integer;
      M_Data_A_Size :      Integer;
      M_Start_Time :       TDateTime;
      Procedure Show_Status (F_Status : String);
      Function Validate_Array_Index (F_Index : Integer; F_Array_Size : Integer) : Integer;
      Function Calculate_Velocity (F_Data : Int_32) : Double;
      Procedure Read_Data;
      Procedure Output_Current_Data (F_Measurement_N : Integer; F_Zero_Crossings_N : Integer; F_End_Time : TDateTime);
  Public
  Published
  End; { TMain_F }

Var
  Main_F : TMain_F;

Implementation

{$R *.frm}

Uses
  DB,
  Math,
  DateUtils,
  TAChartUtils;

{ ####################################################################################### }
{ ## TMain_F                                                                           ## }
{ ####################################################################################### }

{#####################################################################################}
{ Build form                                                                          }
{#####################################################################################}

{ --------------------------------------------------------------------------------------- }
Procedure TMain_F.FormCreate (Sender : TObject);
{ Create main from                                                                        }
{ --------------------------------------------------------------------------------------- }
Begin { TMain_F.FormCreate }
  FillByte (M_Data_X_A [0], DATA_ARRAY_SIZE * SizeOf (TDateTime), $00);
  FillByte (M_Data_Y_A [0], DATA_ARRAY_SIZE * SizeOf (Int_32), $00);

  { Initialize current chart }
  Data_LS.Clear;

  Data_Chart_C.Extent.UseXMin          := TRUE;
  Data_Chart_C.Extent.UseXMax          := TRUE;
  Data_Chart_C.Extent.UseYMin          := TRUE;
  Data_Chart_C.Extent.UseYMax          := TRUE;
  Data_Chart_C.ExtentSizeLimit.YMax    := DATA_CHART_EXTEND_Y_MAX * 2;
  Data_Chart_C.ExtentSizeLimit.YMin    := -DATA_CHART_EXTEND_Y_MAX * 2;
  Data_Chart_C.ExtentSizeLimit.UseYMax := TRUE;
  Data_Chart_C.ExtentSizeLimit.UseYMin := TRUE;

  Data_Chart_C.AxisList.Axes[0].LabelSize    := DATA_CHART_LABEL_SIZE;
  Data_Chart_C.AxisList.Axes[0].Marks.Format := DATA_CHART_UNIT_FORMAT;

  M_DB_Start_id      := 0;
  M_DB_End_id        := 0;
  M_Data_Start_Index := 0;
  M_Data_End_Index   := 0;

  Reset_BClick (Sender);
End; { TMain_F.FormCreate }


{ --------------------------------------------------------------------------------------- }
Procedure TMain_F.FormDestroy (Sender : TObject);
{ Free data                                                                               }
{ --------------------------------------------------------------------------------------- }
Begin { TMain_F.FormDestroy }
  Chart_T.Enabled := FALSE;

  Postgres_C.Close;
End; { TMain_F.FormDestroy }


{ --------------------------------------------------------------------------------------- }
Procedure TMain_F.FormShow (Sender : TObject);
{ Show main form                                                                          }
{ --------------------------------------------------------------------------------------- }
Begin { TMain_F.FormShow }
  ADC_Values_Q.Active  := FALSE;
  ID_Values_Q.Active   := FALSE;
  Postgres_T.Active    := FALSE;
  Postgres_C.Connected := FALSE;

  Postgres_C.Transaction  := Postgres_T;
  Postgres_C.HostName     := SQL_HOSTNAME;
  Postgres_C.DatabaseName := SQL_DATABASENAME;
  Postgres_C.UserName     := SQL_USERNAME;
  Postgres_C.Password     := SQL_PASSWORD;
  Postgres_C.Connected    := TRUE;

  ADC_Values_Q.Close;
  ADC_Values_Q.SQL.Clear;

  ID_Values_Q.Close;
  ID_Values_Q.SQL.Clear;
  ID_Values_Q.SQL.Add (SQL_READ_ID_TXT);

  { Read last id from db }
  ID_Values_Q.Open;
  M_DB_Start_id := ID_Values_Q.Fields [0].AsLargeInt;
  ID_Values_Q.Close;

  { Start timer }
  Chart_T.Enabled := TRUE;

  Application.ProcessMessages;
End; { TMain_F.FormShow }


{#####################################################################################}
{ Buttons and edit components                                                         }
{#####################################################################################}

{ --------------------------------------------------------------------------------------- }
Procedure TMain_F.Show_Status (F_Status : String);
{ Show status and update form                                                             }
{ --------------------------------------------------------------------------------------- }
Begin { TMain_F.Show_Status }
  Status_L.Caption := F_Status;

  Update;
  Application.ProcessMessages;
End; { TMain_F.Show_Status }


{ --------------------------------------------------------------------------------------- }
Procedure TMain_F.Output_Current_Data (F_Measurement_N : Integer; F_Zero_Crossings_N : Integer; F_End_Time : TDateTime);
{ --------------------------------------------------------------------------------------- }
Var
  I :         Integer;
  Bit_N_Max : Double;

Begin { TMain_F.Output_Current_Data }
  Bit_N_Max := 0;
  For I := 0 To BIT_N_RANGE - 1 Do
    Begin { For }
      Bit_N_Max := Max (Bit_N_Max, Log2 (Max (Abs (M_Data_Y_A [Validate_Array_Index (M_Data_A_Size - 1 - I, DATA_ARRAY_SIZE)]), 1)));
    End; { For }

  Output_NPS_E.Text     := FormatFloat ('#,##0.0', Double (F_Measurement_N) / (Double (MilliSecondSpan (M_Start_Time, F_End_Time)) / 1000));
  Output_Counter_E.Text := FormatFloat ('#,##0', M_DB_id);
  Output_Bits_E.Text    := FormatFloat ('#,##0.0', Bit_N_Max);
  Output_Freq_E.Text    := FormatFloat ('#,##0.0', F_Zero_Crossings_N / 2 / Double (MilliSecondSpan (M_Start_Time, F_End_Time)) * 1000);
  M_Start_Time          := F_End_Time;
End; { TMain_F.Output_Current_Data }


{ --------------------------------------------------------------------------------------- }
Procedure TMain_F.Close_BClick (Sender : TObject);
{ Close button pressed                                                                    }
{ --------------------------------------------------------------------------------------- }
Begin { TMain_F.Close_BClick }
  Close;
End; { TMain_F.Close_BClick }


{ --------------------------------------------------------------------------------------- }
Procedure TMain_F.Start_Stop_BClick (Sender : TObject);
{ Start/Stop button pressed                                                               }
{ --------------------------------------------------------------------------------------- }
Begin { TMain_F.Start_Stop_BClick }
  Chart_T.Enabled := not Chart_T.Enabled;
End; { TMain_F.Start_Stop_BClick }


{ --------------------------------------------------------------------------------------- }
Procedure TMain_F.Reset_BClick (Sender : TObject);
{ --------------------------------------------------------------------------------------- }
Begin { TMain_F.Reset_BClick }
  Data_Chart_C.Extent.XMin := Now - (60 / 24 / 60 / 60);
  Data_Chart_C.Extent.XMax := Now;
  Data_Chart_C.Extent.YMax := DATA_CHART_EXTEND_Y;
  Data_Chart_C.Extent.YMin := -DATA_CHART_EXTEND_Y;
End; { TMain_F.Reset_BClick }


{#####################################################################################}
{ Utilities                                                                           }
{#####################################################################################}

{ --------------------------------------------------------------------------------------- }
Function TMain_F.Validate_Array_Index (F_Index : Integer; F_Array_Size : Integer) : Integer;
  { Validate index into array                                                               }
  { --------------------------------------------------------------------------------------- }
Begin { TMain_F.Validate_Array_Index }
  If F_Index >= 0 Then
    Begin { then }
      Result := F_Index mod F_Array_Size;
    End { then }
  Else
    Begin { else  }
      Result := ((F_Index mod F_Array_Size) + F_Array_Size) mod F_Array_Size;
    End; { else  }
End; { TMain_F.Validate_Array_Index }


{ --------------------------------------------------------------------------------------- }
Function TMain_F.Calculate_Velocity (F_Data : Int_32) : Double;
  { Calculate voltage from raw data                                                         }
  { --------------------------------------------------------------------------------------- }
Begin { TMain_F.Calculate_Velocity }
  Result := F_Data * VOLTAGE_LIMIT / $7FFFFFFF / SM24_VELOCITY_FACTOR * 1000;
End; { TMain_F.Calculate_Velocity }


{#####################################################################################}
{ DB data                                                                             }
{#####################################################################################}

{ --------------------------------------------------------------------------------------- }
Procedure TMain_F.Read_Data;
{ Read current data from db                                                               }
{ --------------------------------------------------------------------------------------- }
Begin { TMain_F.Read_Data }
  { Read last id from db }
  ID_Values_Q.Open;
  M_DB_End_id := ID_Values_Q.Fields [0].AsLargeInt;
  ID_Values_Q.Close;

  { Read data }
  ADC_Values_Q.SQL.Clear;
  ADC_Values_Q.SQL.Add (Format (SQL_READ_DATA_TXT, [M_DB_Start_id, M_DB_End_id]));
  ADC_Values_Q.Open;

  M_Data_Start_Index := M_Data_End_Index;
  While ADC_Values_Q.EOF = FALSE Do
    Begin { While }
      M_DB_id                      := ADC_Values_Q.Fields [0].AsLargeInt;
      M_Data_X_A[M_Data_End_Index] := ADC_Values_Q.Fields [1].AsDateTime;
      M_Data_Y_A[M_Data_End_Index] := ADC_Values_Q.Fields [2].AsInteger;
      ADC_Values_Q.Next;

      M_Data_End_Index := Validate_Array_Index (M_Data_End_Index + 1, DATA_ARRAY_SIZE);
    End; { While }
  ADC_Values_Q.Close;

  M_Data_A_Size := Validate_Array_Index (M_Data_End_Index - M_Data_Start_Index, DATA_ARRAY_SIZE);
  M_DB_Start_id := M_DB_End_id;
End; { TMain_F.Read_Data }


{#####################################################################################}
{ Chart                                                                               }
{#####################################################################################}

{ --------------------------------------------------------------------------------------- }
Procedure TMain_F.Chart_TTimer (Sender : TObject);
{ Repaint chart                                                                           }
{ --------------------------------------------------------------------------------------- }
Var
  I :                Integer;
  Index :            Integer;
  Time_Value :       TDateTime;
  Voltage :          Double;
  Old_Voltage :      Double;
  Zero_Crossings_C : Integer;
  Current_Extend :   TDoubleRect;
  Extend_Dif :       Double;

Begin { TMain_F.Chart_TTimer }
  { Read data from db }
  Read_Data;

  If M_Data_A_Size = 0 Then
    Begin { then }
      Exit;
    End; { then }

  Data_LS.BeginUpdate;

  Zero_Crossings_C := 0;
  Voltage          := 0;
  Old_Voltage      := Voltage;

  Index := M_Data_Start_Index;
  For I := 0 To M_Data_A_Size - 1 Do
    Begin { For }
      Time_Value := M_Data_X_A [Index];
      Voltage    := Calculate_Velocity (M_Data_Y_A [Index]);
      Data_LS.AddXY (Time_Value, Voltage);

      If ((Old_Voltage < 0) and (Voltage > 0) or (Old_Voltage > 0) and (Voltage < 0)) Then
        Begin { then }
          Inc (Zero_Crossings_C);
        End; { then }

      Old_Voltage := Voltage;
      Index       := Validate_Array_Index (Index + 1, DATA_ARRAY_SIZE);
    End; { For }

  Current_Extend             := Data_Chart_C.LogicalExtent;
  Extend_Dif                 := Current_Extend.b.X - Current_Extend.a.X;
  Current_Extend.b.X         := Time_Value;
  Current_Extend.a.X         := Current_Extend.b.X - Extend_Dif;
  Data_Chart_C.LogicalExtent := Current_Extend;

  Data_LS.EndUpdate;

  Output_Current_Data (M_Data_A_Size, Zero_Crossings_C, Time_Value);

  Show_Status ('');
End; { TMain_F.Chart_TTimer }


{ --------------------------------------------------------------------------------------- }
Procedure TMain_F.Chart_TS_DataPointHintToolHint (ATool : TDataPointHintTool; Const APoint : TPoint; Var AHint : String);
{ --------------------------------------------------------------------------------------- }
Const
  SPACE_START = '         ';

Var
  Time_Value : TDateTime;
  Voltage :    Double;

Begin { TMain_F.Chart_TS_DataPointHintToolHint }
    Try
      Time_Value := TChartSeries (ATool.Series).Source.Item [ATool.PointIndex]^.X;
      Voltage    := TChartSeries (ATool.Series).Source.Item [ATool.PointIndex]^.Y;
      AHint      := SPACE_START + 'X : ' + FormatDateTime ('hh:nn:ss.zzz', Time_Value) + #13 + Format (SPACE_START + 'Y : %.8f', [Voltage]);
    Except
      AHint := '';
    End; { Try }
End; { TMain_F.Chart_TS_DataPointHintToolHint }


End. { Main_Unit }



Retrieve, analyse and display the data

The file project_defines.inc as listed below will retrieve the data from the ring buffer, analyse the data by calculating the velocity from the 32 bit ADC values and display the data on a form. This program together with the transmitter program uses about 40% of the CPU resourses of the Raspberry Pi, including the work of the PostgreSQL server for saving and retrieving data.

{ ####################################################################################### }
{ ##                                                                                   ## }
{ ## Includes                                                                          ## }
{ ##                                                                                   ## }
{ ## Copyright (C) 2018-2019  : Dr. Jürgen Abel                                        ## }
{ ## Email                    : juergen@juergen-abel.info                              ## }
{ ## Internet                 : www.seismometer.info                                   ## }
{ ##                                                                                   ## }
{ ####################################################################################### }


{ ####################################################################################### }
{ ##                                                                                   ## }
{ ## Global defines                                                                    ## }
{ ##                                                                                   ## }
{ ####################################################################################### }

{$PACKRECORDS C}

{ ####################################################################################### }
{ ##                                                                                   ## }
{ ## Following Defines can be enabled or disabled                                      ## }
{ ##                                                                                   ## }
{ ####################################################################################### }

{   $DEFINE DEBUG_GPIO_OUTPUT}
{   $DEFINE DEBUG_SPI_OUTPUT}
{   $DEFINE DEBUG_ADS1262_OUTPUT}
{   $DEFINE DEBUG_MAIN_OUTPUT}
{   $DEFINE DEBUG_ERROR}
{   $DEFINE DEBUG_ADC1_DATA}
{   $DEFINE SELF_CALIBRATION_ON}
{$DEFINE USE_INTERRUPT}
{$DEFINE USE_DB_LOCALHOST}
{   $DEFINE INIT_DB_AT_START}
{   $DEFINE CLEAR_DB_AT_START}
{   $DEFINE INSERT_DUMMIES_AT_START}
{   $DEFINE UPDATE_VALUES_AFTER_START}


Results

First real results from the seismometer

This figure reveals the first real results from the seismometer, which is setup in the soil in Norf, Germany, measured on January 2nd, 2020.