pre-pre alpha of Network_CAN_clientserver

This commit is contained in:
Edward Emelianov 2025-04-28 13:35:00 +03:00
parent 882c782c52
commit c16e343961
18 changed files with 922 additions and 0 deletions

View File

@ -0,0 +1,73 @@
cmake_minimum_required(VERSION 3.20)
set(PROJ canserv)
set(MINOR_VERSION "1")
set(MID_VERSION "0")
set(MAJOR_VERSION "0")
set(VERSION "${MAJOR_VERSION}.${MID_VERSION}.${MINOR_VERSION}")
project(${PROJ} VERSION ${VERSION} LANGUAGES C)
message("VERSION: ${VERSION}")
# list of options
option(DEBUG "Compile in debug mode" OFF)
option(EXAMPLES "Compile also all examples" ON)
# default flags
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -W -Wextra -std=gnu99")
set(CMAKE_COLOR_MAKEFILE ON)
# here is one of two variants: all .c in directory or .c files in list
aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR} SOURCES)
# cmake -DDEBUG=yes -> debugging
if(DEBUG)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Og -g3 -ggdb -fno-builtin-strlen -Werror")
add_definitions(-DEBUG)
set(CMAKE_BUILD_TYPE DEBUG)
set(CMAKE_VERBOSE_MAKEFILE "ON")
else()
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O3 -march=native -fdata-sections -ffunction-sections")
SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--gc-sections")
set(CMAKE_BUILD_TYPE RELEASE)
endif()
message("Build type: ${CMAKE_BUILD_TYPE}")
###### pkgconfig ######
# pkg-config modules (for pkg-check-modules)
set(MODULES usefull_macros>=0.3.2)
# find packages:
find_package(PkgConfig REQUIRED)
pkg_check_modules(${PROJ} REQUIRED ${MODULES})
# external modules like OpenMP:
include(FindOpenMP)
if(OPENMP_FOUND)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${OpenMP_EXE_LINKER_FLAGS}")
add_definitions(-DOMP_FOUND)
endif()
###### additional flags ######
#list(APPEND ${PROJ}_LIBRARIES "-lfftw3_threads")
add_executable(${PROJ} ${SOURCES} ${PO_FILE})
# -I
target_include_directories(${PROJ} PUBLIC ${${PROJ}_INCLUDE_DIRS} .)
# -L
target_link_directories(${PROJ} PUBLIC ${${PROJ}_LIBRARY_DIRS} )
# -D
add_definitions(-D_XOPEN_SOURCE=1234 -D_DEFAULT_SOURCE -D_GNU_SOURCE -DPACKAGE_VERSION=\"${VERSION}\"
-DMINOR_VERSION=\"${MINOR_VERSION}\" -DMID_VERSION=\"${MID_VERSION}\"
-DMAJOR_VERSION=\"${MAJOR_VESION}\")
# -l
target_link_libraries(${PROJ} ${${PROJ}_LIBRARIES} -lm)
# Installation of the program
include(GNUInstallDirs)
install(TARGETS ${PROJ} DESTINATION "bin")
# EXAMPLES
if(EXAMPLES)
add_subdirectory(examples)
endif()

View File

@ -0,0 +1 @@
simple server for operate with FX3U-clone over TCP sockets

View File

@ -0,0 +1,92 @@
/*
* This file is part of the fx3u project.
* Copyright 2024 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
// command parameter flag means this is a setter
#define SETTER_FLAG (0x80)
#define ISSETTER(data) ((data[2] & SETTER_FLAG))
// parameter number 127 means there no parameter number at all (don't need paremeter or get all)
#define NO_PARNO (0x7f)
// base value of parameter (even if it is a setter)
#define PARBASE(x) (x & 0x7f)
// get parameter value of msg->data
#define PARVAL(data) (data[2] & 0x7f)
// make error for CAN answer
#define FORMERR(m, err) do{m->data[3] = err; if(m->length < 4) m->length = 4;}while(0)
// error codes for answer message
typedef enum{
ERR_OK, // 0 - all OK
ERR_BADPAR, // 1 - parameter is wrong
ERR_BADVAL, // 2 - wrong value
ERR_WRONGLEN, // 3 - wrong message length
ERR_BADCMD, // 4 - unknown command
ERR_CANTRUN, // 5 - can't run given command due to bad parameters or other
ERR_AMOUNT // amount of error codes
} errcodes;
// set command bytes in CAN message
#define MSG_SET_CMD(msg, cmd) do{*((uint16_t*)msg.data) = (cmd);}while(0)
#define MSGP_SET_CMD(msg, cmd) do{*((uint16_t*)msg->data) = (cmd);}while(0)
#define MSG_GET_CMD(msg) (*(uint16_t*)msg.data)
#define MSGP_GET_CMD(msg) (*(uint16_t*)msg->data)
// set command parameter number
#define MSG_SET_PARNO(msg, n) do{msg.data[2] = (n);}while(0)
#define MSGP_SET_PARNO(msg, n) do{msg->data[2] = (n);}while(0)
// set error
#define MSG_SET_ERR(msg, err) do{msg.data[3] = (err);}while(0)
#define MSGP_SET_ERR(msg, err) do{msg->data[3] = (err);}while(0)
// set uint32_t data
#define MSG_SET_U32(msg, d) do{*((uint32_t*)(&msg.data[4])) = (d);}while(0)
#define MSGP_SET_U32(msg, d) do{*((uint32_t*)(&msg->data[4])) = (d);}while(0)
// get uint32_t data
#define MSG_GET_U32(msg) (*(uint32_t*)&msg.data[4])
#define MSGP_GET_U32(msg) (*(uint32_t*)&msg->data[4])
// CAN commands indexes
enum{
CMD_PING, // just ping
CMD_RESET, // reset MCU
CMD_TIME, // get/set Tms
CMD_MCUTEMP, // get MCU temperature (*10)
CMD_ADCRAW, // get ADC raw values
CMD_CANSPEED, // get/set CAN speed (kbps)
CMD_CANID, // get/set common CAN ID (both in and out)
CMD_CANIDin, // input CAN ID
CMD_CANIDout, // output CAN ID
CMD_SAVECONF, // save configuration
CMD_ERASESTOR, // erase all flash storage
CMD_RELAY, // switch relay ON/OFF
CMD_GETESW, // current ESW state, bounce-free
CMD_GETESWNOW, // current ESW state, absolute
CMD_BOUNCE, // get/set bounce constant (ms)
CMD_USARTSPEED, // get/set USART1 speed
CMD_LED, // onboard LED
CMD_FLAGS, // flags setter/getter
CMD_INCHNLS, // all bits set are active supported IN channels
CMD_OUTCHNLS, // all bits set are active supported OUT channels
CMD_MODBUSID, // set/get modbus slave ID (or 0 if master)
CMD_MODBUSIDOUT,// slave ID to send modbus relay command if IN changes
CMD_MODBUSSPEED,// speed of modbus interface
// should be the last:
CMD_AMOUNT // amount of CAN commands
};

View File

@ -0,0 +1 @@
-std=c17

View File

@ -0,0 +1,5 @@
// Add predefined macros for your project here. For example:
// #define THE_ANSWER 42
#define XOPEN_SOURCE 1234
#define DEFAULT_SOURCE
#define GNU_SOURCE

View File

@ -0,0 +1 @@
[General]

View File

@ -0,0 +1,184 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE QtCreatorProject>
<!-- Written by QtCreator 16.0.0, 2025-04-28T11:38:19. -->
<qtcreator>
<data>
<variable>EnvironmentId</variable>
<value type="QByteArray">{cf63021e-ef53-49b0-b03b-2f2570cdf3b6}</value>
</data>
<data>
<variable>ProjectExplorer.Project.ActiveTarget</variable>
<value type="qlonglong">0</value>
</data>
<data>
<variable>ProjectExplorer.Project.EditorSettings</variable>
<valuemap type="QVariantMap">
<value type="bool" key="EditorConfiguration.AutoDetect">true</value>
<value type="bool" key="EditorConfiguration.AutoIndent">true</value>
<value type="bool" key="EditorConfiguration.CamelCaseNavigation">true</value>
<valuemap type="QVariantMap" key="EditorConfiguration.CodeStyle.0">
<value type="QString" key="language">Cpp</value>
<valuemap type="QVariantMap" key="value">
<value type="QByteArray" key="CurrentPreferences">CppGlobal</value>
</valuemap>
</valuemap>
<valuemap type="QVariantMap" key="EditorConfiguration.CodeStyle.1">
<value type="QString" key="language">QmlJS</value>
<valuemap type="QVariantMap" key="value">
<value type="QByteArray" key="CurrentPreferences">QmlJSGlobal</value>
</valuemap>
</valuemap>
<value type="qlonglong" key="EditorConfiguration.CodeStyle.Count">2</value>
<value type="QByteArray" key="EditorConfiguration.Codec">KOI8-R</value>
<value type="bool" key="EditorConfiguration.ConstrainTooltips">false</value>
<value type="int" key="EditorConfiguration.IndentSize">4</value>
<value type="bool" key="EditorConfiguration.KeyboardTooltips">false</value>
<value type="int" key="EditorConfiguration.LineEndingBehavior">0</value>
<value type="int" key="EditorConfiguration.MarginColumn">80</value>
<value type="bool" key="EditorConfiguration.MouseHiding">true</value>
<value type="bool" key="EditorConfiguration.MouseNavigation">true</value>
<value type="int" key="EditorConfiguration.PaddingMode">1</value>
<value type="int" key="EditorConfiguration.PreferAfterWhitespaceComments">0</value>
<value type="bool" key="EditorConfiguration.PreferSingleLineComments">false</value>
<value type="bool" key="EditorConfiguration.ScrollWheelZooming">false</value>
<value type="bool" key="EditorConfiguration.ShowMargin">false</value>
<value type="int" key="EditorConfiguration.SmartBackspaceBehavior">1</value>
<value type="bool" key="EditorConfiguration.SmartSelectionChanging">true</value>
<value type="bool" key="EditorConfiguration.SpacesForTabs">true</value>
<value type="int" key="EditorConfiguration.TabKeyBehavior">0</value>
<value type="int" key="EditorConfiguration.TabSize">8</value>
<value type="bool" key="EditorConfiguration.UseGlobal">true</value>
<value type="bool" key="EditorConfiguration.UseIndenter">false</value>
<value type="int" key="EditorConfiguration.Utf8BomBehavior">1</value>
<value type="bool" key="EditorConfiguration.addFinalNewLine">true</value>
<value type="bool" key="EditorConfiguration.cleanIndentation">true</value>
<value type="bool" key="EditorConfiguration.cleanWhitespace">true</value>
<value type="QString" key="EditorConfiguration.ignoreFileTypes">*.md, *.MD, Makefile</value>
<value type="bool" key="EditorConfiguration.inEntireDocument">true</value>
<value type="bool" key="EditorConfiguration.skipTrailingWhitespace">true</value>
<value type="bool" key="EditorConfiguration.tintMarginArea">true</value>
</valuemap>
</data>
<data>
<variable>ProjectExplorer.Project.PluginSettings</variable>
<valuemap type="QVariantMap">
<valuemap type="QVariantMap" key="AutoTest.ActiveFrameworks">
<value type="bool" key="AutoTest.Framework.Boost">true</value>
<value type="bool" key="AutoTest.Framework.CTest">false</value>
<value type="bool" key="AutoTest.Framework.Catch">true</value>
<value type="bool" key="AutoTest.Framework.GTest">true</value>
<value type="bool" key="AutoTest.Framework.QtQuickTest">true</value>
<value type="bool" key="AutoTest.Framework.QtTest">true</value>
</valuemap>
<value type="bool" key="AutoTest.ApplyFilter">false</value>
<valuemap type="QVariantMap" key="AutoTest.CheckStates"/>
<valuelist type="QVariantList" key="AutoTest.PathFilters"/>
<value type="int" key="AutoTest.RunAfterBuild">0</value>
<value type="bool" key="AutoTest.UseGlobal">true</value>
<valuemap type="QVariantMap" key="ClangTools">
<value type="bool" key="ClangTools.AnalyzeOpenFiles">true</value>
<value type="bool" key="ClangTools.BuildBeforeAnalysis">true</value>
<value type="QString" key="ClangTools.DiagnosticConfig">Builtin.DefaultTidyAndClazy</value>
<value type="int" key="ClangTools.ParallelJobs">4</value>
<value type="bool" key="ClangTools.PreferConfigFile">true</value>
<valuelist type="QVariantList" key="ClangTools.SelectedDirs"/>
<valuelist type="QVariantList" key="ClangTools.SelectedFiles"/>
<valuelist type="QVariantList" key="ClangTools.SuppressedDiagnostics"/>
<value type="bool" key="ClangTools.UseGlobalSettings">true</value>
</valuemap>
</valuemap>
</data>
<data>
<variable>ProjectExplorer.Project.Target.0</variable>
<valuemap type="QVariantMap">
<value type="QString" key="DeviceType">Desktop</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Desktop</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Desktop</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">{91347f2c-5221-46a7-80b1-0a054ca02f79}</value>
<value type="qlonglong" key="ProjectExplorer.Target.ActiveBuildConfiguration">0</value>
<value type="qlonglong" key="ProjectExplorer.Target.ActiveDeployConfiguration">0</value>
<value type="qlonglong" key="ProjectExplorer.Target.ActiveRunConfiguration">0</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.BuildConfiguration.0">
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/home/eddy/Docs/SAO/BTA/ACS-АСУ/Network_CAN_clientserver</value>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
<valuelist type="QVariantList" key="GenericProjectManager.GenericMakeStep.BuildTargets">
<value type="QString">all</value>
</valuelist>
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">GenericProjectManager.GenericMakeStep</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">1</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Сборка</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Сборка</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Build</value>
</valuemap>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.1">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
<valuelist type="QVariantList" key="GenericProjectManager.GenericMakeStep.BuildTargets">
<value type="QString">clean</value>
</valuelist>
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">GenericProjectManager.GenericMakeStep</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">1</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Очистка</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Очистка</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Clean</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">2</value>
<value type="bool" key="ProjectExplorer.BuildConfiguration.ClearSystemEnvironment">false</value>
<valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.CustomParsers"/>
<value type="bool" key="ProjectExplorer.BuildConfiguration.ParseStandardOutput">false</value>
<valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.UserEnvironmentChanges"/>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">По умолчанию</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">GenericProjectManager.GenericBuildConfiguration</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.Target.BuildConfigurationCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.DeployConfiguration.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">0</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Развёртывание</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Развёртывание</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Deploy</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.DeployConfiguration.CustomData"/>
<value type="bool" key="ProjectExplorer.DeployConfiguration.CustomDataEnabled">false</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.DefaultDeployConfiguration</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.Target.DeployConfigurationCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.RunConfiguration.0">
<value type="bool" key="Analyzer.Perf.Settings.UseGlobalSettings">true</value>
<value type="bool" key="Analyzer.QmlProfiler.Settings.UseGlobalSettings">true</value>
<value type="int" key="Analyzer.Valgrind.Callgrind.CostFormat">0</value>
<value type="bool" key="Analyzer.Valgrind.Settings.UseGlobalSettings">true</value>
<value type="QList&lt;int&gt;" key="Analyzer.Valgrind.VisibleErrorKinds"></value>
<valuelist type="QVariantList" key="CustomOutputParsers"/>
<value type="int" key="PE.EnvironmentAspect.Base">2</value>
<valuelist type="QVariantList" key="PE.EnvironmentAspect.Changes"/>
<value type="bool" key="PE.EnvironmentAspect.PrintOnRun">false</value>
<value type="QString" key="PerfRecordArgsId">-e cpu-cycles --call-graph dwarf,4096 -F 250</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.CustomExecutableRunConfiguration</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey"></value>
<value type="bool" key="ProjectExplorer.RunConfiguration.Customized">false</value>
<value type="bool" key="RunConfiguration.UseCppDebuggerAuto">true</value>
<value type="bool" key="RunConfiguration.UseQmlDebuggerAuto">true</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.Target.RunConfigurationCount">1</value>
</valuemap>
</data>
<data>
<variable>ProjectExplorer.Project.TargetCount</variable>
<value type="qlonglong">1</value>
</data>
<data>
<variable>ProjectExplorer.Project.Updater.FileVersion</variable>
<value type="int">22</value>
</data>
<data>
<variable>Version</variable>
<value type="int">22</value>
</data>
</qtcreator>

View File

@ -0,0 +1 @@
-std=c++17

View File

@ -0,0 +1,8 @@
canproto.h
clientserver.c
clientserver.h
globopts.c
globopts.h
main.c
parsecanmsgs.c
parsecanmsgs.h

View File

@ -0,0 +1,245 @@
/*
* This file is part of the canserver project.
* Copyright 2025 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <inttypes.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
#include "clientserver.h"
#include "parsecanmsgs.h"
static pthread_t clientthread;
sl_sock_t *serialsock = NULL;
#define CMDIN "in"
#define CMDOUT "out"
#define CMDMCUT "mcut"
#define CMDLED "led"
#define CMDINCH "inchnls"
#define CMDOUTCH "outchnls"
#define CMDTIME "time"
#define CMDPING "ping"
typedef struct{
const char *textcmd;
int cancmd;
int havesetter;
} commands;
// list of all supported commands
static commands allcommands[] = {
{CMDIN, CMD_GETESW, 0},
{CMDOUT, CMD_RELAY, 1},
{CMDMCUT, CMD_MCUTEMP, 0},
{CMDLED, CMD_LED, 1},
{CMDINCH, CMD_INCHNLS, 0},
{CMDOUTCH, CMD_OUTCHNLS, 0},
{CMDTIME, CMD_TIME, 1},
{CMDPING, CMD_PING, 1},
{NULL, 0, 0}
};
static int Relay1cmds(CAN_message *msg, char buf[BUFSIZ]){
int L = 0;
uint16_t cmd = MSGP_GET_CMD(msg);
uint8_t par = PARVAL(msg->data);
uint32_t data = MSGP_GET_U32(msg);
commands *c = allcommands;
while(c->textcmd){
if(cmd == c->cancmd) break;
++c;
}
if(!c->textcmd) return 0;
DBG("found text cmd is %s (%u)", c->textcmd, data);
if(par == NO_PARNO) L = snprintf(buf, BUFSIZ-1, "%s=%u\n", c->textcmd, data);
else L = snprintf(buf, BUFSIZ-1, "%s[%d]=%u\n", c->textcmd, par, data);
return L;
}
// check CAN_message and if recognize send ans to all clients
static void gotCANans(CAN_message *msg){
if(!msg) return;
char buf[BUFSIZ];
int L = 0;
switch(msg->ID){
case CANIDOUT:
L = Relay1cmds(msg, buf);
break;
default:
return;
}
if(L < 1) return;
DBG("BUF: %s", buf);
int N = sl_sock_sendall((uint8_t*) buf, L);
green("Send to %d clients\n", N);
}
/**
* @brief clientproc - process data received from serial terminal
* @param par - socket
* @return NULL
*/
void *clientproc(void _U_ *par){
FNAME();
char rbuf[BUFSIZ];
if(!serialsock) return NULL;
do{
ssize_t got = sl_sock_readline(serialsock, rbuf, BUFSIZ);
if(got < 0){ // disconnected
WARNX("Serial server disconnected");
if(serialsock) sl_sock_delete(&serialsock);
return NULL;
}else if(got == 0){ // nothing to read from serial port
usleep(1000);
continue;
}
// process data
DBG("GOT: %s", rbuf);
CAN_message msg;
if(parseCANstr(rbuf, &msg)){
DBG("Got CAN message");
gotCANans(&msg);
}
} while(serialsock && serialsock->connected);
WARNX("disconnected");
if(serialsock) sl_sock_delete(&serialsock);
return NULL;
}
static sl_sock_hresult_e dtimeh(sl_sock_t *client, _U_ sl_sock_hitem_t *item, _U_ const char *req){
char buf[32];
snprintf(buf, 31, "UNIXT=%.2f\n", sl_dtime());
sl_sock_sendstrmessage(client, buf);
return RESULT_SILENCE;
}
static sl_sock_hresult_e procUserCmd(sl_sock_t _U_ *client, sl_sock_hitem_t *item, const char *req){
#define CBUFLEN (128)
char buf[CBUFLEN];
int buflen = 0;
CAN_message msg;
msg.ID = CANIDIN;
int issetter = 0;
uint8_t parno = NO_PARNO;
uint32_t setterval = 0;
if(req){
int N;
if(!sl_str2i(&N, req) || N < 0){
DBG("BAD value %d", N);
return RESULT_BADVAL;
}
setterval = (uint32_t) N;
issetter = 1;
}
commands *c = allcommands;
while(c->textcmd){
if(0 == strcmp(c->textcmd, item->key)) break;
++c;
}
if(!c->textcmd) return RESULT_BADKEY;
if(issetter){
DBG("setter");
if(!c->havesetter) return RESULT_BADVAL;
if(CANu32setter(c->cancmd, parno, setterval, &msg) && (buflen = formCANmsg(&msg, buf, CBUFLEN-1)) > 0){
}else{
WARNX("Can't form message");
return RESULT_FAIL;
}
}else{
DBG("getter");
if(CANu32getter(c->cancmd, parno, &msg) && (buflen = formCANmsg(&msg, buf, CBUFLEN-1)) > 0){
}else{
WARNX("Can't form message");
return RESULT_FAIL;
}
}
if(buflen) sl_sock_sendstrmessage(serialsock, buf);
return RESULT_SILENCE;
#undef CBUFLEN
}
/*
static sl_sock_hresult_e show(sl_sock_t *client, _U_ sl_sock_hitem_t *item, _U_ const char *req){
if(!client) return RESULT_FAIL;
if(client->type != SOCKT_UNIX){
if(*client->IP){
printf("Client \"%s\" (fd=%d) ask for flags:\n", client->IP, client->fd);
}else printf("Can't get client's IP, flags:\n");
}else printf("Socket fd=%d asks for flags:\n", client->fd);
return RESULT_OK;
}*/
// Too much clients handler
static void toomuch(int fd){
const char m[] = "Try later: too much clients connected\n";
send(fd, m, sizeof(m)-1, MSG_NOSIGNAL);
shutdown(fd, SHUT_WR);
DBG("shutdown, wait");
double t0 = sl_dtime();
uint8_t buf[8];
while(sl_dtime() - t0 < 11.){
if(sl_canread(fd)){
ssize_t got = read(fd, buf, 8);
DBG("Got=%zd", got);
if(got < 1) break;
}
}
DBG("Disc after %gs", sl_dtime() - t0);
LOGWARN("Client fd=%d tried to connect after MAX reached", fd);
}
// new connections handler
static void connected(sl_sock_t *c){
if(c->type == SOCKT_UNIX) LOGMSG("New client fd=%d connected", c->fd);
else LOGMSG("New client fd=%d, IP=%s connected", c->fd, c->IP);
}
// disconnected handler
static void disconnected(sl_sock_t *c){
if(c->type == SOCKT_UNIX) LOGMSG("Disconnected client fd=%d", c->fd);
else LOGMSG("Disconnected client fd=%d, IP=%s", c->fd, c->IP);
}
static sl_sock_hitem_t handlers[] = {
{dtimeh, "dtime", "get server's UNIX time for all clients connected", NULL},
{procUserCmd, CMDOUT, "outputs setter/getter", NULL},
{procUserCmd, CMDIN, "inputs getter", NULL},
{procUserCmd, CMDMCUT, "get MCU temperature (/10degC)", NULL},
{procUserCmd, CMDLED, "onboard LED set/get", NULL},
{procUserCmd, CMDINCH, "get available input channels mask", NULL},
{procUserCmd, CMDOUTCH, "get available output channels mask", NULL},
{procUserCmd, CMDTIME, "get MCU milliseconds counter value", NULL},
{procUserCmd, CMDPING, "ping controller", NULL},
{NULL, NULL, NULL, NULL}
};
sl_sock_t *RunSrv(sl_socktype_e type, const char *node){
sl_sock_maxclhandler(toomuch);
sl_sock_connhandler(connected);
sl_sock_dischandler(disconnected);
return sl_sock_run_server(type, node, 4096, handlers);
}
sl_sock_t *RunClt(sl_socktype_e type, const char *node){
serialsock = sl_sock_run_client(type, node, 4096);
if(!serialsock){
DBG("Can't run client");
return NULL;
}
if(pthread_create(&clientthread, NULL, clientproc, NULL)) return NULL;
return serialsock;
}

View File

@ -0,0 +1,24 @@
/*
* This file is part of the canserver project.
* Copyright 2025 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <usefull_macros.h>
sl_sock_t *RunSrv(sl_socktype_e type, const char *node);
sl_sock_t *RunClt(sl_socktype_e type, const char *node);

View File

@ -0,0 +1,41 @@
/*
* This file is part of the canserver project.
* Copyright 2025 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "globopts.h"
#include <usefull_macros.h>
globopts UserOpts = {
.maxclients = 10,
};
static sl_option_t cmdlnopts[] = {
{"help", NO_ARGS, NULL, 'h', arg_int, APTR(&UserOpts.help), "show this help"},
{"verbose", NO_ARGS, NULL, 'v', arg_none, APTR(&UserOpts.verbose), "verbose level (each -v adds 1)"},
{"logfile", NEED_ARG, NULL, 'l', arg_string, APTR(&UserOpts.logfile), "log file name"},
{"srvnode", NEED_ARG, NULL, 'N', arg_string, APTR(&UserOpts.srvnode), "server node \"port\", \"name:port\" or path (could be \"\\0path\" for anonymous UNIX-socket)"},
{"srvunix", NO_ARGS, NULL, 'U', arg_int, APTR(&UserOpts.srvunix), "server use UNIX socket instead of INET"},
{"maxclients", NEED_ARG, NULL, 'm', arg_int, APTR(&UserOpts.maxclients), "max amount of clients connected to server (default: 2)"},
{"cltnode", NEED_ARG, NULL, 'n', arg_string, APTR(&UserOpts.cltnode), "serial client node \"IP\", \"name:IP\" or path (could be \"\\0path\" for anonymous UNIX-socket)"},
{"cltunix", NO_ARGS, NULL, 'u', arg_int, APTR(&UserOpts.cltunix), "serial client use UNIX socket instead of INET"},
end_option
};
void parseargs(int argc, char **argv){
sl_parseargs(&argc, &argv, cmdlnopts);
if(UserOpts.help) sl_showhelp(-1, cmdlnopts);
}

View File

@ -0,0 +1,34 @@
/*
* This file is part of the canserver project.
* Copyright 2025 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
typedef struct{
int help;
int verbose;
int srvunix;
int cltunix;
int maxclients;
char *logfile;
char *srvnode;
char *cltnode;
} globopts;
extern globopts UserOpts;
void parseargs(int argc, char **argv);

View File

@ -0,0 +1,81 @@
/*
* This file is part of the canserver project.
* Copyright 2025 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <signal.h>
#include <stdio.h>
#include <usefull_macros.h>
#include "clientserver.h"
#include "globopts.h"
static sl_sock_t *srv = NULL, *clt = NULL;
void signals(int sig){
if(sig){
signal(sig, SIG_IGN);
DBG("Get signal %d, quit.\n", sig);
LOGERR("Exit with status %d", sig);
}else LOGERR("Exit");
if(srv){
DBG("Del server");
sl_sock_delete(&srv);
}
if(clt){
DBG("Del client");
sl_sock_delete(&clt);
}
exit(sig);
}
int main(int argc, char **argv){
sl_init();
parseargs(argc, argv);
if(!UserOpts.srvnode) ERRX("Point server node");
if(!UserOpts.cltnode) ERRX("Point serial client node");
sl_socktype_e type = (UserOpts.srvunix) ? SOCKT_UNIX : SOCKT_NET;
sl_sock_changemaxclients(UserOpts.maxclients);
srv = RunSrv(type, UserOpts.srvnode);
if(!srv) ERRX("Server: can't create socket and/or run threads");
DBG("Server done");
type = (UserOpts.cltunix) ? SOCKT_UNIX : SOCKT_NET;
clt = RunClt(type, UserOpts.cltnode);
if(!clt) ERRX("Serial client: can't connect to socket and/or run threads");
DBG("Client done");
sl_loglevel_e lvl = UserOpts.verbose + LOGLEVEL_ERR;
if(lvl >= LOGLEVEL_AMOUNT) lvl = LOGLEVEL_AMOUNT - 1;
if(UserOpts.logfile) OPENLOG(UserOpts.logfile, lvl, 1);
LOGMSG("Started");
signal(SIGTERM, signals);
signal(SIGINT, signals);
signal(SIGQUIT, signals);
signal(SIGTSTP, SIG_IGN);
signal(SIGHUP, signals);
while(srv && srv->connected && clt && clt->connected){
if(!srv->rthread){
WARNX("Server handlers thread is dead");
LOGERR("Server handlers thread is dead");
break;
}
}
LOGMSG("End");
DBG("Close");
if(srv) sl_sock_delete(&srv);
if(clt) sl_sock_delete(&clt);
return 0;
}

View File

@ -0,0 +1,91 @@
/*
* This file is part of the canserver project.
* Copyright 2025 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <string.h>
#include <usefull_macros.h>
#include "parsecanmsgs.h"
/**
* @brief parseCANstr - parser of string like "timestamp #ID [data0 data1 ...]"
* @param str - input string
* @param msg - message to fill
* @return FALSE if given string isn't CAN message
*/
int parseCANstr(const char *str, CAN_message *msg){
if(!str || !msg) return FALSE;
const char *IDstart = strchr(str, '#');
if(!IDstart) return FALSE;
++IDstart;
msg->timestamp = atoi(str);
DBG("Timestamp: %u", msg->timestamp);
unsigned bytes[9];
int N = sscanf(IDstart, "%x %x %x %x %x %x %x %x %x",
&bytes[0], &bytes[1], &bytes[2], &bytes[3], &bytes[4], &bytes[5], &bytes[6], &bytes[7], &bytes[8]);
if(N < 1) return FALSE; // where's ID?
DBG("ID=%u", bytes[0]);
msg->length = N - 1;
msg->ID = bytes[0];
for(int i = 0; i < msg->length; ++i) msg->data[i] = bytes[i + 1];
DBG("length: %u", msg->length);
return TRUE;
}
// return result of snprintf
int formCANmsg(CAN_message *msg, char *buf, int buflen){
if(!msg || !buf || buflen < 8) return 0;
DBG("msg ID=%u, len=%u", msg->ID, msg->length);
int L = snprintf(buf, buflen, "s %u ", msg->ID);
for(int i = 0; i < msg->length && L < buflen; ++i)
L += snprintf(buf + L, buflen - L, "%u ", msg->data[i]);
if(L >= buflen){
WARNX("buffer overflow");
return 0;
}
L += snprintf(buf + L, buflen - L, "\n");
DBG("convert CAN message to %d symbols: _%s_", L, buf);
return L;
}
// fill command and parameter into message
// !!!!! msg->ID should be set by user !!!!!
static int fillcmd(int cancmd, uint8_t par, CAN_message *msg){
if(!msg || cancmd > 0xffff) return FALSE;
MSGP_SET_CMD(msg, cancmd);
MSGP_SET_PARNO(msg, par);
MSGP_SET_ERR(msg, 0);
msg->length = 4;
return TRUE;
}
int CANu32setter(int cancmd, uint8_t par, uint32_t data, CAN_message *msg){
if(!msg) return FALSE;
par |= SETTER_FLAG;
if(!fillcmd(cancmd, par, msg)) return FALSE;
MSGP_SET_U32(msg, data);
msg->length = 8;
return TRUE;
}
int CANu32getter(int cancmd, uint8_t par, CAN_message *msg){
if(!msg) return FALSE;
if(!fillcmd(cancmd, par, msg)) return FALSE;
return TRUE;
}

View File

@ -0,0 +1,39 @@
/*
* This file is part of the canserver project.
* Copyright 2025 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stdint.h>
#include "canproto.h"
#define CANIDIN 1
#define CANIDOUT 2
typedef struct{
uint16_t ID; // message ID
uint32_t timestamp; // milliseconds timestamp
uint8_t length; // data length
uint8_t data[8]; // up to 8 bytes of data
} CAN_message;
int parseCANstr(const char *str, CAN_message *msg);
int formCANmsg(CAN_message *msg, char *buf, int buflen);
int CANu32setter(int cancmd, uint8_t par, uint32_t data, CAN_message *msg);
int CANu32getter(int cancmd, uint8_t par, CAN_message *msg);

View File

@ -0,0 +1 @@
socat tcp-l:22222,reuseaddr,fork file:/dev/multistepper0,rawer