This is the standardized communication between modules using I2C (Wire)
protocol.h
: The base protocol, worked on top ofWire.h
("Arduino" library).packaging.h
: The package system implemented and used everywhere in this project between modules. This has callback for secondary modules and tools for master module.flags.h
: Useful flags forpackaging
to work efficiently.
auto wire = new CS::PackagedWired(CS::config().set_master()); // you may want to change pins, or LED indicator.
// ...
// in an infinite while loop...
for(uint8_t p = 0; p < CS::d2u(CS::device_id::_MAX); ++p) {
const auto curr = static_cast<CS::device_id>(p);
// used to get flags, if answered
CS::FlagWrapper fw;
// example of variables to hold the data. Of course you'd use this ...
// in an object attribute or save in an array, I don't know.
bool is_online = false;
bool has_issues = false;
bool has_new_data = false;
auto lst = wire->master_smart_request_all(curr, fw, is_online);
has_issues = fw & CS::device_flags::HAS_ISSUES; // way to get this flag
has_new_data = fw & CS::device_flags::HAS_NEW_DATA; // in this example ...
// we don't use this because I am lazy, but you can check if new values were sent.
if (has_issues || !is_online) continue; // a reason to skip further calls
if (lst.empty()) continue; // no data sent, so no data to read.
for(const auto& i : lst) {
switch(i.get_type()) {
case CS::Command::vtype::TD:
{
const auto path = i.get_path(); // path sent
const auto value = i.get_val<double>(); // value, double
// do something with it here...
}
break;
case CS::Command::vtype::TF:
{
const auto path = i.get_path(); // path sent
const auto value = i.get_val<float>(); // value, float
// do something with it here...
}
break;
case CS::Command::vtype::TI:
{
const auto path = i.get_path(); // path sent
const auto value = i.get_val<int64_t>(); // value, int64_t
// do something with it here...
}
break;
case CS::Command::vtype::TU:
{
const auto path = i.get_path(); // path sent
const auto value = i.get_val<uint64_t>(); // value, uint64_t
// do something with it here...
}
break;
default: // no match, not useful for us. Skip.
break;
}
}
}
// somewhere, once, you do:
auto wire = new PackagedWired(config()
.set_slave(device_id::MICS_6814_SENSOR)
.set_slave_callback(callback)
);
// somewhere else you do this:
void callback(void* rw, const uint8_t expects, const char* received, const uint8_t length)
{
// Check if the length is the length expected to avoid weird behavior.
if (length != sizeof(Requester)) return;
PackagedWired& w = *(PackagedWired*) rw;
Requester req(received);
// Switch on the offset from master. This will go from 0 to 32-bit size_t, if you allow it to.
switch(req.get_offset()) {
case 0: // special case, used to send flags of the device
{
FlagWrapper fw;
if (has_issues()) fw |= device_flags::HAS_ISSUES;
if (has_new_data_autoreset()) fw |= device_flags::HAS_NEW_DATA;
Command cmd("#FLAGS", (uint64_t)fw); // special case, returns as "#FLAGS"
w.slave_reply_from_callback(cmd);
}
break;
case 1:
{
// I am not even sure if this will work, but this is an example of random number to reply
const int64_t val = rand();
// Path is key to make things readable later, as of paths and variable names in logs.
Command cmd("/my/random", val);
w.slave_reply_from_callback(cmd);
}
break;
case 2:
{
const float val = 3.1415f; // I don't know, you do you
Command cmd("/my/pi", val);
w.slave_reply_from_callback(cmd);
}
break;
default: // This is important to make sure master will stop after the previous case. ...
// Any other case will return as "end" by doing this.
{
Command cmd; // "invalid", "end of file", you call it
w.slave_reply_from_callback(cmd);
}
}
}
// this may be useful if you're on Arduino IDE as I am. Just kill this loop thread.
void loop() { vTaskDelete(NULL); }