diff --git a/.gitmodules b/.gitmodules index d2b2562..b278661 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "src/json"] - path = src/json + path = include/json url = https://github.com/nlohmann/json.git diff --git a/CMakeLists.txt b/CMakeLists.txt index a376af1..f12cec8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,25 +6,28 @@ set(CMAKE_CXX_STANDARD 17) set(CMAKE_VERBOSE_MAKEFILE ON) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake-modules) +set(TESTING_ON FALSE) find_package(OpenGL REQUIRED) find_package(GLUT REQUIRED) find_package(OpenCV HINTS /usr/local/opt/opencv /usr/local/Cellar/opencv REQUIRED) -set( NAME_SRC - src/main.cpp -) +file(GLOB SOURCES "src/*.cpp") -ENABLE_TESTING() -ADD_SUBDIRECTORY( test ) -set(UNIT_TEST state_test) -add_test(NAME ${UNIT_TEST} COMMAND ${UNIT_TEST}) -add_custom_target(run_unit_test ALL - COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure - DEPENDS ${UNIT_TEST}) +if(${TESTING_ON}) + ENABLE_TESTING() + ADD_SUBDIRECTORY( test ) + SET(UNIT_TEST state_test) + add_test(NAME ${UNIT_TEST} COMMAND ${UNIT_TEST}) + add_custom_target(run_unit_test ALL + COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure + DEPENDS ${UNIT_TEST}) +endif() set(EXECUTABLE_OUTPUT_PATH ${CMAKE_BINARY_DIR}/bin) -add_executable( fd ${NAME_SRC} ${NAME_HEADERS} ) +add_executable( fd ${SOURCES} ) + +target_include_directories(fd PRIVATE include) if (APPLE) # Equivalent to pass flags -framework OpenGL diff --git a/src/image.h b/include/image.hpp similarity index 89% rename from src/image.h rename to include/image.hpp index 0d59360..4443581 100644 --- a/src/image.h +++ b/include/image.hpp @@ -24,7 +24,8 @@ class Image { uint16_t height; Mat blank; public: - Image(uint16_t w, uint16_t h); + Image(); + void setDimensions(uint16_t w, uint16_t h); Mat getBlank(); }; diff --git a/src/json b/include/json similarity index 100% rename from src/json rename to include/json diff --git a/include/state.hpp b/include/state.hpp new file mode 100644 index 0000000..8ee5a60 --- /dev/null +++ b/include/state.hpp @@ -0,0 +1,61 @@ +#ifndef STATE_h +#define STATE_h + +#include "json/single_include/nlohmann/json.hpp" +#include +#include + +using json = nlohmann::json; +using namespace std; + +enum Action { + NONE, + LOAD, + DISPLAY, + STOP +}; + +enum Mode { + RGB, + BW, + + INVERT, + BW_INVERT, + + RGB_CHANNELS, + INVERT_CHANNELS +}; + +class State { + private: + Action action = STOP; + Mode mode = RGB; + string image; + vector exposure = {}; + uint64_t x; + uint64_t y; + uint64_t width; + uint64_t height; + bool ERROR = false; + + void error(); + bool imageExists(string& image); + void setAction(json& msgData); + void setImage(json& msgData); + void setMode(json& msgData); + void setExposure(json& msgData); + void setPosition(json& msgData); + + public : + State(); + void receiveMessage(string msgString); + string createMessage(bool success); + Action getAction () { return action; } + Mode getMode() { return mode; } + string getImage () { return image; } + vector getExposure() { return exposure; } + vector getPosition() { return { x, y, width, height }; } + bool isError () { return ERROR; } +}; + +#endif diff --git a/src/image.cpp b/src/image.cpp new file mode 100644 index 0000000..0f8bc70 --- /dev/null +++ b/src/image.cpp @@ -0,0 +1,5 @@ +#include "image.hpp" + +Image::Image () { + // +} \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index dce72ab..d0a8bfe 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,10 +1,141 @@ -#include "image.h" -#include "state.h" +#include "image.hpp" +#include "state.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include using namespace std; -int main(int argc, char** argv) { - string action_load = "{\"action\" : 0 }"; - json msg_data = json::parse(action_load); - MessageIn msgIn(msg_data); +uint16_t PORT = 8081; +int BUFFER_SIZE = 1024; + +Image img; +State state; + +bool receivedMessage = false; +bool sendMessage = false; +string incomingMessage; +string outgoingMessage; + +mutex receivedMutex; +condition_variable receivedCondVar; + +mutex sendMutex; +condition_variable sendCondVar; + +void performAction () { + if (receivedMessage) { + cout << "RECEIVED" << endl; + state.receiveMessage(incomingMessage); + receivedMessage = false; + if (state.isError()) { + outgoingMessage = state.createMessage(false); + } else { + outgoingMessage = state.createMessage(true); + } + sendMessage = true; + } +} + +void handleTCPConnection(int clientSocket) { + char buffer[BUFFER_SIZE]; + memset(buffer, 0, BUFFER_SIZE); + + while (true) { + int bytesRead = recv(clientSocket, buffer, BUFFER_SIZE - 1, 0); + if (bytesRead <= 0) { + // Client disconnected or error occurred + break; + } + + std::string iMessage(buffer, bytesRead); + + { + lock_guard lock(receivedMutex); + receivedMessage = true; + incomingMessage = iMessage; + receivedCondVar.notify_all(); + } + + performAction(); + + bool localSendMessage; + string localOutgoingMessage; + + { + unique_lock lock(sendMutex); + sendCondVar.wait(lock, [&] { return sendMessage; }); + localSendMessage = sendMessage; + localOutgoingMessage = outgoingMessage; + } + + if (localSendMessage) { + const char* msg = localOutgoingMessage.c_str(); + int bytesSent = send(clientSocket, msg, strlen(msg), 0); + if (bytesSent < 0) { + std::cerr << "Error sending ACK to client" << std::endl; + break; + } + } + + memset(buffer, 0, BUFFER_SIZE); + } +} + +void runTCPServer (int serverSocket) { + while (true) { + struct sockaddr_in clientAddr; + socklen_t clientAddrLen = sizeof(clientAddr); + + int clientSocket = accept(serverSocket, (struct sockaddr*)&clientAddr, &clientAddrLen); + if (clientSocket < 0) { + std::cerr << "Error accepting client connection" << std::endl; + continue; + } + + cout << "New client connected" << endl; + + handleTCPConnection(clientSocket); + } +} + +int main (int argc, char** argv) { + int serverSocket = socket(AF_INET, SOCK_STREAM, 0); + if (serverSocket < 0) { + cerr << "Error creating socket" << endl; + return 1; + } + + struct sockaddr_in serverAddr; + serverAddr.sin_family = AF_INET; + serverAddr.sin_addr.s_addr = INADDR_ANY; + serverAddr.sin_port = htons(PORT); + + int bindResult = ::bind(serverSocket, reinterpret_cast(&serverAddr), sizeof(serverAddr)); + if (bindResult < 0) { + std::cerr << "Error binding socket" << std::endl; + close(serverSocket); + return 1; + } + + if (listen(serverSocket, 5) < 0) { + cerr << "Error listening on socket" << endl; + return 1; + } + + cout << "TCP server listening on port " << PORT << endl; + + thread serverThread(runTCPServer, serverSocket); + + serverThread.join(); + close(serverSocket); + + return 0; } \ No newline at end of file diff --git a/src/state.cpp b/src/state.cpp index b76ce74..0f63dc3 100644 --- a/src/state.cpp +++ b/src/state.cpp @@ -1,7 +1,92 @@ -#include "state.h" +#include "state.hpp" -MessageIn::MessageIn (json msgData) { - if (msgData.contains("action")) { - action = static_cast(msgData["action"]); +State::State () { + // +} + +void State::error () { + ERROR = true; + image.clear(); +} + +bool State::imageExists (string& image) { + struct stat buffer; + return (stat (image.c_str(), &buffer) == 0); +} + +void State::setAction (json& msgData) { + action = static_cast(msgData["action"]); + cout << "Action = " << action << endl; +} + +void State::setImage (json& msgData) { + image = msgData["image"]; + if (!imageExists(image)) { + error(); + cerr << "Image " << msgData["image"] << " does not exist" << endl; + return; } + cout << "Image = " << image << endl; +} + +void State::setMode (json& msgData) { + mode = static_cast(msgData["mode"]); + cout << "Mode = " << mode << endl; +} + +void State::setPosition (json& msgData) { + x = msgData["position"]["x"]; + y = msgData["position"]["y"]; + width = msgData["position"]["w"]; + height = msgData["position"]["h"]; + cout << "Position[x] = " << x << " [y] = " << y << " [width] = " << width << " [height] = " << height << endl; +} + +void State::setExposure (json& msgData) { + if (msgData["exposure"].size() == 1) { + exposure = { msgData["exposure"][0] }; + } else if (msgData["exposure"].size() == 3) { + exposure = { msgData["exposure"][0], msgData["exposure"][1], msgData["exposure"][2] }; + } else { + error(); + cerr << "Exposure array is incorrect length, " << msgData["exposure"].size() << endl; + return; + } +} + +void State::receiveMessage (string msgString) { + ERROR = false; + json msgData = json::parse(msgString); + if (msgData.contains("action")) { + setAction(msgData); + } else { + action = NONE; + error(); + cerr << "Received invalid message: " << msgString << endl; + return; + } + + if (msgData.contains("image")) { + setImage(msgData); + } + + if (action == LOAD && msgData.contains("mode")) { + setMode(msgData); + } + + if (action == LOAD && msgData.contains("position")) { + setPosition(msgData); + } + + if (action == DISPLAY && msgData.contains("exposure")) { + setExposure(msgData); + } +} + +string State::createMessage (bool success) { + json msgData = { + { "action", action }, + { "success", success } + }; + return msgData.dump(); } \ No newline at end of file diff --git a/src/state.h b/src/state.h deleted file mode 100644 index 1e84476..0000000 --- a/src/state.h +++ /dev/null @@ -1,56 +0,0 @@ -#ifndef STATE_h -#define STATE_h - -#include "json/single_include/nlohmann/json.hpp" -#include - -using json = nlohmann::json; -using namespace std; - -enum Action { - LOAD, - DISPLAY, - STOP -}; - -enum Mode { - RGB, - BW, - - INVERT, - BW_INVERT, - - RGB_CHANNELS, - INVERT_CHANNELS -}; - -class MessageIn { - private: - Action action; - string file; - - Mode mode; - vector exposure; - bool start; - public: - MessageIn(json msgJson); - Action getAction () { return action;} -}; -/* -class MessageOut { - public: - MessageOut(Action a, bool s); - Action action; - bool success; - string toString(); -}; - -class State { - public : - MessageIn active; - State(); - void processMessage(string msgString); - void createMessage(Action action, bool success); -}; -*/ -#endif diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 4f11378..4f14aa3 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,2 +1,2 @@ -ADD_EXECUTABLE( state_test state_test.cpp ) +ADD_EXECUTABLE( state_test state_test.cpp ${SOURCES} ) ADD_TEST( fd state_test ) \ No newline at end of file diff --git a/test/state_test.cpp b/test/state_test.cpp index 34dd162..a532dd0 100644 --- a/test/state_test.cpp +++ b/test/state_test.cpp @@ -1,5 +1,5 @@ #include -#include "../src/state.h" +#include "state.hpp" void IS_TRUE(string name, bool x) { if (!(x)) { @@ -10,6 +10,7 @@ void IS_TRUE(string name, bool x) { } void test_message_in() { + State state; //string action_load = "{\"action\" : 0 }"; //json msg_data = json::parse(action_load); //MessageIn msgIn(msg_data); diff --git a/test/test_messages.js b/test/test_messages.js new file mode 100644 index 0000000..61a4419 --- /dev/null +++ b/test/test_messages.js @@ -0,0 +1,43 @@ +const net = require('net'); + +const serverAddress = 'localhost'; +const serverPort = 8081; + +const client = new net.Socket(); + +console.log(`Connecting to ${serverAddress}:${serverPort}...`); + +async function delay (ms) { + return new Promise((resolve, reject) => { + return setTimeout(resolve, ms); + }); +} + +client.connect(serverPort, serverAddress, async () => { + const data = { + action : 1, + image: 'filename.tif', + }; + const jsonData = JSON.stringify(data); + console.log('SENDING'); + console.log(jsonData); + client.write(jsonData); + await delay(2000); + jsonData.action = 2; + console.log('SENDING'); + console.log(jsonData); + client.write(jsonData); +}); + +client.on('data', (data) => { + console.log('RECEIVED'); + console.log(data.toString()); +}); + +client.on('close', () => { + console.log('Closing connection'); +}); + +client.on('error', (err) => { + console.error('Error:', err); +}); \ No newline at end of file