# GE ODBC Wind Turbines

This plugin will read from the GE Data Historian, which is a Microsoft SQL Server, and send the data to the Ardexa cloud. It can be used to discover data, and also log data to the local device, and send it to the Ardexa cloud. This plugin works by using configuration files to control the names of variables, tables and sources.

## Data Discovery

The plugin can be used to discover data, in particular:

1. All the tables in the GE database
2. All the fields within those tables, and/or
3. A sample of the data within those fields

The following sections detail how to undertake data discovery.

## Discovering Tables

In order to list all the table names, the following command can be executed either on the REMOTE SHELL, or the via the plugin RUN DISCOVERY function. To run a discovery via the plugin's RUN DISCOVERY function, do the following:

1. Ensure the `Database config YAML` has the correct credentials to connect to the database.
2. Ensure the `Output config file` is set to `show_tables`.
3. Select `RUN DISCOVERY`.

This will then show a list of tables, which should look like this:

```
CV_English_ChannelData
CV_English_ErrorData
OFW_English_SendCommandData
Systems
VersionInfo
... and other tables
```

The equivalent REMOTE SHELL command to view all the database tables is: `ge_wind_odbc_ardexa discover data {CONFIG_FILE} {SHOW_TABLES_FILE}`. The CONFIG\_FILE is the location of the file containing the credentials. The SHOW\_TABLES\_FILE file is an empty file that ends with `show_tables` (with this exact name - path does not matter)

## Discovering Fields

In order to list all the field names within a table, the following command can be executed either on the REMOTE SHELL, or the via the plugin RUN DISCOVERY function. To run a discovery via the plugin's RUN DISCOVERY function, do the following:

1. Ensure the `Database config YAML` has the correct credentials to connect to the database.
2. Ensure the `Output config file` is set to `show_fields`.
3. Include the table, for which the fields must be known. For example; using the above tables output, this could `CV_English_ChannelData`. This should only be a single table entry.
4. Select `RUN DISCOVERY`

The output of this command will show all the fields in the specified database table, along with a sample of the rows in the table. For some of the tables, the fields will also details the name and units of the fields. This will be something like:

```
Fields of CV_English_ErrorData Table...
Table                 Name                  Type        Start
--------------------  --------------------  --------  -------
CV_English_ErrorData  Systemnumber          int             1
CV_English_ErrorData  TimeStampLocalSystem  datetime        2
CV_English_ErrorData  TimeStampUTCSystem    datetime        3
CV_English_ErrorData  DataPointID           int             4
CV_English_ErrorData  Status                nvarchar        5
CV_English_ErrorData  OriginalMainError     int             6
CV_English_ErrorData  ShortName             nvarchar        7
CV_English_ErrorData  Name                  nvarchar        8
CV_English_ErrorData  Power                 real            9
CV_English_ErrorData  WindSpeed             real           10
CV_English_ErrorData  GenSpeed              real           11
CV_English_ErrorData  MainError             int            12

Sample of 30 rows from the CV_English_ErrorData Table...
  Systemnumber  TimeStampUTCSystem            DataPointID  Status           ShortName    Name               Power       WindSpeed   GenSpeed    MainError
  ------------  --------------------------  -------------  ---------------  -----------  -----------------  ----------- ----------  ---------   ---------
      12345678  2023-04-13 03:34:29.183000          17530  Error is going   EVENT_182    Start-up           -1.75562    3.557       1125.58             0
      12345678  2023-04-13 03:34:29.183000          17531  Error is coming  EVENT_183    Load operation     -1.75562    3.557       1125.58             0
      12345678  2023-04-13 03:31:56.900000          17530  Error is coming  EVENT_182    Start-up           -24.8265    2.06729     971.213             0
      12345678  2023-04-13 03:31:56.900000          17531  Error is going   EVENT_183    Load operation     -24.8265    2.06729     971.213             0
      12345678  2023-04-13 00:45:09.587000          17530  Error is going   EVENT_182    Start-up           -26.74      3.74024     1131.53             0
      12345678  2023-04-13 00:45:09.587000          17531  Error is coming  EVENT_183    Load operation     -26.74      3.74024     1131.53             0
      12345678  2023-04-13 00:43:23.903000          17530  Error is coming  EVENT_182    Start-up           -14.7356    3.64841     971.291             0
      12345678  2023-04-13 00:43:23.903000          17531  Error is going   EVENT_183    Load operation     -14.7356    3.64841     971.291             0
      12345678  2023-04-13 00:41:33.140000          17530  Error is going   EVENT_182    Start-up           -23.944     5.36561     1124.18             0
      12345678  2023-04-13 00:41:33.140000          17531  Error is coming  EVENT_183    Load operation     -23.944     5.36561     1124.18             0
      12345678  2023-04-13 00:40:09.020000          17530  Error is coming  EVENT_182    Start-up           -26.964     2.57887     969.3               0
      12345678  2023-04-13 00:40:09.020000          17531  Error is going   EVENT_183    Load operation     -26.964     2.57887     969.3               0
```

The equivalent REMOTE SHELL command to view all the database tables is: `ge_wind_odbc_ardexa discover fields {CONFIG_FILE} {TABLE_NAME}`. The CONFIG\_FILE is the location of the file containing the credentials. The TABLE\_NAME is an entry from the output of `Discovering Tables` discussed above. For example: `ge_wind_odbc_ardexa discover fields /opt/ardexa/config/ge-wind-odbc-ardexa/task/testonly.config_file CV_English_ChannelData`

## Discovering Data Using Custom Mapping File

The primary purpose of a configuration file driven plugin is to provide as much flexibility as possible when defining what to capture and what to call each field. This plugin is able to read a mapping file to undertaken one or more of the following tasks:

1. Only collecting required variables and ignoring anything else.
2. Renaming all or selected variables to something else.
3. Scaling variables.
4. Changing units.
5. Sending data to customised tables and sources in the Ardexa cloud.

The mapping file is a comma separated file containing 11 items as follows:

1. Ardexa Table. This is the name of the Ardexa table, where the data will be copied.
2. Ardexa Source. This is the Ardexa source name given to the data.
3. Ardexa Variable Name. This is the name given to the variable as it will appear in the Ardexa cloud.
4. GE Table. This is the GE table that needs to be queried to get the variable. This name **IS** case sensitive.
5. GE Source Name. This is the source name (usually the "turbine name") to get the data. This name is not case sensitive.
6. GE Source Column Name. GE uses a "tall and skinny" database structure. This item tells the plugin in which column name it can find the "source name". Use the `discover fields` item discussed previously to find the name. This name is not case sensitive.
7. GE Variable Column Name. The name of the column to find the variable. Use the `discover fields` item discussed previously to find the name. This name is not case sensitive.
8. GE Field Name. The name of the required variable in the GE table. Use the `discover fields` item discussed previously to find the name. This name is not case sensitive.
9. Type. The Ardexa variable type. Can be one of `decimal`, `int`, `keyword` or `date`
10. Units. The units if the variable. May be empty.
11. Scale. The scale of the variable. May be empty

Here is an example of a mapping file:

```
# Ardexa Table[0],  Ardexa Source [1],  Ardexa Variable Name [2],   GE Table [3],               GE Source Name [4], GE Source Column Name [5], GE Variable Column Name [6], GE Field Name [7],      Type [8],   Units [9], Scale [10]
ge_test_table,      turbine1,           UTC TimeStamp,              CV_English_ChannelData,     12345678,           Systemnumber,               TimeStampUTCSystem,         TimeStampUTCSystem,     date,       ,          
ge_test_table,      turbine1,           UTC TimeStamp,              CV_English_ChannelData,     12345678,           Systemnumber,               timestamplocalsystem,       timestamplocalsystem,   keyword,    ,          
ge_test_table,      turbine1,           WROT1_PtAngValBl1_avg,      CV_English_ChannelData,     12345678,           Systemnumber,               Minute10Average,            Blade 1 set value,      dec,      °,          
ge_test_table,      turbine1,           WROT1_PtAngValBl2_avg,      CV_English_ChannelData,     12345678,           Systemnumber,               Minute10Average,            Blade 2 set value,      dec,      °,          
ge_test_table,      turbine1,           WROT1_PtAngValBl3_avg,      CV_English_ChannelData,     12345678,           Systemnumber,               Minute10Average,            Blade 3 set value,      dec,      °,          
ge_test_table,      turbine1,           WNAC1_WdSpd1_avg,           CV_English_ChannelData,     12345678,           Systemnumber,               Minute10Average,            Wind speed,             dec,      m/s,        
ge_test_table,      turbine1,           WCNV1_GriPhV1_avg,          CV_English_ChannelData,     12345678,           Systemnumber,               Minute10Average,            Voltage A-N,            dec,      V,
ge_test_table,      turbine1,           WCNV1_GriPhV2_avg,          CV_English_ChannelData,     12345678,           Systemnumber,               Minute10Average,            Voltage B-N,            dec,      V,
ge_test_table,      turbine1,           WCNV1_GriPhV3_avg,          CV_English_ChannelData,     12345678,           Systemnumber,               Minute10Average,            Voltage C-N,            dec,      V,
ge_test_table,      turbine1,           WCNV1_GriPF_avg,            CV_English_ChannelData,     12345678,           Systemnumber,               Minute10Average,            Power factor,           dec,      ,
ge_test_table,      turbine1,           WGEN1_W_avg,                CV_English_ChannelData,     12345678,           Systemnumber,               Minute10Average,            Power,                  dec,      W,          1000
```

Note:

1. Anything starting with `#` is a comment and can be ignored.
2. If timestamp need to be included, then use either `TimeStampUTCSystem` or `TimeStampLocalSystem`. See the first 1 lines above as an example.

The above mapping file will produce the following output:

```
Table                             ge_test_table       
Source                            turbine1            
UTC TimeStamp(date)               2024-08-19T03:20:00Z
UTC TimeStamp(keyword)            2024-08-19T04:20:00 
WROT1_PtAngValBl1_avg(decimal:°)  2.17141             
WROT1_PtAngValBl2_avg(decimal:°)  2.21502             
WROT1_PtAngValBl3_avg(decimal:°)  2.31764             
WNAC1_WdSpd1_avg(decimal:m/s)     9.08464             
WCNV1_GriPhV1_avg(decimal:V)      398.861             
WCNV1_GriPhV2_avg(decimal:V)      401.837             
WCNV1_GriPhV3_avg(decimal:V)      400.355             
WCNV1_GriPF_avg(decimal)          1                   
WGEN1_W_avg(decimal:W)            1.008e+06 
```

The equivalent REMOTE SHELL command to view all the database tables is: `ge_wind_odbc_ardexa discover data {CONFIG_FILE} {MAPPING_FILE}`. The CONFIG\_FILE is the location of the file containing the credentials. The MAPPING\_FILE is the mapping file name and must exist For example: `ge_wind_odbc_ardexa discover data /opt/ardexa/config/ge-wind-odbc-ardexa/task/testonly.config_file /opt/ardexa/config/ge-wind-odbc-ardexa/full_mapping.csv`

## Discovering Data Using Custom "Per Source" Files

Instead of using a single mapping file (as discussed above), a separate file for each "source" can be used in conjunction with the file system to produce the desired table/source names. If the "mapping file" argument ends with the (exact) name `per_source` (an empty file with this exact name, path does not matter), then the plugin will recursively look for files in the directory `/opt/ardexa/config/ge-wind-odbc-ardexa/per-source`. The file structure is very similar to that used in `/opt/ardexa/logs`, where the first directory is the `Ardexa Table`, and any subsequent directories, plus the file name (minus the extension) will form the `Ardexa Source Name`. For example, take the following file structure:

```
/opt/ardexa/config/ge-wind-odbc-ardexa/per-source
                                        └── wind_iec_600s
                                            ├── WTG01
                                            │   └── turbine1.csv
                                            └── turbine2.csv
```

This file structure will produce two sources (`WTG01`, `WTG01/Data2`) that will both log data to the Ardexa table called `wind_iec_600s`. The contents of the file is the same as the single mapping file, minus the "Table" and "Source Name" fields.

1. Ardexa Table. This is the name of the Ardexa table, where the data will be copied.
2. Ardexa Source. This is the Ardexa source name given to the data.
3. Ardexa Variable Name. This is the name given to the variable as it will appear in the Ardexa cloud.
4. GE Table. This is the GE table that needs to be queried to get the variable. This name **IS** case sensitive.
5. GE Source Name. This is the source name (usually the "turbine name") to get the data. This name is not case sensitive.
6. GE Source Column Name. GE uses a "tall and skinny" database structure. This item tells the plugin in which column name it can find the "source name". Use the `discover fields` item discussed previously to find the name. This name is not case sensitive.
7. GE Variable Column Name. The name of the column to find the variable. Use the `discover fields` item discussed previously to find the name. This name is not case sensitive.
8. GE Field Name. The name of the required variable in the GE table. Use the `discover fields` item discussed previously to find the name. This name is not case sensitive.
9. Type. The Ardexa variable type. Can be one of `decimal`, `int`, `keyword` or `date`
10. Units. The units if the variable. May be empty.
11. Scale. The scale of the variable. May be empty

Here is an example of two mapping files:

* `turbine1.csv`

```
# Ardexa Table[0],  Ardexa Source [1],  Ardexa Variable Name [2],   GE Table [3],               GE Source Name [4], GE Source Column Name [5], GE Variable Column Name [6], GE Field Name [7],      Type [8],   Units [9], Scale [10]
ge_test_table,      turbine1,           UTC TimeStamp,              CV_English_ChannelData,     11111111,           Systemnumber,               TimeStampUTCSystem,         TimeStampUTCSystem,     date,       ,          
ge_test_table,      turbine1,           UTC TimeStamp,              CV_English_ChannelData,     11111111,           Systemnumber,               timestamplocalsystem,       timestamplocalsystem,   keyword,    ,          
ge_test_table,      turbine1,           WROT1_PtAngValBl1_avg,      CV_English_ChannelData,     11111111,           Systemnumber,               Minute10Average,            Blade 1 set value,      dec,      °,          
ge_test_table,      turbine1,           WROT1_PtAngValBl2_avg,      CV_English_ChannelData,     11111111,           Systemnumber,               Minute10Average,            Blade 2 set value,      dec,      °,          
ge_test_table,      turbine1,           WROT1_PtAngValBl3_avg,      CV_English_ChannelData,     11111111,           Systemnumber,               Minute10Average,            Blade 3 set value,      dec,      °,          
ge_test_table,      turbine1,           WNAC1_WdSpd1_avg,           CV_English_ChannelData,     11111111,           Systemnumber,               Minute10Average,            Wind speed,             dec,      m/s,        
ge_test_table,      turbine1,           WCNV1_GriPhV1_avg,          CV_English_ChannelData,     11111111,           Systemnumber,               Minute10Average,            Voltage A-N,            dec,      V,
ge_test_table,      turbine1,           WCNV1_GriPhV2_avg,          CV_English_ChannelData,     11111111,           Systemnumber,               Minute10Average,            Voltage B-N,            dec,      V,
ge_test_table,      turbine1,           WCNV1_GriPhV3_avg,          CV_English_ChannelData,     11111111,           Systemnumber,               Minute10Average,            Voltage C-N,            dec,      V,
ge_test_table,      turbine1,           WCNV1_GriPF_avg,            CV_English_ChannelData,     11111111,           Systemnumber,               Minute10Average,            Power factor,           dec,      ,
ge_test_table,      turbine1,           WGEN1_W_avg,                CV_English_ChannelData,     11111111,           Systemnumber,               Minute10Average,            Power,                  dec,      W,          1000
```

* `turbine2.csv`

```
# Ardexa Table[0],  Ardexa Source [1],  Ardexa Variable Name [2],   GE Table [3],               GE Source Name [4], GE Source Column Name [5], GE Variable Column Name [6], GE Field Name [7],      Type [8],   Units [9], Scale [10]
ge_test_table,      turbine1,           UTC TimeStamp,              CV_English_ChannelData,     22222222,           Systemnumber,               TimeStampUTCSystem,         TimeStampUTCSystem,     date,       ,          
ge_test_table,      turbine1,           UTC TimeStamp,              CV_English_ChannelData,     22222222,           Systemnumber,               timestamplocalsystem,       timestamplocalsystem,   keyword,    ,          
ge_test_table,      turbine1,           WROT1_PtAngValBl1_avg,      CV_English_ChannelData,     22222222,           Systemnumber,               Minute10Average,            Blade 1 set value,      dec,      °,          
ge_test_table,      turbine1,           WROT1_PtAngValBl2_avg,      CV_English_ChannelData,     22222222,           Systemnumber,               Minute10Average,            Blade 2 set value,      dec,      °,          
ge_test_table,      turbine1,           WROT1_PtAngValBl3_avg,      CV_English_ChannelData,     22222222,           Systemnumber,               Minute10Average,            Blade 3 set value,      dec,      °,          
ge_test_table,      turbine1,           WNAC1_WdSpd1_avg,           CV_English_ChannelData,     22222222,           Systemnumber,               Minute10Average,            Wind speed,             dec,      m/s,        
ge_test_table,      turbine1,           WCNV1_GriPhV1_avg,          CV_English_ChannelData,     22222222,           Systemnumber,               Minute10Average,            Voltage A-N,            dec,      V,
ge_test_table,      turbine1,           WCNV1_GriPhV2_avg,          CV_English_ChannelData,     22222222,           Systemnumber,               Minute10Average,            Voltage B-N,            dec,      V,
ge_test_table,      turbine1,           WCNV1_GriPhV3_avg,          CV_English_ChannelData,     22222222,           Systemnumber,               Minute10Average,            Voltage C-N,            dec,      V,
ge_test_table,      turbine1,           WCNV1_GriPF_avg,            CV_English_ChannelData,     22222222,           Systemnumber,               Minute10Average,            Power factor,           dec,      ,
ge_test_table,      turbine1,           WGEN1_W_avg,                CV_English_ChannelData,     22222222,           Systemnumber,               Minute10Average,            Power,                  dec,      W,          1000
```

Note:

1. Anything starting with `#` is a comment and can be ignored.
2. If timestamp need to be included, then use either `TimeStampUTCSystem` or `TimeStampLocalSystem`. See the first 1 lines above as an example.

The above "per source" mapping files will produce the following output. Notice the table and source names.

```
  Table                             wind_iec_600s       
  Source                            turbine2            
  UTC TimeStamp(date)               2025-02-19T04:20:00Z
  UTC TimeStamp(keyword)            2025-02-19T05:20:00 
  WROT1_PtAngValBl1_avg(decimal:°)  2.60894             
  WROT1_PtAngValBl2_avg(decimal:°)  2.65578             
  WROT1_PtAngValBl3_avg(decimal:°)  2.76517             
  WNAC1_WdSpd1_avg(decimal:m/s)     9.40772             
  WCNV1_GriPhV1_avg(decimal:V)      402.115             
  WCNV1_GriPhV2_avg(decimal:V)      405.238             
  WCNV1_GriPhV3_avg(decimal:V)      403.377             
  WCNV1_GriPF_avg(decimal)          1                   
  WGEN1_W_avg(decimal:W)            4.21223e+06         


  Table                             wind_iec_600s       
  Source                            WTG01/turbine1      
  UTC TimeStamp(date)               2025-02-19T04:20:00Z
  UTC TimeStamp(keyword)            2025-02-19T05:20:00 
  WROT1_PtAngValBl1_avg(decimal:°)  3.21962             
  WROT1_PtAngValBl2_avg(decimal:°)  2.79063             
  WROT1_PtAngValBl3_avg(decimal:°)  2.47563             
  WNAC1_WdSpd1_avg(decimal:m/s)     8.48745             
  WCNV1_GriPhV1_avg(decimal:V)      402.649             
  WCNV1_GriPhV2_avg(decimal:V)      405.388             
  WCNV1_GriPhV3_avg(decimal:V)      403.295             
  WCNV1_GriPF_avg(decimal)          1                   
  WGEN1_W_avg(decimal:W)            4.29313e+06  
```

The equivalent REMOTE SHELL command to view all the database tables is: `ge_wind_odbc_ardexa discover data {CONFIG_FILE} {PER_SOURCE_FILE}`. The CONFIG\_FILE is the location of the file containing the credentials. The MAPPING\_FILE argument must ends with the name `per_source` (an empty file with this exact name, path does not matter), then the plugin will recursively look for files in the directory `/opt/ardexa/config/ge-wind-odbc-ardexa/per-source` For example: `ge_wind_odbc_ardexa discover data /opt/ardexa/config/ge-wind-odbc-ardexa/task/testonly.config_file /opt/ardexa/config/ge-wind-odbc-ardexa/per_source`

Remember that the per-source directory must be created, as follows: `mkdir -p /opt/ardexa/config/ge-wind-odbc-ardexa/per-source`. Then create directories and files as required, and in accordance with the discussion above.

## "Per Source" Mapping Generator

As discussed above, mapping files allow as much flexibility as possible. There is a very high probability that most of the time, the same OEM input field will need to be to mapped to the same output field name (such as an IEC name), for all the turbines at a site. To assist with this repetitive process, the plugin includes a `generate` sub-command that will accept a `template` CSV file, which describes the general mapping and applies it to any turbines that can be automatically discovered. The template file for a "per source" generate command will look a "mapping" file discussed above, except that the first 2 columns (table and source) have been removed. This is an example "per source" mapping file:

```
# PlaceHolder [0],  Ardexa Variable Name [1],   GE Table [2],               GE Source Column Name [5],  GE Variable Column Name [6],    GE Field Name [7],          Type [8],   Units [9],  Scale [10]
${turbine},         WTEST_Power,                CV_English_ChannelData,     Systemnumber,               Minute10Average,                Power,                      float,      W,          1
${turbine},         WTEST_PF,                   CV_English_ChannelData,     Systemnumber,               Minute10Average,                Power factor,               float,      ,           1
${turbine},         WTEST_VoltAN,               CV_English_ChannelData,     Systemnumber,               Minute10Average,                Voltage A-N,                float,      V,          1
${turbine},         WTEST_VoltBN,               CV_English_ChannelData,     Systemnumber,               Minute10Average,                Voltage B-N,                float,      V,          1
${turbine},         WTEST_VoltCN,               CV_English_ChannelData,     Systemnumber,               Minute10Average,                Voltage C-N,                float,      V,          1
${turbine},         WTEST_GenSpd,               CV_English_ChannelData,     Systemnumber,               Minute10Average,                Generator speed (CCU),      float,      rpm,        1
${turbine},         WTEST_RotSpd,               CV_English_ChannelData,     Systemnumber,               Minute10Average,                Rotor speed (PLC),          float,      rpm,        1
${turbine},         WTEST_Blade1,               CV_English_ChannelData,     Systemnumber,               Minute10Average,                Blade 1 actual value,       float,      °,          1
${turbine},         WTEST_WindSped,             CV_English_ChannelData,     Systemnumber,               Minute10Average,                Wind speed,                 float,      m/s,        1
${turbine},         WTEST_NacPos,               CV_English_ChannelData,     Systemnumber,               Minute10Average,                Nacelle position,           float,      °,          1
```

The equivalent REMOTE SHELL command to generate the "per source" mappings files is: `ge_wind_odbc_ardexa generate {CONFIG_FILE} {PER_SOURCE MAPPING FILE} {TABLE NAME}`. The CONFIG\_FILE is the location of the file containing the credentials. The MAPPING\_FILE is the per source mapping file. The "TABLE NAME" is the name of the Ardexa table, where the events will be sent. For example: `ge_wind_odbc_ardexa generate /opt/ardexa/config/ge-wind-odbc-ardexa/task/testonly.config_file /opt/ardexa/config/ge-wind-odbc-ardexa/oem-to-iec-test_v1.csv wind_iec_600s` Running this command will create the following directories and files.

```
/opt/ardexa/config/ge-wind-odbc-ardexa/per-source
                                        └── wind_iec_600s
                                            ├── turbine1.csv
                                            └── turbine2.csv
```

The data can then be discovered using the section titled `Discovering Data Using Custom "Per Source" Files` above, to display the data

## Log

Logging data to the cloud can be enacted via the Ardexa Front End. The equivalent REMOTE SHELL command is: `ge_wind_odbc_ardexa log {CONFIG_FILE} {PER_SOURCE_FILE or MAPPING FILE}`. The CONFIG\_FILE is the location of the file containing the credentials. The MAPPING\_FILE argument must ends with the name `per_source` (an empty file with this exact name, path does not matter), then the plugin will recursively look for files in the directory `/opt/ardexa/config/ge-wind-odbc-ardexa/per-source`. Otherwise, if a MAPPING FILE is used, it will use that log data For example: \`ge\_wind\_odbc\_ardexa log /opt/ardexa/config/ge-wind-odbc-ardexa/task/testonly.config\_file

```
ge_wind_odbc_ardexa log /opt/ardexa/config/ge-wind-odbc-ardexa/task/testonly.config_file /opt/ardexa/config/ge-wind-odbc-ardexa/per_source
```
