├── README.md ├── English-Communication.protocol.MODBUS.RTU.for.KWS-301L.ods └── MODBUS_POWER_METER.ino /README.md: -------------------------------------------------------------------------------- 1 | # KWS-AC301 2 | 3 | Modbus commands and Arduino sketch for the KWS-AC301 power meter 4 | 5 | provided by iggnator 6 | -------------------------------------------------------------------------------- /English-Communication.protocol.MODBUS.RTU.for.KWS-301L.ods: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SensorsIot/KWS-AC301/main/English-Communication.protocol.MODBUS.RTU.for.KWS-301L.ods -------------------------------------------------------------------------------- /MODBUS_POWER_METER.ino: -------------------------------------------------------------------------------- 1 | //Test code to see if I can get the KWS-AC301L-100A power meter to respond to a MODBUS master quarry. 2 | 3 | const byte numChars = 20;//none of the received hex byte requests from the power meter have more then 7 bytes. NOT TRUE 4 | uint8_t receivedChars[numChars]; // an array to store the received data 5 | uint8_t message[8] = {0x02, 0x03, 0x00, 0x0E, 0x00, 0x01}; //transmit data to meter, element 3 is swapped with get_data[z].command_HEX 6 | uint8_t size;//size of message to meter 7 | 8 | byte result[8]; //result is stored in this array 9 | boolean data_found;//looking for reply to a message 10 | boolean newData;//looking for reply to a message 11 | uint32_t time_to_reply;//what is message to reply time? 12 | 13 | struct KWS_301L_command 14 | { 15 | uint8_t command_HEX; 16 | uint8_t div_by_10; 17 | String command_type; 18 | }; 19 | 20 | KWS_301L_command get_data[38]; 21 | 22 | void setup() 23 | { 24 | Serial.begin(115200); 25 | Serial1.begin(9600); // USART0 for RS485 to AC301L-100A power meter on MOD BUS type interface 26 | Serial.println(""); 27 | get_data[0].command_HEX = 0x01; 28 | get_data[0].div_by_10 = 2; 29 | get_data[0].command_type = "Rated Voltage"; 30 | 31 | get_data[1].command_HEX = 0x02; 32 | get_data[1].div_by_10 = 3; 33 | get_data[1].command_type = "Rated current"; 34 | 35 | get_data[2].command_HEX = 0x03; 36 | get_data[2].div_by_10 = 1; 37 | get_data[2].command_type = "Active power under 0.5L"; 38 | 39 | get_data[3].command_HEX = 0x04; 40 | get_data[3].div_by_10 = 1; 41 | get_data[3].command_type = "Reactive power under 0.5L"; 42 | 43 | get_data[4].command_HEX = 0x05; 44 | get_data[4].div_by_10 = 0; 45 | get_data[4].command_type = "Hardware version low"; 46 | 47 | get_data[5].command_HEX = 0x06; 48 | get_data[5].div_by_10 = 0; 49 | get_data[5].command_type = "Hardware version high"; 50 | 51 | get_data[6].command_HEX = 0x07; 52 | get_data[6].div_by_10 = 0; 53 | get_data[6].command_type = "Software version low"; 54 | 55 | get_data[7].command_HEX = 0x08; 56 | get_data[7].div_by_10 = 0; 57 | get_data[7].command_type = "Software version high"; 58 | 59 | get_data[8].command_HEX = 0x09; 60 | get_data[8].div_by_10 = 0; 61 | get_data[8].command_type = "Protocol version low"; 62 | 63 | get_data[9].command_HEX = 0x0A; 64 | get_data[9].div_by_10 = 0; 65 | get_data[9].command_type = "Protocol version high"; 66 | 67 | get_data[10].command_HEX = 0x0B; 68 | get_data[10].div_by_10 = 0; 69 | get_data[10].command_type = "Module type"; 70 | 71 | get_data[11].command_HEX = 0x0C; 72 | get_data[11].div_by_10 = 0; 73 | get_data[11].command_type = "comm addr & baud status"; 74 | 75 | get_data[12].command_HEX = 0x0D; 76 | get_data[12].div_by_10 = 0; 77 | get_data[12].command_type = "Pulse constant"; 78 | 79 | get_data[13].command_HEX = 0x0E; 80 | get_data[13].div_by_10 = 1; 81 | get_data[13].command_type = "Channel Voltage"; 82 | 83 | get_data[14].command_HEX = 0x0F; 84 | get_data[14].div_by_10 = 3; 85 | get_data[14].command_type = "Channel current low"; 86 | 87 | get_data[15].command_HEX = 0x10; 88 | get_data[15].div_by_10 = 3; 89 | get_data[15].command_type = "Channel current high"; 90 | 91 | get_data[16].command_HEX = 0x11; 92 | get_data[16].div_by_10 = 1; 93 | get_data[16].command_type = "Channel active power low Watts"; 94 | 95 | get_data[17].command_HEX = 0x12; 96 | get_data[17].div_by_10 = 4; 97 | get_data[17].command_type = "Channel active power high"; 98 | 99 | get_data[18].command_HEX = 0x13; 100 | get_data[18].div_by_10 = 4; 101 | get_data[18].command_type = "Channel reactive power low"; 102 | 103 | get_data[19].command_HEX = 0x14; 104 | get_data[19].div_by_10 = 1; 105 | get_data[19].command_type = "Channel reactive power high"; 106 | 107 | get_data[20].command_HEX = 0x15; 108 | get_data[20].div_by_10 = 1; 109 | get_data[20].command_type = "Channel apparent power low"; 110 | 111 | get_data[21].command_HEX = 0x16; 112 | get_data[21].div_by_10 = 4; 113 | get_data[21].command_type = "Channel apparent power high"; 114 | 115 | get_data[22].command_HEX = 0x17; 116 | get_data[22].div_by_10 = 0; 117 | get_data[22].command_type = "Channel energy low"; 118 | 119 | get_data[23].command_HEX = 0x18; 120 | get_data[23].div_by_10 = 3; 121 | get_data[23].command_type = "Channel energy high"; 122 | 123 | get_data[24].command_HEX = 0x19; 124 | get_data[24].div_by_10 = 0; 125 | get_data[24].command_type = "Operating minutes"; 126 | 127 | get_data[25].command_HEX = 0x1A; 128 | get_data[25].div_by_10 = 0; 129 | get_data[25].command_type = "Current external temperature"; 130 | 131 | get_data[26].command_HEX = 0x1B; 132 | get_data[26].div_by_10 = 0; 133 | get_data[26].command_type = "Current internal temperature"; 134 | 135 | get_data[27].command_HEX = 0x1C; 136 | get_data[27].div_by_10 = 0; 137 | get_data[27].command_type = "RTC battery voltage"; 138 | 139 | get_data[28].command_HEX = 0x1D; 140 | get_data[28].div_by_10 = 2; 141 | get_data[28].command_type = "Power factor"; 142 | 143 | get_data[29].command_HEX = 0x1E; 144 | get_data[29].div_by_10 = 1; 145 | get_data[29].command_type = "Voltage Frequency"; 146 | 147 | get_data[30].command_HEX = 0x1F; 148 | get_data[30].div_by_10 = 0; 149 | get_data[30].command_type = "Alarm status byte"; 150 | 151 | get_data[31].command_HEX = 0x28; 152 | get_data[31].div_by_10 = 0; 153 | get_data[31].command_type = "RTC clock year"; 154 | 155 | get_data[32].command_HEX = 0x29; 156 | get_data[32].div_by_10 = 0; 157 | get_data[32].command_type = "RTC clock month"; 158 | 159 | get_data[33].command_HEX = 0x2A; 160 | get_data[33].div_by_10 = 0; 161 | get_data[33].command_type = "RTC clock day"; 162 | 163 | get_data[34].command_HEX = 0x2B; 164 | get_data[34].div_by_10 = 0; 165 | get_data[34].command_type = "RTC clock Hour"; 166 | 167 | get_data[35].command_HEX = 0x2C; 168 | get_data[35].div_by_10 = 0; 169 | get_data[35].command_type = "RTC clock Minute"; 170 | 171 | get_data[36].command_HEX = 0x2D; 172 | get_data[36].div_by_10 = 0; 173 | get_data[36].command_type = "RTC clock Second"; 174 | 175 | get_data[37].command_HEX = 0x00; 176 | get_data[37].div_by_10 = 0; 177 | get_data[37].command_type = "Write parameter password"; 178 | } 179 | int j; 180 | void loop() 181 | { 182 | /* 183 | j=37; 184 | message[3] = get_data[37].command_HEX;//is this a write unlock? 185 | ModRTU_CRC(message, result); //call (modbusdatastream[]) this inserts the CRC in last two bytes 186 | delay(650);//send new request every half second minimum is ~85ms as response time is ~80ms from request to send 187 | serial_flush_buffer();//clear out buffer 188 | Serial1.write(message, sizeof(message));//write the unlock for write 189 | delay(100); 190 | Serial.print("Data sent ->");//print the bytes of the data sent 191 | print_hex_MODBUS(); 192 | receive_Data();//see if there is data 193 | j=31; 194 | message[3] = 0x28;//write year 2022 195 | message[4] = 0x20;//write year 2022 196 | message[5] = 0x22;//write year 2022 197 | ModRTU_CRC(message, result); //call (modbusdatastream[]) this inserts the CRC in last two bytes 198 | Serial1.write(message, sizeof(message)); 199 | Serial.print("Data sent ->");//print the bytes of the data sent 200 | print_hex_MODBUS(); 201 | delay(100); 202 | receive_Data();//see if there is data 203 | j=32; 204 | message[3] = 0x29;//write month 205 | message[4] = 0x00;//write month 206 | message[5] = 0x01;//write month 207 | ModRTU_CRC(message, result); //call (modbusdatastream[]) this inserts the CRC in last two bytes 208 | Serial1.write(message, sizeof(message)); 209 | Serial.print("Data sent ->");//print the bytes of the data sent 210 | print_hex_MODBUS(); 211 | delay(100); 212 | receive_Data();//see if there is data 213 | j=33; 214 | message[3] = 0x2A;//write day of month 215 | message[4] = 0x00;//write day of month 216 | message[5] = 0x01;//write day of month 217 | ModRTU_CRC(message, result); //call (modbusdatastream[]) this inserts the CRC in last two bytes 218 | Serial1.write(message, sizeof(message)); 219 | Serial.print("Data sent ->");//print the bytes of the data sent 220 | print_hex_MODBUS(); 221 | delay(100); 222 | receive_Data();//see if there is data 223 | j=34; 224 | message[3] = 0x2B;//write hour 225 | message[4] = 0x00;//write hour 226 | message[5] = 0x16;//write hour 227 | ModRTU_CRC(message, result); //call (modbusdatastream[]) this inserts the CRC in last two bytes 228 | Serial1.write(message, sizeof(message)); 229 | Serial.print("Data sent ->");//print the bytes of the data sent 230 | print_hex_MODBUS(); 231 | delay(100); 232 | receive_Data();//see if there is data 233 | j=35; 234 | message[3] = 0x2C;//write minutes 235 | message[4] = 0x00;//write minutes 236 | message[5] = 0x1A;//write minutes 237 | ModRTU_CRC(message, result); //call (modbusdatastream[]) this inserts the CRC in last two bytes 238 | Serial1.write(message, sizeof(message)); 239 | Serial.print("Data sent ->");//print the bytes of the data sent 240 | print_hex_MODBUS(); 241 | delay(100); 242 | receive_Data();//see if there is data 243 | j=37; 244 | message[3] = 0x00;//write lock for write 245 | message[4] = 0x00;//write lock for write 246 | message[5] = 0x00;//write lock for write 247 | ModRTU_CRC(message, result); //call (modbusdatastream[]) this inserts the CRC in last two bytes 248 | Serial1.write(message, sizeof(message)); 249 | Serial.print("Data sent ->");//print the bytes of the data sent 250 | print_hex_MODBUS(); 251 | delay(100); 252 | receive_Data();//see if there is data 253 | message[5] = 0x01;//set this back to 1 word reply 254 | */ 255 | for (j = 0; j <= 36; j++) 256 | //So run through the data that the LianKe EElectric program puts out 257 | { 258 | if (j == 0) message[3] = get_data[0].command_HEX; 259 | if (j == 1) message[3] = get_data[1].command_HEX; 260 | if (j == 2) message[3] = get_data[2].command_HEX; 261 | if (j == 3) message[3] = get_data[3].command_HEX; 262 | if (j == 4) message[3] = get_data[4].command_HEX; 263 | if (j == 5) message[3] = get_data[5].command_HEX; 264 | if (j == 6) message[3] = get_data[6].command_HEX; 265 | if (j == 7) message[3] = get_data[7].command_HEX; 266 | if (j == 8) message[3] = get_data[8].command_HEX; 267 | if (j == 9) message[3] = get_data[9].command_HEX; 268 | if (j == 10) message[3] = get_data[10].command_HEX; 269 | if (j == 11) message[3] = get_data[11].command_HEX; 270 | if (j == 12) message[3] = get_data[12].command_HEX; 271 | if (j == 13) message[3] = get_data[13].command_HEX; 272 | if (j == 14) message[3] = get_data[14].command_HEX; 273 | if (j == 15) message[3] = get_data[15].command_HEX; 274 | if (j == 16) message[3] = get_data[16].command_HEX; 275 | if (j == 17) message[3] = get_data[17].command_HEX; 276 | if (j == 18) message[3] = get_data[18].command_HEX; 277 | if (j == 19) message[3] = get_data[19].command_HEX; 278 | if (j == 20) message[3] = get_data[20].command_HEX; 279 | if (j == 21) message[3] = get_data[21].command_HEX; 280 | if (j == 22) message[3] = get_data[22].command_HEX; 281 | if (j == 23) message[3] = get_data[23].command_HEX; 282 | if (j == 24) message[3] = get_data[24].command_HEX; 283 | if (j == 25) message[3] = get_data[25].command_HEX; 284 | if (j == 26) message[3] = get_data[26].command_HEX; 285 | if (j == 27) message[3] = get_data[27].command_HEX; 286 | if (j == 28) message[3] = get_data[28].command_HEX; 287 | if (j == 29) message[3] = get_data[29].command_HEX; 288 | if (j == 30) message[3] = get_data[30].command_HEX; 289 | if (j == 31) message[3] = get_data[31].command_HEX; 290 | if (j == 32) message[3] = get_data[32].command_HEX; 291 | if (j == 33) message[3] = get_data[33].command_HEX; 292 | if (j == 34) message[3] = get_data[34].command_HEX; 293 | if (j == 35) message[3] = get_data[35].command_HEX; 294 | if (j == 36) message[3] = get_data[36].command_HEX; 295 | 296 | size = 8; 297 | 298 | ModRTU_CRC(message, size); //this inserts the CRC in last two bytes 299 | delay(85);//send new request every 85ms as response time is ~80ms from request to send 300 | serial_flush_buffer();//clear out buffer 301 | //ModRTU_CRC_check(message,result); //returns byte array 302 | Serial1.write(message, sizeof(message)); 303 | print_hex_MODBUS(); 304 | /* 305 | Serial.print("Data sent ->");//print the bytes of the data sent 306 | for (uint8_t i = 0; i < sizeof(message); i++)// 307 | { 308 | if (message[i] < 16) Serial.print("0"); 309 | Serial.print(message[i], HEX); 310 | Serial.print(" ");//space between hex bytes 311 | } 312 | Serial.print(" "); 313 | */ 314 | receive_Data();//see if there is data 315 | //void showNewData(); 316 | //delay(650);//send new request every half second minimum is ~85ms as response time is ~80ms from request to send 317 | } 318 | delay(5000);//delay 5 seconds between sending the requests 319 | Serial.println(""); 320 | } 321 | 322 | void print_hex_MODBUS() 323 | { 324 | Serial.print(" Data sent ->0x"); 325 | for (uint8_t i = 0; i < sizeof(message); i++)// 326 | { 327 | if (message[i] < 16) Serial.print("0"); 328 | Serial.print(message[i], HEX); 329 | if (i != size - 1) //if there's another byte, we want the "0x" prefix 330 | { 331 | Serial.print(", 0x"); 332 | } 333 | }//i for loop 334 | Serial.print(" -->"); 335 | } 336 | 337 | void serial_flush_buffer() 338 | { 339 | while (Serial.read() >= 0) 340 | ; // do nothing 341 | } 342 | 343 | void receive_Data() 344 | { //need to time out after some 8 bit no response after bytes start coming in 345 | data_found = false; 346 | uint32_t zero_timer = millis();//want to know how long from send message to receive 347 | boolean no_reply_timeout = false; 348 | uint8_t rc; 349 | uint32_t last_rx; 350 | //Serial.println("in read data routine"); 351 | while (no_reply_timeout == false && data_found == false) 352 | { 353 | if (Serial1.available()) 354 | { 355 | Serial.print("got data "); 356 | time_to_reply = millis() - zero_timer; 357 | data_found = true; 358 | last_rx = millis();//read the timer to see when the meter stops sending 359 | } 360 | else 361 | { 362 | uint32_t time_since_message = millis() - zero_timer; 363 | if (time_since_message > 1450) 364 | { 365 | no_reply_timeout = true; 366 | } 367 | } 368 | } 369 | 370 | //Serial.print("no reply timeout="); 371 | //Serial.print(no_reply_timeout, DEC); 372 | //Serial.print(" data found="); 373 | //Serial.println(data_found, DEC); 374 | 375 | 376 | if (data_found == true) 377 | { //read in response 378 | Serial.print("RX t="); 379 | Serial.print(time_to_reply); 380 | Serial.print(" data="); 381 | uint8_t k = 0;//array index for read characters 382 | while (millis() - last_rx < 4) //the bytes are close together, bit time of 9600 baud is .104 millsecond 383 | { 384 | if (Serial1.available()) 385 | { 386 | rc = Serial1.read(); 387 | //Serial.println(rc, HEX); 388 | receivedChars[k] = rc; 389 | k++; 390 | last_rx = millis();//read the timer to see when the meter stops sending 391 | } 392 | } 393 | for (uint8_t i = 0; i < k; i++)// 394 | { 395 | if (receivedChars[i] < 16) Serial.print("0"); 396 | Serial.print(receivedChars[i], HEX); 397 | if (i != k - 1) //if there's another byte, we want the "0x" prefix 398 | { 399 | Serial.print(", 0x"); 400 | } 401 | } 402 | Serial.print(" data="); 403 | int A = (receivedChars[3] << 8 | receivedChars[4]); 404 | 405 | Serial.print(get_data[j].command_type); 406 | Serial.print("-->"); 407 | float Data = (float) A / pow(10, get_data[j].div_by_10); 408 | Serial.println(Data); 409 | for (uint8_t i = 0; i < sizeof(receivedChars); i++)// 410 | { 411 | receivedChars[i] = 0x00; 412 | } 413 | } 414 | else { 415 | Serial.println("No data"); 416 | } 417 | } 418 | 419 | 420 | 421 | 422 | void showNewData() 423 | { 424 | if (newData == true) { 425 | Serial.print("data "); 426 | for (uint8_t i = 0; i < sizeof(receivedChars); i++)//Is there a CR-LF sent, I hope not. 427 | { 428 | if (receivedChars[i] < 16) Serial.print("0"); 429 | Serial.print(receivedChars[i], HEX); 430 | } 431 | Serial.println(""); 432 | } 433 | } 434 | 435 | //Function for CRC insertion 436 | byte ModRTU_CRC(uint8_t message[], uint8_t size) //, uint8_t result[]) //returns byte array 437 | { 438 | uint16_t crc = 0xFFFF;//initialize 439 | for (int pos = 0; pos < size - 2; pos++) //size is data array length so pos ranges 0 to size-2 as array index 440 | { 441 | //result[pos] = message[pos]; 442 | crc ^= (uint16_t)message[pos]; // XOR byte into least sig. byte of crc 443 | for (int i = 8; i != 0; i--) 444 | { // Loop over each bit 445 | if ((crc & 0x0001) != 0) // If the LSB is set 446 | { 447 | crc >>= 1; // Shift right and XOR 0xA001 448 | crc ^= 0xA001; // this is reflection of 0x8005 449 | } 450 | else // Else LSB is not set 451 | crc >>= 1; // Just shift right 452 | } 453 | } 454 | message[size - 2] = lowByte(crc); 455 | message[size - 1] = highByte(crc); 456 | } 457 | 458 | //Function for CRC 459 | byte ModRTU_CRC_check(byte message[], byte result[]) //returns byte array 460 | { 461 | uint16_t crc = 0xFFFF;//initialize 462 | for (int pos = 0; pos < 8; pos++) 463 | { //8 is data array length so pos ranges 0 to 7 as array index 464 | result[pos] = message[pos]; 465 | crc ^= (uint16_t)message[pos]; // XOR byte into least sig. byte of crc 466 | for (int i = 8; i != 0; i--) 467 | { // Loop over each bit 468 | if ((crc & 0x0001) != 0) 469 | { // If the LSB is set 470 | crc >>= 1; // Shift right and XOR 0xA001 471 | crc ^= 0xA001; // this is reflection of 0x8005 472 | } 473 | else // Else LSB is not set 474 | crc >>= 1; // Just shift right 475 | } 476 | } 477 | Serial.print("CRC-"); 478 | Serial.print(lowByte(crc), HEX); 479 | Serial.println(highByte(crc), HEX); 480 | 481 | } 482 | --------------------------------------------------------------------------------