From d1304224a01f3aebb8d83e55ae2f968f5b68b79e Mon Sep 17 00:00:00 2001 From: Dennis Gunia Date: Thu, 18 Sep 2025 15:59:15 +0200 Subject: [PATCH] added webserver --- software/pc_client/doc/api-doc.md | 251 +++++++++++++++++ software/pc_client/src/console.c | 416 ++++++++++++++++++++++++++++ software/pc_client/src/console.h | 6 + software/pc_client/src/devicemgr.c | 351 +++++++++++++++++++---- software/pc_client/src/devicemgr.h | 33 ++- software/pc_client/src/main.c | 69 +---- software/pc_client/src/sfbus-util.c | 48 ++++ software/pc_client/src/sfbus-util.h | 7 + software/pc_client/src/sfbus.c | 6 +- software/pc_client/src/wsserver.c | 106 +++++++ software/pc_client/src/wsserver.h | 11 + 11 files changed, 1167 insertions(+), 137 deletions(-) create mode 100644 software/pc_client/doc/api-doc.md create mode 100644 software/pc_client/src/console.c create mode 100644 software/pc_client/src/console.h create mode 100644 software/pc_client/src/sfbus-util.c create mode 100644 software/pc_client/src/sfbus-util.h create mode 100644 software/pc_client/src/wsserver.c create mode 100644 software/pc_client/src/wsserver.h diff --git a/software/pc_client/doc/api-doc.md b/software/pc_client/doc/api-doc.md new file mode 100644 index 0000000..da6e407 --- /dev/null +++ b/software/pc_client/doc/api-doc.md @@ -0,0 +1,251 @@ +# Websockets Interface documentation +All requests and responses are sent as json objects. + +## Request +Every request must conatin at least one value: `command` +``` +{ + "command": "" +} +``` +### Device manager commands + +#### Save config `dm_save` +Saves config to ./flapconfig.json. + +Request: +``` +{ + "command": "dm_load" +} +``` +Response: +``` +{ + "ack": true +} +``` +#### Load config `dm_load` +Loads config from ./flapconfig.json. + +Request: +``` +{ + "command": "dm_load" +} +``` +Response: +``` +{ + "ack": true +} +``` +#### Dump config `dm_dump` +Dumps current config to socket. + +Request: +``` +{ + "command": "dm_dump" +} +``` +Response: +``` +{ + "devices_all": + "devices_online": , + "devices": , + "map": <2D array of device locations> +} +``` + +#### Describe single device `dm_describe` +Gets all information for specified device id. + +Request: +``` +{ + "command": "dm_describe", + "id": +} +``` +Response: +``` +{ + ... +} +``` +#### Register new device `dm_register` +Register device, assign new id and assign a location. +``` +{ + "command": "dm_describe", + "address": , + "x": , + "y": , +} +``` +Response: +``` +{ + "id": +} +``` + +#### Remove device from config `dm_remove` +Removes device from config and frees location in screen + +Request: +``` +{ + "command": "dm_remove", + "id": +} +``` +Response: +``` +{ + "ack": true +} +``` + +#### Remove device from config `dm_refresh` +Refresh device config + +Request: +``` +{ + "command": "dm_refresh" +} +``` +Response: +``` +{ + "devices_online": +} +``` + +### Device raw commands + +#### Ping module `dr_ping` +Checks if a module reponds on the given address. + +Request: +``` +{ + "command": "dr_ping", + "address":
+} +``` +Response: +``` +{ + "success": +} +``` + +#### Set module address `dr_setaddress` +Changes the hardware address of an module. + +Request: +``` +{ + "command": "dr_setaddress", + "address":
, + "newaddress": +} +``` +Response: +``` +{ + "success": +} +``` + +#### Set module offset calibration `dr_setcalibration` +Sets the offset calibration for an module. + +Request: +``` +{ + "command": "dr_setcalibration", + "address":
, + "calibration": +} +``` +Response: +``` +{ + "success": +} +``` + +#### Reset module `dr_reset` +Resets the controller of an module + +Request: +``` +{ + "command": "dr_reset", + "address":
+} +``` +Response: +``` +{ + "ack": true +} +``` + +#### Display flap `dr_display` +Sets the module to the specified flap directly or with recalibration. + +Request: +``` +{ + "command": "dr_display", + "address":
, + "flap": , + ("full": ) + +} +``` +Response: +``` +{ + "ack": true +} +``` + +#### Power module on/off `dr_power` +Sets the power-state for the motor of the given module. + +Request: +``` +{ + "command": "dr_power", + "address":
, + "power": + +} +``` +Response: +``` +{ + "ack": true +} +``` + +## Responses +### Error: +``` +{ + "error": "", + "detail": "" +} +``` +The following responses are valid: +- `parsing error`: the json request is malformated and cannot be parsed +- `format error`: the json object is missing required fields. Check details for more information. +- `internal error`: an internal error occured. This should not happen. Chaeck server logs. + diff --git a/software/pc_client/src/console.c b/software/pc_client/src/console.c new file mode 100644 index 0000000..6c492c4 --- /dev/null +++ b/software/pc_client/src/console.c @@ -0,0 +1,416 @@ +#include "console.h" + +const char *device_config_file = "./flapconfig.json"; +int fd; +// command handlers + +// dump config/ all devices +void cmd_dm_dump(json_object *req, json_object *res) +{ + devicemgr_printDetailsAll(res); +} + +// describe single device +void cmd_dm_describe(json_object *req, json_object *res) +{ + json_object *id; + if (json_object_object_get_ex(req, "id", &id)) + { + devicemgr_printDetails(json_object_get_int(id), res); + } + else + { + json_object_object_add(res, "error", json_object_new_string("format error")); + json_object_object_add(res, "detail", json_object_new_string("missing key: id")); + } +} + +// register new device +void cmd_dm_register(json_object *req, json_object *res) +{ + json_object *jaddress, *jx, *jy; + int address, x, y; + if (!json_object_object_get_ex(req, "address", &jaddress)) + { + json_object_object_add(res, "error", json_object_new_string("format error")); + json_object_object_add(res, "detail", json_object_new_string("missing key: address")); + } + else if (!json_object_object_get_ex(req, "x", &jx)) + { + json_object_object_add(res, "error", json_object_new_string("format error")); + json_object_object_add(res, "detail", json_object_new_string("missing key: x")); + } + else if (!json_object_object_get_ex(req, "y", &jy)) + { + json_object_object_add(res, "error", json_object_new_string("format error")); + json_object_object_add(res, "detail", json_object_new_string("missing key: y")); + } + else + { + address = json_object_get_int(jaddress); + x = json_object_get_int(jx); + y = json_object_get_int(jy); + + printf("[INFO][console] register new device wit addr %i at (%i,%i)", address, x, y); + + int newId = devicemgr_register(fd, address, x, y, -1); + json_object_object_add(res, "id", json_object_new_int(newId)); + } +} + +// refresh all devices +void cmd_dm_refresh(json_object *req, json_object *res) +{ + int devices_online = devicemgr_refresh(); + json_object_object_add(res, "devices_online", json_object_new_int(devices_online)); +} + + +// remove device +void cmd_dm_remove(json_object *req, json_object *res) +{ + json_object *jid; + int id; + if (!json_object_object_get_ex(req, "id", &jid)) + { + json_object_object_add(res, "error", json_object_new_string("format error")); + json_object_object_add(res, "detail", json_object_new_string("missing key: id")); + } + else + { + id = json_object_get_int(jid); + devicemgr_remove(id); + json_object_object_add(res, "ack", json_object_new_boolean(true)); + } +} + +// save all devices +void cmd_dm_save(json_object *req, json_object *res) +{ + devicemgr_save(device_config_file); + json_object_object_add(res, "ack", json_object_new_boolean(true)); +} + +// load all devices +void cmd_dm_load(json_object *req, json_object *res) +{ + devicemgr_load(device_config_file); + json_object_object_add(res, "ack", json_object_new_boolean(true)); +} + + +// print string on display +void cmd_dm_print(json_object *req, json_object *res) +{ + json_object *jx = json_object_object_get(req, "x"); + json_object *jy = json_object_object_get(req, "y"); + json_object *jstr = json_object_object_get(req, "string"); + if (jx == NULL) + { + json_object_object_add(res, "error", json_object_new_string("format error")); + json_object_object_add(res, "detail", json_object_new_string("missing key: x")); + } + else if (jy == NULL) + { + json_object_object_add(res, "error", json_object_new_string("format error")); + json_object_object_add(res, "detail", json_object_new_string("missing key: y")); + } + else if (jstr == NULL) + { + json_object_object_add(res, "error", json_object_new_string("format error")); + json_object_object_add(res, "detail", json_object_new_string("missing key: string")); + } + else + { + int x = json_object_get_int(jx); + int y = json_object_get_int(jy); + char *str = json_object_get_string(jstr); + devicemgr_printText(str, x, y); + json_object_object_add(res, "ack", json_object_new_boolean(true)); + } +} + +// set flap on display +void cmd_dm_print_single(json_object *req, json_object *res) +{ + json_object *jx = json_object_object_get(req, "x"); + json_object *jy = json_object_object_get(req, "y"); + json_object *jflap = json_object_object_get(req, "flap"); + if (jx == NULL) + { + json_object_object_add(res, "error", json_object_new_string("format error")); + json_object_object_add(res, "detail", json_object_new_string("missing key: x")); + } + else if (jy == NULL) + { + json_object_object_add(res, "error", json_object_new_string("format error")); + json_object_object_add(res, "detail", json_object_new_string("missing key: y")); + } + else if (jflap == NULL) + { + json_object_object_add(res, "error", json_object_new_string("format error")); + json_object_object_add(res, "detail", json_object_new_string("missing key: string")); + } + else + { + int x = json_object_get_int(jx); + int y = json_object_get_int(jy); + int flap = json_object_get_int(jflap); + + devicemgr_printFlap(flap, x, y); + json_object_object_add(res, "ack", json_object_new_boolean(true)); + } +} + + +// ping device +void cmd_dr_ping(json_object *req, json_object *res) +{ + json_object *jaddr = json_object_object_get(req, "address"); + if (jaddr == NULL) + { + json_object_object_add(res, "error", json_object_new_string("format error")); + json_object_object_add(res, "detail", json_object_new_string("missing key: address")); + } + else + { + if (sfbus_ping(fd, json_object_get_int(jaddr)) == 0) + { + json_object_object_add(res, "success", json_object_new_boolean(true)); + } + else + { + json_object_object_add(res, "success", json_object_new_boolean(false)); + } + } +} + +// set device address +void cmd_dr_setaddress(json_object *req, json_object *res) +{ + json_object *jaddr = json_object_object_get(req, "address"); + json_object *jaddrn = json_object_object_get(req, "newaddress"); + if (jaddr == NULL) + { + json_object_object_add(res, "error", json_object_new_string("format error")); + json_object_object_add(res, "detail", json_object_new_string("missing key: address")); + } + else if (jaddrn == NULL) + { + json_object_object_add(res, "error", json_object_new_string("format error")); + json_object_object_add(res, "detail", json_object_new_string("missing key: newaddress")); + } + else + { + if (sfbusu_write_address(fd, json_object_get_int(jaddr), json_object_get_int(jaddrn)) == 0) + { + json_object_object_add(res, "success", json_object_new_boolean(true)); + } + else + { + json_object_object_add(res, "success", json_object_new_boolean(false)); + } + } +} + +void cmd_dr_setcalibration(json_object *req, json_object *res) +{ + json_object *jaddr = json_object_object_get(req, "address"); + json_object *jcal = json_object_object_get(req, "calibration"); + if (jaddr == NULL) + { + json_object_object_add(res, "error", json_object_new_string("format error")); + json_object_object_add(res, "detail", json_object_new_string("missing key: address")); + } + else if (jcal == NULL) + { + json_object_object_add(res, "error", json_object_new_string("format error")); + json_object_object_add(res, "detail", json_object_new_string("missing key: calibration")); + } + else + { + if (sfbusu_write_calibration(fd, json_object_get_int(jaddr), json_object_get_int(jcal)) == 0) + { + json_object_object_add(res, "success", json_object_new_boolean(true)); + } + else + { + json_object_object_add(res, "success", json_object_new_boolean(false)); + } + } +} + +void cmd_dr_reset(json_object *req, json_object *res) +{ + json_object *jaddr = json_object_object_get(req, "address"); + if (jaddr == NULL) + { + json_object_object_add(res, "error", json_object_new_string("format error")); + json_object_object_add(res, "detail", json_object_new_string("missing key: address")); + } + else + { + sfbus_reset_device(fd, json_object_get_int(jaddr)); + json_object_object_add(res, "ack", json_object_new_boolean(true)); + } +} + +void cmd_dr_display(json_object *req, json_object *res) +{ + json_object *jaddr = json_object_object_get(req, "address"); + json_object *jflap = json_object_object_get(req, "flap"); + json_object *jfullrot = json_object_object_get(req, "full"); + if (jaddr == NULL) + { + json_object_object_add(res, "error", json_object_new_string("format error")); + json_object_object_add(res, "detail", json_object_new_string("missing key: address")); + } + else if (jflap == NULL) + { + json_object_object_add(res, "error", json_object_new_string("format error")); + json_object_object_add(res, "detail", json_object_new_string("missing key: flap")); + } + else + { + if (jfullrot == NULL) + { + sfbus_display(fd, json_object_get_int(jaddr), json_object_get_int(jflap)); + } + else if (json_object_get_boolean(jfullrot) == false) + { + sfbus_display(fd, json_object_get_int(jaddr), json_object_get_int(jflap)); + } + else + { + sfbus_display_full(fd, json_object_get_int(jaddr), json_object_get_int(jflap)); + } + json_object_object_add(res, "ack", json_object_new_boolean(true)); + } +} + +void cmd_dr_power(json_object *req, json_object *res) +{ + json_object *jaddr = json_object_object_get(req, "address"); + json_object *jpower = json_object_object_get(req, "power"); + if (jaddr == NULL) + { + json_object_object_add(res, "error", json_object_new_string("format error")); + json_object_object_add(res, "detail", json_object_new_string("missing key: address")); + } + else if (jpower == NULL) + { + json_object_object_add(res, "error", json_object_new_string("format error")); + json_object_object_add(res, "detail", json_object_new_string("missing key: power")); + } + else + { + if (json_object_get_boolean(jpower) == false) + { + sfbus_motor_power(fd, json_object_get_int(jaddr), 0); + } + else + { + sfbus_motor_power(fd, json_object_get_int(jaddr), 1); + } + json_object_object_add(res, "ack", json_object_new_boolean(true)); + } +} + + +// command parser +json_object *parse_command(json_object *req) +{ + json_object *commandObj; + json_object *res = json_object_new_object(); + json_object_object_get_ex(req, "command", &commandObj); + char *command = json_object_get_string(commandObj); + free(commandObj); + // command 'table' + if (strcmp(command, "dm_dump") == 0) + { + cmd_dm_dump(req, res); + return res; + } + else if (strcmp(command, "dm_describe") == 0) + { + cmd_dm_describe(req, res); + return res; + } + else if (strcmp(command, "dm_register") == 0) + { + cmd_dm_register(req, res); + return res; + } + else if (strcmp(command, "dm_remove") == 0) + { + cmd_dm_remove(req, res); + return res; + } + else if (strcmp(command, "dm_refresh") == 0) + { + cmd_dm_refresh(req, res); + return res; + } + else if (strcmp(command, "dm_save") == 0) + { + cmd_dm_save(req, res); + return res; + } + else if (strcmp(command, "dm_load") == 0) + { + cmd_dm_load(req, res); + return res; + } + else if (strcmp(command, "dm_print") == 0) + { + cmd_dm_print(req, res); + return res; + } + else if (strcmp(command, "dr_ping") == 0) + { + cmd_dr_ping(req, res); + return res; + } + else if (strcmp(command, "dr_setaddress") == 0) + { + cmd_dr_setaddress(req, res); + return res; + } + else if (strcmp(command, "dr_setcalibration") == 0) + { + cmd_dr_setcalibration(req, res); + return res; + } + else if (strcmp(command, "dr_reset") == 0) + { + cmd_dr_reset(req, res); + return res; + } + else if (strcmp(command, "dr_display") == 0) + { + cmd_dr_display(req, res); + return res; + } + else if (strcmp(command, "dr_power") == 0) + { + cmd_dr_power(req, res); + return res; + } + else + { + json_object_object_add(res, "error", json_object_new_string("invalid command")); + json_object_object_add(res, "detail", json_object_new_string("")); + return res; + } + return NULL; +} + + +void start_console(int _fd) +{ + fd = _fd; + // init device manager + devicemgr_init(fd); + // start server + start_webserver(&parse_command); +} \ No newline at end of file diff --git a/software/pc_client/src/console.h b/software/pc_client/src/console.h new file mode 100644 index 0000000..94e389e --- /dev/null +++ b/software/pc_client/src/console.h @@ -0,0 +1,6 @@ +#include "devicemgr.h" +#include "sfbus-util.h" +#include "wsserver.h" +#include + +void start_console(); \ No newline at end of file diff --git a/software/pc_client/src/devicemgr.c b/software/pc_client/src/devicemgr.c index 8188265..4d0a8e7 100644 --- a/software/pc_client/src/devicemgr.c +++ b/software/pc_client/src/devicemgr.c @@ -3,15 +3,19 @@ #include /* - * This file provides an abstraction layer to access many devices - * simultaneously. by Dennis Gunia - 2025 - wwwm.dennisgunia.de + * This section provides an abstraction layer to access many devices + * simultaneously. + * + * by Dennis Gunia - 2025 - www.dennisgunia.de */ enum SFDEVICE_STATE { + UNALLOCATED, NEW, OFFLINE, ONLINE, - FAILED + FAILED, + REMOVED }; enum SFDEVICE_POWER { @@ -25,6 +29,7 @@ struct SFDEVICE int pos_x; int pos_y; u_int16_t address; + u_int16_t calibration; int rs485_descriptor; double reg_voltage; u_int32_t reg_counter; @@ -38,21 +43,23 @@ enum { SFDEVICE_MAXDEV = 128, SFDEVICE_MAX_X = 20, - SFDEVICE_MAX_Y = 4 + SFDEVICE_MAX_Y = 4, + JSON_MAX_LINE_LEN = 256 }; // next free slot to register device int nextFreeSlot = -1; int deviceMap[SFDEVICE_MAX_X][SFDEVICE_MAX_Y]; - +int deviceFd; struct SFDEVICE devices[SFDEVICE_MAXDEV]; const char *symbols[45] = {" ", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", - "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "Ä", "Ö", "Ü", - "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ":", ".", "-", "?", "!"}; + "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "Ä", "Ö", "Ü", + "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ":", ".", "-", "?", "!"}; -void devicemgr_init() +void devicemgr_init(int fd) { + deviceFd = fd; // reserve memory buffer for (int y = 0; y < SFDEVICE_MAX_Y; y++) { @@ -61,30 +68,66 @@ void devicemgr_init() deviceMap[x][y] = -1; //all empty slots are -1 } } + for (int ix = 0; ix < SFDEVICE_MAXDEV; ix++) + { + devices[ix].address = 0; // Adress 0 is only used for new units. should never be used for active unit + devices[ix].deviceState = UNALLOCATED; + } } int devicemgr_readStatus(int device_id) { - double _voltage = 0; - u_int32_t _counter = 0; - u_int8_t _status = - sfbus_read_status(devices[device_id].rs485_descriptor, devices[device_id].address, &_voltage, &_counter); - if (_status == 0xFF) - { - devices[device_id].powerState = UNKNOWN; - devices[device_id].deviceState = OFFLINE; - return -1; + if (devices[device_id].address > 0) + { // only if defined + double _voltage = 0; + u_int32_t _counter = 0; + u_int8_t _status = + sfbus_read_status(devices[device_id].rs485_descriptor, devices[device_id].address, &_voltage, &_counter); + if (_status == 0xFF) + { + devices[device_id].powerState = UNKNOWN; + devices[device_id].deviceState = OFFLINE; + return -1; + } + devices[device_id].reg_voltage = _voltage; + devices[device_id].reg_counter = _counter; + devices[device_id].reg_status = _status; + devices[device_id].powerState = ~((devices[device_id].reg_status >> 4)) & 0x01; + devices[device_id].deviceState = ONLINE; + if ((((devices[device_id].reg_status) >> 5) & 0x01) > 0) + { + devices[device_id].deviceState = FAILED; + } + return 0; } - devices[device_id].reg_voltage = _voltage; - devices[device_id].reg_counter = _counter; - devices[device_id].reg_status = _status; - devices[device_id].powerState = ~((devices[device_id].reg_status >> 4)) & 0x01; - devices[device_id].deviceState = ONLINE; - if ((((devices[device_id].reg_status) >> 5) & 0x01) > 0) + else { - devices[device_id].deviceState = FAILED; + return -2; + } +} + +int devicemgr_readCalib(int device_id) +{ + if (devices[device_id].deviceState == ONLINE) + { + char *buffer_r = malloc(256); + if (sfbus_read_eeprom(devices[device_id].rs485_descriptor, devices[device_id].address, buffer_r) > 0) + { + uint16_t calib_data = (*(buffer_r + 2) & 0xFF | ((*(buffer_r + 3) << 8) & 0xFF00)); + devices[device_id].calibration = calib_data; + free(buffer_r); + } + else + { + printf("Error reading eeprom from %i\n", device_id); + free(buffer_r); + return -1; + } + } + else + { + return -2; } - return 0; } json_object *devicemgr_printMap() @@ -102,12 +145,12 @@ json_object *devicemgr_printMap() return rows_array; } -json_object *devicemgr_printDetails(int device_id) +void devicemgr_printDetails(int device_id, json_object *root) { // generate json object with status - json_object *root = json_object_new_object(); json_object_object_add(root, "id", json_object_new_int(device_id)); json_object_object_add(root, "address", json_object_new_int(devices[device_id].address)); + json_object_object_add(root, "calibration", json_object_new_int(devices[device_id].calibration)); json_object_object_add(root, "flapID", json_object_new_int(devices[device_id].current_flap)); json_object_object_add(root, "flapChar", json_object_new_string(symbols[devices[device_id].current_flap])); json_object *position = json_object_new_object(); @@ -134,6 +177,12 @@ json_object *devicemgr_printDetails(int device_id) case NEW: json_object_object_add(status, "device", json_object_new_string("NEW")); break; + case REMOVED: + json_object_object_add(status, "device", json_object_new_string("REMOVED")); + break; + default: + json_object_object_add(status, "device", json_object_new_string("UNALLOCATED")); + break; } json_object *status_flags = json_object_new_object(); json_object_object_add(status_flags, @@ -159,31 +208,30 @@ json_object *devicemgr_printDetails(int device_id) json_object_new_boolean(((devices[device_id].reg_status) >> 6) & 0x01)); json_object_object_add(status, "flags", status_flags); json_object_object_add(root, "status", status); - - return root; } -json_object *devicemgr_printDetailsAll() +void devicemgr_printDetailsAll(json_object *root) { - json_object *root = json_object_new_object(); json_object_object_add(root, "devices_all", json_object_new_int(nextFreeSlot + 1)); json_object *devices_arr = json_object_new_array(); int devices_online = 0; for (int i = 0; i < (nextFreeSlot + 1); i++) { - devicemgr_readStatus(i); - if (devices[i].deviceState == ONLINE) + if (devices[i].address > 0) { - devices_online++; + devicemgr_readStatus(i); + if (devices[i].deviceState == ONLINE) + { + devices_online++; + } + json_object *device = json_object_new_object(); + devicemgr_printDetails(i, device); + json_object_array_add(devices_arr, device); } - json_object_array_add(devices_arr, devicemgr_printDetails(i)); } json_object_object_add(root, "map", devicemgr_printMap()); json_object_object_add(root, "devices", devices_arr); json_object_object_add(root, "devices_online", json_object_new_int(devices_online)); - - printf("The json representation:\n\n%s\n\n", json_object_to_json_string_ext(root, JSON_C_TO_STRING_PRETTY)); - return root; } void setSingle(int id, char flap) @@ -191,9 +239,11 @@ void setSingle(int id, char flap) // first convert char to flap id char test_char = toupper(flap); printf("find char %c\n", test_char); - for (int ix = 0; ix < 45; ix++){ - if (*symbols[ix] == test_char){ - printf("match char %i %i %i\n",test_char, *symbols[ix], ix); + for (int ix = 0; ix < 45; ix++) + { + if (*symbols[ix] == test_char) + { + printf("match char %i %i %i\n", test_char, *symbols[ix], ix); sfbus_display_full(devices[id].rs485_descriptor, devices[id].address, ix); break; } @@ -201,42 +251,225 @@ void setSingle(int id, char flap) devices[nextFreeSlot].current_flap = flap; } -void printText(char *text, int x, int y) +void setSingleRaw(int id, int flap) +{ + sfbus_display_full(devices[id].rs485_descriptor, devices[id].address, flap); + devices[nextFreeSlot].current_flap = flap; +} + +void devicemgr_printText(char *text, int x, int y) { for (int i = 0; i < strlen(text); i++) { int this_id = deviceMap[x + i][y]; if (this_id >= 0) { - printf("print char %c to %i\n",*(text + i), devices[this_id].address); + printf("print char %c to %i\n", *(text + i), devices[this_id].address); setSingle(this_id, *(text + i)); - usleep(5000); } } } -int devicemgr_register(int rs485_descriptor, u_int16_t address, int x, int y) +void devicemgr_printFlap(int flap, int x, int y) { - nextFreeSlot++; - devices[nextFreeSlot].pos_x = x; - devices[nextFreeSlot].pos_y = y; - devices[nextFreeSlot].address = address; - devices[nextFreeSlot].rs485_descriptor = rs485_descriptor; - devices[nextFreeSlot].reg_voltage = 0; - devices[nextFreeSlot].reg_counter = 0; - devices[nextFreeSlot].reg_status = 0; - devices[nextFreeSlot].current_flap = 0; - devices[nextFreeSlot].deviceState = NEW; - devices[nextFreeSlot].powerState = DISABLED; + int this_id = deviceMap[x][y]; + if (this_id >= 0) + { + setSingleRaw(this_id, flap); + } +} + +int devicemgr_register(int rs485_descriptor, u_int16_t address, int x, int y, int nid) +{ + if (nid < 0) + { + nextFreeSlot++; + nid = nextFreeSlot; + } + + devices[nid].pos_x = x; + devices[nid].pos_y = y; + devices[nid].address = address; + devices[nid].calibration = 0; + devices[nid].rs485_descriptor = rs485_descriptor; + devices[nid].reg_voltage = 0; + devices[nid].reg_counter = 0; + devices[nid].reg_status = 0; + devices[nid].current_flap = 0; + devices[nid].deviceState = NEW; + devices[nid].powerState = DISABLED; // try to reach device - devicemgr_readStatus(nextFreeSlot); + devicemgr_readStatus(nid); + devicemgr_readCalib(nid); if (deviceMap[x][y] >= 0) { // rest old ones int old_id = deviceMap[x][y]; devices[old_id].pos_x = -1; devices[old_id].pos_y = -1; } - deviceMap[x][y] = nextFreeSlot; - return nextFreeSlot; + deviceMap[x][y] = nid; + return nid; } + +// refreshes status of all devices +int devicemgr_refresh() +{ + int devices_online = 0; + for (int ix = 0; ix < SFDEVICE_MAXDEV; ix++) + { + if (devices[ix].address > 0) + { + devicemgr_readStatus(ix); + if (devices[ix].deviceState == ONLINE) + { + devices_online++; + } + } + } + return devices_online; +} + +// remove devices from system +int devicemgr_remove(int id) +{ + devices[nextFreeSlot].deviceState = REMOVED; + devices[nextFreeSlot].address = 0; + devices[nextFreeSlot].rs485_descriptor = NULL; + return 0; +} + +int devicemgr_save(char *file) +{ + json_object *root = json_object_new_object(); + json_object_object_add(root, "nextFreeSlot", json_object_new_int(nextFreeSlot)); + json_object *device_array = json_object_new_array(); + for (int ix = 0; ix < SFDEVICE_MAXDEV; ix++) + { + if (devices[ix].address > 0) + { + json_object *device = json_object_new_object(); + devicemgr_printDetails(ix, device); + json_object_array_add(device_array, device); + } + } + + json_object_object_add(root, "devices", device_array); + + char *data = json_object_to_json_string_ext(root, JSON_C_TO_STRING_PRETTY); + printf("[INFO][console] store data to %s\n", file); + + FILE *fptr; + fptr = fopen(file, "w"); + fwrite(data, sizeof(char), strlen(data), fptr); + fclose(fptr); +} + +int devicemgr_load(char *file) +{ + FILE *fptr; + const char *line_in_file = malloc(JSON_MAX_LINE_LEN); // maximum of 256 bytes per line; + fptr = fopen(file, "r"); + json_tokener *tok = json_tokener_new(); + json_object *jobj = NULL; + int stringlen = 0; + enum json_tokener_error jerr; + + do + { + int read_ret = fgets(line_in_file, JSON_MAX_LINE_LEN, fptr); // read line from file + stringlen = strlen(line_in_file); + // printf("Read line with chars: %i : %s", stringlen, line_in_file); // only for testing + jobj = json_tokener_parse_ex(tok, line_in_file, stringlen); + if (read_ret == NULL) + { + break; + } + } while ((jerr = json_tokener_get_error(tok)) == json_tokener_continue); + if (jerr != json_tokener_success) + { + free(fptr); //free file pointer + fprintf(stderr, "Error: %s\n", json_tokener_error_desc(jerr)); + // Handle errors, as appropriate for your application. + return -1; + } + + // cleanup + free(fptr); //free file pointer + free(tok); //free tokenizer + + // dump loadad data to terminal ( for tetsting) + // char *data = json_object_to_json_string_ext(jobj, JSON_C_TO_STRING_PRETTY); + // printf("%s",data); + + // load data + json_object *next_free; + if (!json_object_object_get_ex(jobj, "nextFreeSlot", &next_free)) + { + fprintf(stderr, "Error: %s\n", "Key 'nextFreeSlot' not found."); + return -1; + } + else + { + nextFreeSlot = json_object_get_int(next_free); + free(next_free); + } + + // clear config + devicemgr_init(deviceFd); + + // load devices + json_object *devices; + if (!json_object_object_get_ex(jobj, "devices", &devices)) + { + fprintf(stderr, "Error: %s\n", "Key 'devices' not found."); + return -1; + } + else + { + int arraylen = json_object_array_length(devices); + for (int i = 0; i < arraylen; i++) + { + devicemgr_load_single(json_object_array_get_idx(devices, i)); + } + + free(devices); + } +} + +int devicemgr_load_single(json_object *device_obj) +{ + json_object *jid = json_object_object_get(device_obj, "id"); + json_object *jaddr = json_object_object_get(device_obj, "address"); + json_object *jpos = json_object_object_get(device_obj, "position"); + json_object *jposx = json_object_object_get(jpos, "x"); + json_object *jposy = json_object_object_get(jpos, "y"); + // verify values are present + if (jid == NULL) + { + fprintf(stderr, "Error: Key 'device.%s' not found\n", "id"); + return -1; + } + if (jaddr == NULL) + { + fprintf(stderr, "Error: Key 'address.%s' not found\n", "id"); + return -1; + } + if (jposx == NULL) + { + fprintf(stderr, "Error: Key 'device.%s' not found\n", "position.x"); + return -1; + } + if (jposy == NULL) + { + fprintf(stderr, "Error: Key 'device.%s' not found\n", "position.y"); + return -1; + } + + // create device + devicemgr_register(deviceFd, + json_object_get_int(jaddr), + json_object_get_int(jposx), + json_object_get_int(jposy), + json_object_get_int(jid)); +} \ No newline at end of file diff --git a/software/pc_client/src/devicemgr.h b/software/pc_client/src/devicemgr.h index 6ef3311..7170da8 100644 --- a/software/pc_client/src/devicemgr.h +++ b/software/pc_client/src/devicemgr.h @@ -1,21 +1,26 @@ +#include "sfbus.h" +#include +#include // Error integer and strerror() function +#include // Contains file controls like O_RDWR +#include +#include +#include #include #include -#include -#include // Contains file controls like O_RDWR -#include // Error integer and strerror() function #include +#include +#include #include // Contains POSIX terminal control definitions #include // write(), read(), close() -#include -#include "sfbus.h" -#include -#include -#include -#include -int devicemgr_readStatus(int device_id) ; -json_object * devicemgr_printDetails(int device_id); -json_object * devicemgr_printDetailsAll(); -int devicemgr_register(int rs485_descriptor, u_int16_t address, int x,int y); +int devicemgr_readStatus(int device_id); +int devicemgr_readCalib(int device_id); +void devicemgr_printDetails(int device_id, json_object *root); +void devicemgr_printDetailsAll(json_object *root); +int devicemgr_register(int rs485_descriptor, u_int16_t address, int x, int y, int nid); void devicemgr_init(); -int devicemgr_print(char* text); \ No newline at end of file +int devicemgr_print(char *text); +int devicemgr_refresh(); +int devicemgr_save(char *file); +void devicemgr_printText(char *text, int x, int y); +void devicemgr_printFlap(int flap, int x, int y); \ No newline at end of file diff --git a/software/pc_client/src/main.c b/software/pc_client/src/main.c index b376901..e076529 100644 --- a/software/pc_client/src/main.c +++ b/software/pc_client/src/main.c @@ -12,6 +12,8 @@ #include "ftdi485.h" #include "sfbus.h" #include "devicemgr.h" +#include "console.h" + void printUsage(char *argv[]) { fprintf(stderr, "Usage: %s -p -c [value]\n", argv[0]); @@ -65,20 +67,10 @@ int main(int argc, char *argv[]) { printf("Open device at %s\n", port); int fd = rs485_init(port, B19200); // setup rs485 - // test - devicemgr_init(); - devicemgr_register(fd,1,0,0); - devicemgr_register(fd,2,1,0); - devicemgr_register(fd,3,2,0); - devicemgr_register(fd,4,3,0); - devicemgr_printDetailsAll(); - //exit(1); - if (strcmp(command, "ping") == 0) { - sfbus_ping(fd, addr_int); - exit(0); + } else if (strcmp(command, "printf") == 0) { - printText(data,0,0); + devicemgr_printText(data,0,0); } else if (strcmp(command, "r_eeprom") == 0) { char *buffer = malloc(64); sfbus_read_eeprom(fd, addr_int, buffer); @@ -89,44 +81,12 @@ int main(int argc, char *argv[]) { exit(0); } else if (strcmp(command, "w_addr") == 0) { int n_addr = strtol(data, NULL, 10); - if (n_addr < 1) { - printf("Please specify new address > 0 with -d\n"); - exit(-1); - } - // read current eeprom status - char *buffer_w = malloc(64); - char *buffer_r = malloc(64); - if (sfbus_read_eeprom(fd, addr_int, buffer_w) < 0) { - fprintf(stderr, "Error reading eeprom\n"); - exit(1); - } - // modify current addr - u_int16_t n_addr_16 = n_addr; - memcpy(buffer_w, &n_addr_16, 2); - if (sfbus_write_eeprom(fd, addr_int, buffer_w, buffer_r) < 0) { - fprintf(stderr, "Error writing eeprom\n"); - exit(1); - } - - exit(0); + int ret = sfbusu_write_address(fd,addr_int,n_addr); + exit(ret); } else if (strcmp(command, "w_cal") == 0) { int n_addr = strtol(data, NULL, 10); - // read current eeprom status - char *buffer_w = malloc(64); - char *buffer_r = malloc(64); - if (sfbus_read_eeprom(fd, addr_int, buffer_w) < 0) { - fprintf(stderr, "Error reading eeprom\n"); - exit(1); - } - // modify current addr - u_int16_t n_addr_16 = n_addr; - memcpy(buffer_w+2, &n_addr_16, 2); - if (sfbus_write_eeprom(fd, addr_int, buffer_w, buffer_r) < 0) { - fprintf(stderr, "Error writing eeprom\n"); - exit(1); - } - - exit(0); + int ret = sfbusu_write_calibration(fd,addr_int,n_addr); + exit(ret); } else if (strcmp(command, "status") == 0) { double voltage = 0; u_int32_t counter = 0; @@ -165,20 +125,11 @@ int main(int argc, char *argv[]) { } else if (strcmp(command, "power_off") == 0) { sfbus_motor_power(fd, addr_int,0); exit(0); + } else if (strcmp(command, "server") == 0){ + start_console(fd); } else { fprintf(stderr, "Invalid command specified!\n"); printUsage(argv); } - - char *buffer = malloc(256); - char *cmd = "\xF0"; - - sfbus_send_frame(fd, 0, strlen(cmd), cmd); - - int len = sfbus_recv_frame_wait(fd, 0xFFFF, buffer); - // printf("TEST:%i %s\n", len, buffer); - - free(buffer); - return 0; } \ No newline at end of file diff --git a/software/pc_client/src/sfbus-util.c b/software/pc_client/src/sfbus-util.c new file mode 100644 index 0000000..f19db6d --- /dev/null +++ b/software/pc_client/src/sfbus-util.c @@ -0,0 +1,48 @@ +#include "sfbus-util.h" +int sfbusu_write_address(int fd, u_int16_t current, u_int16_t new) +{ + if (new < 1) + { + printf("Please specify new address > 0 with -d\n"); + return -1; + } + // read current eeprom status + char *buffer_w = malloc(64); + char *buffer_r = malloc(64); + if (sfbus_read_eeprom(fd, current, buffer_w) < 0) + { + fprintf(stderr, "Error reading eeprom\n"); + return 1; + } + // modify current addr + u_int16_t n_addr_16 = new; + memcpy(buffer_w, &n_addr_16, 2); + if (sfbus_write_eeprom(fd, current, buffer_w, buffer_r) < 0) + { + fprintf(stderr, "Error writing eeprom\n"); + return 1; + } + + return 0; +} + +int sfbusu_write_calibration(int fd, u_int16_t address, u_int16_t data) +{ + // read current eeprom status + char *buffer_w = malloc(64); + char *buffer_r = malloc(64); + if (sfbus_read_eeprom(fd, address, buffer_w) < 0) + { + fprintf(stderr, "Error reading eeprom\n"); + return 1; + } + // modify current calibration + memcpy(buffer_w + 2, &data, 2); + if (sfbus_write_eeprom(fd, address, buffer_w, buffer_r) < 0) + { + fprintf(stderr, "Error writing eeprom\n"); + return 1; + } + + return 0; +} diff --git a/software/pc_client/src/sfbus-util.h b/software/pc_client/src/sfbus-util.h new file mode 100644 index 0000000..e7ae5b2 --- /dev/null +++ b/software/pc_client/src/sfbus-util.h @@ -0,0 +1,7 @@ +#include "sfbus.h" +#include +#include +#include + +int sfbusu_write_address(int fd, u_int16_t current, u_int16_t new); +int sfbusu_write_calibration(int fd, u_int16_t address, u_int16_t data); \ No newline at end of file diff --git a/software/pc_client/src/sfbus.c b/software/pc_client/src/sfbus.c index bfd3512..1bd2b17 100644 --- a/software/pc_client/src/sfbus.c +++ b/software/pc_client/src/sfbus.c @@ -107,14 +107,10 @@ void sfbus_send_frame(int fd, u_int16_t address, u_int8_t length, } *frame = '$'; // startbyte - //rs485_trdir(fd, 0); - int result = write(fd, frame_ptr, frame_size_complete); print_bufferHexTx(frame_ptr + 5, frame_size_complete - 6, address); free(frame_ptr); - // tcdrain(fd); - //usleep(470 * (frame_size_complete + 1)); - //rs485_trdir(fd, 1); + } int sfbus_ping(int fd, u_int16_t address) { diff --git a/software/pc_client/src/wsserver.c b/software/pc_client/src/wsserver.c new file mode 100644 index 0000000..0cac2c9 --- /dev/null +++ b/software/pc_client/src/wsserver.c @@ -0,0 +1,106 @@ +#include "wsserver.h" + +/* + * This section provides a web server to controll the + * device manager through web sockets + * + * by Dennis Gunia - 2025 - www.dennisgunia.de + */ + +json_object *(*commandparser_func)(json_object *); + +// this sections handles ws connections and communications +// called on opening websocket client +void onopen(ws_cli_conn_t client) +{ + char *cli; + cli = ws_getaddress(client); + printf("Connection opened, addr: %s\n", cli); +} + +// called on closing websocket client +void onclose(ws_cli_conn_t client) +{ + char *cli; + cli = ws_getaddress(client); + printf("Connection closed, addr: %s\n", cli); +} + +// called on receiving websocket message +void onmessage(ws_cli_conn_t client, const unsigned char *msg, uint64_t size, int type) +{ + char *cli = ws_getaddress(client); + printf("received message: %s (%zu), from: %s\n", msg, size, cli); + + json_tokener *tok = json_tokener_new(); + json_object *req = json_tokener_parse_ex(tok, msg, size); + enum json_tokener_error jerr; + jerr = json_tokener_get_error(tok); + if (jerr != json_tokener_success) + { + // check if request can be parsed, if not retrun error + send_json_error(client, "parsing error", json_tokener_error_desc(jerr)); + } + else + { + // if it can be parsed, get command. + json_object *commandObj = json_object_object_get(req, "command"); + printf("test"); + if (commandObj != NULL) + { + char *command = json_object_to_json_string(commandObj); + // get key + json_object *res = commandparser_func(req); + if (res == NULL) + { + send_json_error(client, "internal error", ""); + } + else + { + send_json_response(client, res); + } + free(res); + } + else + { + // if key is missing, send error + send_json_error(client, "format error", "missing key: command"); + } + } + free(tok); // always free tokenizer, to prevent memory leak +} + +void send_json_response(ws_cli_conn_t client, json_object *res) +{ + char *message = json_object_to_json_string_ext(res, JSON_C_TO_STRING_PRETTY); + ws_sendframe_txt(client, message); +} + +void send_json_error(ws_cli_conn_t client, char *error, char *detail) +{ + json_object *root = json_object_new_object(); + json_object_object_add(root, "error", json_object_new_string(error)); + json_object_object_add(root, "detail", json_object_new_string(detail)); + send_json_response(client, root); +} + +// starting webserver +int start_webserver(json_object *(*commandparser_func_ptr)(json_object *)) +{ + commandparser_func = commandparser_func_ptr; + ws_socket(&(struct ws_server){/* + * Bind host, such as: + * localhost -> localhost/127.0.0.1 + * 0.0.0.0 -> global IPv4 + * :: -> global IPv4+IPv6 (Dual stack) + */ + .host = WS_SERVER_ADDR, + .port = WS_SERVER_PORT, + .thread_loop = 0, + .timeout_ms = 1000, + .evs.onopen = &onopen, + .evs.onclose = &onclose, + .evs.onmessage = &onmessage}); + + return (0); +} \ No newline at end of file diff --git a/software/pc_client/src/wsserver.h b/software/pc_client/src/wsserver.h new file mode 100644 index 0000000..e5822d9 --- /dev/null +++ b/software/pc_client/src/wsserver.h @@ -0,0 +1,11 @@ +#include +#include +#include +#include +#include +#include + +#define WS_SERVER_PORT 8080 +#define WS_SERVER_ADDR "localhost" + +int start_webserver();