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-leveldb_xxx()function takes it as the first argument. Get it after connecting to the experiment withcm_get_experiment_database(&hDB, nullptr).hKey- the handle of a single ODB entry (directory, value or array). Find it from a path withdb_find_key(hDB, 0, "/Some/path", &hKey). Many functions also take anhKeyas the "root" for a relative path; pass0to mean the top of the ODB.
KEY- a struct describing an ODB entry, filled in bydb_get_key(hDB, hKey, &key). Useful fields includetype(TID_xxx),num_values,name,item_sizeandlast_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)! -->
- You can also use
modbselectfor select dropdowns,modbcheckboxfor 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)! -->
- You can also use
modbselectfor select dropdowns,modbcheckboxfor 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);
db_get_value()returnsDB_TRUNCATEDifsizeis 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);
- 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);
- The last four arguments are
omit_names,omit_last_written,omit_old_timestampandpreserve_case. Withpreserve_caseset, 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);
});
- 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"]
- A dict with
type,access_modeandlast_written(as a Unix timestamp).num_valuesis only included for arrays, anditem_sizeonly 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);
});
nullif the key doesn't exist, with the reason inrpc.result.status[0]. Directories also havenum_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)!
- Returns
DB_KEY_EXISTif the directory already exists.
client.odb_set("/Example/Subdir", {}, remove_unspecified_keys=False) # (1)!
- Without
remove_unspecified_keys=False, any existing contents of/Example/Subdirwould 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)!
- 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)!
- The file contains
Settingsitself 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)!
- 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, ordb_save()for the.odbformat.
with open("settings.json", "w") as f:
json.dump(client.odb_get("/Equipment/Trigger/Settings"), f, indent=2) # (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);
});
- Webpages can only save files in the
userfilessubdirectory of the experiment directory, so this saves<experiment_dir>/userfiles/settings.json. The filename can include a subdirectory ofuserfiles(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)!
savesaves the contents of the current directory (set with-d). Files ending in.jsonor.xmlare saved in that format, and anything else in the.odbformat.
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)!
- 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)!
- 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)!
- 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);
});
- Webpages can only read files in the
userfilessubdirectory of the experiment directory, so this reads<experiment_dir>/userfiles/settings.json. The filename can include a subdirectory ofuserfiles, 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);
});
- Webpages can only read files in the
userfilessubdirectory 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 usefetch("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 |
Symlinks
Creating a symlink
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);
});
Changing the destination of a symlink
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);
});
Deleting a symlink
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)!
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);
}
- Use
db_find_link(), notdb_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)!
- Pass
follow_links=Trueif 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);
});
- Unlike the other examples, this does not recurse into sub-directories. Only
/Some/pathitself is changed.
odbedit -c 'chmod 3 "/Some/path"' #(1)!
- odbedit automatically recurses into sub-directories, there is no flag to disable it.
Hotlinks (get notified when a value changes)
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.
Creating a hotlink using a struct
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.
Creating a hotlink with a callback function
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 updateskey. - If the callback gets triggered AND
Apply updatesis now true, do the "real" work, and set the ODB key back to false. - If the callback gets triggered and
Apply updatesis 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>
Querying which hotlinks exist
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:
db_check_record()/db_create_record()can be replaced with theconnect_and_fix_structure()function inmidas::odb, or theupdate_structure_only=Trueargument of the pythonodb_set()function.db_open_record()can be replaced with thewatch()function inmidas::odb, or theodb_watch()function in python.
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 structdb_set_record- One-time write a struct to the ODBdb_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- Likedb_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 withdb_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