/********************************************************************** * COPYRIGHT 2020 MULTI-TECH SYSTEMS, INC. * * ALL RIGHTS RESERVED BY AND FOR THE EXCLUSIVE BENEFIT OF * MULTI-TECH SYSTEMS, INC. * * MULTI-TECH SYSTEMS, INC. - CONFIDENTIAL AND PROPRIETARY * INFORMATION AND/OR TRADE SECRET. * * NOTICE: ALL CODE, PROGRAM, INFORMATION, SCRIPT, INSTRUCTION, * DATA, AND COMMENT HEREIN IS AND SHALL REMAIN THE CONFIDENTIAL * INFORMATION AND PROPERTY OF MULTI-TECH SYSTEMS, INC. * USE AND DISCLOSURE THEREOF, EXCEPT AS STRICTLY AUTHORIZED IN A * WRITTEN AGREEMENT SIGNED BY MULTI-TECH SYSTEMS, INC. IS PROHIBITED. * ***********************************************************************/ #include "Device.h" const std::vector Device::apIdentifiers = {"ap1", "ap2", "lora", "lora-2", "slot1", "slot2"}; const std::regex Device::apFilters("(modalias)|(power)(.*)|(subsystem)|(uevent)"); const std::regex Device::lora15Filters("(MTAC-LORA-)(.*)|(MTCAP-LORA-)(.*)|(MTLGA-)(.*)|(MTCDT3-)(.*)"); const std::regex Device::loraG16Filters("(MTAC-LORA-G16)(.*)"); const std::regex Device::loraG64Filters("(MTAC-LORA-G64)(.*)"); const std::regex Device::gpiobFilters("(MTAC-GPIOB)|(MMTAC-GPIOI)"); const std::regex Device::mfserFilters("(MTAC-MFSER-DTE)|(MTAC-MFSER-DCE)|(MTR-)(.*)"); const std::regex Device::serialModeFilter("(.*)(serial-mode)"); const std::regex Device::storeFilters("(.*)(mac-)(.*)|(.*)(-id)|(uuid)|(.*)(/eui)|(.*)(/cdone)|(.*)(hw-version)|(imei)|(capability)(.*)|(radio-reset-backoff-seconds)|(modalias)|(power)|((subsystem)(.*))|(uevent)|(board-temperature)|(reset)|(led3)|(led-ls)|(usbhd-ps-oc)|(.*)(adc[0-9])|(.*)(din[0-9])|(gpi[0-9])|(gpi[0-9][0-9])"); const std::regex Device::showFilters("(modalias)|(subsystem)|(uevent)"); Device::Device() { isRoot = !getuid(); verbose = false; } void Device::exitHandler(int code) { if (code != 0) { logError("exiting with " + std::to_string(code)); } exit(code); } bool Device::fileExists(std::string file) { struct stat buffer; return (stat (file.c_str(), &buffer) == 0) ? true : false; } mode_t Device::fileType(std::string file) { struct stat buf; stat (file.c_str(), &buf); return buf.st_mode & S_IFMT; } void Device::getSystemTreeJson(const char * dir_name) { std::string fullPath = SYSFS_PLATFORM + std::string(dir_name); DIR * d = opendir (fullPath.c_str()); if (!d) { logError("Cannot open directory " + fullPath); exitHandler(99); } while (1) { struct dirent * entry; const char * d_name; entry = readdir (d); // Gets subsequent entries from "d" if (!entry) { // If there are no more entries, exit break; } d_name = entry->d_name; // Get file name if (!(entry->d_type & DT_DIR)) { std::string fileData; MTS::System::readFile(fullPath + "/" + std::string(d_name), fileData); fileData = MTS::Text::trim(fileData); if (strlen(dir_name) > 0) { if (std::binary_search(apIdentifiers.begin(), apIdentifiers.end(), dir_name) && !regex_match(d_name, apFilters)) { if (accessoryCard.IsNull()) { accessoryCard.SetObject(); } else if (accessoryCard.HasMember(d_name)) { accessoryCards.PushBack(accessoryCard, accessoryCardsAlloc); accessoryCard.SetObject(); } if (strcmp(d_name, "product-id") == 0) { if (regex_match(fileData, lora15Filters)) { setupLora15(fileData, dir_name); } else if (regex_match(fileData, loraG16Filters)) { setupLoraG16(fileData, dir_name); } else if (regex_match(fileData, loraG64Filters)) { setupLoraG64(fileData, dir_name); } else if (regex_match(fileData, gpiobFilters)) { setupGpiob(fileData, dir_name); } else if (regex_match(fileData, mfserFilters)) { setupMfser(fileData, dir_name); } } accessoryCard.AddMember(rapidjson::Value().SetString(toCamelCase(d_name).c_str(), accessoryCardsAlloc), rapidjson::Value().SetString(fileData.c_str(), accessoryCardsAlloc), accessoryCardsAlloc); } else if (strcmp (dir_name, "capability") == 0 && fileData == "1") { capabilityList[toCamelCase(d_name)] = true; } } else if ((entry->d_type != DT_LNK)){ if (deviceInfoList.count(toCamelCase(d_name)) > 0) { deviceInfoList[toCamelCase(d_name)] = fileData; } else if (strcmp(d_name, "hw-version") == 0) { deviceInfoList["hardwareVersion"] = fileData; } else if (strcmp(d_name, "mac-eth") == 0) { deviceInfoList["macAddress"] = fileData; } else if (strcmp(d_name, "has-radio") == 0 && fileData == "1") { capabilityList["cell"] = true; } } } if (entry->d_type & DT_DIR) { /* Check that the directory is not "d" or d's parent. */ if (strcmp (d_name, "..") != 0 && strcmp (d_name, ".") != 0) { std::string path = fullPath + "/" + std::string(d_name); if (path.length() >= PATH_MAX) { logError("Path length has got too long.\n"); exitHandler(99); } getSystemTreeJson(d_name); /* Recursively call with the new path */ } } } if (closedir (d)) { /* After going through all the entries, close the directory. */ logError("Could not close " + std::string(fullPath)); exitHandler(errno); } } void Device::init() { if (!isRoot) { logError("Must be root to generate device_info.json"); exitHandler(99); } load(); writeJson(); exitHandler(0); } void Device::json() { if (!isRoot) { logError("Must be root to generate json"); exitHandler(99); } load(); printJson(); exitHandler(0); } void Device::load() { deviceInfo.SetObject(); capabilities.SetObject(); accessoryCards.SetArray(); getSystemTreeJson(""); if (!accessoryCard.IsNull()) { accessoryCards.PushBack(accessoryCard, accessoryCardsAlloc); } mapFileToCapability(); mapFirmware(); for (const auto capability : capabilityList) { capabilities.AddMember(rapidjson::Value().SetString(capability.first.c_str(), capability.first.length(), accessoryCardsAlloc), capability.second, accessoryCardsAlloc); } for (const auto device : deviceInfoList) { deviceInfo.AddMember(rapidjson::Value().SetString(device.first.c_str(), device.first.length(), alloc), rapidjson::Value().SetString(device.second.c_str(), device.second.length(), alloc), alloc); } deviceInfo.AddMember("capabilities", capabilities, alloc); deviceInfo.AddMember("accessoryCards", accessoryCards, alloc); } void Device::logError(std::string info) { printf("error: %s\n", info.c_str()); } void Device::logInfo(std::string info) { if (verbose) { printf("info: %s\n", info.c_str()); } } void Device::mapFileToCapability() { if (fileType("/opt/node-red") == S_IFDIR) { /* node-red is a directory */ capabilityList["nodeRed"] = true; } if (fileType("/opt/lora/lora-network-server") == S_IFREG) { /* lora-network-server is a regular file */ capabilityList["loraNetworkServer"] = true; } if (fileType("/dev/cdc-wdm0") == S_IFCHR) { /* Cellular modem is wwan/qmi character device */ capabilityList["cellWwan"] = true; } if (fileType("/dev/ext_serial") == S_IFCHR) { /* ext_serial is a character device */ capabilityList["externalSerialPort"] = true; } } void Device::mapFirmware() { std::ifstream file(FIRMWARE_FILE); if (!file.is_open()){ return; } std::string line; while (std::getline(file, line)){ if (line.find(FIRMWARE_VERSION) != std::string::npos){ deviceInfoList["firmware"] = line.substr(line.find(" ") + 1); } else if (line.find(FIRMWARE_DATE) != std::string::npos){ deviceInfoList["firmwareDate"] = line.substr(line.find(" ") + 1); } } } void Device::printDir(const std::string dir_name, std::vector &results) { std::string fullPath = SYSFS_PLATFORM + std::string(dir_name); DIR * d = opendir(fullPath.c_str()); if (!d) { logError("Cannot open directory " + std::string(fullPath)); exitHandler(99); } while (1) { struct dirent * entry = readdir (d); if (!entry) { break; } const char * d_name = entry->d_name; if (!(entry->d_type & DT_DIR)) { std::string result; if (dir_name.size() > 0) { result = (dir_name + "/"); } result.append(d_name); results.push_back(result); } if ((entry->d_type & DT_DIR) && strcmp (d_name, "..") != 0 && strcmp (d_name, ".") != 0) { std::string path = fullPath + "/" + std::string(d_name); if (path.length() >= PATH_MAX) { logError("Path length has got too long.\n"); exitHandler(99); } printDir(d_name, results); } } if (closedir (d)) { /* After going through all the entries, close the directory. */ logError("Could not close " + std::string(fullPath)); exitHandler(errno); } } void Device::printJson() { rapidjson::StringBuffer buffer; rapidjson::Writer writer(buffer); deviceInfo.Accept(writer); std::cout << buffer.GetString(); } void Device::printVersion (std::string name) { printf("%s %s\nCopyright (C) 2019 by Multi-Tech Systems\nThis program is free software; you may redistribute it under the terms of\nthe GNU General Public License version 2 or (at your option) any later version.\nThis program has absolutely no warranty.\n",name.c_str(), Version::version.c_str()); } void Device::printUsage(std::string program) { std::vector showResults; printf("Usage: %s [ OPTIONS ] OBJECT [--] [ ARGUMENTS ]\n", program.c_str()); printf("where OBJECT := {\n"); printf(" init |\n"); printf(" show SHOW-NAME |\n"); printf(" store STORE-NAME |\n"); printf(" json |\n"); printf(" }\n"); printf("\n"); printf(" SHOW-NAME := {\n"); printDir("", showResults); sort(showResults.begin(), showResults.end()); // Unix file tree is not sorted for (std::string showResult: showResults) { if (!regex_match(showResult, showFilters)) printf (" %s\n", showResult.c_str()); } printf(" }\n"); printf("\n"); printf(" STORE-NAME := {\n"); for (std::string showResult: showResults) { if (showResult == "radio-reset") { printf (" %s { 0 }\n", showResult.c_str()); } else if (showResult == "reset-monitor") { printf (" %s { pid short-signal long-signal [extra-long-signal] }\n", showResult.c_str()); } else if (showResult == "reset-monitor-intervals") { printf (" %s { short-interval long-interval }\n", showResult.c_str()); } else if (regex_match(showResult, serialModeFilter)) { printf (" %s { loopback | rs232 | rs485-half | rs422-485-full }\n", showResult.c_str()); } else if (!regex_match(showResult, storeFilters)) { printf (" %s BOOLEAN\n", showResult.c_str()); } } printf(" OPTIONS := {\n"); printf(" --verbose\n"); printf(" }\n"); printf("\n"); printf(" BOOLEAN := { OFF | ON }\n"); printf(" OFF := 0\n"); printf(" ON := 1\n"); printf("\n"); exitHandler(1); } void Device::setupLora15(std::string fileData, const char * dir_name) { capabilityList["lora"] = true; AccessoryCardLora15 mSPI(fileData, dir_name); mSPI.getFPGAVersion(); accessoryCard.AddMember("fpgaVersion", mSPI.getFPGAVersion(), accessoryCardsAlloc); } void Device::setupLoraG16(std::string fileData, const char * dir_name) { capabilityList["lora"] = true; std::string fpgaVersion; MTS::System::cmd(LORA_2_1_FPGA_VERSION, fpgaVersion); accessoryCard.AddMember("fpgaVersion", std::stoi(fpgaVersion), accessoryCardsAlloc); } void Device::setupLoraG64(std::string fileData, const char * dir_name) { capabilityList["lora"] = true; std::string fpgaVersion; std::string fpgaVersion2; MTS::System::cmd(LORA_2_1_FPGA_VERSION, fpgaVersion); MTS::System::cmd(LORA_2_1_EXT_FPGA_VERSION, fpgaVersion2); accessoryCard.AddMember("fpgaVersion", std::stoi(fpgaVersion), accessoryCardsAlloc); accessoryCard.AddMember("fpgaVersion2", std::stoi(fpgaVersion2), accessoryCardsAlloc); } void Device::setupGpiob(std::string fileData, const char * dir_name) { capabilityList["adc"] = true; capabilityList["din"] = true; capabilityList["dout"] = true; capabilityList["gpio"] = true; } void Device::setupMfser(std::string fileData, const char * dir_name) { capabilityList["rs232"] = true; capabilityList["rs422"] = true; capabilityList["rs485"] = true; capabilityList["serial"] = true; if (!fileExists("/dev/ext_serial") && strlen(dir_name) > 0) { std::string temp; MTS::System::cmd("ln -s /dev/ttyAP" + std::string(dir_name + strlen(dir_name) - 1) + " /dev/ext_serial", temp); } } void Device::show(std::string name) { std::string fileData; int32_t code = MTS::System::readFile(SYSFS_PLATFORM + name, fileData); if (code == 0) { printf("%s",fileData.c_str()); exitHandler(0); } else { logError("cat: can't open " + std::string(SYSFS_PLATFORM) + name + ": No such file or directory"); exitHandler(99); } } void Device::showTrigger(std::string name) { std::string fileData; int32_t code = MTS::System::readFile(LEDS_GPIO_DIR + name + "/trigger", fileData); if (code == 0) { printf("%s",fileData.c_str()); exitHandler(0); } else { logError("can't not open '" + std::string(LEDS_GPIO_DIR) + name + "/trigger': No such file or directory"); exitHandler(99); } } void Device::store(std::string name, std::string value) { if (!isRoot) { logError("Must be root to store to " + name); exitHandler(99); } logInfo("setting " + name + " to " + value); std::ofstream fileToWrite(SYSFS_PLATFORM + name); if (!fileToWrite.bad()) { fileToWrite << value; fileToWrite.close(); exitHandler(0); } else { logError("can't not open '" + std::string(SYSFS_PLATFORM) + name + "': No such file or directory"); exitHandler(99); } } void Device::storeTrigger(std::string name, std::string value) { if (!isRoot) { logError("Must be root to storeTrigger to " + name + "/trigger"); exitHandler(99); } logInfo("setting " + name + " to " + value); std::ofstream fileToWrite(LEDS_GPIO_DIR + name + "/trigger"); if (!fileToWrite.bad()) { fileToWrite << value; fileToWrite.close(); exitHandler(0); } else { logError("can't not open '" + std::string(LEDS_GPIO_DIR) + name + "/trigger': No such file or directory"); exitHandler(99); } } std::string Device::toCamelCase(const char * d_name) { std::string camelString = strdup(d_name); std::string tempString = ""; for (size_t x = 0; x < camelString.length(); x++){ if (camelString[x] == '-' || camelString[x] == '_'){ tempString = camelString.substr(x + 1, 1); transform(tempString.begin(), tempString.end(), tempString.begin(), toupper); camelString.erase(x, 2); camelString.insert(x, tempString); } } return camelString; } void Device::Verbose(const bool val) { verbose = val; } bool Device::Verbose() { return verbose; } void Device::writeJson() { rapidjson::StringBuffer buffer; rapidjson::Writer writer(buffer); deviceInfo.Accept(writer); std::ofstream os(DEVICE_INFO_FILE); if (!os) { logError("Can't write to " + std::string(DEVICE_INFO_FILE)); exitHandler(99); } else { os << buffer.GetString(); } }