/****************************************************************** Example for reading modbus registers over TCP in Pico-C Not perfect but working Check how your modbus device behaves when more than one client is connecting... Andrej Konkow, 01/2026 ...Pico-C is in somekind very special in syntax and possibilities... ******************************************************************/ /********** Main configuration block **************** */ // Do we configure this code from inside ourself or is a // Textgenerator-component connected. Either keep the line or // outcomment the next line like this: // #define CODE... #define CODECONFIG_BY_TEXTGENERATOR // If not using the textgenerator specify your data here #ifndef CODECONFIG_BY_TEXTGENERATOR #define MODBUS_INPUT_IP_PORT "192.168.13.xx:1502,1" #define MODBUS_INPUT_ADDRESS_HEX "9D0E" #define MODBUS_INPUT_RESULTS_DT "20x2" #define MODBUS_INPUT_BIGENDIAN 1 #endif /* ****************************************************/ // supported modbus datatypes enum registerDatatype { DT_NONE = 0, UINT16 = 1, INT16 = 2, UINT32 = 3, INT32 = 4, FLOAT16 = 5, FLOAT32 = 6, STRING16 = 7, STRING32 = 8, COIL = 9 }; // Shall we run in debugmode and print some message on the outputchannels? 0 or 1 #define DEBUGMODE 0 // Inputchannels when using the textgenerator component #ifdef CODECONFIG_BY_TEXTGENERATOR #define MODBUS_INPUT_IP_PORT getinputtext(0) #define MODBUS_INPUT_ADDRESS_HEX getinputtext(1) #define MODBUS_INPUT_RESULTS_DT getinputtext(2) #define MODBUS_INPUT_BIGENDIAN getinput(0) #endif // Supported modbus functioncodes #define MODBUS_FC_READCOILS 0x01 #define MODBUS_FC_READHOLDINGREGISTERS 0x03 // byte-size of message being sent to modbus-device #define MODBUS_MSG_SIZE 12 // Payload bytes which can be ignored when getting the values from the response #define MODBUS_RESPONSE_OFFSET 9 // Our own transaction id, as we like but unique #define MODBUS_TRANSACTIONID 140 // Size of char-array holding the connectionstring for target device #define MODBUS_CONNECTIONSTRING_SIZE 60 // Time spent in reading the responsestream/waiting for data, timeout in ms #define MODBUS_READTIME_MS 1000 // Blocksize upper limit for reading response data #define RD_BLOCK_SIZE 300 // Define how many Output channels are available before handing over // the remaining results. In Loxone programs currently can address // 13 output channels for number-values in maximum #define MAX_OUTPUT_CHANNELS 13 // Maximum of displayable results over all components #define MAX_OUTPUT_CHANNELS_TOTAL 2*MAX_OUTPUT_CHANNELS // Maximum of sum of result datatypes to be defined in input component #define MAX_DATATYPES_DEFINED_EXT 100 // Sleeptime before repeating request in milliseconds #define REPEATER_SLEEP_MS 10000 // Definition of delimiters for datainput, defining // datatypes and the number of results #define DELIMITER_DT_BLOCKS "," #define DELIMITER_DT_NUMBER "x" #define STRING_NOPARAM -99999 /* Convert characters/bytes to readable hex-string Caution: Don't use strlen or other string-oriented functions as byte-characters don't behave like normal alphabetical characters Due to efficiency reasons the parameter firstfill tells us with "0" that "dest" is used for the first time. Else Non-"0". Otherwise we would have to use strlen() which can cause time delays when used excessively. */ void convertToReadableHex(char *bytes, int byteLen, char *dest, int firstfill) { int i; char tmpString[5]; for (i=0; i hexlen) break; bit4_1 = hexString[i]; bit4_2 = hexString[i+1]; // Ignore whitespaces if (bit4_1 == ' ') { i++; continue; } // Do the transformation: 1 Byte is 2x 4bits to be converted // Read pairwise if (bit4_1 >= '0' && bit4_1 <= '9') bit4_1 = bit4_1 - '0'; else if (bit4_1 >= 'a' && bit4_1 <='f') bit4_1 = bit4_1 - 'a' + 10; else if (bit4_1 >= 'A' && bit4_1 <='F') bit4_1 = bit4_1 - 'A' + 10; if (bit4_2 >= '0' && bit4_2 <= '9') bit4_2 = bit4_2 - '0'; else if (bit4_2 >= 'a' && bit4_2 <='f') bit4_2 = bit4_2 - 'a' + 10; else if (bit4_2 >= 'A' && bit4_2 <='F') bit4_2 = bit4_2 - 'A' + 10; // stick together the two 4bit values resultBytes[resultcounter] = (bit4_1 << 4) | (bit4_2 & 0xF); i = i+2; resultcounter++; } while (i < hexlen); return resultcounter; } /* The bytes represent ascii-encoded values. Decode them... */ void decodeBytes2Ascii(char *src, char *dest, int len) { char hlp[33], tmpString[35]; init(hlp, 33, 0); int tmpLen = strlen(src); if (tmpLen < len) len = tmpLen; if (len == 0) return; strncpy(hlp, src, len); if (dest[0] == 0) sprintf(tmpString, "%s", hlp); else sprintf(tmpString, ", %s", hlp); strcat(dest, tmpString); } /* Swap all bytes according to their datatype */ void swapAllBytes(char *bytes, int *listDataTypes, int sizeListDataTypes, int littleendian) { int i, result_type, offsetCnt=0; if (littleendian) return; for (i=0; i> 7; exponent = ((flt16_bits[0] & 0x7C) >> 2); significand = ((flt16_bits[0] & 0x03) << 8) | (flt16_bits[1]); return (float)(pow(-1,sign_bit) * (1 + significand/pow(2,10)) * pow(2,(exponent - 31))); } /* calc float32 value */ float calc_float32(char *flt32_bits) { char sign_bit; char exponent; unsigned int significand; // IEEE 754 single precision sign_bit = (flt32_bits[1] & 0x80) >> 7; exponent = ((flt32_bits[1] & 0x7F) << 1) | ((flt32_bits[0] & 0x80) >> 7); significand = (((flt32_bits[0] & 0x7F) << 16) | (flt32_bits[3] << 8) | (flt32_bits[2])); return (float)(pow(-1,sign_bit) * (1 + significand/pow(2,23)) * pow(2,(exponent - 127))); } /* Helperfunctions for initialization */ void init (char *array, int size, int value) { int x; for (x=0; x 8 && modbusResp[7] >= 129 && modbusResp[7] <= 134; if ( bytesReceived - MODBUS_RESPONSE_OFFSET < 0 || bytesReceived != expected_bytesize || error_code == 1 ) { if (error_code == 0) sprintf(errorMsg,"%s", "Reading nok or no connection to device."); else sprintf(errorMsg,"%s Exceptioncode hex: %02x", "Reading nok.", modbusResp[8]); setoutputtext(0, errorMsg); setoutputtext(1, ""); return 1; } return 0; } /* Get the datatype per outputchannel and store it in an array Keep your data clean as errorhandling is not good Returns the total number of results (not bytes) to read */ int parseDatatypes(char *dtString, int *listOfDatatypes, int *listOfDatatypesView, int maxOutputChannels) { int delim_block=0, delim_number=0, dtSum=0, dtSumView=0, i, noResults, datatype; char szResults[3], szDatatype[2]; do { delim_number = strfind(&dtString[delim_block], DELIMITER_DT_NUMBER, 0); strncpy(szResults, &dtString[delim_block], delim_number); strncpy(szDatatype, &dtString[delim_block+delim_number+1], 1); noResults = batoi(szResults); datatype = batoi(szDatatype); for (i=0; i 0) sprintf(status,"Reading ok, %s, %s", results, addresses); else sprintf(status,"%s, %s", "Reading ok", addresses); // Remove existing errormsg/refresh output channel if (clearError == 1) setoutputtext(0, ""); setoutputtext(1, status); } /* Hexify message for handover Only handover results to be displayed and already byte-swapped */ void msgMarshalling(char *hexOutput, int hexOutputSize, int *szListOfDatatypes, int *szListOfDatatypesView, int maxResults, char *modbusResponse, int nextStartingAddress) { int i, j, result_type, result_bytesize, idxOffset, outputCnt, outputChannel; char szResultHelper[3]; short sNextStartingAddress; // Handover the remaining results as hex-string init(hexOutput, hexOutputSize, 0); // First hexify the nextStartingAddress (just a displaying issue in the next component) sNextStartingAddress = (short)nextStartingAddress; memcpy(szResultHelper, &sNextStartingAddress, 2); convertToReadableHex(szResultHelper, 2, hexOutput, 0); // Hexify the remaining result datatypes for (i=0; i 0) sleep(sleepMs); } /* Returns the index offset in the response for the given output channel If we haven't enough data to be displayed -1 is returned. */ int ctrl_getResponseOffset(int *listOfDatatypes, int listMaxSize, int outputChannel) { int i, result_size, outputCnt = 0, currentType, resultIdx = MODBUS_RESPONSE_OFFSET; for (i=0; i 0 && szListOfDatatypes[0] == COIL) bReadCoils = 1; modbus_resultregister_type_bytesize = getBytesizeByList(szListOfDatatypes, no_resultvalues, 1); startAddressChar = MODBUS_INPUT_ADDRESS_HEX; convertHexToBytes(startAddressChar, &handledAddressChar[0]); startAddress = (handledAddressChar[1]) | (handledAddressChar[0] << 8); buildConnectionString(MODBUS_INPUT_IP_PORT, connectionString, &modbusId); // Coils are handled different if (bReadCoils) { buildModbusRequest(modbusRequest, startAddressChar, no_resultvalues, MODBUS_FC_READCOILS, modbusId); no_printValues = modbus_resultregister_type_bytesize; } else { buildModbusRequest(modbusRequest, startAddressChar, modbus_resultregister_type_bytesize/2, MODBUS_FC_READHOLDINGREGISTERS, modbusId); no_printValues = no_resultvalues; } continuedAddress = getDisplayedRegisters(szListOfDatatypes, no_resultvalues, handledAddresses, MAX_OUTPUT_CHANNELS, startAddress); // Keep polling while (TRUE) { init(modbusStrings, RD_BLOCK_SIZE, 0); initUShort(us_resultValues, MAX_OUTPUT_CHANNELS, 0); initSShort(ss_resultValues, MAX_OUTPUT_CHANNELS, 0); initUInt(ui_resultValues, MAX_OUTPUT_CHANNELS, 0); initSInt(si_resultValues, MAX_OUTPUT_CHANNELS, 0); initFloat(f_resultValues, MAX_OUTPUT_CHANNELS, 0.0); errorInCurrentIteration = 0; virtChannel = 0; channel = 0; messageParamTimedChannel("Reading Modbus-TCP", STRING_NOPARAM, 0, 1); /****************/ // TCP Request/Response. Putting this in a function leads to instability on // the miniserver when deploying. pTcpStream = stream_create(connectionString, 0, 0); // create tcp stream stream_write(pTcpStream, modbusRequest, MODBUS_MSG_SIZE); // write to stream stream_flush(pTcpStream); // flush output stream buffer nBytesReceived = stream_read(pTcpStream, modbusResponse, RD_BLOCK_SIZE, MODBUS_READTIME_MS); stream_close(pTcpStream); /****************/ if (checkForErrors(nBytesReceived, modbusResponse, MODBUS_RESPONSE_OFFSET+modbus_resultregister_type_bytesize)) { errorInLastIteration = 1; sleep(REPEATER_SLEEP_MS); continue; } if (!bReadCoils) swapAllBytes(&modbusResponse[MODBUS_RESPONSE_OFFSET], szListOfDatatypes, no_resultvalues, bLittle_endian); messageParamTimedChannel("Processing received bytes: ", nBytesReceived, 0, 1); // Resulthandling // Buffers are big enough. So take the bytes from the responsestream and put them // into the result types/values for (i=0; i