diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..c500c9f --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,92 @@ +name: Build Binarys + +on: + push: + paths: + - 'src/**' + - '.github/workflows/build.yml' + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + build_linux: + name: Build linux binaries + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Configure CMake + run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + + - name: Build + run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} + + - name: Upload artifact + uses: actions/upload-artifact@v3 + with: + name: linux-binary + path: ${{github.workspace}}/build/src/libserialport.so + + build_windows: + name: Build win64 binaries + runs-on: windows-latest + + steps: + - uses: actions/checkout@v3 + + - name: Configure CMake + run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + + - name: Build + run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} + + - name: Upload artifact + uses: actions/upload-artifact@v3 + with: + name: windows-binary + path: ${{github.workspace}}\build\src\Release\serialport.dll + + commit: + needs: [build_windows, build_linux] + name: Commit binaries + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + with: + persist-credentials: false # otherwise, the token used is the GITHUB_TOKEN, instead of your personal access token. + fetch-depth: 0 # otherwise, there would be errors pushing refs to the destination repository. + + - name: Download artifact + uses: actions/download-artifact@v3 + with: + path: ${{github.workspace}}/tmp + + - name: Create bin folder if it does not exist + run: mkdir -p ${{github.workspace}}/lib/bin + + - name: Move and rename linux binary + run: mv ${{github.workspace}}/tmp/linux-binary/libserialport.so ${{github.workspace}}/lib/bin/linux.so + + - name: Move and rename windows binary + run: mv ${{github.workspace}}/tmp/windows-binary/serialport.dll ${{github.workspace}}/lib/bin/windows.dll + + - name: Configure Git + run: | + git config --local user.email "github-actions[bot]@users.noreply.github.com" + git config --local user.name "github-actions[bot]" + + - name: Commit binaries + run: | + git add ${{github.workspace}}/lib/bin/* + git diff-index --quiet HEAD || git commit -m "Commited compiled binaries" + + - name: Push changes + uses: ad-m/github-push-action@master + with: + github_token: ${{ secrets.RELEASE_TOKEN }} + branch: ${{ github.ref }} + diff --git a/.github/workflows/build_linux.yml b/.github/workflows/build_linux.yml deleted file mode 100644 index d6245a4..0000000 --- a/.github/workflows/build_linux.yml +++ /dev/null @@ -1,48 +0,0 @@ -name: Build Linux SO - -on: - push: - branches: [ "main" ] - paths: - - 'include/**' - - 'src/**' - - '.github/workflows/build_linux.yml' - pull_request: - branches: [ "main" ] - paths: - - 'include/**' - - 'src/**' - -env: - # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) - BUILD_TYPE: Release - -jobs: - build: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 - - - name: Configure CMake - run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} - - - name: Build - run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} - - - name: Move compiled file - run: mv ${{github.workspace}}/build/libserialport.so ${{github.workspace}}/lib/dls/ - - # - name: Commit artifact changes - # run: | - # git config --local user.email "action@github.com" - # git config --local user.name "GitHub Action" - # git add lib/dls/* - # git commit -m "Add artifact" - # git push - - - name: Archive artifact - uses: actions/upload-artifact@v3 - with: - name: Compiled-Binary - path: ${{github.workspace}}/lib/dls/ diff --git a/.github/workflows/build_windows.yml b/.github/workflows/build_windows.yml deleted file mode 100644 index 8b837a4..0000000 --- a/.github/workflows/build_windows.yml +++ /dev/null @@ -1,40 +0,0 @@ -name: Build Win32 DLL - -on: - push: - branches: [ "main" ] - paths: - - 'include/**' - - 'src/**' - - '.github/workflows/build_windows.yml' - pull_request: - branches: [ "main" ] - paths: - - 'include/**' - - 'src/**' - -env: - # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) - BUILD_TYPE: Release - -jobs: - build: - runs-on: windows-latest - - steps: - - uses: actions/checkout@v3 - - - name: Configure CMake - run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} - - - name: Build - run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} - - - name: Move compiled file - run: move ${{github.workspace}}\build\Release\serialport.dll ${{github.workspace}}\lib\dls\ - - - name: Archive artifact - uses: actions/upload-artifact@v3 - with: - name: Compiled-Binary - path: ${{github.workspace}}/lib/dls/ diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 43ae51f..db7cb61 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2,17 +2,11 @@ name: "Pre-Release" on: - push: - branches: [ "main" ] - paths: - - 'include/**' - - 'src/**' - - '.github/workflows/release.yml' pull_request: branches: [ "main" ] paths: - - 'include/**' - 'src/**' + - '.github/workflows/release.yml' env: # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) @@ -69,18 +63,18 @@ jobs: path: ${{github.workspace}}/tmp - name: Rename linux Binary - run: mv ${{github.workspace}}/tmp/linux-binary/libserialport.so ${{github.workspace}}/lib/dls/linux.so + run: mv ${{github.workspace}}/tmp/linux-binary/libserialport.so ${{github.workspace}}/lib/bin/linux.so - name: Rename windows Binary - run: mv ${{github.workspace}}/tmp/windows-binary/serialport.dll ${{github.workspace}}/lib/dls/windows.dll + run: mv ${{github.workspace}}/tmp/windows-binary/serialport.dll ${{github.workspace}}/lib/bin/windows.dll - run: cd ${{github.workspace}} - name: make .tar.gz file - run: tar -czvf lib.tar.gz ./lib + run: tar -czvf SerialLink-lib.tar.gz ./lib - name: make .zip - run: zip -r lib.zip ./lib + run: zip -r SerialLink-lib.zip ./lib - uses: "marvinpinto/action-automatic-releases@latest" with: @@ -89,5 +83,5 @@ jobs: prerelease: true title: "Development Build" files: | - lib.tar.gz - lib.zip + SerialLink-lib.tar.gz + SerialLink-lib.zip diff --git a/.gitignore b/.gitignore index dc7956b..22d9298 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,5 @@ #directories .vscode/ .cache/ -.github/ build/ test/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 7b28c4b..90d3fa3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.10) +cmake_minimum_required(VERSION 3.22) set(PROJECT_N serialport) project(${PROJECT_N} VERSION 1.0) @@ -6,41 +6,6 @@ project(${PROJECT_N} VERSION 1.0) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED True) -set(LIB true) - -file(GLOB_RECURSE SRCS ${PROJECT_SOURCE_DIR}/src/*.cpp) - -# a macro that gets all of the header containing directories. -MACRO(header_directories return_list includes_base_folder extention ) - FILE(GLOB_RECURSE new_list ${includes_base_folder}/*.${extention}) - SET(dir_list "") - FOREACH(file_path ${new_list}) - GET_FILENAME_COMPONENT(dir_path ${file_path} PATH) - SET(dir_list ${dir_list} ${dir_path}) - ENDFOREACH() - LIST(REMOVE_DUPLICATES dir_list) - SET(${return_list} ${dir_list}) -ENDMACRO() - -# using said macro. -header_directories(INCLUDES ${PROJECT_SOURCE_DIR}/include/ hpp) - -message("src files:") -foreach(file ${SRCS}) - message(STATUS ${file}) -endforeach() - -message("include directories:") -foreach(dir ${INCLUDES}) - message(STATUS ${dir}) -endforeach() - -if(LIB) - add_library(${PROJECT_N} SHARED ${SRCS}) -else() - add_executable(${PROJECT_N} ${SRCS}) -endif(LIB) +add_subdirectory(src) target_include_directories(${PROJECT_N} PUBLIC include) - -# set(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} "-shared -fPIC -Wall") diff --git a/README.md b/README.md index 549fee3..4e7f66a 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,9 @@ -[Build Win32 DLL]: https://img.shields.io/github/actions/workflow/status/TypeScriptPlayground/Serial/build_windows.yml?label=Build%20Win32%20DLL&labelColor=343b42&logo=github&logoColor=959DA5 'Win32 Build' -[Build Linux SO]: https://img.shields.io/github/actions/workflow/status/TypeScriptPlayground/Serial/build_linux.yml?label=Build%20Linux%20SO&labelColor=343b42&logo=github&logoColor=959DA5 'Linux Build' +[Build binaries]: https://img.shields.io/github/actions/workflow/status/TypeScriptPlayground/Serial/build_windows.yml?label=Build%20binaries&labelColor=343b42&logo=github&logoColor=959DA5 'Build binaries' [Release Downloads]: https://img.shields.io/github/downloads/TypeScriptPlayground/Serial/total?label=Downloads%20&labelColor=343b42&logo=docusign&logoColor=959DA5 'Total Release Downloads' # Serial -[![Build Win32 DLL]](https://github.com/TypeScriptPlayground/Serial/actions/workflows/build_windows.yml) -[![Build Linux SO]](https://github.com/TypeScriptPlayground/Serial/actions/workflows/build_linux.yml) +[![Build binaries]](https://github.com/TypeScriptPlayground/Serial/actions/workflows/build.yml) [![Release Downloads]](https://github.com/TypeScriptPlayground/Serial/releases) the deno mascot dinosaur standing in the rain @@ -33,10 +31,10 @@ This library provides an interface for the communication with serial devices and - Works on different operating systems (check [compatibility](#compatibility) for mor info). ## Compatibility -| OS | Tested version | Current state | -|---------|------------------|---------------| -| Windows | Windows 10 (x64) | implemented | -| Linux | - | in progress | +| OS | Tested version | Current state | +|---------|-------------------------|---------------| +| Windows | Windows 10 (x64) | implemented | +| Linux | Ubuntu Server 22.04 LTS | implemented | ## Possible/Known issues - What happens if you open multiple connections from the same instance. @@ -45,6 +43,10 @@ This library provides an interface for the communication with serial devices and - Linux write currently not working ## Examples - How to use +To use this library you need the following flags to run it: +- `--unstable` +- `--allow-ffi` + ### Ports Get a list with all serial ports and their info that are currently available on your system. @@ -77,17 +79,17 @@ Send data to a serial device. For exampe to an [Arduino](https://www.arduino.cc/ `main.ts` ```typescript -import { Serial } from "./mod.ts"; +import { Serial, baudrate } from "./mod.ts"; // create new instance of a serial object const serial = new Serial(); // open the connection -serial.open(); +serial.open('COM1', baudrate.B9600); // encode the message to a Uint8Array -const textToSend = 'Hello from TypeScript!' -const encodedTextToSend = new TextEncoder().encode(textToSend) +const textToSend = 'Hello from TypeScript!'; +const encodedTextToSend = new TextEncoder().encode(textToSend); // send the message serial.send(encodedTextToSend, encodedTextToSend.length); @@ -113,13 +115,13 @@ void loop() { `main.ts` ```typescript -import { Serial } from "./mod.ts"; +import { Serial, baudrate } from "./mod.ts"; // create new instance of a serial object const serial = new Serial(); // open the connection -serial.open(); +serial.open('COM1', baudrate.B9600); // create a new buffer to store incoming bytes, // in this example we want to read a maximum of 100 bytes diff --git a/include/serial.h b/include/serial.h deleted file mode 100644 index 687047e..0000000 --- a/include/serial.h +++ /dev/null @@ -1,79 +0,0 @@ -#pragma once - -#if defined(_WIN32) || defined(__WIN32__) || defined(WIN32) -# ifdef SERIALPORT_EXPORTS -# define MODULE_API __declspec(dllexport) -# else -# define MODULE_API __declspec(dllimport) -# endif -#else -# define MODULE_API -#endif - -// Windows -#if defined(_WIN32) || defined(__WIN32__) || defined(WIN32) -# include "serial_windows.h" -# define systemOpen(port, baudrate, dataBits, parity, stopBits) windowsSystemOpen(port, baudrate, dataBits, parity, stopBits) -# define systemClose() windowsSystemClose() -# define systemRead(buffer, bufferSize, timeout, multiplier) windowsSystemRead(buffer, bufferSize, timeout, multiplier) -# define systemReadUntil(buffer, bufferSize, timeout, multiplier, untilChar) windowsSystemReadUntil(buffer, bufferSize, timeout, multiplier, untilChar) -# define systemWrite(buffer, bufferSize, timeout, multiplier) windowsSystemWrite(buffer, bufferSize, timeout, multiplier) -# define systemGetAvailablePorts(buffer, bufferSize, separator) windowsSystemGetAvailablePorts(buffer, bufferSize, separator) -#endif - -// Linux, Apple -#if defined(__unix__) || defined(__unix) || defined(__APPLE__) -# include "serial_unix.h" -# define systemOpen(port, baudrate, dataBits, parity, stopBits) unixSystemOpen(port, baudrate, dataBits, parity, stopBits) -# define systemClose() unixSystemClose() -# define systemRead(buffer, bufferSize, timeout, multiplier) unixSystemRead(buffer, bufferSize, timeout, multiplier) -# define systemReadUntil(buffer, bufferSize, timeout, multiplier, untilChar) unixSystemReadUntil(buffer, bufferSize, timeout, multiplier, untilChar) -# define systemWrite(buffer, bufferSize, timeout, multiplier) unixSystemWrite(buffer, bufferSize, timeout, multiplier) -# define systemGetAvailablePorts(buffer, bufferSize, separator) unixSystemGetAvailablePorts(buffer, bufferSize, separator) -#endif - -#ifdef __cplusplus -extern "C" { -#endif - - MODULE_API auto serialOpen( - void* port, - const int baudrate, - const int dataBits, - const int parity = 0, - const int stopBits = 0 - ) -> int; - - MODULE_API auto serialClose() -> int; - - MODULE_API auto serialRead( - void* buffer, - const int bufferSize, - const int timeout, - const int multiplier - ) -> int; - - MODULE_API auto serialReadUntil( - void* buffer, - const int bufferSize, - const int timeout, - const int multiplier, - void* untilChar - ) -> int; - - MODULE_API auto serialWrite( - void* buffer, - const int bufferSize, - const int timeout, - const int multiplier - ) -> int; - - MODULE_API auto serialGetAvailablePorts( - void* buffer, - const int bufferSize, - void* separator - ) -> int; - -#ifdef __cplusplus -} -#endif diff --git a/include/serial_unix.h b/include/serial_unix.h deleted file mode 100644 index f33647e..0000000 --- a/include/serial_unix.h +++ /dev/null @@ -1,55 +0,0 @@ -#pragma once -#if defined(__unix__) || defined(__unix) || defined(__APPLE__) - -#include // String function definitions -#include // UNIX standard function definitions -#include // File control definitions -#include // Used for TCGETS2, which is required for custom baud rates -#include -#include -// #include - -#include "status_codes.h" - -extern int hSerialPort; -extern termios2 tty; - -auto unixSystemOpen( - void* port, - const int baudrate, - const int dataBits, - const int parity = 0, - const int stopBits = 0 -) -> int; - -auto unixSystemClose() -> int; - -auto unixSystemRead( - void* buffer, - const int bufferSize, - const int timeout, - const int multiplier -) -> int; - -auto unixSystemReadUntil( - void* buffer, - const int bufferSize, - const int timeout, - const int multiplier, - void* untilChar -) -> int; - -auto unixSystemWrite( - void* buffer, - const int bufferSize, - const int timeout, - const int multiplier -) -> int; - -auto unixSystemGetAvailablePorts( - void* buffer, - const int bufferSize, - void* separator -) -> int; - -#endif diff --git a/include/serial_windows.h b/include/serial_windows.h deleted file mode 100644 index 0b5f7d8..0000000 --- a/include/serial_windows.h +++ /dev/null @@ -1,51 +0,0 @@ -#pragma once -#if defined(_WIN32) || defined(__WIN32__) || defined(WIN32) - -#include -#include -#include -#include "status_codes.h" - -extern HANDLE hSerialPort; -extern DCB dcbSerialParams; -extern COMMTIMEOUTS timeouts; - -auto windowsSystemOpen( - void* port, - const int baudrate, - const int dataBits, - const int parity = 0, - const int stopBits = 0 -) -> int; - -auto windowsSystemClose() -> int; - -auto windowsSystemRead( - void* buffer, - const int bufferSize, - const int timeout, - const int multiplier -) -> int; - -auto windowsSystemReadUntil( - void* buffer, - const int bufferSize, - const int timeout, - const int multiplier, - void* untilChar -) -> int; - -auto windowsSystemWrite( - void* buffer, - const int bufferSize, - const int timeout, - const int multiplier -) -> int; - -auto windowsSystemGetAvailablePorts( - void* buffer, - const int bufferSize, - void* separator -) -> int; - -#endif diff --git a/lib/Serial.ts b/lib/Serial.ts index 2e3df82..8bb631a 100644 --- a/lib/Serial.ts +++ b/lib/Serial.ts @@ -1,12 +1,16 @@ import { checkForErrorCode } from "./check_for_error_code.ts"; +import { Baudrate } from "./constants/baudrates.ts"; import { dataBits } from "./constants/data_bits.ts"; import { parity } from "./constants/parity.ts"; import { stopBits } from "./constants/stop_bits.ts"; import { decode } from "./decode.ts"; +import { encode } from "./encode.ts"; import { Ports } from "./interfaces/ports.ts"; import { SerialFunctions } from "./interfaces/serial_functions.d.ts"; import { SerialOptions } from "./interfaces/serial_options.d.ts"; -import { loadDL } from "./load_dl.ts"; +import { loadBinaryForOS } from "./load_binary_for_os.ts"; + +const pathToBinariesDirectory = './lib/bin'; export class Serial { private _isOpen : boolean; @@ -17,7 +21,18 @@ export class Serial { */ constructor() { this._isOpen = false; - this._dl = loadDL('./lib/dls', Deno.build.os); + this._dl = loadBinaryForOS(pathToBinariesDirectory, Deno.build.os); + this._dl.onError((code) => { + throw new Error(`An error has occurred. Error code: ${code}`); + }); + + this._dl.onRead((bytes) => { + console.log('onRead():', bytes); + + }) + this._dl.onWrite((bytes) => { + console.log('onWrite():', bytes); + }) } /** @@ -30,50 +45,42 @@ export class Serial { /** * Opens the serial connection. - * @param {string} port The port to connect - * @param {number} baudrate The baudrate - * @param {SerialOptions} serialOptions Additional options for the serial connection (`data bits`, `parity`, `stop bits`) + * @param port The port to connect + * @param baudrate The baudrate + * @param serialOptions Additional options for the serial connection (`data bits`, `parity`, `stop bits`) */ open( port : string, - baudrate : number, + baudrate : Baudrate, serialOptions? : SerialOptions - ) : number { - const status = this._dl.open( - port, + ) : void { + this._dl.open( + encode(port + '\0'), baudrate, serialOptions?.dataBits || dataBits.EIGHT, serialOptions?.parity || parity.NONE, serialOptions?.stopBits || stopBits.ONE ); - - checkForErrorCode(status); this._isOpen = true; - - return status; } /** * Closes the serial connection. */ - close() : number { - const status = this._dl.close(); - - checkForErrorCode(status); + close() { + this._dl.close(); this._isOpen = false; - - return status; } /** * Read data from serial connection. - * @param {Uint8Array} buffer Buffer to read the bytes into - * @param {number} bytes The number of bytes to read - * @param {number} timeout The timeout in `ms` - * @param {number} multiplier The timeout between reading individual bytes in `ms` - * @returns {number} Returns number of bytes read + * @param buffer Buffer to read the bytes into + * @param bytes The number of bytes to read + * @param timeout The timeout in `ms` + * @param multiplier The timeout between reading individual bytes in `ms` + * @returns The number of bytes read */ read( buffer : Uint8Array, @@ -81,26 +88,26 @@ export class Serial { timeout = 0, multiplier = 10 ) : number { - const status = this._dl.read( + const result = this._dl.read( buffer, bytes, timeout, multiplier ); - checkForErrorCode(status); + checkForErrorCode(result); - return status; + return result; } /** - * Read data from serial connection until a linebreak (`\n`) gets send. - * @param {Uint8Array} buffer Buffer to read the bytes into - * @param {number} bytes The number of bytes to read - * @param {number} timeout The timeout in `ms` - * @param {number} multiplier The timeout between reading individual bytes in `ms` - * @param {string} searchString A string to search for - * @returns {number} Returns number of bytes read + * Read data from serial connection until the specified `searchString` is found. + * @param buffer Buffer to read the bytes into + * @param bytes The number of bytes to read + * @param timeout The timeout in `ms` + * @param multiplier The timeout between reading individual bytes in `ms` + * @param searchString A string to search for + * @returns The number of bytes read */ readUntil( buffer : Uint8Array, @@ -109,25 +116,25 @@ export class Serial { multiplier = 10, searchString = '', ) : number { - const status = this._dl.readUntil( + const result = this._dl.readUntil( buffer, bytes, timeout, multiplier, - searchString + encode(searchString + '\0') ) - checkForErrorCode(status); + checkForErrorCode(result); - return status + return result } /** * Write data to serial connection. - * @param {Uint8Array} buffer The data to write/send - * @param {number} bytes The number of bytes to read - * @param {number} timeout The timeout in `ms` - * @param {number} multiplier The timeout between reading individual bytes in `ms` - * @returns {number} Returns number of bytes written + * @param buffer The data to write/send + * @param bytes The number of bytes to read + * @param timeout The timeout in `ms` + * @param multiplier The timeout between reading individual bytes in `ms` + * @returns The number of bytes written */ write( buffer : Uint8Array, @@ -135,16 +142,16 @@ export class Serial { timeout = 0, multiplier = 10 ) : number { - const status = this._dl.write( + const result = this._dl.write( buffer, bytes, timeout, multiplier ) - checkForErrorCode(status); + checkForErrorCode(result); - return status + return result } /** @@ -152,16 +159,16 @@ export class Serial { * @returns {Ports[]} Returns a list of ports info */ getPortsInfo() : Ports[] { - const buffer = new Uint8Array(1024); - const status = this._dl.getAvailablePorts( + const buffer = new Uint8Array(2048); + const result = this._dl.getPortsInfo( buffer, buffer.length, - ',' + encode(',' + '\0') ) - checkForErrorCode(status); + checkForErrorCode(result); - const ports = decode(buffer).replaceAll('\x00','').split(',').map((port) => { + const ports = decode(buffer).replaceAll('\0','').split(',').map((port) => { return { name: port }; diff --git a/lib/bin/linux.so b/lib/bin/linux.so new file mode 100644 index 0000000..f78d8af Binary files /dev/null and b/lib/bin/linux.so differ diff --git a/lib/bin/windows.dll b/lib/bin/windows.dll new file mode 100644 index 0000000..420ffef Binary files /dev/null and b/lib/bin/windows.dll differ diff --git a/lib/check_for_error_code.ts b/lib/check_for_error_code.ts index 625a511..b12c634 100644 --- a/lib/check_for_error_code.ts +++ b/lib/check_for_error_code.ts @@ -1,5 +1,10 @@ import { ErrorCode } from "./errors/error_code.ts"; +/** + * This function is used to check for an error code. If the code is below 0 an error is thrown + * @param code Error code + * @throws The error with the code + */ export function checkForErrorCode(code : number) { if (code >= 0) { return; diff --git a/lib/constants/baudrate.ts b/lib/constants/baudrate.ts deleted file mode 100644 index 6914ce1..0000000 --- a/lib/constants/baudrate.ts +++ /dev/null @@ -1,25 +0,0 @@ -interface Baudrate { - B1200: 1200, - B2400: 2400, - B9600: 9600, - B19200: 19200, - B38400: 38400, - B57600: 57600, - B74880: 74880, - B115200: 115200, - B230400: 230400, - B250000: 250000 -} - -export const baudrate : Baudrate = { - B1200: 1200, - B2400: 2400, - B9600: 9600, - B19200: 19200, - B38400: 38400, - B57600: 57600, - B74880: 74880, - B115200: 115200, - B230400: 230400, - B250000: 250000 -} diff --git a/lib/constants/baudrates.ts b/lib/constants/baudrates.ts new file mode 100644 index 0000000..38bdee3 --- /dev/null +++ b/lib/constants/baudrates.ts @@ -0,0 +1,16 @@ +import { ObjectValuesMap } from "../types/object_values_map.ts"; + +export const baudrates = { + B1200: 1200, + B2400: 2400, + B9600: 9600, + B19200: 19200, + B38400: 38400, + B57600: 57600, + B74880: 74880, + B115200: 115200, + B230400: 230400, + B250000: 250000 +} as const; + +export type Baudrate = ObjectValuesMap diff --git a/lib/constants/binary_extensions.ts b/lib/constants/binary_extensions.ts new file mode 100644 index 0000000..7929e76 --- /dev/null +++ b/lib/constants/binary_extensions.ts @@ -0,0 +1,11 @@ +export const binaryExtensions : {[key : string] : string} = { + windows: 'dll', + linux: 'so', + darwin: '', + freebsd: '', + netbsd: '', + aix: '', + solaris: '', + illumos: '' +} as const; + diff --git a/lib/constants/data_bits.ts b/lib/constants/data_bits.ts index 67993e8..6270834 100644 --- a/lib/constants/data_bits.ts +++ b/lib/constants/data_bits.ts @@ -1,13 +1,10 @@ -interface DataBits { - FIVE: 5, - SIX: 6, - SEVEN: 7, - EIGHT: 8, -} +import { ObjectValuesMap } from "../types/object_values_map.ts"; -export const dataBits : DataBits = { +export const dataBits = { FIVE: 5, SIX: 6, SEVEN: 7, EIGHT: 8, -} +} as const; + +export type DataBits = ObjectValuesMap diff --git a/lib/constants/parity.ts b/lib/constants/parity.ts index 218afc7..3de4ca7 100644 --- a/lib/constants/parity.ts +++ b/lib/constants/parity.ts @@ -1,14 +1,11 @@ -interface Parity { +import { ObjectValuesMap } from "../types/object_values_map.ts"; + +export const parity = { NONE: 0, ODD: 1, EVEN: 2, MARK: 3, SPACE: 4 -} -export const parity : Parity = { - NONE: 0, - ODD: 1, - EVEN: 2, - MARK: 3, - SPACE: 4 -} +} as const; + +export type Parity = ObjectValuesMap diff --git a/lib/constants/status_codes.ts b/lib/constants/status_codes.ts index 9f54f4c..f8acf17 100644 --- a/lib/constants/status_codes.ts +++ b/lib/constants/status_codes.ts @@ -1,17 +1,6 @@ -interface StatusCodes { - SUCCESS: 0, - CLOSE_HANDLE_ERROR: -1, - INVALID_HANDLE_ERROR: -2, - READ_ERROR: -3, - WRITE_ERROR: -4, - GET_PROPERTY_ERROR: -5, - SET_PROPERTY_ERROR: -6, - SET_TIMEOUT_ERROR: -7, - BUFFER_ERROR: -8, - NOT_FOUND_ERROR: -9 -} +import { ObjectValuesMap } from "../types/object_values_map.ts"; -export const statusCodes : StatusCodes = { +export const statusCodes = { SUCCESS: 0, CLOSE_HANDLE_ERROR: -1, INVALID_HANDLE_ERROR: -2, @@ -22,4 +11,6 @@ export const statusCodes : StatusCodes = { SET_TIMEOUT_ERROR: -7, BUFFER_ERROR: -8, NOT_FOUND_ERROR: -9 -} +} as const; + +export type StatusCode = ObjectValuesMap diff --git a/lib/constants/stop_bits.ts b/lib/constants/stop_bits.ts index 64f4a6c..73b41e9 100644 --- a/lib/constants/stop_bits.ts +++ b/lib/constants/stop_bits.ts @@ -1,10 +1,9 @@ -interface StopBits { +import { ObjectValuesMap } from "../types/object_values_map.ts" + +export const stopBits = { ONE: 0, ONE_DOT_FIVE: 1, TWO: 2 -} -export const stopBits : StopBits = { - ONE: 0, - ONE_DOT_FIVE: 1, - TWO: 2 -} +} as const; + +export type StopBits = ObjectValuesMap diff --git a/lib/decode.ts b/lib/decode.ts index e26e737..e46746b 100644 --- a/lib/decode.ts +++ b/lib/decode.ts @@ -1 +1,8 @@ -export const decode = (buffer : Uint8Array) => new TextDecoder().decode(buffer); +const textDecoder = new TextDecoder() + +/** + * This function is used as a wrapper for the default text decoder. + * @param buffer Data to decode + * @returns The decoded data + */ +export const decode = (buffer : Uint8Array) : string => textDecoder.decode(buffer); diff --git a/lib/dls/windows.dll b/lib/dls/windows.dll deleted file mode 100644 index b0d598b..0000000 Binary files a/lib/dls/windows.dll and /dev/null differ diff --git a/lib/encode.ts b/lib/encode.ts index 0c7aa5b..49f7675 100644 --- a/lib/encode.ts +++ b/lib/encode.ts @@ -1 +1,8 @@ -export const encode = (string : string) => new TextEncoder().encode(string); +const textEncoder = new TextEncoder() + +/** + * This function is used as a wrapper for the default text encoder + * @param buffer Data to encode + * @returns The encoded data + */ +export const encode = (string : string) => textEncoder.encode(string); diff --git a/lib/errors/error_code.ts b/lib/errors/error_code.ts index 88aabb3..97f5645 100644 --- a/lib/errors/error_code.ts +++ b/lib/errors/error_code.ts @@ -1,8 +1,8 @@ -import { statusCodes } from "../constants/status_codes.ts"; - export class ErrorCode extends Error { + code : number + constructor(code : number) { - const errorCodeKey = Object.keys(statusCodes).find(key => statusCodes[key] === code); - super(`An error has occurred. Error code: ${errorCodeKey} (${code})`); + super(`An error has occurred. Error code: ${code}`); + this.code = code; } } diff --git a/lib/interfaces/serial_functions.d.ts b/lib/interfaces/serial_functions.d.ts index 6cd483b..890b9c4 100644 --- a/lib/interfaces/serial_functions.d.ts +++ b/lib/interfaces/serial_functions.d.ts @@ -1,36 +1,48 @@ -import { parity } from "../constants/parity.ts"; +import { Baudrate } from "../constants/baudrates.ts"; +import { DataBits } from "../constants/data_bits.ts"; +import { Parity } from "../constants/parity.ts"; +import { StopBits } from "../constants/stop_bits.ts"; export interface SerialFunctions { - open: ( - port : string, - baudrate : number, - dataBits : number, - parity : parity, - stopBits : number - ) => number, - close: () => number, - read: ( + open : ( + port : Uint8Array, + baudrate : Baudrate, + dataBits : DataBits, + parity : Parity, + stopBits : StopBits + ) => void, + close : () => void, + read : ( buffer : Uint8Array, bufferSize : number, timeout : number, multiplier : number ) => number, - readUntil: ( + readUntil : ( buffer : Uint8Array, bufferSize : number, timeout : number, multiplier : number, - searchString : string + searchString : Uint8Array ) => number, - write: ( + write : ( buffer : Uint8Array, bufferSize : number, timeout : number, multiplier : number ) => number, - getAvailablePorts: ( + getPortsInfo : ( buffer : Uint8Array, bufferSize : number, - separator : string - ) => number + separator : Uint8Array + ) => number, + onError : (callback : ( + code : number + ) => void) => void, + onRead : (callback : ( + bytes : number + ) => void) => void, + onWrite : (callback : ( + bytes : number + ) => void) => void } diff --git a/lib/interfaces/serial_options.d.ts b/lib/interfaces/serial_options.d.ts index 3495589..306b4e4 100644 --- a/lib/interfaces/serial_options.d.ts +++ b/lib/interfaces/serial_options.d.ts @@ -1,9 +1,9 @@ -import { dataBits } from "../constants/data_bits.ts"; -import { parity } from "../constants/parity.ts"; -import { stopBits } from "../constants/stop_bits.ts"; +import { DataBits } from "../constants/data_bits.ts"; +import { Parity } from "../constants/parity.ts"; +import { StopBits } from "../constants/stop_bits.ts"; export interface SerialOptions { - dataBits? : dataBits, - parity? : parity, - stopBits? : stopBits + dataBits? : DataBits, + parity? : Parity, + stopBits? : StopBits } diff --git a/lib/load_binary_for_os.ts b/lib/load_binary_for_os.ts new file mode 100644 index 0000000..8169ee5 --- /dev/null +++ b/lib/load_binary_for_os.ts @@ -0,0 +1,27 @@ +import { binaryExtensions } from "./constants/binary_extensions.ts"; +import { SerialFunctions } from "./interfaces/serial_functions.d.ts"; +import { registerSerialFunctions } from "./register_serial_functions.ts"; + +/** + * This function is used to load the specific binary based on the current os. + * @param path The default path to the binaries directory + * @param os The current OS + * @returns A serial functions object with all the functions from the binary + */ +export function loadBinaryForOS(path : string, os : string) : SerialFunctions { + if (!binaryExtensions[os]) { + throw new Error( + `There is currently no implementation for this operating system. + We are currently working on an implementation for all missing operating systems. + Current stage: + + - Windows (implemented) + - Linux (in progress) + + For more information feel free to check out the repository: + https://github.com/TypeScriptPlayground/Serial#compatibility` + ); + } + + return registerSerialFunctions(path, os, binaryExtensions[os]); +} diff --git a/lib/load_dl.ts b/lib/load_dl.ts deleted file mode 100644 index 7c01e97..0000000 --- a/lib/load_dl.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { SerialFunctions } from "./interfaces/serial_functions.d.ts"; -import { registerSerialFunctions } from "./register_serial_functions.ts"; - -export function loadDL(path : string, os : string) : SerialFunctions { - let libSuffix = ''; - - switch(os) { - case 'windows': { - libSuffix = 'dll'; - break; - } - - case 'linux': - case 'darwin': - case 'freebsd': - case 'netbsd': - case 'aix': - case 'solaris': - case 'illumos': { - throw new Error( - `There is currently no implementation for this operating system. - We are currently working on an implementation for all missing operating systems. - Current stage: - - - Windows (implemented) - - Linux (in progress) - - MacOS (planned) - - For more information feel free to check out the repository: - https://github.com/TypeScriptPlayground/Serial` - ); - } - - default: { - break; - } - } - - return registerSerialFunctions(path, os, libSuffix); -} diff --git a/lib/register_serial_functions.ts b/lib/register_serial_functions.ts index 99c2e63..94e6351 100644 --- a/lib/register_serial_functions.ts +++ b/lib/register_serial_functions.ts @@ -1,6 +1,4 @@ import { SerialFunctions } from "./interfaces/serial_functions.d.ts"; -import { encode } from "./encode.ts"; -import { parity } from "./constants/parity.ts"; export function registerSerialFunctions( path : string, @@ -8,7 +6,7 @@ export function registerSerialFunctions( libSuffix : string ) : SerialFunctions { const serialFunctions = Deno.dlopen(`${path}/${os}.${libSuffix}`, { - 'open': { + 'serialOpen': { parameters: [ // Port 'buffer', @@ -22,14 +20,14 @@ export function registerSerialFunctions( 'i32' ], // Status code - result: 'i32' + result: 'void' }, - 'close': { + 'serialClose': { parameters: [], // Status code result: 'i32' }, - 'read': { + 'serialRead': { parameters: [ // Buffer 'buffer', @@ -40,10 +38,10 @@ export function registerSerialFunctions( // Multiplier 'i32' ], - // Status code/Bytes read + // Bytes read result: 'i32' }, - 'readUntil': { + 'serialReadUntil': { parameters: [ // Buffer 'buffer', @@ -56,10 +54,10 @@ export function registerSerialFunctions( // SearchString 'buffer' ], - // Status code/Bytes read + // Bytes read result: 'i32' }, - 'write': { + 'serialWrite': { parameters: [ // Buffer 'buffer', @@ -70,10 +68,10 @@ export function registerSerialFunctions( // Multiplier 'i32' ], - // Status code/Bytes written + // Bytes written result: 'i32' }, - 'getAvailablePorts': { + 'serialGetPortsInfo': { parameters: [ // Buffer 'buffer', @@ -82,69 +80,54 @@ export function registerSerialFunctions( // Separator 'buffer' ], - // Status code/Amount of ports + // Amount of ports result: 'i32' + }, + 'serialOnError': { + // on error callback function + parameters: ['function'], + result: 'void' + }, + 'serialOnRead': { + // on error callback function + parameters: ['function'], + result: 'void' + }, + 'serialOnWrite': { + // on error callback function + parameters: ['function'], + result: 'void' } }).symbols - + return { - open: ( - port : string, - baudrate : number, - dataBits : number, - parity : parity, - stopBits : number - ) : number => serialFunctions.open( - encode(port + '\0'), - baudrate, - dataBits, - parity, - stopBits - ), - close: () : number => serialFunctions.close(), - read: ( - buffer : Uint8Array, - bytes : number, - timeout : number, - multiplier : number - ) : number => serialFunctions.read( - buffer, - bytes, - timeout, - multiplier - ), - readUntil: ( - buffer : Uint8Array, - bytes : number, - timeout : number, - multiplier : number, - searchString : string - ) : number => serialFunctions.readUntil( - buffer, - bytes, - timeout, - multiplier, - encode(searchString + '\0') - ), - write: ( - buffer : Uint8Array, - bytes : number, - timeout : number, - multiplier : number - ) : number => serialFunctions.write( - buffer, - bytes, - timeout, - multiplier - ), - getAvailablePorts: ( - buffer : Uint8Array, - bytes : number, - separator : string - ) : number => serialFunctions.getAvailablePorts( - buffer, - bytes, - encode(separator + '\0') - ) + open: serialFunctions.serialOpen, + close: serialFunctions.serialClose, + read: serialFunctions.serialRead, + readUntil: serialFunctions.serialReadUntil, + write: serialFunctions.serialWrite, + getPortsInfo: serialFunctions.serialGetPortsInfo, + onError: (callback) => { + serialFunctions.serialOnError(new Deno.UnsafeCallback({ + parameters: ['i32'], + result: "void", + } as const, + (errorCode) => {callback(errorCode)}).pointer) + }, + onRead: (callback) => { + serialFunctions.serialOnRead(new Deno.UnsafeCallback({ + parameters: ['i32'], + result: "void", + } as const, + (bytes) => {callback(bytes)}).pointer) + }, + onWrite: (callback) => { + serialFunctions.serialOnWrite(new Deno.UnsafeCallback({ + parameters: ['i32'], + result: "void", + } as const, + (bytes) => {callback(bytes)}).pointer) + } } } + diff --git a/lib/types/object_values_map.ts b/lib/types/object_values_map.ts new file mode 100644 index 0000000..643ed22 --- /dev/null +++ b/lib/types/object_values_map.ts @@ -0,0 +1 @@ +export type ObjectValuesMap = ObjectType[keyof ObjectType] diff --git a/mod.ts b/mod.ts index a643f41..57749b1 100644 --- a/mod.ts +++ b/mod.ts @@ -1,5 +1,5 @@ export { Serial } from './lib/Serial.ts'; -export { baudrate } from './lib/constants/baudrate.ts'; +export { baudrates } from './lib/constants/baudrates.ts'; export { dataBits } from './lib/constants/data_bits.ts'; export { parity } from './lib/constants/parity.ts'; export { stopBits } from './lib/constants/stop_bits.ts'; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..6329d11 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,46 @@ +cmake_minimum_required(VERSION 3.22) + +set(PROJECT_N serialport) +project(${PROJECT_N} VERSION 1.0) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED True) + +include_directories(${PROJECT_N} PUBLIC include) + +set(LIB true) + +file(GLOB_RECURSE SRCS ${PROJECT_SOURCE_DIR}/src/*.cpp) + +# a macro that gets all of the header containing directories. +MACRO(header_directories return_list includes_base_folder extention ) + FILE(GLOB_RECURSE new_list ${includes_base_folder}/*.${extention}) + SET(dir_list "") + FOREACH(file_path ${new_list}) + GET_FILENAME_COMPONENT(dir_path ${file_path} PATH) + SET(dir_list ${dir_list} ${dir_path}) + ENDFOREACH() + LIST(REMOVE_DUPLICATES dir_list) + SET(${return_list} ${dir_list}) +ENDMACRO() + +# using said macro. +header_directories(INCLUDES ${PROJECT_SOURCE_DIR}/src/include/ h) + +message("src files:") +foreach(file ${SRCS}) + message(STATUS ${file}) +endforeach() + +message("include directories:") +foreach(dir ${INCLUDES}) + message(STATUS ${dir}) +endforeach() + +if(LIB) + add_library(${PROJECT_N} SHARED ${SRCS}) +else() + add_executable(${PROJECT_N} ${SRCS}) +endif(LIB) + +# set(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} "-shared -fPIC -Wall") diff --git a/src/include/serial.h b/src/include/serial.h new file mode 100644 index 0000000..6eded48 --- /dev/null +++ b/src/include/serial.h @@ -0,0 +1,71 @@ +#pragma once +#include + +#include "status_codes.h" + + +#if defined(_WIN32) || defined(__WIN32__) || defined(WIN32) +# ifdef SERIALPORT_EXPORTS +# define MODULE_API __declspec(dllexport) +# else +# define MODULE_API __declspec(dllimport) +# endif +#else +# define MODULE_API +#endif + +extern void (*errorCallback)(int errorCode); +extern void (*readCallback)(int bytes); +extern void (*writeCallback)(int bytes); + +#ifdef __cplusplus +extern "C" { +#endif + + MODULE_API void serialOpen( + void* port, + const int baudrate, + const int dataBits, + const int parity = 0, + const int stopBits = 0 + ); + + MODULE_API void serialClose(); + + MODULE_API auto serialRead( + void* buffer, + const int bufferSize, + const int timeout, + const int multiplier + ) -> int; + + MODULE_API auto serialReadUntil( + void* buffer, + const int bufferSize, + const int timeout, + const int multiplier, + void* untilChar + ) -> int; + + MODULE_API auto serialWrite( + void* buffer, + const int bufferSize, + const int timeout, + const int multiplier + ) -> int; + + MODULE_API auto serialGetPortsInfo( + void* buffer, + const int bufferSize, + void* separator + ) -> int; + + MODULE_API auto serialOnError(void (*func)(int code)) -> void; + + MODULE_API auto serialOnRead(void (*func)(int bytes)) -> void; + + MODULE_API auto serialOnWrite(void (*func)(int bytes)) -> void; + +#ifdef __cplusplus +} +#endif diff --git a/include/status_codes.h b/src/include/status_codes.h similarity index 81% rename from include/status_codes.h rename to src/include/status_codes.h index 0569e3f..83eb667 100644 --- a/include/status_codes.h +++ b/src/include/status_codes.h @@ -11,4 +11,4 @@ enum class StatusCodes { NOT_FOUND_ERROR = -9 }; -#define returnStatus(status) return static_cast(status) +#define status(status) static_cast(status) diff --git a/src/serial.cpp b/src/serial.cpp deleted file mode 100644 index 19f7224..0000000 --- a/src/serial.cpp +++ /dev/null @@ -1,51 +0,0 @@ -#include "serial.h" - -auto serialOpen( - void* port, - const int baudrate, - const int dataBits, - const int parity, - const int stopBits -) -> int { - return systemOpen(port, baudrate, dataBits, parity, stopBits); -} - -auto serialClose() -> int { - return systemClose(); -} - -auto serialRead( - void* buffer, - const int bufferSize, - const int timeout, - const int multiplier -) -> int { - return systemRead(buffer, bufferSize, timeout, multiplier); -} - -auto serialReadUntil( - void* buffer, - const int bufferSize, - const int timeout, - const int multiplier, - void* untilChar -) -> int { - return systemReadUntil(buffer, bufferSize, timeout, multiplier, untilChar); -} - -auto serialWrite( - void* buffer, - const int bufferSize, - const int timeout, - const int multiplier -) -> int { - return systemWrite(buffer, bufferSize, timeout, multiplier); -} - -auto serialGetAvailablePorts( - void* buffer, - const int bufferSize, - void* separator -) -> int { - return systemGetAvailablePorts(buffer, bufferSize, separator); -} \ No newline at end of file diff --git a/src/serial_unix.cpp b/src/serial_unix.cpp deleted file mode 100644 index 9373752..0000000 --- a/src/serial_unix.cpp +++ /dev/null @@ -1,176 +0,0 @@ -#if defined(__unix__) || defined(__unix) || defined(__APPLE__) -#include "serial_unix.h" - -// namespace fs = std::filesystem; - -int hSerialPort; -termios2 tty; - -auto unixSystemOpen( - void* port, - const int baudrate, - const int dataBits, - const int parity, - const int stopBits -) -> int { - char *portName = static_cast(port); - // Open new serial connection - hSerialPort = open(portName, O_RDWR); - - // Error if open fails - ioctl(hSerialPort, TCGETS2, &tty); - // if (tcgetattr(hSerialPort, &tty) != 0) { - // returnStatus(StatusCodes::INVALID_HANDLE_ERROR); - // } - - tty.c_cflag &= ~PARENB; // Clear parity bit, disabling parity (most common) - tty.c_cflag &= ~CSTOPB; // Clear stop field, only one stop bit used in communication (most common) - tty.c_cflag &= ~CSIZE; // Clear all bits that set the data size - tty.c_cflag |= CS8; // 8 bits per byte (most common) - tty.c_cflag &= ~CRTSCTS; // Disable RTS/CTS hardware flow control (most common) - tty.c_cflag |= CREAD | CLOCAL; // Turn on READ & ignore ctrl lines (CLOCAL = 1) - - tty.c_lflag &= ~ICANON; - tty.c_lflag &= ~ECHO; // Disable echo - tty.c_lflag &= ~ECHOE; // Disable erasure - tty.c_lflag &= ~ECHONL; // Disable new-line echo - tty.c_lflag &= ~ISIG; // Disable interpretation of INTR, QUIT and SUSP - tty.c_iflag &= ~(IXON | IXOFF | IXANY); // Turn off s/w flow ctrl - tty.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL); // Disable any special handling of received bytes - tty.c_oflag &= ~OPOST; // Prevent special interpretation of output bytes (e.g. newline chars) - tty.c_oflag &= ~ONLCR; // Prevent conversion of newline to carriage return/line feed - - tty.c_cc[VTIME] = 10; // Wait for up to 1s (10 deciseconds), returning as soon as any data is received. - tty.c_cc[VMIN] = 0; - - // Set in/out baud rate to be 9600 - // cfsetispeed(&tty, B9600); - // cfsetospeed(&tty, B9600); - tty.c_ispeed = baudrate; - tty.c_ospeed = baudrate; - - - // Data bits - tty.c_cflag &= ~CSIZE; // CSIZE is a mask for the number of bits per character - switch(dataBits) { - case 5: - tty.c_cflag |= CS5; - break; - case 6: - tty.c_cflag |= CS6; - break; - case 7: - tty.c_cflag |= CS7; - break; - default: - tty.c_cflag |= CS8; - break; - } - - - // parity - switch(parity) { - case 0: - tty.c_cflag &= ~PARENB; - break; - case 1: - tty.c_cflag |= PARENB; - tty.c_cflag &= ~PARODD; // Clearing PARODD makes the parity even - break; - case 2: - tty.c_cflag |= PARENB; - tty.c_cflag |= PARODD; - break; - } - - // stop bits - // Set num. stop bits - switch(stopBits) { - case 0: - tty.c_cflag &= ~CSTOPB; - break; - // TODO case 1: 1.5 would break the code - case 2: - tty.c_cflag |= CSTOPB; - break; - } - - // Save tty settings, also checking for error - ioctl(hSerialPort, TCSETS2, &tty); - - returnStatus(StatusCodes::SUCCESS); -} - -auto unixSystemClose() -> int { - return close(hSerialPort); -} - -auto unixSystemRead( - void* buffer, - const int bufferSize, - const int timeout, - const int multiplier -) -> int { - return read(hSerialPort, static_cast(buffer), bufferSize); -} - -auto unixSystemReadUntil( - void* buffer, - const int bufferSize, - const int timeout, - const int multiplier, - void* searchString -) -> int { - return 0; -} - -auto unixSystemWrite( - void* buffer, - const int bufferSize, - const int timeout, - const int multiplier -) -> int { - - std::string tmp(static_cast(buffer), bufferSize); - - return write(hSerialPort, tmp.c_str(), tmp.length() + 1); -} - -// auto unixSystemGetAvailablePorts( -// void* buffer, -// const int bufferSize, -// void* separator -// ) -> int { -// std::string result; - -// int portsCounter = 0; - -// fs::path p("/dev/serial/by-id"); - -// try { -// if (!exists(p)) { -// returnStatus(StatusCodes::NOT_FOUND_ERROR); -// } - -// else { -// for (auto de : fs::directory_iterator(p)) { -// if (is_symlink(de.symlink_status())) { -// fs::path symlink_points_at = read_symlink(de); -// fs::path canonical_path = fs::canonical(p / symlink_points_at); -// result += canonical_path.generic_string().append(std::string(static_cast(separator))); -// portsCounter++; -// } -// } -// } -// } catch (const fs::filesystem_error &exeption) { -// } - -// if (result.length() + 1 <= bufferSize){ -// memcpy(buffer, result.c_str(), result.length() + 1); -// } else { -// returnStatus(StatusCodes::BUFFER_ERROR); -// } -// return portsCounter; -// } - -#endif diff --git a/src/src/serial_unix.cpp b/src/src/serial_unix.cpp new file mode 100644 index 0000000..7fca11b --- /dev/null +++ b/src/src/serial_unix.cpp @@ -0,0 +1,276 @@ +#if defined(__unix__) || defined(__unix) || defined(__APPLE__) +#include "serial.h" +#include +#include // UNIX standard function definitions +#include // File control definitions +#include // Used for TCGETS2, which is required for custom baud rates +#include +#include +#include +#include + +int hSerialPort; +termios2 tty; +std::string data; + +void (*errorCallback)(int errorCode); +void (*readCallback)(int bytes); +void (*writeCallback)(int bytes); + +void serialOpen( + void* port, + const int baudrate, + const int dataBits, + const int parity, + const int stopBits +) { + char *portName = static_cast(port); + // Open new serial connection + hSerialPort = open(portName, O_RDWR); + + // Error if open fails + if(hSerialPort == -1){ + errorCallback(status(StatusCodes::INVALID_HANDLE_ERROR)); + return; + } + + // Get the current com configuration + if(ioctl(hSerialPort, TCGETS2, &tty) == -1){ + errorCallback(status(StatusCodes::GET_STATE_ERROR)); + return; + } + + tty.c_cflag &= ~PARENB; // Clear parity bit, disabling parity (most common) + tty.c_cflag &= ~CSTOPB; // Clear stop field, only one stop bit used in communication (most common) + tty.c_cflag &= ~CSIZE; // Clear all bits that set the data size + tty.c_cflag |= CS8; // 8 bits per byte (most common) + tty.c_cflag &= ~CRTSCTS; // Disable RTS/CTS hardware flow control (most common) + tty.c_cflag |= CREAD | CLOCAL; // Turn on READ & ignore ctrl lines (CLOCAL = 1) + + tty.c_lflag &= ~ICANON; + tty.c_lflag &= ~ECHO; // Disable echo + tty.c_lflag &= ~ECHOE; // Disable erasure + tty.c_lflag &= ~ECHONL; // Disable new-line echo + tty.c_lflag &= ~ISIG; // Disable interpretation of INTR, QUIT and SUSP + tty.c_iflag &= ~(IXON | IXOFF | IXANY); // Turn off s/w flow ctrl + tty.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL); // Disable any special handling of received bytes + tty.c_oflag &= ~OPOST; // Prevent special interpretation of output bytes (e.g. newline chars) + tty.c_oflag &= ~ONLCR; // Prevent conversion of newline to carriage return/line feed + + tty.c_cc[VTIME] = 10; // Wait for up to 1s (10 deciseconds), returning as soon as any data is received. + tty.c_cc[VMIN] = 0; + + // Set in/out baud rate to be 9600 + // cfsetispeed(&tty, B9600); + // cfsetospeed(&tty, B9600); + tty.c_ispeed = baudrate; + tty.c_ospeed = baudrate; + + // Data bits + tty.c_cflag &= ~CSIZE; // CSIZE is a mask for the number of bits per character + + switch(dataBits) { + case 5: + tty.c_cflag |= CS5; + break; + case 6: + tty.c_cflag |= CS6; + break; + case 7: + tty.c_cflag |= CS7; + break; + default: + tty.c_cflag |= CS8; + break; + } + + // parity + switch(parity) { + case 0: + tty.c_cflag &= ~PARENB; + break; + case 1: + tty.c_cflag |= PARENB; + tty.c_cflag &= ~PARODD; // Clearing PARODD makes the parity even + break; + case 2: + tty.c_cflag |= PARENB; + tty.c_cflag |= PARODD; + break; + } + + // stop bits + // Set num. stop bits + switch(stopBits) { + case 0: + tty.c_cflag &= ~CSTOPB; + break; + // TODO case 1: 1.5 would break the code + case 2: + tty.c_cflag |= CSTOPB; + break; + } + + // Save tty settings, also checking for error + if (ioctl(hSerialPort, TCSETS2, &tty) == -1){ + errorCallback(status(StatusCodes::SET_STATE_ERROR)); + return; + } + + return; +} + +void serialClose() { + // Error if close fails + if (close(hSerialPort) == -1) { + errorCallback(status(StatusCodes::CLOSE_HANDLE_ERROR)); + return; + } + + return; +} + +auto serialRead( + void* buffer, + const int bufferSize, + const int timeout, + const int multiplier +) -> int { + + if (ioctl(hSerialPort, TCGETS2, &tty) == -1){ + errorCallback(status(StatusCodes::SET_STATE_ERROR)); + return 0; + } + + if (timeout > 0 && timeout < 100) { + tty.c_cc[VTIME] = 1; + } else { + tty.c_cc[VTIME] = timeout / 100; + } + + tty.c_cc[VMIN] = bufferSize; + + if (ioctl(hSerialPort, TCSETS2, &tty) == -1) { + errorCallback(status(StatusCodes::SET_STATE_ERROR)); + return 0; + } + + int bytesRead = read(hSerialPort, static_cast(buffer), bufferSize); + + if (bytesRead >= 0){ + return bytesRead; + } + + errorCallback(status(StatusCodes::READ_ERROR)); + return 0; +} + +auto serialReadUntil( + void* buffer, + const int bufferSize, + const int timeout, + const int multiplier, + void* searchString +) -> int { + + data = ""; + + for (int i{0}; i < bufferSize && data.find(std::string(static_cast(searchString))) == std::string::npos; i++) { + char bufferChar[1]; + + // Error if read fails + int bytesRead = read(hSerialPort, static_cast(bufferChar), 1); + if (bytesRead == -1) { + errorCallback(status(StatusCodes::READ_ERROR)); + return 0; + } + + if (bytesRead == 0) { + break; + } + + data.append(std::string(bufferChar, bytesRead)); + } + + memcpy(buffer, data.c_str(), data.length() + 1); + + readCallback(data.length()); + + return data.length(); +} + +auto serialWrite( + void* buffer, + const int bufferSize, + const int timeout, + const int multiplier +) -> int { + + const char* tmp = static_cast(buffer); + + int bytesWritten = write(hSerialPort, tmp, bufferSize); + + if (bytesWritten == -1) { + errorCallback(status(StatusCodes::WRITE_ERROR)); + return 0; + } + + writeCallback(bytesWritten); + return bytesWritten; +} + +auto serialOnError(void (*func)(int code)) -> void { + errorCallback = func; +}; + +auto serialOnRead(void (*func)(int bytes)) -> void { + readCallback = func; +}; + +auto serialOnWrite(void (*func)(int bytes)) -> void { + writeCallback = func; +}; + +auto serialGetPortsInfo(void *buffer, const int bufferSize, void *separator) -> int { + std::string result = ""; + + int portsCounter = 0; + + DIR* dir = opendir("/dev/serial/by-id"); + if (dir == nullptr) { + // Handle directory not found error + return -1; // Return an appropriate error code or define your own + } + + struct dirent* entry; + while ((entry = readdir(dir)) != nullptr) { + if (entry->d_type == DT_LNK) { + std::string symlinkPath = "/dev/serial/by-id/"; + symlinkPath += entry->d_name; + + char canonicalPath[PATH_MAX]; + if (realpath(symlinkPath.c_str(), canonicalPath) != nullptr) { + result += std::string(canonicalPath) + std::string(static_cast(separator)); + portsCounter++; + } + } + } + + closedir(dir); + + // Remove last trailing comma + if (result.length() > 0) { + result.erase(result.length() - 1); + } + + if (result.length() + 1 <= bufferSize) { + memcpy(buffer, result.c_str(), result.length() + 1); + } else { + errorCallback(status(StatusCodes::BUFFER_ERROR)); + return 0; + } + + return portsCounter; +} + +#endif diff --git a/src/serial_windows.cpp b/src/src/serial_windows.cpp similarity index 50% rename from src/serial_windows.cpp rename to src/src/serial_windows.cpp index 479ec09..1044cbb 100644 --- a/src/serial_windows.cpp +++ b/src/src/serial_windows.cpp @@ -1,5 +1,7 @@ #if defined(_WIN32) || defined(__WIN32__) || defined(WIN32) -#include "serial_windows.h" +#include + +#include "serial.h" HANDLE hSerialPort; @@ -7,23 +9,17 @@ DCB dcbSerialParams = {0}; COMMTIMEOUTS timeouts = {0}; std::string data; -/** -* @fn auto open(void* port, const int baudrate, const int dataBits, const int parity, const int stopBits) -> int -* @brief Opens the specified connection to a serial device. -* @param port The port to open the serial connection to -* @param baudrate The baudrate for the serial connection when reading/writing -* @param dataBits The data bits -* @param parity The parity bits -* @param stopBits The stop bits -* @return Returns the current status code -*/ -auto windowsSystemOpen( +void (*errorCallback)(int errorCode); +void (*readCallback)(int bytes); +void (*writeCallback)(int bytes); + +void serialOpen( void* port, const int baudrate, const int dataBits, const int parity, const int stopBits -) -> int { +) { char *portName = static_cast(port); @@ -41,13 +37,21 @@ auto windowsSystemOpen( // Error if open fails if (hSerialPort == INVALID_HANDLE_VALUE) { - returnStatus(StatusCodes::INVALID_HANDLE_ERROR); + errorCallback(status(StatusCodes::INVALID_HANDLE_ERROR)); + return; } // Error if configuration get fails if (!GetCommState(hSerialPort, &dcbSerialParams)) { - CloseHandle(hSerialPort); - returnStatus(StatusCodes::GET_STATE_ERROR); + // Error if close fails + if (!CloseHandle(hSerialPort)) { + errorCallback(status(StatusCodes::CLOSE_HANDLE_ERROR)); + return; + } + + errorCallback(status(StatusCodes::GET_STATE_ERROR)); + + return; } dcbSerialParams.BaudRate = baudrate; @@ -57,8 +61,14 @@ auto windowsSystemOpen( // Error if configuration set fails if (!SetCommState(hSerialPort, &dcbSerialParams)) { - CloseHandle(hSerialPort); - returnStatus(StatusCodes::SET_STATE_ERROR); + // Error if close fails + if (!CloseHandle(hSerialPort)) { + errorCallback(status(StatusCodes::CLOSE_HANDLE_ERROR)); + return; + } + + errorCallback(status(StatusCodes::SET_STATE_ERROR)); + return; } timeouts.ReadIntervalTimeout = 50; @@ -69,43 +79,32 @@ auto windowsSystemOpen( // Error if timeout set fails if (!SetCommTimeouts(hSerialPort, &timeouts)) { - CloseHandle(hSerialPort); - returnStatus(StatusCodes::SET_TIMEOUT_ERROR); - } + // Error if close fails + if (!CloseHandle(hSerialPort)) { + errorCallback(status(StatusCodes::CLOSE_HANDLE_ERROR)); + return; + } - returnStatus(StatusCodes::SUCCESS); + errorCallback(status(StatusCodes::SET_TIMEOUT_ERROR)); + return; + } } -/** -* @fn auto close() -> int -* @brief Closes the specified connection to a serial device. -* @return Returns the current status code -*/ -auto windowsSystemClose() -> int { +void serialClose() { // Error if handle is invalid if (hSerialPort == INVALID_HANDLE_VALUE) { - returnStatus(StatusCodes::INVALID_HANDLE_ERROR); + errorCallback(status(StatusCodes::INVALID_HANDLE_ERROR)); + return; } // Error if close fails if (!CloseHandle(hSerialPort)) { - returnStatus(StatusCodes::CLOSE_HANDLE_ERROR); + errorCallback(status(StatusCodes::CLOSE_HANDLE_ERROR)); + return; } - - returnStatus(StatusCodes::SUCCESS); } -/** -* @fn auto read(void* buffer, const int bufferSize, const int timeout, const int multiplier) -> int -* @brief Reads the specified number of bytes into the buffer. -* **It is not guaranteed that the complete buffer will be fully read.** -* @param buffer The buffer in which the bytes should be read into -* @param bufferSize The size of the buffer -* @param timeout Timeout to cancel the read -* @param multiplier The time multiplier between reading -* @return Returns the current status code (negative) or number of bytes read -*/ -auto windowsSystemRead( +auto serialRead( void* buffer, const int bufferSize, const int timeout, @@ -113,7 +112,8 @@ auto windowsSystemRead( ) -> int { // Error if handle is invalid if (hSerialPort == INVALID_HANDLE_VALUE) { - returnStatus(StatusCodes::INVALID_HANDLE_ERROR); + errorCallback(status(StatusCodes::INVALID_HANDLE_ERROR)); + return 0; } timeouts.ReadIntervalTimeout = timeout; @@ -122,40 +122,33 @@ auto windowsSystemRead( // Error if timeout set fails if (!SetCommTimeouts(hSerialPort, &timeouts)) { - returnStatus(StatusCodes::SET_TIMEOUT_ERROR); + errorCallback(status(StatusCodes::SET_TIMEOUT_ERROR)); + return 0; } - DWORD bytesRead; + DWORD bytesRead = 0; // Error if read fails if (!ReadFile(hSerialPort, buffer, bufferSize, &bytesRead, NULL)) { - returnStatus(StatusCodes::READ_ERROR); + errorCallback(status(StatusCodes::READ_ERROR)); + return 0; } + readCallback(bytesRead); return bytesRead; } -/** -* @fn auto readUntil(void* buffer, const int bufferSize, const int timeout, const int mutilplier, void* searchString) -> int -* @brief Reads until the specified string is found. If the specified string is not found, the buffer is read full until there are no more bytes to read. -* **It is not guaranteed that the complete buffer will be fully read.** -* @param buffer The buffer in which the bytes should be read into -* @param bufferSize The size of the buffer -* @param timeout Timeout to cancel the read -* @param multiplier The time multiplier between reading -* @param searchString The string to search for -* @return Returns the current status code (negative) or number of bytes read -*/ -auto windowsSystemReadUntil( +auto serialReadUntil( void* buffer, const int bufferSize, const int timeout, const int multiplier, void* searchString ) -> int { - + // Error if handle is invalid if (hSerialPort == INVALID_HANDLE_VALUE) { - returnStatus(StatusCodes::INVALID_HANDLE_ERROR); + errorCallback(status(StatusCodes::INVALID_HANDLE_ERROR)); + return 0; } timeouts.ReadIntervalTimeout = timeout; @@ -164,7 +157,8 @@ auto windowsSystemReadUntil( // Error if timeout set fails if (!SetCommTimeouts(hSerialPort, &timeouts)) { - returnStatus(StatusCodes::SET_TIMEOUT_ERROR); + errorCallback(status(StatusCodes::SET_TIMEOUT_ERROR)); + return 0; } data = ""; @@ -175,7 +169,8 @@ auto windowsSystemReadUntil( // Error if read fails if (!ReadFile(hSerialPort, bufferChar, sizeof(bufferChar), &bytesRead, NULL)) { - returnStatus(StatusCodes::READ_ERROR); + errorCallback(status(StatusCodes::READ_ERROR)); + return 0; } if (bytesRead == 0) { @@ -187,47 +182,39 @@ auto windowsSystemReadUntil( memcpy(buffer, data.c_str(), data.length() + 1); + readCallback(data.length()); return data.length(); } -/** -* @fn auto write(void* buffer, const int bufferSize, const int timeout, const int multiplier) -> int -* @brief Writes the buffer to the serial device. -* **It is not guaranteed that the complete buffer will be fully written.** -* @param buffer The buffer in which the bytes should be read into -* @param bufferSize The size of the buffer -* @param timeout Timeout to cancel the read -* @param multiplieer The time multiplier between writing -* @return Returns the current status code (negative) or number of bytes written -*/ -auto windowsSystemWrite(void* buffer, const int bufferSize, const int timeout, const int multiplier) -> int { - DWORD bytesWritten; +auto serialWrite( + void* buffer, + const int bufferSize, + const int timeout, + const int multiplier +) -> int { + + DWORD bytesWritten = 0; timeouts.WriteTotalTimeoutConstant = timeout; timeouts.WriteTotalTimeoutMultiplier = multiplier; // Error if timeout set fails if (!SetCommTimeouts(hSerialPort, &timeouts)) { - returnStatus(StatusCodes::SET_TIMEOUT_ERROR); + errorCallback(status(StatusCodes::SET_TIMEOUT_ERROR)); + return 0; } // Error if write fails if (!WriteFile(hSerialPort, buffer, bufferSize, &bytesWritten, NULL)) { - returnStatus(StatusCodes::WRITE_ERROR); + errorCallback(status(StatusCodes::WRITE_ERROR)); + return 0; } + writeCallback(bytesWritten); return bytesWritten; } -/** -* @fn auto getAvailablePorts(void* buffer, const int bufferSize, void* separator) -> int -* @brief Get all the available serial ports. -* @param buffer The buffer in which the bytes should be read into -* @param bufferSize The size of the buffer -* @param separator The separator for the array buffer -* @return Returns the current status code (negative) or number of ports found -*/ -auto windowsSystemGetAvailablePorts( +auto serialGetPortsInfo( void* buffer, const int bufferSize, void* separator @@ -242,7 +229,7 @@ auto windowsSystemGetAvailablePorts( // Error if open fails if (hPort == INVALID_HANDLE_VALUE) { CloseHandle(hPort); - returnStatus(StatusCodes::INVALID_HANDLE_ERROR); + continue; } portsCounter++; @@ -250,9 +237,16 @@ auto windowsSystemGetAvailablePorts( CloseHandle(hPort); } + + // Remove last trailing comma + if (result.length() > 0) { + result.erase(result.length() - 1); + } + // Error if buffer size is to small if (result.length() + 1 > bufferSize) { - returnStatus(StatusCodes::BUFFER_ERROR); + errorCallback(status(StatusCodes::BUFFER_ERROR)); + return 0; } memcpy(buffer, result.c_str(), result.length() + 1); @@ -260,4 +254,16 @@ auto windowsSystemGetAvailablePorts( return portsCounter; } +auto serialOnError(void (*func)(int code)) -> void { + errorCallback = func; +}; + +auto serialOnRead(void (*func)(int bytes)) -> void { + readCallback = func; +}; + +auto serialOnWrite(void (*func)(int bytes)) -> void { + writeCallback = func; +}; + #endif