GoobySoft
General software for logging and controlling devices. Controlling Modbus, CAN bus via STM32PLC etc
Install / Use
/learn @DanielMartensson/GoobySoftREADME
GoobySoft
This is a measuring and controlling software. The purpose of this software is to provide an open source measurement software and control software that is easy to maintain and easy to use. The unique thing with this project is that this project is written so anybody can implement their own device.
Features
- CANopen/Open SAE J1939/Modbus RTU/Modbus TCP/USB support
- Measure digital/analog inputs
- Control output actuators
- Easy to add own
deviceby usingcallbackfunctions - Database connection
- Real time data acquisition
- Multifunctional plots
- CAN Traffic reader
- Multiple connections to multiple devices at the same time during data acquisition
- Latest modern C++ standard
Supported devices
- ADL400 DIN Rail Energy Meter
- Uponor X-148 Home Automation System
- STM32-PLC
- Weller to JBC converter
- ISO 11783 ControlValve according to SAE J1939
How to add a new device
Assume that you have for example a Modbus or a special USB dongle that can communicate with an input/output device, and you want GoobySoft to read, write and store measurement data and also have a control connection to that device. All you need to do, is to work inside Devices folder of this project.
- Begin first to add your new
protocoland newdevicehere. Notice the nameTools_Communications_Devices_createDevices(). The project is structured so each folder name begins with a capital letter and subfolders path are displayed with_and functions begins with lower case letters. So to findTools_Communications_Devices_createDevices(), head over toTools/Communications/Devicesand open the fileDevices.cpp. Each folder have the same folder name as the header and source file. So inside folderDevices, it existsDevices.cppandDevices.h.
void Tools_Communications_Devices_createDevices() {
// Get the parameter holder
Protocol* protocols = Tools_Hardware_ParameterStore_getParameterHolder()->protocols;
// Reset all
for (int i = 0; i < MAX_PROTOCOLS; i++) {
protocols[i].isProtocolUsed = false;
}
// Create devices for protocols
createProtocolTool(&protocols[0], PROTOCOL_STRING[USB_PROTOCOL_ENUM_MODBUS_RTU], 1); // Modbus RTU, 1 device
createProtocolTool(&protocols[1], PROTOCOL_STRING[USB_PROTOCOL_ENUM_RAW_USB], 1); protocol), // 1 device
// Add new protocol here...
// Create devices
createDeviceTool(&protocols[0].devices[0], "ADL400", Tools_Communications_Devices_ADL400_getFunctionValues, Tools_Communications_Devices_ADL400_getTableColumnIDs, Tools_Communications_Devices_ADL400_getInput, Tools_Communications_Devices_ADL400_setOutput, Tools_Communications_Devices_ADL400_getColumnFunction);
createDeviceTool(&protocols[1].devices[0], "STM32 PLC", Tools_Communications_Devices_STM32PLC_getFunctionValues, Tools_Communications_Devices_STM32PLC_getTableColumnIDs, Tools_Communications_Devices_STM32PLC_getInput, Tools_Communications_Devices_STM32PLC_setOutput, Tools_Communications_Devices_STM32PLC_getColumnFunction);
// Add new device here...
}
- Create the
getFunctionValues()callback. This function should return a string of function values with\0as null termination e.gRead Input A\0Read Input B\0Write Output C\0. The reason for that is thatImGui::Combobox want an argument that contains aconst char*that null terminations
std::string Tools_Communications_Devices_<NAME_OF_YOUR_DEVICE>_getFunctionValues(){
std::string functionNames;
functionNames += "Read Input A";
functionNames += '\0';
functionNames += "Read Input B";
functionNames += '\0';
functionNames += "Write Output C";
functionNames += '\0';
return functionNames;
}
- Create the
getTableColumnsID()callback. Here you can determine the name of your column when you are going to configure your e.g measurementdeviceorCAN-bus device. Here are some examples below. You don't need to use them all, but some of them are mandatory.
std::vector<TableColumnID> Tools_Communications_Devices_<NAME_OF_YOUR_DEVICE>_getTableColumnIDs() {
/*
* This can:
* Measure analog/digital inputs, control analog outputs, control analog outputs via e.g CAN-bus field, measure analog/digital inputs via e.g CAN-bus field
*/
// Only one column definition is allowed.
std::vector<TableColumnID> tableColumnIDs;
tableColumnIDs.emplace_back(Tools_Communications_Devices_createTableIDs("Port", COLUMN_DEFINITION_PORT)); // Mandatory field
tableColumnIDs.emplace_back(Tools_Communications_Devices_createTableIDs("Function", COLUMN_DEFINITION_FUNCTION)); // Mandatory field
tableColumnIDs.emplace_back(Tools_Communications_Devices_createTableIDs("CAN address", COLUMN_DEFINITION_ADDRESS));
tableColumnIDs.emplace_back(Tools_Communications_Devices_createTableIDs("Min value raw", COLUMN_DEFINITION_MIN_VALUE_RAW));
tableColumnIDs.emplace_back(Tools_Communications_Devices_createTableIDs("Max value raw", COLUMN_DEFINITION_MAX_VALUE_RAW));
tableColumnIDs.emplace_back(Tools_Communications_Devices_createTableIDs("Min value real", COLUMN_DEFINITION_MIN_VALUE_REAL));
tableColumnIDs.emplace_back(Tools_Communications_Devices_createTableIDs("Max value real", COLUMN_DEFINITION_MAX_VALUE_REAL));
tableColumnIDs.emplace_back(Tools_Communications_Devices_createTableIDs("Display name", COLUMN_DEFINITION_DISPLAY_NAME)); // Mandatory field
return tableColumnIDs;
/*
* This can:
* Measure analog/digital inputs, control analog outputs
*/
// Only one column definition is allowed.
std::vector<TableColumnID> tableColumnIDs;
tableColumnIDs.emplace_back(Tools_Communications_Devices_createTableIDs("Port", COLUMN_DEFINITION_PORT)); // Mandatory field
tableColumnIDs.emplace_back(Tools_Communications_Devices_createTableIDs("Function", COLUMN_DEFINITION_FUNCTION)); // Mandatory field
tableColumnIDs.emplace_back(Tools_Communications_Devices_createTableIDs("Min value raw", COLUMN_DEFINITION_MIN_VALUE_RAW));
tableColumnIDs.emplace_back(Tools_Communications_Devices_createTableIDs("Max value raw", COLUMN_DEFINITION_MAX_VALUE_RAW));
tableColumnIDs.emplace_back(Tools_Communications_Devices_createTableIDs("Min value real", COLUMN_DEFINITION_MIN_VALUE_REAL));
tableColumnIDs.emplace_back(Tools_Communications_Devices_createTableIDs("Max value real", COLUMN_DEFINITION_MAX_VALUE_REAL));
tableColumnIDs.emplace_back(Tools_Communications_Devices_createTableIDs("Display name", COLUMN_DEFINITION_DISPLAY_NAME)); // Mandatory field
return tableColumnIDs;
/*
* This can:
* Control analog outputs
*/
// Only one column definition is allowed.
std::vector<TableColumnID> tableColumnIDs;
tableColumnIDs.emplace_back(Tools_Communications_Devices_createTableIDs("Port", COLUMN_DEFINITION_PORT)); // Mandatory field
tableColumnIDs.emplace_back(Tools_Communications_Devices_createTableIDs("Function", COLUMN_DEFINITION_FUNCTION)); // Mandatory field
tableColumnIDs.emplace_back(Tools_Communications_Devices_createTableIDs("Min value raw", COLUMN_DEFINITION_MIN_VALUE_RAW));
tableColumnIDs.emplace_back(Tools_Communications_Devices_createTableIDs("Max value raw", COLUMN_DEFINITION_MAX_VALUE_RAW));
tableColumnIDs.emplace_back(Tools_Communications_Devices_createTableIDs("Display name", COLUMN_DEFINITION_DISPLAY_NAME)); // Mandatory field
return tableColumnIDs;
/*
* This can:
* Measure analog/digital inputs via e.g CAN-bus field
*/
// Only one column definition is allowed.
std::vector<TableColumnID> tableColumnIDs;
tableColumnIDs.emplace_back(Tools_Communications_Devices_createTableIDs("Port", COLUMN_DEFINITION_PORT)); // Mandatory field
tableColumnIDs.emplace_back(Tools_Communications_Devices_createTableIDs("Function", COLUMN_DEFINITION_FUNCTION)); // Mandatory field
tableColumnIDs.emplace_back(Tools_Communications_Devices_createTableIDs("CAN/Modbus address", COLUMN_DEFINITION_ADDRESS));
tableColumnIDs.emplace_back(Tools_Communications_Devices_createTableIDs("Display name", COLUMN_DEFINITION_DISPLAY_NAME)); // Mandatory field
return tableColumnIDs;
/*
* This can:
* Control analog outputs via e.g CAN-bus field, measure analog/digital inputs via e.g CAN-bus field
*/
// Only one column definition is allowed.
std::vector<TableColumnID> tableColumnIDs;
tableColumnIDs.emplace_back(Tools_Communications_Devices_createTableIDs("Port", COLUMN_DEFINITION_PORT)); // Mandatory field
tableColumnIDs.emplace_back(Tools_Communications_Devices_createTableIDs("Function", COLUMN_DEFINITION_FUNCTION)); // Mandatory field
tableColumnIDs.emplace_back(Tools_Communications_Devices_createTableIDs("CAN/Modbus address", COLUMN_DEFINITION_ADDRESS));
tableColumnIDs.emplace_back(Tools_Communications_Devices_createTableIDs("Min value raw", COLUMN_DEFINITION_MIN_VALUE_RAW));
tableColumnIDs.emplace_back(Tools_Communications_Devices_createTableIDs("Max value raw", COLUMN_DEFINITION_MAX_VALUE_RAW));
tableColumnIDs.emplace_back(Tools_Communications_Devices_createTableIDs("Display name", COLUMN_DEFINITION_DISPLAY_NAME)); // Mandatory field
return tableColumnIDs;
}
If you have this setup above, then your configuration table is going to look like this. Depending on which function you are selecting, some input fields are hidden. The COLUMN_DEFINITION enum can be found in Parameters.h file

- Create the
getInput()callback. This function want to have three arguments. AC-stringport that describe theUSBport, or it can also be theIp AddressifModbus TCPis used. Next argument is thefunctionValueIndex. That index value corresponds to the index ofgetFunctionValues()callback. WhatgetInput()does, is that it's reading the measurements of a device and return it back
float Tools
Related Skills
node-connect
342.5kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
85.3kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
342.5kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
342.5kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。
