Skip to content

ODB: programming

This page covers accessing the ODB from code.

For the concepts and day-to-day use, see ODB: overview.

For full descriptions of the programming interfaces (e.g. all the optional parameters that aren't mentioned here), see the API reference for C++ (db_xxx), C++ (midas::odb), C++ (MVOdb), Python (midas.client) and JavaScript (midas.js).

Overview of APIs

C++

There are three separate ways to access the ODB via C++. You are free to choose whichever one suits your preferred programming style.

C++ API midas::odb MVOdb Low level
Style Modern "magic" map Object-oriented C-like
Example o["Run number"] = 1234; o->WI("Run number", 1234); db_set_value(hDB, 0, path, &value, sizeof(value), 1, TID_INT32);
#include odbxx.h mvodb/mvodb.h midas.h
Supported data types All Missing uint8_t, int8_t, int16_t, int64_t All
Supports std::string ✔ ✔ ✔
Supports std::vector ✔ ✔
Supports std::array ✔
Supports hotlinks ✔ ✔
Same syntax works for offline analysis ✔ ✔

The low-level API (and some of the examples on this page) use three types that you'll see a lot:

  • HNDLE - an integer handle that refers to something inside midas. ODB handles come in two flavours:
    • hDB - the handle of the ODB itself. Almost every low-level db_xxx() function takes it as the first argument. Get it after connecting to the experiment with cm_get_experiment_database(&hDB, nullptr).
    • hKey - the handle of a single ODB entry (directory, value or array). Find it from a path with db_find_key(hDB, 0, "/Some/path", &hKey). Many functions also take an hKey as the "root" for a relative path; pass 0 to mean the top of the ODB.
  • KEY - a struct describing an ODB entry, filled in by db_get_key(hDB, hKey, &key). Useful fields include type (TID_xxx), num_values, name, item_size and last_written.

Which of these you need depends on the C++ API:

C++ API Needs hDB Needs HNDLE/KEY
midas::odb
MVOdb Only once, to call MakeMidasOdb(hDB)
Low level ✔ ✔
HNDLE hDB;
cm_get_experiment_database(&hDB, nullptr);

MVOdb functions

MVOdb uses explicit function for different midas data types. The full list of functions is:

Data type Midas type Read value Read array Read element Write value Write array Write element
bool TID_BOOL RB RBA RBAI WB WBA WBAI
int TID_INT RI RIA RIAI WI WIA WIAI
double TID_DOUBLE RD RDA RDAI WD WDA WDAI
float TID_FLOAT RF RFA RFAI WF WFA WFAI
std::string TID_STRING RS RSA RSAI WS WSA WSAI
uint16_t TID_UINT16 RU16 RU16A RU16AI WU16 WU16A WU16AI
uint32_t TID_UINT32 RU32 RU32A RU32AI WU32 WU32A WU32AI
uint64_t TID_UINT64 RU64 RU64A RU64AI WU64 WU64A WU64AI

Python

Python ODB access is via the odb_xxx() functions in midas.client.MidasClient. It supports all the features of the C++ APIs, and allows you to use either normal python types or numpy arrays.

Web

Custom webpages allow you to either display/edit ODB values using HTML elements, or query/edit the ODB using javascript.

64-bit integers in JavaScript

JavaScript numbers can't hold every 64-bit integer: integers above 2^53 − 1 are rounded. So midas sends INT64 and UINT64 ODB values to JavaScript as strings. Convert them with BigInt() rather than Number(). See 64-bit integers in JavaScript for how to write them back, with an example that reads a value, adds one to it and writes it back.

Full example programs

The examples below connect to a midas experiment, read an ODB value, then set an ODB value. For brevity, they don't do any error-checking.

The expected output of these programs is:

The experiment is currently stopped
The new value of /example/eg_float is 5.670000

#include "midas.h"
#include "odbxx.h"

int main() {
  INT status = cm_connect_experiment("", "", "ctest", nullptr);

  // Read a value from the ODB
  midas::odb runinfo("/Runinfo");
  int state = runinfo["State"];

  if (state == STATE_RUNNING) {
    printf("The experiment is currently running\n");
  } else if (state == STATE_PAUSED) {
    printf("The experiment is currently paused\n");
  } else if (state == STATE_STOPPED) {
    printf("The experiment is currently stopped\n");
  } else {
    printf("The experiment is in an unexpected run state\n");
  }

  // Create a directory in the ODB (keys that already exist are left alone)
  midas::odb example = {{"an_int", 1}, {"eg_float", 4.56f}};
  example.connect("/example");

  // Update a single value in the ODB
  example["eg_float"] = 5.67f;

  // Read the value back
  float readback = example["eg_float"];
  printf("The new value of /example/eg_float is %f\n", readback);

  // Delete the temporary directory we created
  midas::odb::delete_key("/example");

  cm_disconnect_experiment();
}
#include "midas.h"
#include "mvodb.h"

int main() {
  INT status = cm_connect_experiment("", "", "ctest", nullptr);

  HNDLE hDB;
  cm_get_experiment_database(&hDB, nullptr);
  MVOdb* odb = MakeMidasOdb(hDB);

  // Read a value from the ODB
  MVOdb* runinfo = odb->Chdir("Runinfo");
  int state = 0;
  runinfo->RI("State", &state);

  if (state == STATE_RUNNING) {
    printf("The experiment is currently running\n");
  } else if (state == STATE_PAUSED) {
    printf("The experiment is currently paused\n");
  } else if (state == STATE_STOPPED) {
    printf("The experiment is currently stopped\n");
  } else {
    printf("The experiment is in an unexpected run state\n");
  }

  // Create a directory in the ODB (keys that already exist are left alone)
  MVOdb* example = odb->Chdir("example", true);
  int an_int = 1;
  float eg_float = 4.56f;
  example->RI("an_int", &an_int, true);
  example->RF("eg_float", &eg_float, true);

  // Update a single value in the ODB
  example->WF("eg_float", 5.67f);

  // Read the value back
  float readback = 0;
  example->RF("eg_float", &readback);
  printf("The new value of /example/eg_float is %f\n", readback);

  // Delete the temporary directory we created
  odb->Delete("example");

  delete example;
  delete runinfo;
  delete odb;
  cm_disconnect_experiment();
}
#include "midas.h"

int main() {
  INT status = cm_connect_experiment("", "", "ctest", nullptr);

  HNDLE hDB;
  cm_get_experiment_database(&hDB, nullptr);

  // Read a value from the ODB
  INT state = 0;
  INT size = sizeof(state);
  db_get_value(hDB, 0, "/Runinfo/State", &state, &size, TID_INT32, FALSE);

  if (state == STATE_RUNNING) {
    printf("The experiment is currently running\n");
  } else if (state == STATE_PAUSED) {
    printf("The experiment is currently paused\n");
  } else if (state == STATE_STOPPED) {
    printf("The experiment is currently stopped\n");
  } else {
    printf("The experiment is in an unexpected run state\n");
  }

  // Create a directory in the ODB (keys that already exist are left alone).
  // With create = TRUE, db_get_value() creates missing keys using the value we pass in.
  INT an_int = 1;
  size = sizeof(an_int);
  db_get_value(hDB, 0, "/example/an_int", &an_int, &size, TID_INT32, TRUE);

  float eg_float = 4.56f;
  size = sizeof(eg_float);
  db_get_value(hDB, 0, "/example/eg_float", &eg_float, &size, TID_FLOAT, TRUE);

  // Update a single value in the ODB
  eg_float = 5.67f;
  db_set_value(hDB, 0, "/example/eg_float", &eg_float, sizeof(eg_float), 1, TID_FLOAT);

  // Read the value back
  float readback = 0;
  size = sizeof(readback);
  db_get_value(hDB, 0, "/example/eg_float", &readback, &size, TID_FLOAT, FALSE);
  printf("The new value of /example/eg_float is %f\n", readback);

  // Delete the temporary directory we created
  db_delete(hDB, 0, "/example");

  cm_disconnect_experiment();
}
import midas.client

if __name__ == "__main__":
    # The "with" statement makes sure we disconnect cleanly from midas
    with midas.client.MidasClient("pytest") as client:

        # Read a value from the ODB. The return value is a normal python
        # type (an int in this case, but could also be a float, string, bool,
        # list or dict).
        state = client.odb_get("/Runinfo/State")

        if state == midas.STATE_RUNNING:
            print("The experiment is currently running")
        elif state == midas.STATE_PAUSED:
            print("The experiment is currently paused")
        elif state == midas.STATE_STOPPED:
            print("The experiment is currently stopped")
        else:
            print("The experiment is in an unexpected run state")


        # Update or create a directory in the ODB by passing a dict to `odb_set`
        client.odb_set("/example", {"an_int": 1, "eg_float": 4.56})

        # Update a single value in the ODB
        client.odb_set("/example/eg_float", 5.67)

        # Read the value back
        readback = client.odb_get("/example/eg_float")

        print(f"The new value of /example/eg_float is {readback}")

        # Delete the temporary directory we created
        client.odb_delete("/example")

This example uses built-in HTML magic (no custom Javascript needed).

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <link rel="stylesheet" href="midas.css">
  <script src="controls.js"></script>
  <script src="midas.js"></script>
  <script src="mhttpd.js"></script>
  <title>My page</title>
</head>

<body class="mcss" onload="mhttpd_init('My page');">

<!-- header and side navigation will be filled in mhttpd_init -->
<div id="mheader"></div>
<div id="msidenav"></div>

<div id="mmain">
  <!-- 
    HTML elements let you read/write from the ODB, but not create new entries etc.
    To do that, you need to use the Javascript libraries.
  -->
  The experiment is currently in state: <span class="modbvalue" data-odb-path="/Runinfo/State"></span><br>
  <span class="modbvalue" data-odb-path="/example/eg_float" data-odb-editable="1"></span>
</div>

This example uses HTML and Javascript.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <link rel="stylesheet" href="midas.css">
  <script src="controls.js"></script>
  <script src="midas.js"></script>
  <script src="mhttpd.js"></script>
  <script type="text/javascript">
    function create_and_set() {
        let dir = {"path": "/example", "type": TID_KEY};
        let elem = {"path": "/example/eg_float", "type": TID_FLOAT};

        mjsonrpc_db_create([dir, elem]).then(function(rpc) {
            mjsonrpc_db_set_value("/example/eg_float", 5.67);
        });
    }

    function read_state() {
        mjsonrpc_db_get_value("/Runinfo/State").then(function(rpc) {
            let state = "UNKNOWN";

            if (rpc.result.data[0] == STATE_STOPPED) {
                state = "stopped";
            } else if (rpc.result.data[0] == STATE_PAUSED) {
                state = "paused";
            } else if (rpc.result.data[0] == STATE_RUNNING) {
                state = "running";
            }

            document.getElementById("state").innerText = state;
        });
    }

    function delete_dir() {
        mjsonrpc_db_delete(["/example"]);
    }

  </script>
  <title>My page</title>
</head>

<body class="mcss" onload="mhttpd_init('My page'); create_and_set(); read_state();">

<!-- header and side navigation will be filled in mhttpd_init -->
<div id="mheader"></div>
<div id="msidenav"></div>

<div id="mmain">
  The experiment is currently <span id="state"></span>.<br>
  The new value of /example/eg_float is <span class="modbvalue" data-odb-path="/example/eg_float"></span><br>
  <button class="mbutton" onclick="delete_dir()">Click to delete the temporary directory</button>
</div>
</body>
</html>
# Read from the ODB
state=`odbedit -q -c 'ls -v "/Runinfo/State"'`
if [ $state -eq 3 ]; then echo "The experiment is currently running"; fi
if [ $state -eq 2 ]; then echo "The experiment is currently paused"; fi
if [ $state -eq 1 ]; then echo "The experiment is currently stopped"; fi

# Create a directory in the ODB
odbedit -q -c 'mkdir "/example"'

# Create a key in the new directory
odbedit -q -d "/example" -c 'create float "eg_float"'

# Set the value of the new key
odbedit -q -c 'set "/example/eg_float" 5.67'

# Read the value back
readback=`odbedit -q -c 'ls -v "/example/eg_float"'`
echo "The new value of /example/eg_float is $readback"

# Delete the temporary directory we created
odbedit -q -c 'del "/example"'

Error-checking

Errors are reported by throwing exceptions derived from midas::odb_error.

try {
  midas::odb o("/Runinfo");
  int value = o["Run number"];
} catch (midas::odb_error& e) {
  printf("ODB error: %s\n", e.what());
}

Pass an MVOdbError to any function to find out whether it failed.

MVOdbError err;
MVOdb* runinfo = odb->Chdir("Runinfo");
int value = 0;
runinfo->RI("Run number", &value, false, &err);

if (err.fError) {
  printf("ODB error at %s: %s\n", err.fPath.c_str(), err.fErrorString.c_str());
}

Functions return a status code, which is DB_SUCCESS (or SUCCESS) on success.

INT status = db_get_value(hDB, 0, "/Runinfo/Run number", &value, &size, TID_INT32, 0);

if (status != DB_SUCCESS) {
  cm_msg(MERROR, __FUNCTION__, "Failed to read run number, status %d", status);
}

Errors from the midas C library raise a midas.MidasError. Missing ODB keys raise a KeyError.

try:
    value = client.odb_get("/Runinfo/Run number")
except KeyError:
    print("Key doesn't exist")
except midas.MidasError as e:
    print(f"ODB error {e.code}: {e.message}")

Each path has its own status code in rpc.result.status (1 = success). Communication errors reject the promise.

mjsonrpc_db_get_value("/Runinfo/Run number").then(function(rpc) {
  if (rpc.result.status[0] != 1) {
    alert("ODB error, status " + rpc.result.status[0]);
  }
}).catch(function(error) {
  mjsonrpc_error_alert(error);
});

Reading/writing values

Reading a value

midas::odb o("/Runinfo");
int value = o["Run number"];
MVOdb* odb = MakeMidasOdb(hDB);
MVOdb* runinfo = odb->Chdir("Runinfo");
int value = 0;
runinfo->RI("Run number", &value);
int32_t value = 0;
INT size = sizeof(value);
INT status = db_get_value(hDB, 0, "/Runinfo/Run number", &value, &size, TID_INT32, 0);
value = client.odb_get("/Runinfo/Run number")
<div class="modbvalue" data-odb-path="/Runinfo/Run number"></div>
<!-- (1)! -->
  1. You can also use modbselect for select dropdowns, modbcheckbox for checkboxes, and more...
mjsonrpc_db_get_value("/Runinfo/Run number").then(function(rpc) {
  if (rpc.result.status[0] == 1) {
    let value = rpc.result.data[0];
  }
}).catch(function(error) {
  mjsonrpc_error_alert(error);
});
odbedit -c 'ls "/Runinfo/Run number"'

Reading a string

midas::odb o("/Experiment");
std::string value = o["Name"];
MVOdb* odb = MakeMidasOdb(hDB);
MVOdb* experiment = odb->Chdir("Experiment");
std::string value;
experiment->RS("Name", &value);
std::string value;
INT status = db_get_value_string(hDB, 0, "/Experiment/Name", 0, &value);
value = client.odb_get("/Experiment/Name")
mjsonrpc_db_get_value("/Experiment/Name").then(function(rpc) {
  let value = rpc.result.data[0];
}).catch(function(error) {
  mjsonrpc_error_alert(error);
});

Writing a value

midas::odb o("/Runinfo");
o["Run number"] = 12345;
MVOdb* odb = MakeMidasOdb(hDB);
MVOdb* runinfo = odb->Chdir("Runinfo");
runinfo->WI("Run number", 12345);
int32_t value = 12345;
INT status = db_set_value(hDB, 0, "/Runinfo/Run number", &value, sizeof(value), 1, TID_INT32);
client.odb_set("/Runinfo/Run number", 12345)
<div class="modbvalue" data-odb-path="/Runinfo/Run number" data-odb-editable="1"></div>
<!-- (1)! -->
  1. You can also use modbselect for select dropdowns, modbcheckbox for checkboxes, and more...
mjsonrpc_db_set_value("/Runinfo/Run number", 12345).then(function(rpc) {
  if (rpc.result.status[0] == 1) {
    // Success
  }
}).catch(function(error) {
  mjsonrpc_error_alert(error);
});
odbedit -c 'set "/Runinfo/Run number" 12345'

Writing an array

midas::odb o("/Example");
o["Thresholds"] = std::vector<int>{10, 20, 30};
o["Thresholds"][1] = 25; // Single element
MVOdb* odb = MakeMidasOdb(hDB);
MVOdb* example = odb->Chdir("Example", true);
example->WIA("Thresholds", {10, 20, 30});
example->WIAI("Thresholds", 1, 25); // Single element

There are two options for writing the whole array:

// Option 1: C-style array
int32_t values[3] = {10, 20, 30};
INT status = db_set_value(hDB, 0, "/Example/Thresholds", values, sizeof(values), 3, TID_INT32);
// Option 2: std::vector
std::vector<int32_t> values = {10, 20, 30};
INT status = db_set_value(hDB, 0, "/Example/Thresholds", values.data(), values.size() * sizeof(int32_t), values.size(), TID_INT32);
// Write a single element
int32_t value = 25;
INT status = db_set_value_index(hDB, 0, "/Example/Thresholds", &value, sizeof(value), 1, TID_INT32, FALSE);
# Write the whole array
client.odb_set("/Example/Thresholds", [10, 20, 30])

# Write a single element
client.odb_set("/Example/Thresholds[1]", 25)
// Write the whole array (this also resizes it to 3 elements)
mjsonrpc_db_set_value("/Example/Thresholds", [10, 20, 30]).catch(function(error) {
  mjsonrpc_error_alert(error);
});

// Write a single element
mjsonrpc_db_set_value("/Example/Thresholds[1]", 25).catch(function(error) {
  mjsonrpc_error_alert(error);
});

// Write several elements: set element [0] to 15, and element [2] to 35
mjsonrpc_db_set_value("/Example/Thresholds[0,2]", [15, 35]).catch(function(error) {
  mjsonrpc_error_alert(error);
});

// Write the same value to several elements: set elements [0], [1] and [2] to 0
mjsonrpc_db_set_value("/Example/Thresholds[0-2]", 0).catch(function(error) {
  mjsonrpc_error_alert(error);
});

See Array indices in ODB paths for the index syntax, and what happens when you write past the end of an array.

Reading an array

midas::odb o("/Example");
std::vector<int> values = o["Thresholds"];
int value = o["Thresholds"][1]; // Single element
MVOdb* odb = MakeMidasOdb(hDB);
MVOdb* example = odb->Chdir("Example");
std::vector<int> values;
int value = 0;
example->RIA("Thresholds", &values);
example->RIAI("Thresholds", 1, &value); // Single element

There are two options for reading the whole array:

// Option 1: C-style array
int32_t values[3];
INT size = sizeof(values); // (1)!
INT status = db_get_value(hDB, 0, "/Example/Thresholds", values, &size, TID_INT32, 0);
  1. db_get_value() returns DB_TRUNCATED if size is too small for the whole array.
// Option 2: std::vector, sized to match the ODB array
HNDLE hKey;
KEY key;
db_find_key(hDB, 0, "/Example/Thresholds", &hKey);
db_get_key(hDB, hKey, &key);
std::vector<int32_t> values(key.num_values);
INT size = values.size() * sizeof(int32_t);
INT status = db_get_data(hDB, hKey, values.data(), &size, TID_INT32);

db_get_data_vec() also reads into a vector, but it returns the raw bytes as a std::vector<char>, so you would still need to convert them to int32_t.

// Read a single element
int32_t value = 0;
INT size = sizeof(value);
INT status = db_get_value(hDB, 0, "/Example/Thresholds[1]", &value, &size, TID_INT32, 0);
# Read the whole array
values = client.odb_get("/Example/Thresholds")

# Read a single element
value = client.odb_get("/Example/Thresholds[1]")
// Read the whole array, a single element and several elements in one call
mjsonrpc_db_get_values(["/Example/Thresholds", "/Example/Thresholds[1]", "/Example/Thresholds[0,2]"]).then(function(rpc) {
  let values = rpc.result.data[0]; // [10, 20, 30]
  let value = rpc.result.data[1];  // 20
  let some = rpc.result.data[2];   // [10, 30]
}).catch(function(error) {
  mjsonrpc_error_alert(error);
});

See Array indices in ODB paths for the index syntax.

Reading an array of strings

midas::odb o("/Example");
std::vector<std::string> values = o["Names"];
std::string value = o["Names"][1]; // Single element
MVOdb* odb = MakeMidasOdb(hDB);
MVOdb* example = odb->Chdir("Example");
std::vector<std::string> values;
std::string value;
example->RSA("Names", &values);
example->RSAI("Names", 1, &value); // Single element

There are two options for reading the whole array:

// Option 1: C-style array
char values[3][32]; // (1)!
INT size = sizeof(values);
INT status = db_get_value(hDB, 0, "/Example/Names", values, &size, TID_STRING, 0);
  1. The string length (32 here) must match the string length of the ODB array (key.item_size).
// Option 2: std::vector, sized to match the ODB array
HNDLE hKey;
KEY key;
db_find_key(hDB, 0, "/Example/Names", &hKey);
db_get_key(hDB, hKey, &key);
std::vector<std::string> values(key.num_values);

for (int i = 0; i < key.num_values; i++) {
  INT status = db_get_value_string(hDB, 0, "/Example/Names", i, &values[i]);
}
// Read a single element
std::string value;
INT status = db_get_value_string(hDB, 0, "/Example/Names", 1, &value);
# Read the whole array
values = client.odb_get("/Example/Names")

# Read a single element
value = client.odb_get("/Example/Names[1]")
mjsonrpc_db_get_value("/Example/Names").then(function(rpc) {
  let values = rpc.result.data[0];
}).catch(function(error) {
  mjsonrpc_error_alert(error);
});

Resizing an array

When you make an array smaller, the elements at the end are deleted. When you make it larger, the existing values are kept and the new elements are added at the end, set to zero (0 for numbers, false for booleans, and an empty string for strings).

The MVOdb and Python examples below write a whole new array, so you choose the value of every element yourself.

midas::odb o("/Example");
o["Thresholds"].resize(5);

Writing an array of a different length resizes it.

MVOdb* odb = MakeMidasOdb(hDB);
MVOdb* example = odb->Chdir("Example");
example->WIA("Thresholds", {10, 20, 30, 40, 50});
HNDLE hKey;
INT status = db_find_key(hDB, 0, "/Example/Thresholds", &hKey);

if (status == DB_SUCCESS) {
  status = db_set_num_values(hDB, hKey, 5);
}

Writing a list of a different length resizes the array.

client.odb_set("/Example/Thresholds", [10, 20, 30, 40, 50])
mjsonrpc_db_resize(["/Example/Thresholds"], [5]).catch(function(error) {
  mjsonrpc_error_alert(error);
});

Reading an entire tree

midas::odb o("/Runinfo");

for (midas::odb& key : o.items()) {
  std::cout << key.get_name() << " = " << key << std::endl;
}
MVOdb* odb = MakeMidasOdb(hDB);
std::vector<std::string> names;
std::vector<int> tids, num_values, total_sizes, item_sizes;
MVOdb* runinfo = odb->Chdir("Runinfo");
runinfo->ReadDir(&names, &tids, &num_values, &total_sizes, &item_sizes);

ReadDir() only lists the keys; read each value with the relevant Rx() function.

This example copies the whole tree as JSON, then parses it with mjson.

HNDLE hKey;
db_find_key(hDB, 0, "/Runinfo", &hKey);

char* buffer = nullptr;
int buffer_size = 0;
int buffer_end = 0;
INT status = db_copy_json_values(hDB, hKey, &buffer, &buffer_size, &buffer_end, 0, 1, 0, 1); // (1)!

MJsonNode* runinfo = MJsonNode::Parse(buffer);
int value = runinfo->FindObjectNode("Run number")->GetInt();
delete runinfo;
free(buffer);
  1. The last four arguments are omit_names, omit_last_written, omit_old_timestamp and preserve_case. With preserve_case set, key names keep their capitalisation (otherwise they are converted to lower case).
runinfo = client.odb_get("/Runinfo") # A dict
value = runinfo["Run number"]
mjsonrpc_db_get_value("/Runinfo").then(function(rpc) {
  let runinfo = rpc.result.data[0];
  let value = runinfo["run number"]; // (1)!
}).catch(function(error) {
  mjsonrpc_error_alert(error);
});
  1. Key names are converted to lower case.

Reading key metadata

As well as its value, each ODB key has some metadata (the fields of the KEY struct):

Field Meaning
name The name of the key
type The TID_xxx type number (e.g. 7 for TID_INT32, or 15 for TID_KEY, a directory)
num_values The number of elements in an array, or 1 for a single value
item_size The size of one element in bytes. For strings, this is the maximum length of the string, including the terminating null
total_size The size of all the data in bytes
access_mode The permissions of the key, as a bitmask of MODE_READ (1), MODE_WRITE (2), MODE_DELETE (4) and MODE_EXCLUSIVE (8)
notify_count The number of hotlinks open on the key
last_written When the value was last written, as a Unix timestamp (seconds since 1970). It's updated on every write, even if the value didn't change. For directories it's 0

For example, to find when a value was last written, and how many elements an array has:

midas::odb o("/Example");
unsigned int last_written = o["Thresholds"].get_last_written();
int num_values = o["Thresholds"].get_num_values();

get_tid(), get_name() and get_mode() return the type, name and access mode.

MVOdb* odb = MakeMidasOdb(hDB);
MVOdb* example = odb->Chdir("Example");

int last_written = 0;
example->ReadKeyLastWritten("Thresholds", &last_written);

int tid = 0, num_values = 0, total_size = 0, item_size = 0;
example->ReadKey("Thresholds", &tid, &num_values, &total_size, &item_size);
HNDLE hKey;
INT status = db_find_key(hDB, 0, "/Example/Thresholds", &hKey);

if (status == DB_SUCCESS) {
  KEY key;
  db_get_key(hDB, hKey, &key);
  DWORD last_written = key.last_written;
  INT num_values = key.num_values;
}

odb_last_update_time() returns when a key was last written, as a datetime. For the other fields, read the key with include_key_metadata=True (see JSON encodings of ODB data):

last_written = client.odb_last_update_time("/Example/Thresholds") # A datetime

result = client.odb_get("/Example/Thresholds", include_key_metadata=True)
metadata = result["Thresholds/key"] # (1)!
num_values = metadata["num_values"]
  1. A dict with type, access_mode and last_written (as a Unix timestamp). num_values is only included for arrays, and item_size only for strings.
mjsonrpc_db_key(["/Example/Thresholds"]).then(function(rpc) {
  let key = rpc.result.keys[0]; // (1)!
  let last_written = new Date(key.last_written * 1000);
  let num_values = key.num_values;
}).catch(function(error) {
  mjsonrpc_error_alert(error);
});
  1. null if the key doesn't exist, with the reason in rpc.result.status[0]. Directories also have num_keys, the number of keys in the directory.

If you're reading the value anyway, mjsonrpc_db_get_value() and mjsonrpc_db_get_values() also return when it was last written, in rpc.result.last_written[0]. For the keys inside a directory, this is in the /last_written entries (see Key names in db_get_values results).

Creating a subdirectory

midas::odb o("/Example/Subdir"); // Created if it doesn't exist
MVOdb* odb = MakeMidasOdb(hDB);
MVOdb* subdir = odb->Chdir("Example/Subdir", true);
INT status = db_create_key(hDB, 0, "/Example/Subdir", TID_KEY); // (1)!
  1. Returns DB_KEY_EXIST if the directory already exists.
client.odb_set("/Example/Subdir", {}, remove_unspecified_keys=False) # (1)!
  1. Without remove_unspecified_keys=False, any existing contents of /Example/Subdir would be deleted.
let subdir = {"path": "/Example/Subdir", "type": TID_KEY};

mjsonrpc_db_create([subdir]).catch(function(error) {
  mjsonrpc_error_alert(error);
});

Updating the structure of a tree

These examples make sure /Example/Settings contains the keys we expect, creating any missing keys with default values. Existing values are left alone.

midas::odb settings = {
  {"Enabled", true},
  {"Thresholds", std::array<int, 3>{10, 20, 30}}
};
settings.connect_and_fix_structure("/Example/Settings"); // (1)!
  1. Also removes keys that aren't listed, and fixes the order and types of keys. Use connect() to only create missing keys.

If the size of an array is only known when the program runs (e.g. the number of channels), use midas::odb::array(). It creates an array of that size, with every element set to the same value:

size_t n_channels = 16; // e.g. from the command line
midas::odb settings = {
  {"Enabled", midas::odb::array(true, n_channels)},
  {"Thresholds", midas::odb::array(10, n_channels)}
};
settings.connect_and_fix_structure("/Example/Settings");

Pass create = true when reading each value; missing keys are created with the value you pass in.

MVOdb* odb = MakeMidasOdb(hDB);
MVOdb* settings = odb->Chdir("Example/Settings", true);
bool enabled = true;
std::vector<int> thresholds = {10, 20, 30};
settings->RB("Enabled", &enabled, true);
settings->RIA("Thresholds", &thresholds, true, 3);

Pass create = TRUE to db_get_value() for each value; missing keys are created with the value you pass in.

BOOL enabled = TRUE;
INT size = sizeof(enabled);
db_get_value(hDB, 0, "/Example/Settings/Enabled", &enabled, &size, TID_BOOL, TRUE);

int32_t thresholds[3] = {10, 20, 30};
size = sizeof(thresholds);
db_get_value(hDB, 0, "/Example/Settings/Thresholds", thresholds, &size, TID_INT32, TRUE);

The older db_check_record() function can also do this, but it is deprecated.

defaults = {"Enabled": True, "Thresholds": [10, 20, 30]}
client.odb_set("/Example/Settings", defaults, update_structure_only=True)

mjsonrpc_db_create() creates any keys that are missing (keys that already exist are left alone).

let enabled = {"path": "/Example/Settings/Enabled", "type": TID_BOOL};
let thresholds = {"path": "/Example/Settings/Thresholds", "type": TID_INT, "array_length": 3};

mjsonrpc_db_create([enabled, thresholds]).catch(function(error) {
  mjsonrpc_error_alert(error);
});

Saving and loading

You can save part (or all) of the ODB to a file, and load it back later. JSON is the usual format, but XML and the older .odb text format can also be loaded. The logger can also save a copy of the ODB in each midas file, at the start and end of each run.

Saving a tree to a file

midas::odb o("/Equipment/Trigger/Settings");
o.save("settings.json"); // (1)!
  1. The file contains Settings itself as the top-level key, so load it back into the parent directory (/Equipment/Trigger).

Not supported. Use the low-level API.

HNDLE hKey;
db_find_key(hDB, 0, "/Equipment/Trigger/Settings", &hKey);
INT status = db_save_json(hDB, hKey, "settings.json"); // (1)!
  1. The file contains the contents of the directory (not the directory itself), so load it back into the same path. Use db_save_xml() for XML, or db_save() for the .odb format.
with open("settings.json", "w") as f:
    json.dump(client.odb_get("/Equipment/Trigger/Settings"), f, indent=2) # (1)!
  1. This only saves the values, not the ODB types, so the file is in a different format from the files saved by the other APIs. Load it with the Python example in Loading a file into the ODB.

mjsonrpc_db_copy() gets a tree in the same JSON format as a saved file, and the ext_save_file RPC method saves it on the server.

mjsonrpc_db_copy(["/Equipment/Trigger/Settings"]).then(function(rpc) {
  let json = JSON.stringify(rpc.result.data[0], null, 2);
  return mjsonrpc_call("ext_save_file", {"filename": "settings.json", "script": json}); // (1)!
}).then(function(rpc) {
  if (rpc.result.status != 1)
    throw rpc.result.error;
}).catch(function(error) {
  mjsonrpc_error_alert(error);
});
  1. Webpages can only save files in the userfiles subdirectory of the experiment directory, so this saves <experiment_dir>/userfiles/settings.json. The filename can include a subdirectory of userfiles (which must already exist), but not ... The file contains the contents of the directory (not the directory itself), so load it back into the same path.
odbedit -d "/Equipment/Trigger/Settings" -c 'save settings.json' # (1)!
  1. save saves the contents of the current directory (set with -d). Files ending in .json or .xml are saved in that format, and anything else in the .odb format.

Loading a file into the ODB

Loading a file creates any keys that don't exist yet, and overwrites the values of keys that do. Keys in the ODB that aren't in the file are left alone.

midas::odb::load("settings.json", "/Equipment/Trigger"); // (1)!
  1. Loads the file into the given directory, creating the directory if needed. The format (JSON, XML or .odb) is detected from the file contents.

Not supported. Use the low-level API.

HNDLE hKey;
db_find_key(hDB, 0, "/Equipment/Trigger/Settings", &hKey);
INT status = db_load(hDB, hKey, "settings.json", FALSE); // (1)!
  1. The format (JSON, XML or .odb) is detected from the file contents.
with open("settings.json") as f:
    client.odb_set("/Equipment/Trigger/Settings", json.load(f), remove_unspecified_keys=False) # (1)!
  1. For files saved by the Python example in Saving a tree to a file. Without remove_unspecified_keys=False, odb_set() deletes keys that aren't in the file.

The ext_read_file RPC method reads a file from the server, and mjsonrpc_db_paste() writes JSON in the saved-file format into the ODB.

mjsonrpc_call("ext_read_file", {"filename": "settings.json"}).then(function(rpc) { // (1)!
  if (rpc.result.status != 1)
    throw rpc.result.error;
  let data = JSON.parse(rpc.result.content);
  return mjsonrpc_db_paste(["/Equipment/Trigger/Settings"], [data]);
}).catch(function(error) {
  mjsonrpc_error_alert(error);
});
  1. Webpages can only read files in the userfiles subdirectory of the experiment directory, so this reads <experiment_dir>/userfiles/settings.json. The filename can include a subdirectory of userfiles, but not .. or *.
odbedit -d "/Equipment/Trigger/Settings" -c 'load settings.json'

Reading a value from a saved JSON dump

These examples read a saved file without changing the ODB, which is useful in analysis programs. They don't need a running experiment.

midas::odb::set_odb_source(midas::odb::FILE, "odb.json");
midas::odb o("/Runinfo");
int value = o["Run number"];
MVOdb* odb = MakeJsonFileOdb("odb.json");
MVOdb* runinfo = odb->Chdir("Runinfo");
int value = 0;
runinfo->RI("Run number", &value);

This example uses the low-level mjson tool. You could also use nlohmann::json to parse a JSON dump instead.

std::ifstream f("odb.json");
std::string data((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>());

MJsonNode* root = MJsonNode::Parse(data.c_str());
int value = root->FindObjectNode("Runinfo")->FindObjectNode("Run number")->GetInt();
delete root;
with open("odb.json") as f:
    odb = json.load(f)
    value = odb["Runinfo"]["Run number"]
mjsonrpc_call("ext_read_file", {"filename": "odb.json"}).then(function(rpc) { // (1)!
  if (rpc.result.status != 1)
    throw rpc.result.error;
  let odb = JSON.parse(rpc.result.content);
  let value = odb["Runinfo"]["Run number"];
}).catch(function(error) {
  mjsonrpc_error_alert(error);
});
  1. Webpages can only read files in the userfiles subdirectory of the experiment directory, so this reads <experiment_dir>/userfiles/odb.json. For a file served by mhttpd (e.g. in the same directory as your custom page), you can use fetch("odb.json") instead.

Reading a value from a saved midas file

TMReaderInterface* reader = TMNewReader("file.mid");
TMEvent* event = TMReadEvent(reader);

if (event->event_id != 0x8000) {
    // First event should have been an ODB dump, but wasn't!
    exit(1);
}

std::string data(event->GetEventData());
midas::odb::set_odb_source(midas::odb::STRING, data);
midas::odb o("/Runinfo");
int value = o["Run number"];
TMReaderInterface* reader = TMNewReader("file.mid");
TMEvent* event = TMReadEvent(reader);

if (event->event_id != 0x8000) {
    // First event should have been an ODB dump, but wasn't!
    exit(1);
}

MVOdb* odb = MakeFileDumpOdb(event->GetEventData(), event->data_size);
MVOdb* runinfo = odb->Chdir("Runinfo");
int value = 0;
runinfo->RI("Run number", &value);

This example uses the low-level mjson and mxml tools. You could also use nlohmann::json to parse a JSON dump. It is HIGHLY recommended that you use one of the higher-level options instead.

TMReaderInterface* reader = TMNewReader("file.mid");
TMEvent* event = TMReadEvent(reader);

if (event->event_id != 0x8000) {
    // First event should have been an ODB dump, but wasn't!
    exit(1);
}

std::string data(event->GetEventData(), event->data_size);
int value = 0;

if (data[0] == '{') {
    // JSON dump (the default), parsed with mjson.h
    MJsonNode* root = MJsonNode::Parse(data.c_str());
    value = root->FindObjectNode("Runinfo")->FindObjectNode("Run number")->GetInt();
    delete root;
} else if (data[0] == '<') {
    // XML dump, parsed with mxml.h
    PMXML_NODE tree = mxml_parse_buffer(data.c_str(), nullptr, 0, nullptr);
    PMXML_NODE node = mxml_find_node(tree, "/odb/dir[@name=Runinfo]/key[@name=Run number]");
    value = atoi(mxml_get_value(node));
    mxml_free_tree(tree);
}
with midas.file_reader.MidasFile("file.mid") as f:
    odb = f.get_bor_odb_dump().data
    value = odb["Runinfo"]["Run number"]

Not supported. There is no JavaScript reader for midas files. Use the C++ or Python examples instead.

JSON encodings of ODB data

In C++ and JavaScript, midas can encode a part of the ODB as JSON in three different ways, for different uses:

Encoding Used by Contents
"save" Everything needed to restore the ODB: the values, plus a /key entry with the type and other properties of each key. Key names keep their capitalisation, and symlinks are saved as symlinks
"values" Values that are easy to use in a web page. Key names are in lower case, with the original names in /name entries, symlinks are followed, and the only property is when each key was last written. See Key names in db_get_values results
"ls" One directory, without recursion. The same information as odbedit's ls -l. Subdirectories are shown as empty objects ({})

In all three, some values are encoded as strings (e.g. DWORD values in hexadecimal, such as "0x55b96181"). See How values are encoded.

For example, this is the same directory in each encoding, as printed by odbedit (odbedit -d /Example -c json, jsvalues and jsls):

{
  "Run number/key" : { "type" : 7, "access_mode" : 7, "last_written" : 1793311925 },
  "Run number" : 13585,
  "Start time binary/key" : { "type" : 6, "access_mode" : 7, "last_written" : 1793311925 },
  "Start time binary" : "0x55b96181",
  "Thresholds/key" : { "type" : 7, "num_values" : 3, "access_mode" : 7, "last_written" : 1793311925 },
  "Thresholds" : [ 10, 20, 30 ],
  "Sub" : {
    "Enabled/key" : { "type" : 8, "access_mode" : 7, "last_written" : 1793311925 },
    "Enabled" : true
  }
}
{
  "run number/name" : "Run number",
  "run number/last_written" : 1793311925,
  "run number" : 13585,
  "start time binary/name" : "Start time binary",
  "start time binary/last_written" : 1793311925,
  "start time binary" : "0x55b96181",
  "thresholds/name" : "Thresholds",
  "thresholds/last_written" : 1793311925,
  "thresholds" : [ 10, 20, 30 ],
  "sub/name" : "Sub",
  "sub" : {
    "enabled/name" : "Enabled",
    "enabled/last_written" : 1793311925,
    "enabled" : true
  }
}
{
  "Run number/key" : { "type" : 7, "access_mode" : 7, "last_written" : 1793311925 },
  "Run number" : 13585,
  "Start time binary/key" : { "type" : 6, "access_mode" : 7, "last_written" : 1793311925 },
  "Start time binary" : "0x55b96181",
  "Thresholds/key" : { "type" : 7, "num_values" : 3, "access_mode" : 7, "last_written" : 1793311925 },
  "Thresholds" : [ 10, 20, 30 ],
  "Sub" : { }
}

In the /key entries, type is the TID_xxx type number (e.g. 7 for INT32), num_values is the length of an array, item_size is the size of a string key, and access_mode is the permissions of the key.

JSON encoding don't apply to Python

The save/values/ls distinction doesn't matter in Python. odb_get() always returns a Python dict, with the original key names and the values converted to Python types (so a DWORD is an int, not a hexadecimal string). Symlinks are followed.

You can pass different arguments to mimic the results shown above:

Call Similar to Result
odb_get(path) "values" The values of everything below path, with no other properties
odb_get(path, include_key_metadata=True) "save" The values, plus a /key entry for each key with its type, access mode and when it was last written (and its size, for strings and arrays). The result is nested under the directory's own name, next to its own /key entry
odb_get(path, recurse_dir=False) "ls" Only the entries directly in path. Subdirectories are empty dicts
odb_get(path, just_key_list=True) A list of the names of the entries directly in path

The examples below create a link at /Example/Link that points to /Runinfo/Run number.

Not supported. Use the low-level API.

Not supported. Use the low-level API.

INT status = db_create_link(hDB, 0, "/Example/Link", "/Runinfo/Run number");
client.odb_link("/Example/Link", "/Runinfo/Run number")
let parameters = new Object();
parameters.new_links = ["/Example/Link"];
parameters.target_paths = ["/Runinfo/Run number"];

mjsonrpc_call("db_link", parameters).catch(function(error) {
  mjsonrpc_error_alert(error);
});

Creating a link that already exists changes where it points to. The examples below change /Example/Link to point to /Runinfo/State.

Not supported. Use the low-level API.

Not supported. Use the low-level API.

INT status = db_create_link(hDB, 0, "/Example/Link", "/Runinfo/State");
client.odb_link("/Example/Link", "/Runinfo/State")
let parameters = new Object();
parameters.new_links = ["/Example/Link"];
parameters.target_paths = ["/Runinfo/State"];

mjsonrpc_call("db_link", parameters).catch(function(error) {
  mjsonrpc_error_alert(error);
});

The examples below delete the link /Example/Link, but leave the value it points to alone.

Not supported. midas::odb::delete_key() follows the link, so it would delete the value the link points to. Use the low-level API instead.

MVOdb* odb = MakeMidasOdb(hDB);
MVOdb* example = odb->Chdir("Example");
example->Delete("Link");

There are two options:

// Option 1: delete by path
INT status = db_delete(hDB, 0, "/Example/Link"); // (1)!
  1. db_delete() doesn't follow the link, so it deletes the link itself.
// Option 2: delete by handle
HNDLE hKey;
INT status = db_find_link(hDB, 0, "/Example/Link", &hKey); // (1)!

if (status == DB_SUCCESS) {
  status = db_delete_key(hDB, hKey);
}
  1. Use db_find_link(), not db_find_key(). db_find_key() follows the link, so you would get the handle of (and delete) the value the link points to.
client.odb_delete("/Example/Link") # (1)!
  1. Pass follow_links=True if you want to delete the value the link points to instead.
mjsonrpc_db_delete(["/Example/Link"]).catch(function(error) {
  mjsonrpc_error_alert(error);
});

Deleting a value

midas::odb::delete_key("/Example/Thresholds");
MVOdb* odb = MakeMidasOdb(hDB);
MVOdb* example = odb->Chdir("Example");
example->Delete("Thresholds");
INT status = db_delete(hDB, 0, "/Example/Thresholds");
client.odb_delete("/Example/Thresholds")
mjsonrpc_db_delete(["/Example/Thresholds"]).catch(function(error) {
  mjsonrpc_error_alert(error);
});

Re-ordering keys in a directory

midas::odb o("/Example");
o.fix_order({"Thresholds", "Enabled"});

Not supported. Use the low-level API.

// Move "Thresholds" to the top of the directory (-1 moves it to the bottom)
HNDLE hKey;
INT status = db_find_key(hDB, 0, "/Example/Thresholds", &hKey);

if (status == DB_SUCCESS) {
  status = db_reorder_key(hDB, hKey, 0);
}

Keys are re-ordered to match the order of the dict passed to odb_set(). Keys that aren't in the dict are deleted.

client.odb_set("/Example", {"Thresholds": [10, 20, 30], "Enabled": True})
// Move "Thresholds" to the top of the directory (-1 moves it to the bottom)
let parameters = new Object();
parameters.paths = ["/Example/Thresholds"];
parameters.indices = [0];

mjsonrpc_call("db_reorder", parameters).catch(function(error) {
  mjsonrpc_error_alert(error);
});

Changing permissions

Access modes use a bitmask: R = read (MODE_READ, 1), W = write (MODE_WRITE, 2), D = delete (MODE_DELETE, 4), and X = exclusive mode (MODE_EXCLUSIVE, 8). Add the enabled bit values to get the mode. Exclusive mode reserves writes for the client holding the corresponding open record.

The ODB browser also shows H when a key has an active hotlink. H comes from the notification count, not a mode bit, so it has no numeric value to pass to set_mode(), db_set_mode() or odb_chmod(). Client registrations manage H and normally manage X.

The table below lists combinations of the R, W and D permission bits; the default is 7 (RWD).

Value Read (R) Write (W) Delete (D)
0
1 ✔
2 ✔
3 ✔ ✔
4 ✔
6 ✔ ✔
7 (default) ✔ ✔ ✔

In the code examples below we set /Some/path to RW permissions (3), preventing the user from deleting the key unless they first revert the permissions.

midas::odb o("/Some/path");
o.set_mode(MODE_READ | MODE_WRITE);

Not supported. Use the low-level API.

HNDLE hKey;
WORD mode = MODE_READ | MODE_WRITE;
BOOL recurse = 1;
INT status = db_find_key(hDB, 0, "/Some/path", &hKey);

if (status == SUCCESS) {
    status = db_set_mode(hDB, hKey, mode, recurse);
}
client.odb_chmod("/Some/path", midas.MODE_READ|midas.MODE_WRITE)
mjsonrpc_db_set_mode(["/Some/path"], [MODE_READ | MODE_WRITE]).catch(function(error) { // (1)!
  mjsonrpc_error_alert(error);
});
  1. Unlike the other examples, this does not recurse into sub-directories. Only /Some/path itself is changed.
odbedit -c 'chmod 3 "/Some/path"' #(1)!
  1. odbedit automatically recurses into sub-directories, there is no flag to disable it.

Midas allows programs to register their interest in certain ODB keys, and be told about any changes that are made to that key. Depending on context, this can be referred to as creating an open record, creating a hotlink, or watching an ODB key.

Internally, midas uses this feature in the history system, so mlogger is notified whenever /Equipment/xxx/Variables are updated in the ODB.

Users often use hotlinks so that any changes in ODB settings get forwarded to the hardware as soon as possible.

There are different ways of using hotlinks depending on the language in use:

  • C++ (low-level)
    • Have a callback function called whenever the ODB value gets written to.
    • Map the ODB to a C++ struct, and have the struct values get automatically updated.
  • C++ (midas::odb)
    • Have a callback function called whenever the ODB value gets written to.
    • Enable auto-read, and just re-read the ODB value whenever you access it (not technically a hot-link, but functions like one).
  • Python
    • Have a callback function called whenever the ODB value gets written to.

Not supported. Instead, midas::odb objects re-read the ODB every time you access a value (unless you call set_auto_refresh_read(false)), so they always hold the latest value.

midas::odb o("/Example/Settings");
int threshold = o["Threshold"]; // Always the current ODB value

Not supported.

The struct must exactly match the layout of the ODB directory (see ODB records).

struct {
  BOOL enabled;
  INT32 threshold;
} settings;

HNDLE hKey;
db_find_key(hDB, 0, "/Example/Settings", &hKey);
db_open_record(hDB, hKey, &settings, sizeof(settings), MODE_READ, nullptr, nullptr);
// settings is now updated whenever the ODB changes

Not supported. Use a callback function instead.

Not supported. Use a callback function instead.

Take care with large directories/arrays

Note that the callback function gets called each and every time the ODB key gets written to. If you have a large number of settings (or even a large array), your callback function might get called thousands of times in quick succession!

If your callback function takes a few milliseconds to complete, this could quickly lead to a large backlog of callback requests.

In this case, you should not do the "real" work in the actual callback function. Instead you could:

  • Create a boolean flag in your code (e.g. odb_has_changed), which defaults to false.
  • The callback function just sets the flag to true.
  • Periodically, you check to see if the flag is true. If so, do the "real" work and set the flag back to false.

An alternative approach is to create an extra boolean ODB key (e.g. Apply updates), and have your code watch that. Your scripts, webpages etc are then free to make many changes to the ODB without triggering the callback function. In the calling code, you can:

  • Watch the Apply updates key.
  • If the callback gets triggered AND Apply updates is now true, do the "real" work, and set the ODB key back to false.
  • If the callback gets triggered and Apply updates is false, do nothing.

In C++ and Python, callbacks are only called while your program is calling cm_yield() (C++) or client.communicate() (Python). Frontends do this for you automatically.

midas::odb o("/Example/Settings");
o.watch([](midas::odb& changed) {
  std::cout << changed.get_name() << " is now " << changed << std::endl;
});

Not supported.

void callback(INT hDB, INT hKey, INT index, void* info) {
  // hKey is the key that changed
}

HNDLE hKey;
db_find_key(hDB, 0, "/Example/Settings", &hKey);
db_watch(hDB, hKey, callback, nullptr);
def callback(client, path, odb_value):
    print(f"{path} is now {odb_value}")

client.odb_watch("/Example/Settings", callback)

The onchange function is called whenever the page's periodic ODB refresh sees a new value.

<div class="modb" data-odb-path="/Example/Settings" onchange="console.log(this.value)"></div>

Not supported. Use the low-level API.

Not supported. Use the low-level API.

HNDLE hKey;
char str[10000];
db_find_key(hDB, 0, "/Example", &hKey);
db_get_open_records(hDB, hKey, str, sizeof(str), FALSE);
printf("%s", str);

Not supported by the python library. Run odbedit -d "/Example" -c sor instead (see the Bash tab).

let parameters = new Object();
parameters.path = "/Example";

mjsonrpc_call("db_sor", parameters).then(function(rpc) {
  let hotlinks = rpc.result.sor;
}).catch(function(error) {
  mjsonrpc_error_alert(error);
});
odbedit -d "/Example" -c 'sor'

Deprecated features

ODB records

Deprecated

The db_check_record() feature is deprecated, as there are more robust ways of ensuring ODB structures match what you want. The records feature can break ODB links, struggles with non-ASCII characters, may silently ignore empty directories, and more... It also makes your code less robust to changes that are made to your ODB structure.

If you have legacy code that still uses records, they can still be used. For new code, we recommend using the C++ or python interfaces documented on this page:

Exception - hot-links

db_open_record() is still used to create hot-links so your code can be notified when an ODB value changes. The nicest way to do this is to pass a callback function rather than a C++ struct. Convenience functions for this usage are provided in midas::odb::watch() and python odb_watch().

The ODB record system allows you to interact with parts of the ODB using C++ structs. You can choose whether ODB updates get pushed to your struct, changes to your struct get pushed to the ODB, or neither. You cannot have a single struct that both pushes and pulls changes to/from the ODB.

A summary of the functions available are:

  • db_get_record - One-time read of the ODB into a struct
  • db_set_record - One-time write a struct to the ODB
  • db_open_record - Create a hot-link to automatically reflect ODB->struct or struct->ODB. You can also omit the struct, and have a callback function called instead.
  • db_create_record - Create or fix part of the ODB, based on an ASCII string documenting the format.
  • db_check_record - Like db_create_record(), but with the option of returning an error if the structure doesn't match what is expected.
  • db_close_record - Stop watching part of the ODB that was opened with db_open_record().

See the C++ API reference for db_xxx functions for full details.

experim.h

Deprecated

This feature is deprecated, as there are more developer-friendly ways of ensuring ODB structures match what you want, without having to use the manual step of odbedit's make command. It also suffers all the same issues as ODB records.

The odbedit make command creates experim.h, a file containing a C structure and an ASCII representation of part of the ODB. The file is created in the current directory. e.g.

[local:midas:S]/>make
Analyzer "Analyzer" not found in ODB, skipping analyzer parameters.
"experim.h" has been written to /home/midas/online

The resulting file contains C structures for the ODB trees /Experiment, /Analyzer/Parameters and /Equipment/xxx/Settings (if these trees are present). All C structures are accompanied by a string representation which can be used in the db_create_record function to setup an ODB structure which matches the required C structure, ensuring that the required keys are present in the ODB (see ODB records).

If the name of the analyzer is not the default (i.e. "Analyzer"), then in order to include the analyzer section, the ODB key /abc/Parameters has to be present, where abc is the name of the analyzer.

The command used is then make abc.

Example experim.h file

Note how there is a struct TRIGGER_SETTINGS and an equivalent ASCII string TRIGGER_SETTINGS_STR.

/********************************************************************\

Name:         experim.h
Created by:   ODBedit program

Contents:     This file contains C structures for the "Experiment"
                tree in the ODB and the "/Analyzer/Parameters" tree.

                Additionally, it contains the "Settings" subtree for
                all items listed under "/Equipment" as well as their
                event definition.

                It can be used by the frontend and analyzer to work
                with these information.

                All C structures are accompanied with a string represen-
                tation which can be used in the db_create_record function
                to setup an ODB structure which matches the C structure.

Created on:   Thu Feb  1 21:20:48 2007
$Id$
\********************************************************************/

#ifndef EXCL_TRIGGER

#define TRIGGER_SETTINGS_DEFINED

typedef struct {
struct {
    BOOL      external_trigger;
    BOOL      inverse_signal;
    INT       segment_size;
    INT       group_bitwise_all_0x3f_;
    BOOL      raw_data_suppress;
    BOOL      channel_suppress_enable;
    INT       active_channel_mask;
    INT       trigger_threshold;
    INT       hit_threshold;
    INT       pretrigger;
    INT       latency;
    INT       divisor;
    INT       k_coeff;
    INT       l_coeff;
    INT       m_coeff;
} vf48;
struct {
    WORD      threshold1[32];
    WORD      threshold2[32];
    WORD      threshold3[32];
    WORD      threshold4[32];
} v792;
struct {
    INT       leresolution;
    INT       windowoffset;
    INT       windowwidth;
} v1190;
struct {
    DWORD     post_trigger;
} v1729;
} TRIGGER_SETTINGS;

#define TRIGGER_SETTINGS_STR(_name) char *_name[] = {\
"[vf48]",\
"External Trigger = BOOL : y",\
"Inverse Signal = BOOL : y",\
"Segment size = INT : 128",\
"Group(bitwise all:0x3F) = INT : 63",\
"Raw Data Suppress = BOOL : n",\
"Channel Suppress Enable = BOOL : n",\
"Active Channel Mask = INT : 255",\
"Trigger Threshold = INT : 20",\
"Hit Threshold = INT : 100",\
"PreTrigger = INT : 15",\
"Latency = INT : 14",\
"Divisor = INT : 2",\
"K-Coeff = INT : 60",\
"L-Coeff = INT : 72",\
"M-Coeff = INT : 16",\
"",\
"[v792]",\
"threshold1 = WORD[32] :",\
"[0] 1",\
"[1] 511",\
"[2] 511",\
"[3] 511",\
"[4] 511",\
"[5] 511",\
"[6] 511",\
"[7] 511",\
"[8] 511",\
"[9] 511",\
"[10] 511",\
"[11] 511",\
"[12] 511",\
"[13] 511",\
"[14] 511",\
"[15] 511",\
"[16] 511",\
"[17] 511",\
"[18] 511",\
"[19] 511",\
"[20] 511",\
"[21] 511",\
"[22] 511",\
"[23] 511",\
"[24] 511",\
"[25] 511",\
"[26] 511",\
"[27] 511",\
"[28] 511",\
"[29] 511",\
"[30] 511",\
"[31] 511",\
"threshold2 = WORD[32] :",\
"[0] 10",\
"[1] 10",\
"[2] 10",\
"[3] 511",\
"[4] 511",\
"[5] 511",\
"[6] 511",\
"[7] 511",\
"[8] 511",\
"[9] 511",\
"[10] 511",\
"[11] 511",\
"[12] 511",\
"[13] 511",\
"[14] 511",\
"[15] 511",\
"[16] 511",\
"[17] 511",\
"[18] 511",\
"[19] 511",\
"[20] 511",\
"[21] 511",\
"[22] 511",\
"[23] 511",\
"[24] 511",\
"[25] 511",\
"[26] 511",\
"[27] 511",\
"[28] 511",\
"[29] 511",\
"[30] 511",\
"[31] 511",\
"threshold3 = WORD[32] :",\
"[0] 1",\
"[1] 1",\
"[2] 1",\
"[3] 511",\
"[4] 511",\
"[5] 511",\
"[6] 511",\
"[7] 511",\
"[8] 511",\
"[9] 511",\
"[10] 511",\
"[11] 511",\
"[12] 511",\
"[13] 511",\
"[14] 511",\
"[15] 511",\
"[16] 511",\
"[17] 511",\
"[18] 511",\
"[19] 511",\
"[20] 511",\
"[21] 511",\
"[22] 511",\
"[23] 511",\
"[24] 511",\
"[25] 511",\
"[26] 511",\
"[27] 511",\
"[28] 511",\
"[29] 511",\
"[30] 511",\
"[31] 511",\
"threshold4 = WORD[32] :",\
"[0] 10",\
"[1] 10",\
"[2] 10",\
"[3] 511",\
"[4] 511",\
"[5] 511",\
"[6] 511",\
"[7] 511",\
"[8] 511",\
"[9] 511",\
"[10] 511",\
"[11] 511",\
"[12] 511",\
"[13] 511",\
"[14] 511",\
"[15] 511",\
"[16] 511",\
"[17] 511",\
"[18] 511",\
"[19] 511",\
"[20] 511",\
"[21] 511",\
"[22] 511",\
"[23] 511",\
"[24] 511",\
"[25] 511",\
"[26] 511",\
"[27] 511",\
"[28] 511",\
"[29] 511",\
"[30] 511",\
"[31] 511",\
"",\
"[v1190]",\
"LeResolution = INT : 0",\
"WindowOffset = INT : 0",\
"WindowWidth = INT : 0",\
"",\
"[v1729]",\
"post_trigger = DWORD : 0",\
"",\
NULL }

#define TRIGGER_COMMON_DEFINED

typedef struct {
WORD      event_id;
WORD      trigger_mask;
char      buffer[32];
INT       type;
INT       source;
char      format[8];
BOOL      enabled;
INT       read_on;
INT       period;
double    event_limit;
DWORD     num_subevents;
INT       log_history;
char      frontend_host[32];
char      frontend_name[32];
char      frontend_file_name[256];
} TRIGGER_COMMON;

#define TRIGGER_COMMON_STR(_name) char *_name[] = {\
"[.]",\
"Event ID = WORD : 1",\
"Trigger mask = WORD : 0",\
"Buffer = STRING : [32] SYSTEM",\
"Type = INT : 1",\
"Source = INT : 0",\
"Format = STRING : [8] MIDAS",\
"Enabled = BOOL : y",\
"Read on = INT : 1",\
"Period = INT : 1",\
"Event limit = DOUBLE : 0",\
"Num subevents = DWORD : 0",\
"Log history = INT : 0",\
"Frontend host = STRING : [32] laddvme05.triumf.ca",\
"Frontend name = STRING : [32] FEvf48",\
"Frontend file name = STRING : [256] fevf48.c",\
"",\
NULL }

#endif

#ifndef EXCL_SCALER

#define SCALER_COMMON_DEFINED

typedef struct {
WORD      event_id;
WORD      trigger_mask;
char      buffer[32];
INT       type;
INT       source;
char      format[8];
BOOL      enabled;
INT       read_on;
INT       period;
double    event_limit;
DWORD     num_subevents;
INT       log_history;
char      frontend_host[32];
char      frontend_name[32];
char      frontend_file_name[256];
} SCALER_COMMON;

#define SCALER_COMMON_STR(_name) char *_name[] = {\
"[.]",\
"Event ID = WORD : 2",\
"Trigger mask = WORD : 0",\
"Buffer = STRING : [32] SYSTEM",\
"Type = INT : 1",\
"Source = INT : 0",\
"Format = STRING : [8] MIDAS",\
"Enabled = BOOL : n",\
"Read on = INT : 377",\
"Period = INT : 10000",\
"Event limit = DOUBLE : 0",\
"Num subevents = DWORD : 0",\
"Log history = INT : 0",\
"Frontend host = STRING : [32] laddvme05.triumf.ca",\
"Frontend name = STRING : [32] FEvf48",\
"Frontend file name = STRING : [256] fevf48.c",\
"",\
NULL }

#endif