commit 1a6dce142c99fb947a8c67015f47f20bfc0d5c79 Author: Lyubomir Penev Date: Sun Jan 25 15:36:43 2026 +0100 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..89cc49c --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.pio +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..080e70d --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,10 @@ +{ + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": [ + "platformio.platformio-ide" + ], + "unwantedRecommendations": [ + "ms-vscode.cpptools-extension-pack" + ] +} diff --git a/include/README b/include/README new file mode 100644 index 0000000..49819c0 --- /dev/null +++ b/include/README @@ -0,0 +1,37 @@ + +This directory is intended for project header files. + +A header file is a file containing C declarations and macro definitions +to be shared between several project source files. You request the use of a +header file in your project source file (C, C++, etc) located in `src` folder +by including it, with the C preprocessing directive `#include'. + +```src/main.c + +#include "header.h" + +int main (void) +{ + ... +} +``` + +Including a header file produces the same results as copying the header file +into each source file that needs it. Such copying would be time-consuming +and error-prone. With a header file, the related declarations appear +in only one place. If they need to be changed, they can be changed in one +place, and programs that include the header file will automatically use the +new version when next recompiled. The header file eliminates the labor of +finding and changing all the copies as well as the risk that a failure to +find one copy will result in inconsistencies within a program. + +In C, the convention is to give header files names that end with `.h'. + +Read more about using header files in official GCC documentation: + +* Include Syntax +* Include Operation +* Once-Only Headers +* Computed Includes + +https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html diff --git a/include/serialConnector.h b/include/serialConnector.h new file mode 100644 index 0000000..bcecbbb --- /dev/null +++ b/include/serialConnector.h @@ -0,0 +1,66 @@ +#pragma once +#include + +class SerialConnector +{ + +private: + String previouSent; + String received; + String command; + + void (*acknowledgeHandler)(String args); + void (*repeatHandler)(String args); + void (*calibrationBeginHandler)(String args); + void (*calibrationInteruptHandler)(String args); + + // Convert each character of `cmd` to its hexadecimal representation + // and return the concatenated hex string. + String stringToHex(String cmd); + + // Compute a numeric 'check' value from `cmd` by converting chars to + // their hex digits and summing/concatenating (project-specific logic). + // Returns an integer suitable for use as a check-bit value. + int stringToCheckNum(String cmd); + + // Return the index of character `ch` in `str`, or -1 if not found. + int getCharIndex(char ch, String str); + + // Parse an incoming framed message and return the command portion + // (everything before the first '#'). + String getCommandFromIncomming(String incomming); + + // Parse an incoming framed message and return the arguments portion + // (text between the first and second '#'). + String getArgsFromIncomming(String incomming); + + // Parse an incoming framed message and return the final check-bit + // portion (everything after the second '#'). + String getCheckBitFromIncomming(String incomming); + + // Verify that the provided `checkBit` matches the computed check for + // the combination of `cmd` and `args`. Returns true when valid. + bool verifyCheckBit(String cmd, String args, int checkBit); + + // Send a repeat request to the remote side to indicate the last + // message should be re-sent. + void repeat(); + + // Send an acknowledgement message containing `checkBit` back to the + // remote side. + void acknowledge(int checkBit); + + // After sending a command, wait for and handle any follow-up messages + // (repeat or acknowledgement) related to `cmd`. + void afterSendCheck(String cmd); + +public: + void cycle(); + + void onAcknowledge(void (*handler)(String args)); + void onRepeat(void (*handler)(String args)); + void onCalibrationBegin(void (*handler)(String args)); + void onCalibrationInterupt(void (*handler)(String args)); + + void sendCommand(String cmd, String args); +}; \ No newline at end of file diff --git a/lib/README b/lib/README new file mode 100644 index 0000000..9379397 --- /dev/null +++ b/lib/README @@ -0,0 +1,46 @@ + +This directory is intended for project specific (private) libraries. +PlatformIO will compile them to static libraries and link into the executable file. + +The source code of each library should be placed in a separate directory +("lib/your_library_name/[Code]"). + +For example, see the structure of the following example libraries `Foo` and `Bar`: + +|--lib +| | +| |--Bar +| | |--docs +| | |--examples +| | |--src +| | |- Bar.c +| | |- Bar.h +| | |- library.json (optional. for custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html +| | +| |--Foo +| | |- Foo.c +| | |- Foo.h +| | +| |- README --> THIS FILE +| +|- platformio.ini +|--src + |- main.c + +Example contents of `src/main.c` using Foo and Bar: +``` +#include +#include + +int main (void) +{ + ... +} + +``` + +The PlatformIO Library Dependency Finder will find automatically dependent +libraries by scanning project source files. + +More information about PlatformIO Library Dependency Finder +- https://docs.platformio.org/page/librarymanager/ldf.html diff --git a/platformio.ini b/platformio.ini new file mode 100644 index 0000000..58921d4 --- /dev/null +++ b/platformio.ini @@ -0,0 +1,14 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[env:nano_33_iot] +platform = atmelsam +board = nano_33_iot +framework = arduino diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..8e74fa0 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,53 @@ +#include +#include + +SerialConnector *conn = new SerialConnector(); +void calibrationBegin(String args); + +void setup() +{ + pinMode(11, OUTPUT); + digitalWrite(11, HIGH); + + pinMode(2, INPUT); + pinMode(3, INPUT); + pinMode(4, INPUT); + + Serial.begin(9600); + + // Trigered when a CAL-INT command is received + conn->onCalibrationInterupt(&calibrationBegin); +} + +void loop() +{ + conn->cycle(); + + if (!digitalRead(2)) + { + conn->sendCommand("CAL-NXT", "hdsfhdsl"); + } + + if (!digitalRead(3)) + { + conn->sendCommand("CAL-INT", "dgsyy"); + } + + if (!digitalRead(4)) + { + conn->sendCommand("READ", "nvcmxznm"); + } + + delay(300); +} + +void calibrationBegin(String args) +{ + for (int i = 0; i < args.toInt(); i++) + { + digitalWrite(11, LOW); + delay(300); + digitalWrite(11, HIGH); + delay(300); + } +} \ No newline at end of file diff --git a/src/serialConnector.cpp b/src/serialConnector.cpp new file mode 100644 index 0000000..4982693 --- /dev/null +++ b/src/serialConnector.cpp @@ -0,0 +1,257 @@ +#include + +// Main polling loop: read incoming serial frames, validate the check-bit, +// acknowledge valid messages and dispatch handlers based on the command. +void SerialConnector::cycle() +{ + if (Serial.available() > 3) + { + String raw = Serial.readStringUntil('@'); + String cmd = getCommandFromIncomming(raw); + String args = getArgsFromIncomming(raw); + + if (!verifyCheckBit(cmd, args, getCheckBitFromIncomming(raw).toInt())) + { + repeat(); + return; + } + + acknowledge(getCheckBitFromIncomming(raw).toInt()); + if (cmd == "CAL-BGN") + { + if (calibrationBeginHandler) + calibrationBeginHandler(args); + } + else if (cmd == "CAL-INT") + { + if (calibrationInteruptHandler) + calibrationInteruptHandler(args); + } + else + { + } + } +} + +// Convert each character in `cmd` into its ASCII hexadecimal representation +// and return the concatenated hex string. Example: "A" -> "41". +String SerialConnector::stringToHex(String cmd) +{ + int hex_dec; + String output = ""; + + for (const auto &item : cmd) + { + + hex_dec = int(item); + char hexaDeciNum[100]; + int i = 0; + + while (hex_dec != 0) + { + int temp = 0; + temp = hex_dec % 16; + if (temp < 10) + { + hexaDeciNum[i] = temp + 48; + i++; + } + else + { + hexaDeciNum[i] = temp + 55; + i++; + } + hex_dec = hex_dec / 16; + } + for (int j = i - 1; j >= 0; j--) + { + output += hexaDeciNum[j]; + } + } + + return output; +} + +// Produce a numeric check value for `cmd` by converting characters to +// their hexadecimal digits and summing their digit characters into an +// integer representation. This mirrors the protocol used for check-bits. +int SerialConnector::stringToCheckNum(String cmd) +{ + int hex_dec; + int output = 0; + + for (const auto &item : cmd) + { + + hex_dec = int(item); + char hexaDeciNum[100]; + int i = 0; + + while (hex_dec != 0) + { + int temp = 0; + temp = hex_dec % 16; + if (temp < 10) + { + hexaDeciNum[i] = temp + 48; + i++; + } + else + { + hexaDeciNum[i] = temp + 55; + i++; + } + hex_dec = hex_dec / 16; + } + for (int j = i - 1; j >= 0; j--) + { + output += hexaDeciNum[j]; + } + } + + return output; +} + +// Return the index of `ch` inside `str`. Returns -1 if `ch` is not found. +int SerialConnector::getCharIndex(char ch, String str) +{ + for (int i = 0; i < str.length(); i++) + { + if (str.charAt(i) == ch) + return i; + } + + return -1; +} + +// Extract the command portion from an incoming framed message. The +// protocol format is: ##@ — this returns . +String SerialConnector::getCommandFromIncomming(String incomming) +{ + int cmdEnd = getCharIndex('#', incomming); + return incomming.substring(0, cmdEnd); +} + +// Extract the arguments portion from an incoming message: returns the +// text between the first and second '#' characters. +String SerialConnector::getArgsFromIncomming(String incomming) +{ + int cmdEnd = getCharIndex('#', incomming); + String cm = incomming.substring(0, cmdEnd); + + String afterCmd = incomming.substring(cmdEnd + 1, incomming.length()); + int cmdArgsEnd = getCharIndex('#', afterCmd); + return afterCmd.substring(0, cmdArgsEnd); +} + +// Extract the check-bit (final field) from an incoming message: the text +// after the second '#' character (up to the '@' frame terminator). +String SerialConnector::getCheckBitFromIncomming(String incomming) +{ + int cmdEnd = getCharIndex('#', incomming); + String cm = incomming.substring(0, cmdEnd); + + String afterCmd = incomming.substring(cmdEnd + 1, incomming.length()); + int cmdArgsEnd = getCharIndex('#', afterCmd); + return afterCmd.substring(cmdArgsEnd + 1, afterCmd.length()); +} + +// Verify that `checkBit` equals the computed check for `#`. +// Returns true when they match, false otherwise. +bool SerialConnector::verifyCheckBit(String cmd, String args, int checkBit) +{ + String toCheck = cmd + "#" + args; + + if (stringToCheckNum(toCheck) == checkBit) + return true; + else + return false; +} + +// Send a repeat (RPT) frame asking the remote to resend the last message. +void SerialConnector::repeat() +{ + String cmd = "RPT##410@"; + Serial.print(cmd); +} + +// Send an acknowledgement (ACKG) frame that includes the original +// command's check-bit so the sender can confirm delivery. +void SerialConnector::acknowledge(int checkBit) +{ + String cmd = "ACKG"; + String toSend = cmd + "#" + (String)checkBit; + int checkNum = stringToCheckNum(toSend); + String final = toSend + "#" + (String)checkNum + "@"; + Serial.print(final); +} + +// Build and send a framed command with `cmd` and `args`. Appends a computed +// check-bit and frame terminator, then waits for follow-up (ack/repeat). +void SerialConnector::sendCommand(String cmd, String args) +{ + String toSend = cmd + "#" + args; + int checkNum = stringToCheckNum(toSend); + String final = toSend + "#" + (String)checkNum + "@"; + Serial.print(final); + + afterSendCheck(final); +} + +// After sending a command, read the next frame and handle either a +// repeat (RPT) request or an acknowledgement (ACKG). For RPT, resend the +// original command. For ACKG, verify the returned check-bit matches. +void SerialConnector::afterSendCheck(String cmd) +{ + String raw = Serial.readStringUntil('@'); + if (raw == "") + afterSendCheck(cmd); + + String comm = getCommandFromIncomming(raw); + + if (comm == "RPT") + { + sendCommand(getCommandFromIncomming(cmd), getArgsFromIncomming(cmd)); + } + else if (comm == "ACKG") + { + int check = getArgsFromIncomming(raw).toInt(); + int cmdCheck = getCheckBitFromIncomming(cmd).toInt(); + + if (check != cmdCheck) + { + // TODO: Implement a command to resend previous command + } + else + { + } + } +} + +// Register a callback to be invoked when an acknowledgement frame is +// received. The callback receives the acknowledgement's args string. +void SerialConnector::onAcknowledge(void (*handler)(String args)) +{ + acknowledgeHandler = handler; +} + +// Register a callback to be invoked when a repeat (RPT) frame is +// received. The handler receives the repeat frame's args. +void SerialConnector::onRepeat(void (*handler)(String args)) +{ + repeatHandler = handler; +} + +// Register a callback for the 'CAL-BGN' command. The handler receives the +// calibration arguments string when calibration begins. +void SerialConnector::onCalibrationBegin(void (*handler)(String args)) +{ + calibrationBeginHandler = handler; +} + +// Register a callback for the 'CAL-INT' command. The handler receives the +// calibration interrupt arguments string when an interrupt occurs. +void SerialConnector::onCalibrationInterupt(void (*handler)(String args)) +{ + calibrationInteruptHandler = handler; +} \ No newline at end of file diff --git a/test/README b/test/README new file mode 100644 index 0000000..9b1e87b --- /dev/null +++ b/test/README @@ -0,0 +1,11 @@ + +This directory is intended for PlatformIO Test Runner and project tests. + +Unit Testing is a software testing method by which individual units of +source code, sets of one or more MCU program modules together with associated +control data, usage procedures, and operating procedures, are tested to +determine whether they are fit for use. Unit testing finds problems early +in the development cycle. + +More information about PlatformIO Unit Testing: +- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html