/****************************************************************** Example for reading modbus registers over TCP in Pico-C Not perfect but working Be careful: Don't connect two programs with each other. You will get stability problems in loxone when programs are restarted during redeployments. Use a textgenerator with impulses in between. Andrej Konkow, 11/2025 ...Pico-C is in somekind very special in syntax and possibilities... ******************************************************************/ // supported modbus datatypes enum registerDatatype { DT_NONE = 0, UINT16, INT16, UINT32, INT32, FLOAT16, FLOAT32, STRING16, STRING32, COIL }; // Blocksize upper limit for reading response data #define RD_BLOCK_SIZE 300 // Set an upper limit for receiving values #define NO_RESULTVALUES_UPPER_LIMIT 13 // Sleeptime before repeating request in milliseconds #define REPEATER_SLEEP_MS 50 /* Convert hex-String to bytes, hex-characters are handled 4bit-wise Be aware: For example two string-characters like 1C have to result in one 8bit byte-character To avoid free()-statements organize the memory allocation outside of this function and hand over "resultBytes" For the ease of use errorhandling is not priority... Returns the number of characters/bytes created */ int convertHexToBytes(char *hexString, char *resultBytes) { int hexlen, resultcounter, i; char bit4_1, bit4_2; hexlen = strlen(hexString); resultcounter = 0; i = 0; do { resultBytes[resultcounter] = 0; // Unhandled errorcase if (i+1 > 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; } /* PicoC works little endian Supported values are arraylengths of 2 and 4 */ void swapBytes(char *bytes, int result_type) { // In these cases return unmodified if (result_type == STRING16 || result_type == STRING32 || result_type == COIL) return; // Maximum size possible char tmpBytes[4]; int len = 0; int i=0; len = getBytesizeByType(result_type); for (i=0; i < len; i++) tmpBytes[i] = bytes[i]; if (len == 2) { bytes[0] = tmpBytes[1]; bytes[1] = tmpBytes[0]; } else if (len == 4) { bytes[0] = tmpBytes[3]; bytes[1] = tmpBytes[2]; bytes[2] = tmpBytes[1]; bytes[3] = tmpBytes[0]; } } /* Helper function for retrieving bytesizes */ int getBytesizeByType(int type) { if (type == UINT16 || type == INT16 || type == FLOAT16) return 2; else if (type == UINT32 || type == INT32 || type == FLOAT32) return 4; else if (type == STRING16) return 8; else if (type == STRING32) return 16; else if (type == COIL) return 1; return 0; // Unknown } /* calc float16 value */ float calc_float16(char *flt16_bits) { char sign_bit; char exponent; unsigned int significand; // IEEE 754 half precision sign_bit = (flt16_bits[0] & 0x80) >> 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))); } /* Helperfunction for formatting output-value */ void padValue(char *retValue, int value) { if (value <10) sprintf(retValue, "0%d", value); else sprintf(retValue, "%d", value); } /* Check whether a certain bit is set */ int check_bit(int value, int pos) { int result; result = ((value) & (1<<(pos))); return result; } /* Gets the current time and returns it formatted. */ char *getFormattedTime(char *buffer) { int now = getcurrenttime(); int year = getyear(now, 1); char s_month[3], s_day[3]; char s_hour[3], s_minute[3], s_second[3]; padValue(s_month, getmonth(now, 1)); padValue(s_day, getday(now, 1)); padValue(s_hour, gethour(now, 1)); padValue(s_minute, getminute(now, 1)); padValue(s_second, getsecond(now, 1)); sprintf (buffer, "%s.%s.%d %s:%s:%s", s_day, s_month, year, s_hour, s_minute, s_second); return buffer; } /************************************************************************ Now lets do the main work Mainfunctionality encapsulated in a function Due to picoC issues a little awkward */ void main() { int modbus_resultregister_type_bytesize, // Byte-size of result registers result_type, no_resultvalues, no_resultBytes, baseIndex, i; char szRemainingResults[RD_BLOCK_SIZE], szDisplayMsg[100], szCurrenttime_formatted[35], *remainingResultsHex; signed short ss_resultValue; // sizeof(short) == 2 == 16bit unsigned short us_resultValue; signed int si_resultValue; // sizeof(int) == 4 == 32bit unsigned int ui_resultValue; // Keep polling while (TRUE) { if (check_bit(getinputevent(),0) == 0) { sprintf(szDisplayMsg,"Waiting for data update..."); setoutputtext(0, szDisplayMsg); sleep(REPEATER_SLEEP_MS); continue; } //result_type = getinput(0); remainingResultsHex = getinputtext(0); no_resultBytes = convertHexToBytes(remainingResultsHex, szRemainingResults); free(remainingResultsHex); // Ignore first byte containing the resulttype no_resultBytes -= 1; result_type = (int)szRemainingResults[0]; // For the ease of use all resultsizes are assumed to be the same size modbus_resultregister_type_bytesize = getBytesizeByType(result_type); if (no_resultBytes == 0 || modbus_resultregister_type_bytesize == 0 || no_resultBytes % modbus_resultregister_type_bytesize != 0) { sprintf(szDisplayMsg,"Received invalid data. Ignoring and continuing..."); setoutputtext(0, szDisplayMsg); sleep(REPEATER_SLEEP_MS); continue; } no_resultvalues = no_resultBytes / modbus_resultregister_type_bytesize; // Resulthandling // Buffers are big enough. So // take the bytes from the input // Be aware: We have a maximum of 13 output channels for (i=0; i