let fs = require('fs'); let commands = []; let cmdindex = {}; let channels = []; let chanindex = {}; let ios = []; let ioindex = {}; let drvs = []; let drvindex = {}; let drvdefines = {}; // try to save the "#if ENABLE_DRIVER_XY" let flags = []; let flagindex = {}; let cnsts = []; let cnstindex = {}; let evnts = []; let inFile = 0; let oldStyleFlagTitles = []; function mytrim(text) { text = text.trim(); if (text.startsWith('"')) text = text.slice(1, -1); return text; } function writeIfChanged(fname, data) { if (fs.existsSync(fname)) { const existing = fs.readFileSync(fname, "utf8"); if (existing.replace(/\r/g, "") === data.replace(/\r/g, "")) { // console.log("Skipping " + fname + " - no changes"); return; } } fs.writeFileSync(fname, data); console.log('wrote ' + fname); } function getFolder(name, cb) { //console.error('dir:'+name); let list = fs.readdirSync(name); for (let i = 0; i < list.length; i++) { let file = name + '/' + list[i]; let s = fs.statSync(file); if (s.isDirectory()) { getFolder(file, cb); } else { let sourceFile = file.toLowerCase().endsWith('.c') || file.toLowerCase().endsWith('.cpp'); let headerFile = file.toLowerCase().endsWith('.h'); if (sourceFile || headerFile) { inFile++; //console.error('file:'+file); let data = fs.readFileSync(file); let text = data.toString('utf-8'); let lines = text.split('\n'); let newlines = []; let modified = 0; for (let i = 0; i < lines.length; i++) { let line = lines[i].trim(); // like CMD_RegisterCommand("SetChannel", "", CMD_SetChannel, "qqqqq0", NULL); //parse enum starting with "typedef enum channelType_e {" /* if (headerFile && line.startsWith('typedef enum channelType_e {')) { newlines.push(lines[i]); let j; for (j = i; j < lines.length; j++) { let line2raw = lines[j]; let line2 = line2raw.trim(); if (line2.startsWith('//chandetail:')) { let commentlines = []; let j2; for (j2 = j; j2 < lines.length; j2++) { let l = lines[j2].trim(); if (l.startsWith('//chandetail:')) { l = l.slice('//chandetail:'.length); commentlines.push(l); newlines.push(lines[j2]); } else { break; } } // move our parsing forward to skip all found j = j2; let json = commentlines.join('\n'); try { let chan = JSON.parse(json); if (chanindex[chan.name]) { console.error('duplicate command docs at file: ' + file + ' line: ' + line); console.error(line); } else { channels.push(chan); console.log("Loading existing doc for " + chan.name); chanindex[chan.name] = chan; } } catch (e) { console.error('error in json at file: ' + file + ' line: ' + line + ' er ' + e); console.error(json); } } else if (line2.startsWith('//')) { newlines.push(line2); continue; } else if (line2.startsWith("ChType_")) { let chanName = line2.substr("ChType_".length); chanName = chanName.replace(/,$/, ""); console.log("Channel type name: " + chanName); let chan = { name: chanName, title: chanName, descr: mytrim("TODO"), enum: "ChType_"+chanName, file: file.slice(6), driver: mytrim(""), }; if (!chanindex[chan.name]) { console.log("Creating doc for " + chan.name); // it did not have a doc line before let json = JSON.stringify(chan); // insert CR at "fn": json = json.split('"title":'); json = json.join('\n"title":'); json = json.split('"descr":'); json = json.join('\n"descr":'); json = json.split('"enum":'); json = json.join('\n"enum":'); json = json.split('"file":'); json = json.join('\n"file":'); json = json.split('"driver":'); json = json.join('\n"driver":'); let jsonlines = json.split('\n'); for (let j = 0; j < jsonlines.length; j++) { jsonlines[j] = '\t//chandetail:' + jsonlines[j]; } newlines.push(...jsonlines); modified++; channels.push(chan); chanindex[chan.name] = chan; } newlines.push(lines[j]); } if (line2.endsWith('} channelType_t;')) { //newlines.push(line2raw); break; } } i = j; } */ if (headerFile && line.startsWith('typedef enum channelType_e {')) { newlines.push(lines[i]); let j; for (j = i; j < lines.length; j++) { let line2raw = lines[j]; let line2 = line2raw.trim(); if (line2.startsWith('//chandetail:')) { let commentlines = []; let j2; for (j2 = j; j2 < lines.length; j2++) { let l = lines[j2].trim(); if (l.startsWith('//chandetail:')) { l = l.slice('//chandetail:'.length); commentlines.push(l); newlines.push(lines[j2]); } else { break; } } // move our parsing forward to skip all found // we are allready BEHIND comments when we used break, // so we need to skip to j2-1 to handle the line in next loop j = j2 - 1; let json = commentlines.join('\n'); try { let chan = JSON.parse(json); if (chanindex[chan.name]) { console.error('duplicate channel docs at file: ' + file + ' line: ' + line); console.error(line); } else { channels.push(chan); //console.log("Loading existing doc for " + chan.name); chanindex[chan.name] = chan; } } catch (e) { console.error('error in json at file: ' + file + ' line: ' + line + ' er ' + e); console.error(json); } } else if (line2.startsWith('//')) { newlines.push(line2); continue; } else if (line2.startsWith("ChType_")) { let chanName = line2.substr("ChType_".length); chanName = chanName.replace(/,$/, ""); console.log("Channel type name: " + chanName); let chan = { name: chanName, title: chanName, descr: mytrim("TODO"), enum: "ChType_" + chanName, file: file.slice(6), driver: mytrim(""), }; if (!chanindex[chan.name]) { console.log("Creating doc for " + chan.name); // it did not have a doc line before let json = JSON.stringify(chan); // insert CR at "fn": json = json.split('"title":'); json = json.join('\n"title":'); json = json.split('"descr":'); json = json.join('\n"descr":'); json = json.split('"enum":'); json = json.join('\n"enum":'); json = json.split('"file":'); json = json.join('\n"file":'); json = json.split('"driver":'); json = json.join('\n"driver":'); let jsonlines = json.split('\n'); for (let j = 0; j < jsonlines.length; j++) { jsonlines[j] = '\t//chandetail:' + jsonlines[j]; } newlines.push(...jsonlines); modified++; channels.push(chan); chanindex[chan.name] = chan; } newlines.push(lines[j]); } if (line2.endsWith('} channelType_t;')) { //newlines.push(line2raw); break; } } i = j; } if (sourceFile && line.startsWith('const char* g_obk_flagNames[] = {')) { newlines.push(lines[i]); let j; for (j = i; j < lines.length; j++) { let line2raw = lines[j]; let line2 = line2raw.trim(); // console.log("line2 " + line2); if (line2.startsWith('//')) { newlines.push(line2); continue; } else if (line2.startsWith("\"[")) { let flagTitle = line2.trim().replace(/^"|"$/g, ''); // console.log("Old style flag title: " + flagTitle); if (flagTitle.endsWith('",')) { flagTitle = flagTitle.slice(0, -2); } if (flagTitle.endsWith(',')) { flagTitle = flagTitle.slice(0, -1); } oldStyleFlagTitles.push(flagTitle); newlines.push(lines[j]); } if (line2.endsWith('};')) { //newlines.push(line2raw); break; } } i = j; } if (headerFile && line.startsWith('#define')) { const parts = line.split(/[\s\t]+/); if (parts && parts.length > 2) { // The first part is the #define directive, which can be ignored const flagName = parts[1]; // OBK_FLAG_MQTT_BROADCASTLEDPARAMSTOGETHER const flagValue = parts[2]; // 0 // console.log("Define " + flagName); if (flagName.startsWith("OBK_FLAG_")) { let flag = { index: flagValue, enum: flagName, title: "todo", file: file.slice(6), }; if (!flagindex[flag.index]) { // console.log("Creating flag " + flag.enum + " with index " + flag.index); flags.push(flag); flagindex[flag.index] = flag; } } } } // test for events (in cmd_eventHandlers.c) if (sourceFile && line.startsWith('int EVENT_ParseEventName')) { let j; let event; let foundif = ""; for (j = i; j < lines.length; j++) { let line2raw = lines[j]; let line2 = line2raw.trim(); if (line2.endsWith('return CMD_EVENT_NONE;')) { break; } if (line2.startsWith("#if ")) { foundif = line2.replace(/^#if /, ""); } if (line2.startsWith("#endif")) { foundif = ""; } if (/if.*str[ni]*cmp/.test(line2)) { let Ename = line2.replace(/if.*str[ni]*cmp\(s,[ ]*"/, "").replace(/"[^"]*$/, ''); if (Ename == 'channel') { Ename = Ename.replace("channel", "channel"); // special case for channels and its number (...atoi ...) } let l = lines[++j].trim(); i++; let CMD = l.replace(/^.*return /, "").replace(";", ""); if (CMD.startsWith('CMD_EVENT_CHANGE_CHANNEL0')) { CMD = CMD.replace(/[ ]*\+[ ]*atoi.*/, ''); // special case for channels and its number (...atoi ...) } let event = { name: Ename, CMD: CMD, }; if (foundif != "") { event.ifdef = foundif; } evnts.push(event); } } i += j; } if (headerFile && line.startsWith('typedef enum ioRole_e {')) { newlines.push(lines[i]); let j; for (j = i; j < lines.length; j++) { let line2raw = lines[j]; let line2 = line2raw.trim(); if (line2.startsWith('//iodetail:')) { let commentlines = []; let j2; for (j2 = j; j2 < lines.length; j2++) { let l = lines[j2].trim(); if (l.startsWith('//iodetail:')) { l = l.slice('//iodetail:'.length); commentlines.push(l); newlines.push(lines[j2]); } else { break; } } // move our parsing forward to skip all found // we are allready BEHIND comments when we used break, // so we need to skip to j2-1 to handle the line in next loop j = j2 - 1; let json = commentlines.join('\n'); try { let io = JSON.parse(json); if (ioindex[io.name]) { console.error('duplicate io docs at file: ' + file + ' line: ' + line); console.error(line); } else { ios.push(io); ioindex[io.name] = io; } } catch (e) { console.error('error in json at file: ' + file + ' line: ' + line + ' er ' + e); console.error(json); } } else if (line2.startsWith('//')) { newlines.push(line2); continue; } else if (line2.startsWith("IOR_")) { let ioName = line2.substr("IOR_".length); ioName = ioName.replace(/,$/, ""); console.log("ionel type name: " + ioName); let io = { name: ioName, title: ioName, descr: mytrim("TODO"), enum: "IOR_" + ioName, file: file.slice(6), driver: mytrim(""), }; if (!ioindex[io.name]) { // it did not have a doc line before let json = JSON.stringify(io); // insert CR at "fn": json = json.split('"title":'); json = json.join('\n"title":'); json = json.split('"descr":'); json = json.join('\n"descr":'); json = json.split('"enum":'); json = json.join('\n"enum":'); json = json.split('"file":'); json = json.join('\n"file":'); json = json.split('"driver":'); json = json.join('\n"driver":'); let jsonlines = json.split('\n'); for (let j = 0; j < jsonlines.length; j++) { jsonlines[j] = '\t//iodetail:' + jsonlines[j]; } newlines.push(...jsonlines); modified++; ios.push(io); ioindex[io.name] = io; } newlines.push(lines[j]); } if (line2.endsWith('} ioRole_t;')) { //newlines.push(line2raw); break; } } i = j; } if (sourceFile && line.startsWith('static driver_t g_drivers[] = {')) { newlines.push(lines[i]); let j; let lasthash = "nothing yet"; for (j = i; j < lines.length; j++) { let line2raw = lines[j]; let line2 = line2raw.trim(); if (line2.startsWith('#if')) { // try finding #if ENABLE_DRIVER_XY so we can use it in cas of a duplicate driver lasthash = line2; } if (line2.startsWith('//drvdetail:')) { let commentlines = []; let j2; for (j2 = j; j2 < lines.length; j2++) { let l = lines[j2].trim(); if (l.startsWith('//drvdetail:')) { l = l.slice('//drvdetail:'.length); commentlines.push(l); newlines.push(lines[j2]); } else { break; } } // move our parsing forward to skip all found // we are allready BEHIND comments when we used break, // so we need to skip to j2-1 to handle the line in next loop j = j2 - 1; let json = commentlines.join('\n'); try { let drv = JSON.parse(json); if (drvindex[drv.name]) { console.error('duplicate driver docs (in "' + line + '") for drv.name="' + drv.name + '" at file: ' + file + ' -- actual line:' + line2); console.error('\tlast "#if" statement: "' + lasthash + '"' + '\n\tfirst defined with "#if" statement: "' + drvdefines[drv.name] + '"'); let tmpcmd = drvindex[drv.name]; delete tmpcmd.define; // we added "define" on the other element, but it's not present here if (JSON.stringify(tmpcmd) == JSON.stringify(drv)) console.error('\tshould be safe to ignore, because documentation is equal!'); else console.error('\tFirst found:\n\t\t"' + JSON.stringify(tmpcmd).replace(/,\"/g, "\n\t\t\t\"") + '"\n\tactual:\n\t\t"' + JSON.stringify(drv).replace(/,\"/g, "\n\t\t\t\"") + '"'); //console.error(line); } else { drv.define = 'Enabled by defining "' + lasthash.replace("defined(", "").replace(")", "").replace(/#if[^ ]* /, "") + '" for your platform in [obk_config.h](https://github.com/openshwprojects/OpenBK7231T_App/blob/main/src/obk_config.h) '; drvs.push(drv); drvindex[drv.name] = drv; drvdefines[drv.name] = lasthash; } } catch (e) { console.error('error in json at file: ' + file + ' line: ' + line + ' er ' + e); console.error(json); } } else if (line2.startsWith('//')) { newlines.push(line2raw); continue; } else if (line2.startsWith('#')) { newlines.push(line2); continue; } else { // console.log("line2 name: " + line2); const regex = /"([^"]+)"/; // This regular expression matches anything between quotes, but excludes the quotes themselves const startQuote = line2.indexOf('"') + 1; // Find the index of the first double-quote and add 1 to skip over it const endQuote = line2.indexOf('"', startQuote); // Find the index of the second double-quote starting from the first double-quote index let drvName = line2.substring(startQuote, endQuote); // Extract the substring between the first and second double-quotes if (endQuote != -1) { // drvName = drvName.replace(/,$/, ""); console.log("drv type name: " + drvName); let drv = { name: drvName, title: drvName, descr: mytrim("TODO"), requires: mytrim(""), }; if (!drvindex[drv.name]) { // it did not have a doc line before let json = JSON.stringify(drv); // insert CR at "fn": json = json.split('"title":'); json = json.join('\n"title":'); json = json.split('"descr":'); json = json.join('\n"descr":'); json = json.split('"requires":'); json = json.join('\n"requires":'); let jsonlines = json.split('\n'); for (let j = 0; j < jsonlines.length; j++) { jsonlines[j] = '\t//drvdetail:' + jsonlines[j]; } newlines.push(...jsonlines); modified++; drvs.push(drv); drvindex[drv.name] = drv; } newlines.push(lines[j]); // we found a driver definition before, so if its not a "one liner" we already copied before // we need to copy the next lines, until we find the closing "}," if (!lines[j].trim().endsWith('},')) { let j2; for (j2 = j + 1; j2 < lines.length; j2++) { let l = lines[j2].trim(); if (l.endsWith('},')) { newlines.push(lines[j2]); break; } else { newlines.push(lines[j2]); } } // move our parsing forward to skip all found // arguments, // so we need to skip to j2-1 to handle the line in next loop j = j2; } } } if (line2.endsWith('};')) { //newlines.push(line2raw); break; } } i = j; } if (sourceFile && line.startsWith('const constant_t g_constants[] = {')) { newlines.push(lines[i]); let j; for (j = i; j < lines.length; j++) { let line2raw = lines[j]; let line2 = line2raw.trim(); if (line2.startsWith('//cnstdetail:')) { let commentlines = []; let j2; for (j2 = j; j2 < lines.length; j2++) { let l = lines[j2].trim(); if (l.startsWith('//cnstdetail:')) { l = l.slice('//cnstdetail:'.length); commentlines.push(l); newlines.push(lines[j2]); } else { break; } } // move our parsing forward to skip all found // we are allready BEHIND comments when we used break, // so we need to skip to j2-1 to handle the line in next loop j = j2 - 1; let json = commentlines.join('\n'); try { let cnst = JSON.parse(json); if (cnstindex[cnst.name]) { console.error('duplicate constant docs at file: ' + file + ' line: ' + line); console.error(line); } else { cnsts.push(cnst); cnstindex[cnst.name] = cnst; } } catch (e) { console.error('error in json at file: ' + file + ' line: ' + line + ' er ' + e); console.error(json); } } else if (line2.startsWith('//')) { newlines.push(line2); continue; } else if (line2.startsWith('#')) { newlines.push(line2); continue; } else { // console.log("line2 name: " + line2); const regex = /"([^"]+)"/; // This regular expression matches anything between quotes, but excludes the quotes themselves const startQuote = line2.indexOf('"') + 1; // Find the index of the first double-quote and add 1 to skip over it const endQuote = line2.indexOf('"', startQuote); // Find the index of the second double-quote starting from the first double-quote index let cnstName = line2.substring(startQuote, endQuote); // Extract the substring between the first and second double-quotes if (endQuote != -1) { // cnstName = cnstName.replace(/,$/, ""); console.log("cnst type name: " + cnstName); let cnst = { name: cnstName, title: cnstName, descr: mytrim("TODO"), requires: mytrim(""), }; if (!cnstindex[cnst.name]) { // it did not have a doc line before let json = JSON.stringify(cnst); // insert CR at "fn": json = json.split('"title":'); json = json.join('\n"title":'); json = json.split('"descr":'); json = json.join('\n"descr":'); json = json.split('"requires":'); json = json.join('\n"requires":'); let jsonlines = json.split('\n'); for (let j = 0; j < jsonlines.length; j++) { jsonlines[j] = '\t//cnstdetail:' + jsonlines[j]; } newlines.push(...jsonlines); modified++; cnsts.push(cnst); cnstindex[cnst.name] = cnst; } newlines.push(lines[j]); } } if (line2.endsWith('};')) { //newlines.push(line2raw); break; } } i = j; } if (sourceFile && line.startsWith('//cmddetail:')) { let commentlines = []; let j; for (j = i; j < lines.length; j++) { let l = lines[j].trim(); if (l.startsWith('//cmddetail:')) { l = l.slice('//cmddetail:'.length); commentlines.push(l); newlines.push(lines[j]); } else { break; } } // move our parsing forward to skip all found i = j; let json = commentlines.join('\n'); try { let cmd = JSON.parse(json); if (cmdindex[cmd.name]) { console.error('duplicate command "' + cmd.name + '" docs at file: ' + file + ' line: ' + line + '\n\tfirst seen in "' + cmdindex[cmd.name].file + '"'); tmp = cmdindex[cmd.name]; // to test, if oth are equal (despit different file) construct a helper ... tmp.file = cmd.file; // ... and set its "file" to the actual value if (JSON.stringify(tmp) == JSON.stringify(cmd)) console.error('\tshould be safe to ignore, they are equal beside the file name!'); else { console.error('\tFirst found:\n\t\t"' + JSON.stringify(cmdindex[cmd.name]).replace(/,\"/g, "\n\t\t\t\"") + '"\n\tactual:\n\t\t"' + JSON.stringify(cmd).replace(/,\"/g, "\n\t\t\t\"")); } } { //console.error('new command "' + cmd.name + '" docs at file: ' + file + ' line: ' + line + ' -- json='+ json ); if (cmd.file !== file.slice(6)) { console.error('!!!! Posible wrong file location for command "' + cmd.name + '": found in file: "' + file.slice(6) + '" but claimes file: "' + cmd.file + '" - please verify! !!!!') console.error('\t Posible fix: sed -i \'' + (i - 3) + ',' + (i - 1) + ' { /cmddetail:\\"fn\\":\\"' + cmd.fn + '\"/ s%' + cmd.file + "%" + file.slice(6) + '%} \' src/' + file.slice(6)) console.error('\t test posible fix: sed -n \'' + (i - 3) + ',' + (i - 1) + ' {/cmddetail:\\"fn\\":\\"' + cmd.fn + '\"/ s%' + cmd.file + "%" + file.slice(6) + '% p }\' src/' + file.slice(6)) } commands.push(cmd); cmdindex[cmd.name] = cmd; } } catch (e) { console.error('error in json at file: ' + file + ' line: ' + line + ' er ' + e); console.error(json); } } // i may have changed... line = lines[i].trim(); if (sourceFile && line.startsWith('CMD_RegisterCommand(')) { line = line.slice('CMD_RegisterCommand('.length); parts = line.split(','); //cmddetail:{"name":"SetChannel", "args":"TODO", "fn":"CMD_SetChannel", "descr":"qqqqq0", "example":"", "file":""); // // CMD registration command line // CMD_RegisterCommand("SetChannel", CMD_SetChannel, NULL); let cmd = { name: mytrim(parts[0]), args: mytrim("TODO"), descr: mytrim(""), fn: mytrim(parts[1]), file: file.slice(6), requires: "", examples: "", }; //if (cmd.descr !== 'NULL'){ // console.log('replace "'+cmd.descr+'" with NULL'); // lines[i] = lines[i].replace(', NULL, NULL);', ', NULL);'); // modified++; //} // // 20250908 only used once to fix broken auto-generated fn entries with "NULL);" entry //if (cmdindex[cmd.name] && cmdindex[cmd.name].fn == 'NULL);'){ // console.log('replace fn "'+cmdindex[cmd.name].fn+'" with ' + cmd.fn); // console.log('newlines i-2 =' +newlines[i-2]); // cmdindex[cmd.name].fn = cmd.fn; // newlines[i-2] = newlines[i-2].replace('NULL);', cmd.fn); // console.log('newlines i-2 =' +newlines[i-2]); // modified++; //} if (cmdindex[cmd.name] && cmdindex[cmd.name].fn !== cmd.fn) { console.log('!!!! "' + cmd.name + '" in file ' + cmd.file + ' -- fn "' + cmdindex[cmd.name].fn + '" != called function "' + cmd.fn + '" !!!!'); console.log('\t possible fix: sed -i \'' + (i - 3) + ',' + (i - 1) + ' { /cmddetail:\\"fn\\":\\"' + cmdindex[cmd.name].fn + '\\"/ s%' + cmdindex[cmd.name].fn + "%" + cmd.fn + '% }\' src/' + file.slice(6)); console.log('\t test possible fix: sed -n \'' + (i - 3) + ',' + (i - 1) + ' { /cmddetail:\\"fn\\":\\"' + cmdindex[cmd.name].fn + '\\"/ s%' + cmdindex[cmd.name].fn + "%" + cmd.fn + '% p }\' src/' + file.slice(6)); } if (!cmdindex[cmd.name]) { // it did not have a doc line before let json = JSON.stringify(cmd); // insert CR at "fn": json = json.split('"descr":'); json = json.join('\n"descr":'); json = json.split('"fn":'); json = json.join('\n"fn":'); json = json.split('"examples":'); json = json.join('\n"examples":'); let jsonlines = json.split('\n'); for (let j = 0; j < jsonlines.length; j++) { jsonlines[j] = '\t//cmddetail:' + jsonlines[j]; } newlines.push(...jsonlines); modified++; commands.push(cmd); cmdindex[cmd.name] = cmd; } } newlines.push(lines[i]); } if (modified) { let newdata = newlines.join('\n'); try { // write new file as "proposal", so we can choose, if/how to change source file writeIfChanged(file + ".getcommands", newdata); } catch (e) { console.error('failed to update ' + file); } } inFile--; if (!inFile) { //if (cb) cb(); } } } } } // match flag titles console.log('starting'); function removeGetCommandsFiles(dir) { let list = fs.readdirSync(dir); for (let i = 0; i < list.length; i++) { let filename = list[i]; let file = dir + '/' + filename; let s = fs.statSync(file); if (s.isDirectory()) { removeGetCommandsFiles(file); } else { if (filename.endsWith('.getcommands')) { fs.unlinkSync(file); console.log('Removed ' + file); } } } } removeGetCommandsFiles('./src'); getFolder('./src'); function readJSONFile(path) { console.log("Going to read " + path); const data = fs.readFileSync(path, 'utf8'); const jsonData = JSON.parse(data); return jsonData; } let faq = readJSONFile("docs/json/faq.json"); let generic = readJSONFile("docs/json/generic.json"); let commandExamples = readJSONFile("docs/json/commandExamples.json"); let autoexecExamples = readJSONFile("docs/json/autoexecExamples.json"); let scriptExamples = readJSONFile("docs/json/scriptExamples.json"); let mqttTopics = readJSONFile("docs/json/mqttTopics.json"); let subpages = readJSONFile("docs/json/subpages.json"); for (let i = 0; i < scriptExamples.length; i++) { let path = "docs/" + scriptExamples[i].file; const data = fs.readFileSync(path, 'utf8'); scriptExamples[i].fileText = data; } for (let i = 0; i < autoexecExamples.length; i++) { let path = "docs/" + autoexecExamples[i].file; const data = fs.readFileSync(path, 'utf8'); autoexecExamples[i].fileText = data; } let faqmdshort = `# FAQ (Frequently Asked Questions) Here is the latest, up to date FAQ. This file was autogenerated by running 'node scripts/getcommands.js' in the repository. All questions/answers were taken from json file. `; let commandExamplesmdshort = `# Console command examples This file was autogenerated by running 'node scripts/getcommands.js' in the repository. All command examples were taken from json file. `; let autoexecsmdshort = `# Example 'autoexec.bat' files `; let scriptsmdshort = `# Example script files `; let channelsmdshort = `# Channel Types Here is the latest, up to date Channel Types. This file was autogenerated by running 'node scripts/getcommands.js' in the repository. All descriptions were taken from code. Do not add anything here, as it will overwritten with next rebuild. | ChannelType | Description | |:------------- | -------:| `; let iosmdshort = `# IO Pin Roles Here is the latest, up to date IO Roles. This file was autogenerated by running 'node scripts/getcommands.js' in the repository. All descriptions were taken from code. Do not add anything here, as it will overwritten with next rebuild. | RoleName | Description | |:------------- | -------:| `; let flagsmdshort = `# Flags Here is the latest, up to date Flags list. This file was autogenerated by running 'node scripts/getcommands.js' in the repository. All descriptions were taken from code. Do not add anything here, as it will overwritten with next rebuild. | ID | Description | |:--| -------:| `; let driversmdshort = `# Drivers Here is the latest, up to date drivers list. This file was autogenerated by running 'node scripts/getcommands.js' in the repository. All descriptions were taken from code. Remember that some drivers might not be yet enabled on certain platforms, but we can enable them for you per request. Some drivers might also be WIP. Do not add anything here, as it will overwritten with next rebuild. | Driver | Description | |:------------- |:----- | `; let constantsmdshort = `# Constants (script variables) Here is the latest, up to date constants list. This file was autogenerated by running 'node scripts/getcommands.js' in the repository. All descriptions were taken from code. Constants can be accessed in commands, so things like setChannel 15 2*$CH14+5 can work. Do not add anything here, as it will overwritten with next rebuild. | Code | Description | |:------------- | -----:| `; let mdshort = `# Commands Here is the latest, up to date command list. This file was autogenerated by running 'node scripts/getcommands.js' in the repository. All descriptions were taken from code.

Please remember that some commands might require starting a related driver first, so, for example, you must first do 'startDriver DoorSensor' and then 'DSEdge 1'. Also remember that commands can be put in autoexec.bat to run at startup (see Web Application->LittleFS tab).

Do not add anything here, as it will overwritten with next rebuild. | Command | Arguments | Description | |:------------- |:------------- |:----- | `; let mdlong = `# Commands Here is the latest, up to date command list. This file was autogenerated by running 'node scripts/getcommands.js' in the repository. All descriptions were taken from code. Do not add anything here, as it will overwritten with next rebuild. | Command | Arguments | Description | Location | |:------------- |:-------------:|:----- | ------:| `; let mqttText = `# MQTT topics of published variables Some MQTT variables are being published only at the startup, some are published periodically (if you enable "broadcast every N seconds" flag, default time is one minute, customizable with command mqtt_broadcastInterval), some are published only when a given value is changed. Below is the table of used publish topics (TODO: add full descriptions) Hint: in HA, you can use MQTT wildcard to listen to multiple publishes. OBK_DEV_NAME/# Publishes send by OBK device: | Topic | Sample Value | Description | |:------------- |:------------- | -----:| `; let evntsmdshort = `# Events Here is the latest, up to date documentation of possible events (Work in progress). This file was autogenerated by running 'node scripts/getcommands.js' in the repository. All descriptions were taken from code. Do not add anything here, as it will overwritten with next rebuild. | Event | Eventcmd | |:------------- |:------- | `; for (let i = 0; i < evnts.length; i++) { let ev = evnts[i]; let textshort = `| ${ev.name.replace("<", "<").replace(">", ">")} | ${ev.CMD.replace("<", "<").replace(">", ">")}${ev.ifdef ? '\nonly if defined:\n' + ev.ifdef.replace(/\|/g, "\\|") : ''} |`; // allow multi-row entries in table entries. textshort = textshort.replace(/\n/g, '
'); evntsmdshort += textshort + '\n'; } evntsmdshort += '\n'; function genReadMore(name) { let textmore = `See also [${name} on forum](https://www.elektroda.com/rtvforum/find.php?q=${name}).`; return textmore; } for (let i = 0; i < mqttTopics.publishes.length; i++) { let pub = mqttTopics.publishes[i]; let textshort = `| ${pub.topic} | ${pub.example} | ${pub.description} |`; // allow multi-row entries in table entries. textshort = textshort.replace(/\n/g, '
'); mqttText += textshort; mqttText += '\n'; } mqttText += ` Publishes received by OBK device: | Topic | Sample Value | Description | |:------------- |:------------- | -----:| `; let publishes_and_listens = mqttTopics.listens.concat(mqttTopics.publishes); for (let i = 0; i < mqttTopics.listens.length; i++) { let pub = mqttTopics.listens[i]; let textshort = `| ${pub.topic} | ${pub.example} | ${pub.description} |`; // allow multi-row entries in table entries. textshort = textshort.replace(/\n/g, '
'); mqttText += textshort; mqttText += '\n'; } for (let i = 0; i < flags.length; i++) { let flag = flags[i]; //console.log("Test flag " + i + " vs len " + oldStyleFlagTitles.length); if (i < oldStyleFlagTitles.length) { //console.log("Matched flag " + i); flag.descr = oldStyleFlagTitles[i]; } } // Sort commands by Name alphabetically commands.sort((a, b) => { if (a.name.toUpperCase() < b.name.toUpperCase()) return -1; if (a.name.toUpperCase() > b.name.toUpperCase()) return 1; return 0; }); function formatDesc(descBasic) { if (!descBasic.endsWith('.')) { descBasic += '.'; } if (descBasic.length > 0) { descBasic = descBasic.charAt(0).toUpperCase() + descBasic.slice(1); } return descBasic; } for (let i = 0; i < commands.length; i++) { /* like: | Command | Arguments | Description | | ------------- |:-------------:| -----:| | setChannel | [ChannelIndex][ChannelValue] | Sets a raw channel to given value. Relay channels are using 1 and 0 values. PWM channels are within [0,100] range. Do not use this for LED control, because there is a better and more advanced LED driver with dimming and configuration memory (remembers setting after on/off), LED driver commands has "led_" prefix. | | addChannel | [ChannelIndex][ValueToAdd][ClampMin][ClampMax] | Ads a given value to the channel. Can be used to change PWM brightness. Clamp min and max arguments are optional. | */ let cmd = commands[i]; let descMore = "

" + genReadMore(cmd.name); let descBasic = formatDesc(cmd.descr); let textshort = `| ${cmd.name} | ${cmd.args}${cmd.requires ? '\nReq:' + cmd.requires : ''} | ${descBasic}${cmd.examples ? '

Example: ' + cmd.examples : ''}${descMore} |`; let textlong = `| ${cmd.name} | ${cmd.args}${cmd.requires ? '\nReq:' + cmd.requires : ''} | ${descBasic}${cmd.examples ? '

Example: ' + cmd.examples : ''}${descMore} | File: ${cmd.file}\nFunction: ${cmd.fn} |`; // allow multi-row entries in table entries. textshort = textshort.replace(/\n/g, '
'); textlong = textlong.replace(/\n/g, '
'); mdshort += textshort; mdshort += '\n'; mdlong += textlong; mdlong += '\n'; } mdshort += '\n'; mdlong += '\n'; for (let i = 0; i < scriptExamples.length; i++) { let scr = scriptExamples[i]; scriptsmdshort += "" + scr.title + ""; scriptsmdshort += '\n'; scriptsmdshort += '
'; scriptsmdshort += "" + scr.description + ""; scriptsmdshort += '\n'; scriptsmdshort += '
'; scriptsmdshort += 'Requirements:'; scriptsmdshort += '\n'; for (let j = 0; j < scr.requirements.length; j++) { scriptsmdshort += '- ' + scr.requirements[j] + '
'; scriptsmdshort += '\n'; } scriptsmdshort += '\n'; scriptsmdshort += "```"; scriptsmdshort += scr.fileText; scriptsmdshort += '\n'; scriptsmdshort += "```"; scriptsmdshort += '\n'; scriptsmdshort += '\n'; scriptsmdshort += '\n'; } for (let i = 0; i < autoexecExamples.length; i++) { let exe = autoexecExamples[i]; autoexecsmdshort += "" + exe.title + ""; autoexecsmdshort += '\n'; autoexecsmdshort += '
'; autoexecsmdshort += '\n'; autoexecsmdshort += "```c++"; autoexecsmdshort += '\n'; autoexecsmdshort += exe.fileText; autoexecsmdshort += '\n'; autoexecsmdshort += "```"; autoexecsmdshort += '\n'; autoexecsmdshort += '\n'; autoexecsmdshort += '\n'; } for (let i = 0; i < faq.length; i++) { let q = faq[i]; faqmdshort += "**Question:** *" + q.question + "*"; faqmdshort += '
'; faqmdshort += "**A:** " + q.answer; faqmdshort += '\n'; faqmdshort += '\n'; faqmdshort += '\n'; } for (let i = 0; i < commandExamples.length; i++) { let ex = commandExamples[i]; commandExamplesmdshort += "" + ex.description; commandExamplesmdshort += '\n'; commandExamplesmdshort += '
'; commandExamplesmdshort += "```" + ex.command + "```"; commandExamplesmdshort += '
'; commandExamplesmdshort += '\n'; commandExamplesmdshort += '\n'; commandExamplesmdshort += '\n'; commandExamplesmdshort += '\n'; } for (let i = 0; i < cnsts.length; i++) { let cn = cnsts[i]; let textshort = `| ${cn.name} | ${cn.descr} |`; // allow multi-row entries in table entries. textshort = textshort.replace(/\n/g, '
'); constantsmdshort += textshort; constantsmdshort += '\n'; } for (let i = 0; i < drvs.length; i++) { let drv = drvs[i]; let descMore = "
" + genReadMore(drv.name); let descBasic = formatDesc(drv.descr); let textshort = `| ${drv.name} | ${descBasic}\n${drv.define}(for Details see [here](https://www.elektroda.com/rtvforum/topic4033833.html)).${descMore} |`; // allow multi-row entries in table entries. textshort = textshort.replace(/\n/g, '
'); driversmdshort += textshort; driversmdshort += '\n'; } for (let i = 0; i < channels.length; i++) { let chan = channels[i]; let textshort = `| ${chan.name} | ${chan.descr} |`; // allow multi-row entries in table entries. textshort = textshort.replace(/\n/g, '
'); channelsmdshort += textshort; channelsmdshort += '\n'; } for (let i = 0; i < flags.length; i++) { let flag = flags[i]; // let textshort = `| ${flag.index} | ${flag.enum} | ${flag.descr} |`; let textshort = `| ${flag.index} | ${flag.descr} |`; // allow multi-row entries in table entries. textshort = textshort.replace(/\n/g, '
'); flagsmdshort += textshort; flagsmdshort += '\n'; } for (let i = 0; i < ios.length; i++) { let io = ios[i]; let textshort = `| ${io.name} | ${io.descr} |`; // allow multi-row entries in table entries. textshort = textshort.replace(/\n/g, '
'); iosmdshort += textshort; iosmdshort += '\n'; } const dirPath = 'docs/json'; if (!fs.existsSync(dirPath)) { fs.mkdirSync(dirPath, { recursive: true }); } let links = []; function writeDocMD_Array(name, content, json, label, bWriteJSON, desc) { let fullName = "docs/" + name + ".md"; writeIfChanged(fullName, content); if (json) { console.log('There are ' + json.length + " " + name); } if (json && bWriteJSON) { let jsonName = `${dirPath}/${name}.json`; writeIfChanged(jsonName, JSON.stringify(json, null, 2)); } let link = { name: name, content: content, json: json, fullName: fullName, label: label, desc: desc }; links.push(link); } function writeDocMD_Page(page) { let fullName = "docs/" + page.mdName + ".md"; let pageText = "# " + page.topic + "\n" + page.content; writeIfChanged(fullName, pageText); page.fullMDPath = fullName; } writeDocMD_Array('ioRoles', iosmdshort, ios, "IO/Pin Roles", true, generic.pins); writeDocMD_Array('flags', flagsmdshort, flags, "Flags", true, generic.flags); writeDocMD_Array('drivers', driversmdshort, drvs, "Drivers", true, generic.drivers); writeDocMD_Array('constants', constantsmdshort, cnsts, "Script constants", true, generic.constants); writeDocMD_Array('channelTypes', channelsmdshort, channels, "Channel Types", true, generic.channels); writeDocMD_Array('faq', faqmdshort, faq, "FAQ", false, generic.faq); writeDocMD_Array('commands', mdshort, commands, "Console/Script commands", true, generic.commands); writeDocMD_Array('commandExamples', commandExamplesmdshort, commandExamples, "Command Examples", false, generic.commandExamples); writeDocMD_Array('autoexecExamples', autoexecsmdshort, autoexecExamples, "Autoexec.bat examples (configs)", false, generic.autoexecExamples); writeDocMD_Array('mqttTopics', mqttText, publishes_and_listens, "MQTT Topics", false, generic.mqttTopics); writeDocMD_Array('scriptExamples', scriptsmdshort, scriptExamples, "Script examples", false, generic.scriptExamples); writeDocMD_Array('commands-extended', mdlong, commands, "Console/Script commands [Extended Edition]", false, "More details on commands.") writeDocMD_Array('events', evntsmdshort, evnts, "Events", true, generic.events); let links_md = `# Documentation Here is the latest, up to date documentation. This file was autogenerated by running 'node scripts/getcommands.js' in the repository. The descriptions were taken from code and from the JSON files, and then converted to MarkDown. Do not add anything here, as it will overwritten with next rebuild.` links_md += `\n` links_md += `\n` links_md += `# Basic doc pages \n` for (let i = 0; i < subpages.length; i++) { let page = subpages[i]; writeDocMD_Page(page); let shortDesc = page.shortDesc; let base = "https://github.com/openshwprojects/OpenBK7231T_App/blob/main/"; let url = base + page.fullMDPath; let textshort = `- [${page.topic}](${url}) - ${shortDesc}`; // allow multi-row entries in table entries. textshort = textshort.replace(/\n/g, '
'); links_md += textshort; links_md += '\n'; } links_md += `\n` links_md += `# Doc tables ` links_md += ` | Section | Comment | |:------------- |------:| `; for (let i = 0; i < links.length; i++) { let link = links[i]; let base = "https://github.com/openshwprojects/OpenBK7231T_App/blob/main/"; let url = base + link.fullName; let total = link.json.length; let desc = link.desc; let textshort = `| [${link.label}](${url}) (${total} total) | ${desc} |`; // allow multi-row entries in table entries. textshort = textshort.replace(/\n/g, '
'); links_md += textshort; links_md += '\n'; } writeDocMD_Array('README', links_md);