/****************************************************************** Example for reading modbus registers over TCP in Pico-C Not perfect but working Andrej Konkow, 01/2026 ...Pico-C is in somekind very special in syntax and possibilities... ******************************************************************/ // 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 }; // Blocksize upper limit for reading response data #define RD_BLOCK_SIZE 300 // In Loxone programs currently can address // 13 output channels for number-values in maximum #define MAX_OUTPUT_CHANNELS 13 // Hexdata coming from the first component is encoded as: // First byte: endian information // Next MAX_OUTPUT_CHANNELS bytes: Info about datatypes #define HEX_METADATA_OFFSET 1+MAX_OUTPUT_CHANNELS // Sleeptime before repeating request in milliseconds #define REPEATER_SLEEP_MS 50 // Shall we run in debugmode and print some message on the outputchannels? 0 or 1 #define DEBUGMODE 1 /* 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; } /* The bytes represent ascii-encoded values. Decode them... */ void decodeBytes2Ascii(char *src, char *dest, int len) { char hlp[32]; init(hlp, 32, 0); int tmpLen = strlen(src); if (tmpLen < len) len = tmpLen; if (len == 0) return; strncpy(hlp, src, len); if (strlen(dest) > 0) sprintf(dest,"%s, %s", dest, hlp); else sprintf(dest,"%s", hlp); } /* PicoC works little endian Supported values are arraylengths of 2 and 4 */ void swapBytes(char *bytes, int result_type, int littleendian) { // In these cases return unmodified if (littleendian || 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 } /* Helper function for retrieving bytesizes */ int getBytesizeByList(int *types, int sizeOfList) { int bytesize=0, i; 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))); } /* 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; } /* In case of debugmode print a message and have a short break to assure that the message can be read */ void debugMsg(char *msg) { debugMsgParam(msg, -9999); } void debugMsgParam(char *msg, int val) { if (!DEBUGMODE) return; char debugmsg[150]; if (val == -9999) sprintf(debugmsg,"[DEBUG] %s", msg); else sprintf(debugmsg,"[DEBUG] %s %d", msg, val); setoutputtext(0, debugmsg); sleep(1000); } /************************************************************************ Now lets do the main work Mainfunctionality encapsulated in a function Due to picoC issues a little awkward */ void main() { int resultregister_type_total_bytesize, // Byte-size of result registers result_type, result_bytesize, bLittle_endian, no_resultvalues, no_resultBytes, szListOfDatatypes[MAX_OUTPUT_CHANNELS], 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; } remainingResultsHex = getinputtext(0); no_resultBytes = convertHexToBytes(remainingResultsHex, szRemainingResults); free(remainingResultsHex); // First get the endian information bLittle_endian = (int)(!szRemainingResults[0]); // Extract the datatypes for (i=0; i