From d7d9a3567a2c0eeebb40716fe3b2a9586f17e6a8 Mon Sep 17 00:00:00 2001 From: Christopher Vagnetoft Date: Thu, 3 Apr 2025 20:55:28 +0200 Subject: [PATCH] Initial commit --- README.md | 41 +++++++ example/gpio_funcs.cpp | 47 ++++++++ example/gpio_funcs.h | 13 +++ example/main.cpp | 236 +++++++++++++++++++++++++++++++++++++++++ include/shell.h | 15 +++ library.json | 20 ++++ src/shell.cpp | 59 +++++++++++ 7 files changed, 431 insertions(+) create mode 100644 README.md create mode 100644 example/gpio_funcs.cpp create mode 100644 example/gpio_funcs.h create mode 100644 example/main.cpp create mode 100644 include/shell.h create mode 100644 library.json create mode 100644 src/shell.cpp diff --git a/README.md b/README.md new file mode 100644 index 0000000..637e92d --- /dev/null +++ b/README.md @@ -0,0 +1,41 @@ +# SerialShell for embedded devices + +## Installing + +Add to your platformio.ini: + +```text +lib_deps = https://dev.noccylabs.info/noccy/pio-serialshell.git#0.1.0 \ + your_other_deps... +``` + +## Example usage + +```c +void cmdHandler(char* buf, int len) { + // Tokenize the string on spaces + char* tok = strtok(buf, " "); + // If tokenization fails, return + if (tok == NULL) return; + + // Check if the "help" command was entered + if (strncmp(tok, "help\0", 5) == 0) { + // ... + } + +} + +void setup() { + // Setup serial + Serial.begin(115200); + // Initialize the shell, set the prompt, and assign the callback + shell_init(); + shell_prompt("cmd> "); + shell_callback(cmdHandler); +} + +void loop() { + // Process shell stuff + shell_loop(); +} +``` diff --git a/example/gpio_funcs.cpp b/example/gpio_funcs.cpp new file mode 100644 index 0000000..ef000c3 --- /dev/null +++ b/example/gpio_funcs.cpp @@ -0,0 +1,47 @@ +#include "gpio_funcs.h" + +void gpioStatusEsp8266() { + volatile u32_t *gpio_out = (volatile u32_t*)0x60000300; + volatile u32_t *gpio_ena = (volatile u32_t*)0x6000030C; + volatile u32_t *gpio_in = (volatile u32_t*)0x60000318; + for (int i = 0; i < NUM_DIGITAL_PINS; i++) { + int bit = 1 << i; + Serial.printf(" D%d dir=%s in=%s strap=%s out=%s\n", + i, + (*gpio_ena & bit) ? "OUTPUT" : "INPUT", + (*gpio_in & bit) ? "HIGH" : "LOW", + (*gpio_in & (bit << 16)) ? "HIGH" : "LOW", + (*gpio_out & bit) ? "HIGH" : "LOW" + ); + } +} + +void gpioScanArduino() { + for (int i = 0; i < NUM_DIGITAL_PINS; i++) { + int mode = getPinMode(i); + Serial.printf(" D%d dir=%s read=%s\n", i, (mode==OUTPUT) ? "OUTPUT" : (mode==INPUT_PULLUP ? "INPUT_PULLUP" : "INPUT"), digitalRead(i) ? "HIGH" : "LOW"); + } +} + + +void gpioScan() { + #ifdef ESP8266 + gpioStatusEsp8266(); + #else + gpioScanArduino(); + #endif + } + +int getPinMode(int pin) { + uint8_t bit = digitalPinToBitMask(pin); + uint8_t port = digitalPinToPort(pin); + volatile auto *reg = portModeRegister(port); + volatile auto *out = portOutputRegister(port); + volatile auto *in = portInputRegister(port); + + // Serial.printf("d=%d mask=%02x mode=%04x|%d out=%04x|%d in=%04x|%d\n", pin, bit, *reg, (*reg&bit), *out, (*out&bit), *in, (*in&bit)); + + if (*reg & bit) return (OUTPUT); + + return ((*out & bit) ? INPUT_PULLUP : INPUT); +} diff --git a/example/gpio_funcs.h b/example/gpio_funcs.h new file mode 100644 index 0000000..318ad8a --- /dev/null +++ b/example/gpio_funcs.h @@ -0,0 +1,13 @@ +#pragma once + +#include +#include "ESP8266WiFi.h" +#include +#include +#include +#include + +void gpioStatusEsp8266(); +void gpioScanArduino(); +void gpioScan(); +int getPinMode(int pin); \ No newline at end of file diff --git a/example/main.cpp b/example/main.cpp new file mode 100644 index 0000000..927bcac --- /dev/null +++ b/example/main.cpp @@ -0,0 +1,236 @@ +#include +#include "ESP8266WiFi.h" +#include +#include +#include +#include +#include "gpio_funcs.h" + +void scanWifi(); +void scanI2c(); + +void cmdHandler(char* buf, int len) { + + char* tok = strtok(buf, " "); + + if (tok == NULL) return; + + if (strncmp(tok, "help\0", 5) == 0) { + Serial.println("echo Toggle hard echo"); + Serial.println("status Show device status"); + Serial.println("set Update a setting value"); + Serial.println("get Get a setting value"); + Serial.println("list List all settings"); + Serial.println("undo Reload NVRAM and discard changes"); + Serial.println("commit Commit settings to NVRAM"); + Serial.println("scan Scan wifi,i2c"); + Serial.println("gpio Manipulate GPIO"); + Serial.println("reset Reset (restart) the device"); + return; + } + + if (strncmp(tok, "status\0", 7) == 0) { + // char* status = strtok(NULL, " "); + Serial.printf("Hardware.: %s (%s) %s\n", BOARD, BOARD_VARIANT, BOARD_MCU); + Serial.printf("Serial...: %08x\n", system_get_chip_id()); + Serial.printf("WiFi.....: %s (ssid:%s,ip:%s)\n", "connected", "HOtSpOt", "0.0.0.0"); + Serial.printf("MQTT.....: %s (server:%s,user:%s)\n", "disconnected", "10.1.4.2", "d1"); + return; + } + + /* + echo on + echo off + echo "message" + */ + if (strncmp(tok, "echo\0", 5) == 0) { + char* echostate = strtok(NULL, " "); + if (echostate == NULL) { + Serial.println("E: Expected on or off"); + } else { + if (strncmp(echostate, "on\0", 3) == 0) { + Serial.println("Echo on"); + shell_echo(true); + } else if (strncmp(echostate, "off\0", 4) == 0) { + Serial.println("Echo off"); + shell_echo(false); + } else { + + } + } + return; + } + + /* + config + config open GROUP + config set KEY VALUE + config get KEY + config commit + */ + if (strncmp(tok, "set\0", 4) == 0) { + char* arg1 = strtok(NULL, " "); + if (arg1 == NULL) { + Serial.println("E: Missing setting name"); + } else { + char* arg2 = strtok(NULL, "\0"); + if (arg2 == NULL) { + Serial.println("E: Missing setting value"); + } else { + Serial.printf("set: %s = %s\n", arg1, arg2); + } + } + return; + } + + /* + scan → show usage + scan wifi + scan i2c + */ + if (strncmp(tok, "scan\0", 5) == 0) { + char* scantype = strtok(NULL, " "); + if (scantype == NULL) { + Serial.println("E: Missing scan type"); + } else { + if (strncmp(scantype,"wifi\0",5) == 0) { + scanWifi(); + } else if (strncmp(scantype,"i2c\0",4) == 0) { + scanI2c(); + } else { + Serial.println("E: Invalid scan type"); + } + return; + } + return; + } + + /* + gpio → show usage + gpio scan → show all GPIO + gpio N → show GPIO N + gpio N {in|out} → set GPIO N direction + gpio N {high|low} → set GPIO N state + */ + if (strncmp(tok, "gpio\0", 5) == 0) { + char* scantype = strtok(NULL, " "); + if (scantype == NULL) { + // Serial.println("E: Missing gpio operation"); + gpioScan(); + return; + } else { + if (strncmp(scantype,"set\0",4) == 0) { + char* gpiosetpin = strtok(NULL, " "); + if (NULL == gpiosetpin) { + Serial.println("E: missing pin"); + return; + } + int dpin = atoi(gpiosetpin); + char* gpioset = strtok(NULL, " "); + if (NULL == gpioset) { + Serial.println("E: missing new state"); + return; + } + if (strncmp(gpioset, "high\0", 5) == 0) { + digitalWrite(dpin, HIGH); + Serial.printf(" D%d => high\n", dpin); + } else if (strncmp(gpioset, "low\0", 4) == 0) { + digitalWrite(dpin, LOW); + Serial.printf(" D%d => low\n", dpin); + } + return; + } else if (strncmp(scantype,"get\0",4) == 0) { + char* gpiogetpin = strtok(NULL, " "); + if (NULL == gpiogetpin) { + Serial.println("E: missing pin"); + return; + } + int dpin = atoi(gpiogetpin); + int getval = digitalRead(dpin); + Serial.printf(" D%d: %s\n", dpin, getval?"HIGH":"LOW"); + } else if (strncmp(scantype,"mode\0",5) == 0) { + char* gpiomodepin = strtok(NULL, " "); + if (NULL == gpiomodepin) { + Serial.println("E: missing pin"); + return; + } + int dpin = atoi(gpiomodepin); + char* gpiomode = strtok(NULL, " "); + if (NULL == gpiomode) { + Serial.println("E: missing mode"); + return; + } + if (strncmp(gpiomode, "in\0", 3) == 0) { + pinMode(dpin, INPUT); + Serial.printf(" D%d -> input\n", dpin); + } else if (strncmp(gpiomode, "out\0", 4) == 0) { + pinMode(dpin, OUTPUT); + Serial.printf(" D%d -> output\n", dpin); + } else { + Serial.printf("E: invalid mode"); + } + + + } else { + Serial.println("E: Invalid gpio operation"); + } + return; + } + return; + } + + /* + i2c + i2c scan → same as 'scan i2c' + i2c write ADDR DATA.. + i2c read ADDR LEN → read LEN bytes + */ + + Serial.println("E: Invalid command"); +} + +void scanI2c() { + byte error, address; + int nDevices; + + Serial.println("Starting I2C scan..."); + + nDevices = 0; + for(address = 1; address < 127; address++ ) + { + // The i2c_scanner uses the return value of + // the Write.endTransmisstion to see if + // a device did acknowledge to the address. + Wire.beginTransmission(address); + error = Wire.endTransmission(); + + if (error == 0) { + Serial.printf(" Found device at 0x%02x\n", address); + nDevices++; + } else if (error==4) { + Serial.printf(" Unknown error at 0x%02x\n", address); + } + } + Serial.printf("Scan complete (%d items)\n", nDevices); +} + +void scanWifi() { + Serial.println("Starting WiFi scan..."); + int numNetworks = WiFi.scanNetworks(); + for (int i = 0; i < numNetworks; i++) { + Serial.printf(" %s rssi=%d bssid=%s ch=%d enc=%d\n", WiFi.SSID(i).c_str(), WiFi.RSSI(i), WiFi.BSSIDstr(i).c_str(), WiFi.channel(i), WiFi.encryptionType(i)); + } + Serial.printf("Scan complete (%d items)\n", numNetworks); +} + + +void setup() { + Serial.begin(115200); + shell_init(); + shell_prompt("cmd> "); + shell_callback(cmdHandler); +} + +void loop() { + shell_loop(); +} diff --git a/include/shell.h b/include/shell.h new file mode 100644 index 0000000..6cbd3cd --- /dev/null +++ b/include/shell.h @@ -0,0 +1,15 @@ +#pragma once + +// SerialShell header + +void shell_init(); + +void shell_echo(bool echo); + +void shell_prompt(char* prompt); + +void shell_callback(void(*cb)(char*, int)); + +void shell_loop(); + +const int IBUF_MAX = 255; diff --git a/library.json b/library.json new file mode 100644 index 0000000..c4f6348 --- /dev/null +++ b/library.json @@ -0,0 +1,20 @@ +{ + "name": "SerialShell", + "version": "1.0.0", + "description": "", + "keywords": "serial, shell, command, interactive", + "repository": { + "type": "git", + "url": "https://dev.noccylabs.info/noccy/pio-serialshell.git" + }, + "authors": [ + { + "name": "Christopher Vagnetoft", + "email": "labs@noccy.com", + "maintainer": true + } + ], + "license": "GPL-2.0-or-later", + "frameworks": "*", + "platforms": "*" +} diff --git a/src/shell.cpp b/src/shell.cpp new file mode 100644 index 0000000..e0e981a --- /dev/null +++ b/src/shell.cpp @@ -0,0 +1,59 @@ +#include +#include "shell.h" + +bool m_echo = true; +char m_ibuf[256] = { 0 }; +int m_ibuf_pos = 0; +char m_prompt[10] = { 0 }; + +void (*m_callback)(char*, int); + +void shell_init() { + strcat(m_prompt, "#> "); +} + +void shell_echo(bool echo) { + m_echo = echo; +} + +void shell_prompt(char* prompt) { + strncpy(m_prompt, prompt, 10); +} + +void shell_callback(void(*cb)(char*, int)) { + m_callback = cb; +} + +void draw_prompt() { + Serial.print(m_prompt); +} + +void shell_loop() { + int c; + while (Serial.available()) { + c = Serial.read(); + if (c == 13 || c == 4) { + // if (m_echo) + Serial.write(10); + if (m_callback != nullptr && m_ibuf_pos>0) { + m_callback(m_ibuf, m_ibuf_pos); + } + draw_prompt(); + m_ibuf_pos = 0; + memset(m_ibuf, 0, IBUF_MAX); + } else if (c == 8 || c == 127) { + if (m_ibuf_pos > 0) { + m_ibuf[--m_ibuf_pos] = 0; + if (m_echo) + Serial.print("\x08 \x08"); + } + } else if (c >= 32 && c<127) { + if (m_ibuf_pos < IBUF_MAX) { + m_ibuf[m_ibuf_pos++] = c & 0xFF; + if (m_echo) + Serial.write(c); + } + } + } +} +