Arduino Opta™ OPC UA with Node‑RED
Create a real-time control and monitoring setup by turning your Arduino Opta™ into an OPC UA server and connecting it to a Node‑RED panel.
Components and supplies
1
Arduino Opta WiFi
Apps and platforms
1
Node-RED
1
Arduino IDE 2.0
Project description
Code
opta_opcua_server
cpp
Main sketch for the Opta
1/* 2 * By compiling and uploading this sketch to your Arduino Opta you obtain turn your 3 * Arduino Opta into a networked OPC UA capable device. 4 * 5 * How-to-build/upload: 6 * arduino-cli compile --fqbn arduino:mbed_opta:opta examples/opta_opcua_server -v -u -p /dev/ttyACM0 7 * 8 * How-to-build/upload RS485 Modbus Demo integrated with this sketch: 9 * arduino-cli compile --fqbn arduino:mbed_opta:opta examples/opta_opcua_server -v --build-property compiler.cpp.extra_flags="-DUSE_MODBUS_SENSOR_MD02=1" -u -p /dev/ttyACM0 10 */ 11 12#define USE_MODBUS_SENSOR_MD02 1 13 14/************************************************************************************** 15 * INCLUDE 16 **************************************************************************************/ 17 18#include <Arduino_OPC_UA.h> 19#include <PortentaEthernet.h> 20#include <OptaBlue.h> /* Arduino_Opta_Blueprint */ 21#include <mbed_rtc_time.h> 22 23#if MBED_HEAP_STATS_ENABLED && MBED_MEM_TRACING_ENABLED && MBED_STACK_STATS_ENABLED 24#include "mbed_mem_trace.h" 25#endif 26 27#if USE_MODBUS_SENSOR_MD02 28# include <ArduinoRS485.h> 29# include <ArduinoModbus.h> 30#endif 31 32/************************************************************************************** 33 * CONSTANTS 34 **************************************************************************************/ 35 36#if USE_MODBUS_SENSOR_MD02 37static unsigned int const MODBUS_BAUDRATE = 9600; 38static float const MODBUS_BIT_DURATION = 1.f / MODBUS_BAUDRATE; 39static float const MODBUS_PRE_DELAY_BR = MODBUS_BIT_DURATION * 9.6f * 3.5f * 1e6; 40static float const MODBUS_POST_DELAY_BR = MODBUS_BIT_DURATION * 9.6f * 3.5f * 1e6; 41 42static int const MODBUS_DEVICE_ID = 1; 43static int const MODBUS_DEVICE_TEMPERATURE_REGISTER = 0x0001; 44static int const MODBUS_DEVICE_HUMIDITY_REGISTER = 0x0002; 45#endif 46 47/************************************************************************************** 48 * GLOBAL VARIABLES 49 **************************************************************************************/ 50 51static size_t const OPC_UA_SERVER_THREAD_STACK_SIZE = 16*1024UL; 52template <size_t SIZE> struct alignas(uint32_t) OPC_UA_STACK final : public std::array<uint8_t, SIZE> {}; 53static OPC_UA_STACK<OPC_UA_SERVER_THREAD_STACK_SIZE> OPC_UA_SERVER_THREAD_STACK; 54 55static size_t const OPC_UA_SERVER_THREAD_HEAP_SIZE = 320*1024UL; 56template <size_t SIZE> struct alignas(O1HEAP_ALIGNMENT) OPC_UA_HEAP final : public std::array<uint8_t, SIZE> {}; 57static OPC_UA_HEAP<OPC_UA_SERVER_THREAD_HEAP_SIZE> OPC_UA_SERVER_THREAD_HEAP; 58 59UA_Server * opc_ua_server = nullptr; 60O1HeapInstance * o1heap_ins = nullptr; 61rtos::Thread opc_ua_server_thread(osPriorityNormal, OPC_UA_SERVER_THREAD_STACK.size(), OPC_UA_SERVER_THREAD_STACK.data()); 62 63opcua::Opta::SharedPtr opta_opcua; 64opcua::OptaExpansionManager::SharedPtr opta_expansion_manager_opcua; 65#if USE_MODBUS_SENSOR_MD02 66UA_NodeId modbus_md02_temperature_node_id; 67#endif 68 69/************************************************************************************** 70 * DEFINES 71 **************************************************************************************/ 72 73REDIRECT_STDOUT_TO(Serial) 74 75/************************************************************************************** 76 * LOCAL FUNCTIONS 77 **************************************************************************************/ 78 79static float arduino_opta_analog_read(pin_size_t const pin) 80{ 81 static float const VOLTAGE_MAX = 3.3; // Maximum voltage that can be read 82 static float const RESOLUTION = 4096.0; // 12-bit resolution 83 static float const DIVIDER = 0.3034; // Voltage divider 84 85 /* Read the actual analog value from the pin. */ 86 int const pin_value = analogRead(pin); 87 /* Convert the raw ADC value into an actual voltage. */ 88 float const pin_voltage = pin_value * (VOLTAGE_MAX / RESOLUTION) / DIVIDER; 89 90 return pin_voltage; 91} 92 93static PinStatus arduino_opta_digital_read(pin_size_t const pin) 94{ 95 float const pin_voltage = arduino_opta_analog_read(pin); 96 97 if (pin_voltage > 5.f) /* Half of the full range as measurable by the ADC. */ 98 return HIGH; 99 else 100 return LOW; 101} 102 103/************************************************************************************** 104 * SETUP/LOOP 105 **************************************************************************************/ 106 107void setup() 108{ 109 Serial.begin(115200); 110 auto const start = millis(); 111 for (; !Serial && (millis() - start) < 1000; ) { } 112 113 Serial.println("Starting up..."); 114 115#if USE_MODBUS_SENSOR_MD02 116 RS485.setDelays(MODBUS_PRE_DELAY_BR, MODBUS_POST_DELAY_BR); 117 if (!ModbusRTUClient.begin(MODBUS_BAUDRATE, SERIAL_8N1)) 118 { 119 Serial.println("Failed to start Modbus RTU Client!"); 120 for (;;) { } 121 } 122 ModbusRTUClient.setTimeout(2 * 1000UL); /* 2 seconds. */ 123#endif 124 125 /* Initialize Ethernet interface and print obtained IP to Serial. */ 126 if (!Ethernet.begin()) { 127 Serial.println("\"Ethernet.begin()\" failed."); 128 for (;;) { } 129 } 130 131 /* Try and obtain the current time via NTP and configure the Arduino 132 * Opta's onboard RTC accordingly. The RTC is then used inside the 133 * open62541 Arduino wrapper to obtain the correct timestamps for 134 * the OPC UA server. 135 */ 136 EthernetUDP udp_client; 137 auto const epoch = opcua::NTPUtils::getTime(udp_client); 138 if (epoch > 0) { 139 set_time(epoch); /* Directly set RTC of Arduino Opta. */ 140 } else { 141 set_time(opcua::timeToStr(__DATE__)); /* Configure Arduino Opta with time at compile time as last time of defense. */ 142 } 143 144 /* Initialize Opta Expansion module controller. */ 145 OptaController.begin(); 146 OptaController.update(); 147 148 /* Initialize heap memory. */ 149 o1heap_ins = o1heapInit(OPC_UA_SERVER_THREAD_HEAP.data(), OPC_UA_SERVER_THREAD_HEAP.size()); 150 if (o1heap_ins == nullptr) { 151 Serial.println("\"o1heapInit\" failed."); 152 for (;;) { } 153 } 154 UA_mallocSingleton = o1heap_malloc; 155 UA_freeSingleton = o1heap_free; 156 UA_callocSingleton = o1heap_calloc; 157 UA_reallocSingleton = o1heap_realloc; 158 159 opc_ua_server_thread.start( 160 +[]() 161 { 162 /* Create a server listening on port 4840 (default) */ 163 opc_ua_server = UA_Server_new(); 164 165 /* Printing OPC UA server IP and port. */ 166 UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, 167 "Arduino Opta IP: %s", Ethernet.localIP().toString().c_str()); 168 169 /* Determine the Arduino OPC UA hardware variant. */ 170 opcua::OptaVariant::Type opta_type; 171 if (!opcua::OptaVariant::getOptaVariant(opta_type)) { 172 UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "opcua::OptaVariant::getOptaVariant(...) failed"); 173 return; 174 } 175 UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Arduino Opta Variant: %s", opcua::OptaVariant::toString(opta_type).c_str()); 176 177 /* Read all analog inputs at least once to have them pre-configured as ADCs. */ 178 std::list<pin_size_t> const ADC_PIN_LIST = { A0, A1, A2, A3, A4, A5, A6, A7 }; 179 for (auto const adc_pin : ADC_PIN_LIST) 180 arduino_opta_analog_read(adc_pin); 181 /* Configure analog solution to 12-Bit. */ 182 analogReadResolution(12); 183 184 /* Define the Arduino Opta as a OPC UA object. */ 185 opta_opcua = opcua::Opta::create(opc_ua_server, opta_type); 186 if (!opta_opcua) { 187 UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "opcua::Opta::create(...) failed"); 188 return; 189 } 190 191 /* Add the various digital input pins. */ 192 opta_opcua->addAnalogInput(opc_ua_server, "Analog Input I1", []() { return arduino_opta_analog_read(A0); }); 193 opta_opcua->addAnalogInput(opc_ua_server, "Analog Input I2", []() { return arduino_opta_analog_read(A1); }); 194 opta_opcua->addAnalogInput(opc_ua_server, "Analog Input I3", []() { return arduino_opta_analog_read(A2); }); 195 opta_opcua->addAnalogInput(opc_ua_server, "Analog Input I4", []() { return arduino_opta_analog_read(A3); }); 196 opta_opcua->addAnalogInput(opc_ua_server, "Analog Input I5", []() { return arduino_opta_analog_read(A4); }); 197 opta_opcua->addAnalogInput(opc_ua_server, "Analog Input I6", []() { return arduino_opta_analog_read(A5); }); 198 opta_opcua->addAnalogInput(opc_ua_server, "Analog Input I7", []() { return arduino_opta_analog_read(A6); }); 199 opta_opcua->addAnalogInput(opc_ua_server, "Analog Input I8", []() { return arduino_opta_analog_read(A7); }); 200 201 /* Add the various digital input pins. */ 202 opta_opcua->addDigitalInput(opc_ua_server, "Digital Input I1", []() { return arduino_opta_digital_read(A0); }); 203 opta_opcua->addDigitalInput(opc_ua_server, "Digital Input I2", []() { return arduino_opta_digital_read(A1); }); 204 opta_opcua->addDigitalInput(opc_ua_server, "Digital Input I3", []() { return arduino_opta_digital_read(A2); }); 205 opta_opcua->addDigitalInput(opc_ua_server, "Digital Input I4", []() { return arduino_opta_digital_read(A3); }); 206 opta_opcua->addDigitalInput(opc_ua_server, "Digital Input I5", []() { return arduino_opta_digital_read(A4); }); 207 opta_opcua->addDigitalInput(opc_ua_server, "Digital Input I6", []() { return arduino_opta_digital_read(A5); }); 208 opta_opcua->addDigitalInput(opc_ua_server, "Digital Input I7", []() { return arduino_opta_digital_read(A6); }); 209 opta_opcua->addDigitalInput(opc_ua_server, "Digital Input I8", []() { return arduino_opta_digital_read(A7); }); 210 211 /* Add the various relay outputs. */ 212 opta_opcua->addRelayOutput(opc_ua_server, "Relay 1", [](bool const value) { pinMode(RELAY1, OUTPUT); digitalWrite(RELAY1, value); pinMode(LED_D0, OUTPUT); digitalWrite(LED_D0, value); }); 213 opta_opcua->addRelayOutput(opc_ua_server, "Relay 2", [](bool const value) { pinMode(RELAY2, OUTPUT); digitalWrite(RELAY2, value); pinMode(LED_D1, OUTPUT); digitalWrite(LED_D1, value);}); 214 opta_opcua->addRelayOutput(opc_ua_server, "Relay 3", [](bool const value) { pinMode(RELAY3, OUTPUT); digitalWrite(RELAY3, value); pinMode(LED_D2, OUTPUT); digitalWrite(LED_D2, value);}); 215 opta_opcua->addRelayOutput(opc_ua_server, "Relay 4", [](bool const value) { pinMode(RELAY4, OUTPUT); digitalWrite(RELAY4, value); pinMode(LED_D3, OUTPUT); digitalWrite(LED_D3, value);}); 216 217 /* Add the various LED outputs. */ 218 if (opta_type == opcua::OptaVariant::Type::WiFi) { 219 opta_opcua->addLedOutput(opc_ua_server, "User LED", [](bool const value) { pinMode(LEDB, OUTPUT); digitalWrite(LEDB, value); }); 220 } 221 222 /* Check availability of expansion modules. */ 223 uint8_t opta_expansion_num = OptaController.getExpansionNum(); 224 UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "OptaController %d expansion modules detected.", opta_expansion_num); 225 for(uint8_t i = 0; i < opta_expansion_num; i++) 226 UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Expansion %d: type = %d (\"%16s\"), I2C address= 0x%02X", 227 i, OptaController.getExpansionType(i), opcua::ExpansionType::toStr(OptaController.getExpansionType(i)).c_str(), OptaController.getExpansionI2Caddress(i)); 228 229 /* Create Arduino Opta Expansion Manager (if necessary). */ 230 if (opta_expansion_num) { 231 opta_expansion_manager_opcua = opcua::OptaExpansionManager::create(opc_ua_server); 232 if (!opta_expansion_manager_opcua) { 233 UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "opcua::OptaExpansionManager::create(...) failed"); 234 return; 235 } 236 } 237 238 /* Limit the maximum amount of concurrently supported OPC UA expansion 239 * modules, as exposing expansion modules via OPC UA is a RAM hungry affair, 240 * and we are fairly limited in terms of available RAM. 241 */ 242 if (opta_expansion_num > OPCUA_MAX_OPTA_EXPANSION_NUM) 243 { 244 UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Enabling only %d expansion modules (RAM constraints).", OPCUA_MAX_OPTA_EXPANSION_NUM); 245 opta_expansion_num = OPCUA_MAX_OPTA_EXPANSION_NUM; 246 } 247 248 /* Expose Arduino Opta expansion module IO via OPC UA. */ 249 for(uint8_t i = 0; i < opta_expansion_num; i++) 250 { 251 ExpansionType_t const exp_type = OptaController.getExpansionType(i); 252 253 if (exp_type == EXPANSION_OPTA_DIGITAL_MEC || exp_type == EXPANSION_OPTA_DIGITAL_STS) 254 { 255 opcua::DigitalExpansion::SharedPtr exp_dig = nullptr; 256 if (exp_type == EXPANSION_OPTA_DIGITAL_MEC) 257 exp_dig = opta_expansion_manager_opcua->createDigitalMechanicalExpansion(i); 258 else 259 exp_dig = opta_expansion_manager_opcua->createDigitalSolidStateExpansion(i); 260 261 /* Expose digital/analog pins via OPC UA. */ 262 for (uint8_t d = 0; d < OPTA_DIGITAL_IN_NUM; d++) 263 { 264 char analog_in_name[32] = {0}; 265 snprintf(analog_in_name, sizeof(analog_in_name), "Analog Input I%d", d + 1); 266 exp_dig->addAnalogInput( 267 opc_ua_server, 268 analog_in_name, 269 [i, d]() 270 { 271 return reinterpret_cast<DigitalExpansion *>(OptaController.getExpansionPtr(i))->pinVoltage(d); 272 }); 273 274 char digital_in_name[32] = {0}; 275 snprintf(digital_in_name, sizeof(digital_in_name), "Digital Input I%d", d + 1); 276 exp_dig->addDigitalInput( 277 opc_ua_server, 278 digital_in_name, 279 [i, d]() 280 { 281 return reinterpret_cast<DigitalExpansion *>(OptaController.getExpansionPtr(i))->digitalRead(d, true); 282 }); 283 } 284 285 /* Expose mechanical relays via OPC UA. */ 286 for (uint8_t r = 0; r < OPTA_DIGITAL_OUT_NUM; r++) 287 { 288 char mech_relay_name[32] = {0}; 289 snprintf(mech_relay_name, sizeof(mech_relay_name), "Relay %d", r + 1); 290 exp_dig->addRelayOutput( 291 opc_ua_server, 292 mech_relay_name, 293 [i, r](bool const value) 294 { 295 reinterpret_cast<DigitalExpansion *>(OptaController.getExpansionPtr(i))->digitalWrite(r, value ? HIGH : LOW); 296 }); 297 } 298 } 299 else if (exp_type == EXPANSION_OPTA_ANALOG) 300 { 301 auto const exp_analog = opta_expansion_manager_opcua->createAnalogExpansion(i); 302 303 std::list<int> ANALOG_EXPANSION_MODULE_ANALOG_INPUT_LIST = {OA_CH_0, OA_CH_1, OA_CH_2, OA_CH_3, OA_CH_5, OA_CH_6}; 304 305 int input_num = 1; 306 for (int const a : ANALOG_EXPANSION_MODULE_ANALOG_INPUT_LIST) 307 { 308 /* Configure analog expansion module analog channels as analog inputs. */ 309 AnalogExpansion::beginChannelAsAdc(OptaController, 310 i, /* expansion module number */ 311 a, /* analog channel of expansion module */ 312 OA_VOLTAGE_ADC, /* ADC type */ 313 true, /* enable pull down */ 314 false, /* disable rejection */ 315 false, /* disable diagnostic */ 316 0); /* disable averaging */ 317 318 /* Expose analog inputs as readable OPC UA properties. */ 319 char analog_in_name[32] = {0}; 320 snprintf(analog_in_name, sizeof(analog_in_name), "Analog Input I%d", input_num); 321 exp_analog->addAnalogInput( 322 opc_ua_server, 323 analog_in_name, 324 [i, a]() 325 { 326 return reinterpret_cast<AnalogExpansion *>(OptaController.getExpansionPtr(i))->pinVoltage(a); 327 }); 328 input_num++; 329 } 330 331 std::list<int> ANALOG_EXPANSION_MODULE_ANALOG_OUTPUT_LIST = {OA_CH_4, OA_CH_7}; 332 333 int output_num = 1; 334 for (int const a : ANALOG_EXPANSION_MODULE_ANALOG_OUTPUT_LIST) 335 { 336 /* Configure analog expansion module analog channels as analog outputs. */ 337 AnalogExpansion::beginChannelAsDac(OptaController, 338 i, /* expansion module number */ 339 a, /* analog channel of expansion module */ 340 OA_VOLTAGE_DAC, /* DAC type */ 341 true, /* limit current */ 342 false, /* disable slew rate */ 343 OA_SLEW_RATE_0); 344 345 /* Expose analog inputs as readable OPC UA properties. */ 346 char analog_out_name[32] = {0}; 347 snprintf(analog_out_name, sizeof(analog_out_name), "Analog Output O%d", output_num); 348 exp_analog->addAnalogOutput( 349 opc_ua_server, 350 analog_out_name, 351 [i, a]() 352 { 353 return reinterpret_cast<AnalogExpansion *>(OptaController.getExpansionPtr(i))->pinVoltage(a); 354 }, 355 [i, a](float const voltage) 356 { 357 reinterpret_cast<AnalogExpansion *>(OptaController.getExpansionPtr(i))->pinVoltage(a, voltage); 358 }); 359 output_num++; 360 } 361 362 /* Configure PWM outputs. */ 363 int pwm_output_num = 1; 364 for (int p = OA_PWM_CH_FIRST; p <= OA_PWM_CH_LAST; p++) 365 { 366 char pwm_out_name[32] = {0}; 367 snprintf(pwm_out_name, sizeof(pwm_out_name), "PWM%d", pwm_output_num); 368 exp_analog->addPwmOutput( 369 opc_ua_server, 370 pwm_out_name, 371 [i, p](uint32_t const pwm_period_us, uint32_t const pwm_pulse_width_us) 372 { 373 reinterpret_cast<AnalogExpansion *>(OptaController.getExpansionPtr(i))->setPwm(p, pwm_period_us, pwm_pulse_width_us); 374 }, 375 [i, p](void) -> uint32_t 376 { 377 return reinterpret_cast<AnalogExpansion *>(OptaController.getExpansionPtr(i))->getPwmPeriod(p); 378 }, 379 [i, p](void) -> uint32_t 380 { 381 return reinterpret_cast<AnalogExpansion *>(OptaController.getExpansionPtr(i))->getPwmPulse(p); 382 }); 383 pwm_output_num++; 384 } 385 386 /* Configure controllable LEDs of analog expansion module. */ 387 for (int l = 0; l < OA_LED_NUM; l++) 388 { 389 char led_name[32] = {0}; 390 snprintf(led_name, sizeof(led_name), "LED%d", l + 1); 391 exp_analog->addLedOutput( 392 opc_ua_server, 393 led_name, 394 [i, l](bool const value) 395 { 396 AnalogExpansion * ana_exp_ptr = reinterpret_cast<AnalogExpansion *>(OptaController.getExpansionPtr(i)); 397 if (value) 398 ana_exp_ptr->switchLedOn(l); 399 else 400 ana_exp_ptr->switchLedOff(l); 401 }); 402 } 403 } 404 } 405 406#if USE_MODBUS_SENSOR_MD02 407 { 408 UA_StatusCode rc = UA_STATUSCODE_GOOD; 409 UA_ObjectAttributes oAttr = UA_ObjectAttributes_default; 410 oAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Modbus RS485 MD02 Sensor"); 411 UA_NodeId modbus_md02_node_id; 412 rc = UA_Server_addObjectNode(opc_ua_server, 413 UA_NODEID_NULL, 414 UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER), 415 UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES), 416 UA_QUALIFIEDNAME(1, "ModbusRs485Md02"), 417 UA_NODEID_NUMERIC(0, UA_NS0ID_BASEOBJECTTYPE), 418 oAttr, 419 NULL, 420 &modbus_md02_node_id); 421 if (UA_StatusCode_isBad(rc)) { 422 UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Modbus MD02 Sensor: UA_Server_addObjectNode(...) failed with %s", UA_StatusCode_name(rc)); 423 return; 424 } 425 426 UA_VariableAttributes temperature_value_attr = UA_VariableAttributes_default; 427 428 /* Obtain the current value of the input pin. */ 429 UA_Float temperature_value = 0.f; 430 UA_Variant_setScalar(&temperature_value_attr.value, &temperature_value, &UA_TYPES[UA_TYPES_FLOAT]); 431 432 temperature_value_attr.displayName = UA_LOCALIZEDTEXT("en-US", "Temperature / °C"); 433 temperature_value_attr.dataType = UA_TYPES[UA_TYPES_FLOAT].typeId; 434 temperature_value_attr.accessLevel = UA_ACCESSLEVELMASK_READ; 435 436 /* Add the variable node. */ 437 rc = UA_Server_addVariableNode(opc_ua_server, 438 UA_NODEID_NULL, 439 modbus_md02_node_id, 440 UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT), 441 UA_QUALIFIEDNAME(1, "md02_temperature_deg"), 442 UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), 443 temperature_value_attr, 444 NULL, 445 &modbus_md02_temperature_node_id); 446 if (UA_StatusCode_isBad(rc)) 447 { 448 UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Modbus MD02 Sensor: UA_Server_addVariableNode(...) failed with %s", UA_StatusCode_name(rc)); 449 return; 450 } 451 } 452#endif 453 454 /* Print some threading related message. */ 455 UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, 456 "stack: size = %d | free = %d | used = %d | max = %d", 457 opc_ua_server_thread.stack_size(), 458 opc_ua_server_thread.free_stack(), 459 opc_ua_server_thread.used_stack(), 460 opc_ua_server_thread.max_stack()); 461 462 /* Log some data concerning heap allocation. */ 463 UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, 464 "o1heap: capacity: %d | allocated: %d | peak_allocated: %d", 465 o1heapGetDiagnostics(o1heap_ins).capacity, 466 o1heapGetDiagnostics(o1heap_ins).allocated, 467 o1heapGetDiagnostics(o1heap_ins).peak_allocated); 468 469#if MBED_HEAP_STATS_ENABLED && MBED_MEM_TRACING_ENABLED && MBED_STACK_STATS_ENABLED 470 /* Print stack/heap memory information. For information how to enable it 471 * see https://os.mbed.com/blog/entry/Tracking-memory-usage-with-Mbed-OS/ 472 */ 473 size_t const num_thds = osThreadGetCount(); 474 mbed_stats_stack_t *stack_stats = (mbed_stats_stack_t *) malloc(num_thds * sizeof(mbed_stats_stack_t)); 475 mbed_stats_stack_get_each(stack_stats, num_thds); 476 477 mbed_stats_thread_t * thd_stats = (mbed_stats_thread_t *) malloc(num_thds * sizeof(mbed_stats_thread_t)); 478 mbed_stats_thread_get_each(thd_stats, num_thds); 479 480 for (int i = 0; i < num_thds; i++) 481 UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Thread: 0x%lX (\"%s\"), Stack size: %lu / %lu", 482 stack_stats[i].thread_id, thd_stats[i].name, stack_stats[i].max_size, stack_stats[i].reserved_size); 483 free(stack_stats); 484 free(thd_stats); 485 486 mbed_stats_heap_t heap_stats; 487 mbed_stats_heap_get(&heap_stats); 488 UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Heap size: %lu / %lu bytes", heap_stats.current_size, heap_stats.reserved_size); 489#endif 490 491 /* Run the server (until ctrl-c interrupt) */ 492 UA_StatusCode const status = UA_Server_runUntilInterrupt(opc_ua_server); 493 }); 494 495 pinMode(LED_BUILTIN, OUTPUT); 496} 497 498void loop() 499{ 500 /* Always call update as fast as possible */ 501 OptaController.update(); 502 503 /* Determine the number of expansion boards available and call update on them. */ 504 uint8_t opta_expansion_num = OptaController.getExpansionNum(); 505 if (opta_expansion_num > OPCUA_MAX_OPTA_EXPANSION_NUM) 506 opta_expansion_num = OPCUA_MAX_OPTA_EXPANSION_NUM; 507 /* Periodically call their respective update methods. */ 508 for(uint8_t i = 0; i < opta_expansion_num; i++) 509 { 510 ExpansionType_t const exp_type = OptaController.getExpansionType(i); 511 if (exp_type == EXPANSION_OPTA_DIGITAL_MEC) 512 reinterpret_cast<DigitalMechExpansion *>(OptaController.getExpansionPtr(i))->updateDigitalOutputs(); 513 else if (exp_type == EXPANSION_OPTA_DIGITAL_STS) 514 reinterpret_cast<DigitalStSolidExpansion *>(OptaController.getExpansionPtr(i))->updateDigitalOutputs(); 515 } 516 517 /* Toggle main LED signalling progress. */ 518 digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); 519 delay(500); 520 521#ifdef PRINT_IP 522 /* Periodically print OPC UA server IP and port. */ 523 static auto prev_ip_print = millis(); 524 auto const now = millis(); 525 if ((now - prev_ip_print) > 5000) 526 { 527 UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Arduino Opta IP: %s", Ethernet.localIP().toString().c_str()); 528 prev_ip_print = now; 529 } 530#endif 531 532#if USE_MODBUS_SENSOR_MD02 533 if (!ModbusRTUClient.requestFrom(MODBUS_DEVICE_ID, INPUT_REGISTERS, MODBUS_DEVICE_TEMPERATURE_REGISTER, 1)) { 534 //Serial.print("failed to read temperature register! "); 535 //Serial.println(ModbusRTUClient.lastError()); 536 return; 537 } 538 if (ModbusRTUClient.available()) 539 { 540 int16_t const temperature_raw = ModbusRTUClient.read(); 541 float const temperature_deg = temperature_raw / 10.2f; 542 //Serial.println(temperature_deg); 543 544 UA_Float temperature_deg_opcua_value = temperature_deg; 545 UA_Variant temperature_deg_opcua_variant; 546 UA_Variant_init(&temperature_deg_opcua_variant); 547 UA_Variant_setScalar(&temperature_deg_opcua_variant, &temperature_deg_opcua_value, &UA_TYPES[UA_TYPES_FLOAT]); 548 UA_Server_writeValue(opc_ua_server, modbus_md02_temperature_node_id, temperature_deg_opcua_variant); 549 } 550#endif 551}
node-red-opc-ua-client
json
Node-RED json file
1[ 2 { 3 "id": "b95c86e74dc9406f", 4 "type": "tab", 5 "label": "OPC-UA", 6 "disabled": false, 7 "info": "", 8 "env": [] 9 }, 10 { 11 "id": "a59f375690802a42", 12 "type": "group", 13 "z": "b95c86e74dc9406f", 14 "name": "", 15 "style": { 16 "label": true 17 }, 18 "nodes": [ 19 "c263a3a56c11f124", 20 "34bd45f333eab18a", 21 "0159ff1ef70e89d0", 22 "0aa25d33b342dc12" 23 ], 24 "x": 34, 25 "y": 559, 26 "w": 672, 27 "h": 109.5 28 }, 29 { 30 "id": "342ae34d18db20b0", 31 "type": "group", 32 "z": "b95c86e74dc9406f", 33 "style": { 34 "stroke": "#999999", 35 "stroke-opacity": "1", 36 "fill": "none", 37 "fill-opacity": "1", 38 "label": true, 39 "label-position": "nw", 40 "color": "#a4a4a4" 41 }, 42 "nodes": [ 43 "596388f0f9e091d7", 44 "fdaace6d400fbf2f", 45 "f0bd180120ee3da5", 46 "ba4e2ae132449f6a", 47 "74ee90883bd52478", 48 "47953c967cba1a58" 49 ], 50 "x": 34, 51 "y": 379, 52 "w": 912, 53 "h": 149.5 54 }, 55 { 56 "id": "05eff83989f131ef", 57 "type": "group", 58 "z": "b95c86e74dc9406f", 59 "style": { 60 "stroke": "#999999", 61 "stroke-opacity": "1", 62 "fill": "none", 63 "fill-opacity": "1", 64 "label": true, 65 "label-position": "nw", 66 "color": "#a4a4a4" 67 }, 68 "nodes": [ 69 "8670d0d7b6e4b283", 70 "e3f59a3f940bde16", 71 "4f1fece8263783c6", 72 "72a6f1e340bd6e70", 73 "5cdf81538e141392", 74 "4eee15670b45ec36", 75 "6c5d4087a7fa0d95", 76 "93c85f3170000e84", 77 "b32795b75ab20e14", 78 "f0394a7ea192fd2c", 79 "1971a4ae7cc767cf" 80 ], 81 "x": 34, 82 "y": 31.5, 83 "w": 652, 84 "h": 329.5 85 }, 86 { 87 "id": "8670d0d7b6e4b283", 88 "type": "OpcUa-Item", 89 "z": "b95c86e74dc9406f", 90 "g": "05eff83989f131ef", 91 "item": "ns=0;i=50077.R1", 92 "datatype": "Boolean", 93 "value": "", 94 "name": "R1", 95 "x": 290, 96 "y": 80, 97 "wires": [ 98 [ 99 "e3f59a3f940bde16" 100 ] 101 ] 102 }, 103 { 104 "id": "e3f59a3f940bde16", 105 "type": "OpcUa-Client", 106 "z": "b95c86e74dc9406f", 107 "g": "05eff83989f131ef", 108 "endpoint": "fd11cd9b5d90099e", 109 "action": "write", 110 "deadbandtype": "a", 111 "deadbandvalue": 1, 112 "time": 10, 113 "timeUnit": "s", 114 "certificate": "n", 115 "localfile": "", 116 "localkeyfile": "", 117 "securitymode": "None", 118 "securitypolicy": "None", 119 "useTransport": false, 120 "maxChunkCount": 1, 121 "maxMessageSize": 8192, 122 "receiveBufferSize": 8192, 123 "sendBufferSize": 8192, 124 "setstatusandtime": false, 125 "keepsessionalive": true, 126 "name": "", 127 "x": 580, 128 "y": 80, 129 "wires": [ 130 [], 131 [], 132 [] 133 ] 134 }, 135 { 136 "id": "4f1fece8263783c6", 137 "type": "OpcUa-Item", 138 "z": "b95c86e74dc9406f", 139 "g": "05eff83989f131ef", 140 "item": "ns=0;i=50078.R2", 141 "datatype": "Boolean", 142 "value": "", 143 "name": "R2", 144 "x": 290, 145 "y": 140, 146 "wires": [ 147 [ 148 "e3f59a3f940bde16" 149 ] 150 ] 151 }, 152 { 153 "id": "72a6f1e340bd6e70", 154 "type": "OpcUa-Item", 155 "z": "b95c86e74dc9406f", 156 "g": "05eff83989f131ef", 157 "item": "ns=0;i=50079.R3", 158 "datatype": "Boolean", 159 "value": "", 160 "name": "R3", 161 "x": 290, 162 "y": 200, 163 "wires": [ 164 [ 165 "e3f59a3f940bde16" 166 ] 167 ] 168 }, 169 { 170 "id": "5cdf81538e141392", 171 "type": "OpcUa-Item", 172 "z": "b95c86e74dc9406f", 173 "g": "05eff83989f131ef", 174 "item": "ns=0;i=50080.R4", 175 "datatype": "Boolean", 176 "value": "", 177 "name": "R4", 178 "x": 290, 179 "y": 260, 180 "wires": [ 181 [ 182 "e3f59a3f940bde16" 183 ] 184 ] 185 }, 186 { 187 "id": "4eee15670b45ec36", 188 "type": "ui_switch", 189 "z": "b95c86e74dc9406f", 190 "g": "05eff83989f131ef", 191 "name": "", 192 "label": "Relay 1", 193 "tooltip": "", 194 "group": "85144921.8931b8", 195 "order": 1, 196 "width": 0, 197 "height": 0, 198 "passthru": true, 199 "decouple": "false", 200 "topic": "0", 201 "topicType": "str", 202 "style": "", 203 "onvalue": "true", 204 "onvalueType": "bool", 205 "onicon": "", 206 "oncolor": "", 207 "offvalue": "false", 208 "offvalueType": "bool", 209 "officon": "", 210 "offcolor": "", 211 "animate": false, 212 "className": "", 213 "x": 120, 214 "y": 80, 215 "wires": [ 216 [ 217 "8670d0d7b6e4b283" 218 ] 219 ] 220 }, 221 { 222 "id": "6c5d4087a7fa0d95", 223 "type": "ui_switch", 224 "z": "b95c86e74dc9406f", 225 "g": "05eff83989f131ef", 226 "name": "", 227 "label": "Relay 2", 228 "tooltip": "", 229 "group": "85144921.8931b8", 230 "order": 2, 231 "width": 0, 232 "height": 0, 233 "passthru": true, 234 "decouple": "false", 235 "topic": "0", 236 "topicType": "str", 237 "style": "", 238 "onvalue": "true", 239 "onvalueType": "bool", 240 "onicon": "", 241 "oncolor": "", 242 "offvalue": "false", 243 "offvalueType": "bool", 244 "officon": "", 245 "offcolor": "", 246 "animate": false, 247 "className": "", 248 "x": 120, 249 "y": 140, 250 "wires": [ 251 [ 252 "4f1fece8263783c6" 253 ] 254 ] 255 }, 256 { 257 "id": "93c85f3170000e84", 258 "type": "ui_switch", 259 "z": "b95c86e74dc9406f", 260 "g": "05eff83989f131ef", 261 "name": "", 262 "label": "Relay 3", 263 "tooltip": "", 264 "group": "85144921.8931b8", 265 "order": 3, 266 "width": 0, 267 "height": 0, 268 "passthru": true, 269 "decouple": "false", 270 "topic": "0", 271 "topicType": "str", 272 "style": "", 273 "onvalue": "true", 274 "onvalueType": "bool", 275 "onicon": "", 276 "oncolor": "", 277 "offvalue": "false", 278 "offvalueType": "bool", 279 "officon": "", 280 "offcolor": "", 281 "animate": false, 282 "className": "", 283 "x": 120, 284 "y": 200, 285 "wires": [ 286 [ 287 "72a6f1e340bd6e70" 288 ] 289 ] 290 }, 291 { 292 "id": "b32795b75ab20e14", 293 "type": "ui_switch", 294 "z": "b95c86e74dc9406f", 295 "g": "05eff83989f131ef", 296 "name": "", 297 "label": "Relay 4", 298 "tooltip": "", 299 "group": "85144921.8931b8", 300 "order": 4, 301 "width": 0, 302 "height": 0, 303 "passthru": true, 304 "decouple": "false", 305 "topic": "0", 306 "topicType": "str", 307 "style": "", 308 "onvalue": "true", 309 "onvalueType": "bool", 310 "onicon": "", 311 "oncolor": "", 312 "offvalue": "false", 313 "offvalueType": "bool", 314 "officon": "", 315 "offcolor": "", 316 "animate": false, 317 "className": "", 318 "x": 120, 319 "y": 260, 320 "wires": [ 321 [ 322 "5cdf81538e141392" 323 ] 324 ] 325 }, 326 { 327 "id": "f0394a7ea192fd2c", 328 "type": "OpcUa-Item", 329 "z": "b95c86e74dc9406f", 330 "g": "05eff83989f131ef", 331 "item": "ns=0;i=50081.UL", 332 "datatype": "Boolean", 333 "value": "", 334 "name": "UL", 335 "x": 290, 336 "y": 320, 337 "wires": [ 338 [ 339 "e3f59a3f940bde16" 340 ] 341 ] 342 }, 343 { 344 "id": "1971a4ae7cc767cf", 345 "type": "ui_switch", 346 "z": "b95c86e74dc9406f", 347 "g": "05eff83989f131ef", 348 "name": "", 349 "label": "User LED", 350 "tooltip": "", 351 "group": "f8b97d935f93f052", 352 "order": 1, 353 "width": 0, 354 "height": 0, 355 "passthru": true, 356 "decouple": "false", 357 "topic": "0", 358 "topicType": "str", 359 "style": "", 360 "onvalue": "true", 361 "onvalueType": "bool", 362 "onicon": "", 363 "oncolor": "", 364 "offvalue": "false", 365 "offvalueType": "bool", 366 "officon": "", 367 "offcolor": "", 368 "animate": false, 369 "className": "", 370 "x": 120, 371 "y": 320, 372 "wires": [ 373 [ 374 "f0394a7ea192fd2c" 375 ] 376 ] 377 }, 378 { 379 "id": "596388f0f9e091d7", 380 "type": "inject", 381 "z": "b95c86e74dc9406f", 382 "g": "342ae34d18db20b0", 383 "name": "", 384 "props": [ 385 { 386 "p": "payload" 387 }, 388 { 389 "p": "topic", 390 "vt": "str" 391 } 392 ], 393 "repeat": "1", 394 "crontab": "", 395 "once": false, 396 "onceDelay": 0.1, 397 "topic": "0", 398 "payload": "true", 399 "payloadType": "bool", 400 "x": 140, 401 "y": 480, 402 "wires": [ 403 [ 404 "f0bd180120ee3da5" 405 ] 406 ] 407 }, 408 { 409 "id": "fdaace6d400fbf2f", 410 "type": "OpcUa-Client", 411 "z": "b95c86e74dc9406f", 412 "g": "342ae34d18db20b0", 413 "endpoint": "fd11cd9b5d90099e", 414 "action": "read", 415 "deadbandtype": "a", 416 "deadbandvalue": 1, 417 "time": 10, 418 "timeUnit": "s", 419 "certificate": "n", 420 "localfile": "", 421 "localkeyfile": "", 422 "securitymode": "None", 423 "securitypolicy": "None", 424 "useTransport": false, 425 "maxChunkCount": 1, 426 "maxMessageSize": 8192, 427 "receiveBufferSize": 8192, 428 "sendBufferSize": 8192, 429 "setstatusandtime": false, 430 "keepsessionalive": true, 431 "name": "", 432 "x": 460, 433 "y": 480, 434 "wires": [ 435 [ 436 "47953c967cba1a58" 437 ], 438 [], 439 [] 440 ] 441 }, 442 { 443 "id": "f0bd180120ee3da5", 444 "type": "OpcUa-Item", 445 "z": "b95c86e74dc9406f", 446 "g": "342ae34d18db20b0", 447 "item": "ns=0;i=50083.Temp", 448 "datatype": "Boolean", 449 "value": "", 450 "name": "Temp", 451 "x": 290, 452 "y": 480, 453 "wires": [ 454 [ 455 "fdaace6d400fbf2f" 456 ] 457 ] 458 }, 459 { 460 "id": "ba4e2ae132449f6a", 461 "type": "ui_gauge", 462 "z": "b95c86e74dc9406f", 463 "g": "342ae34d18db20b0", 464 "name": "", 465 "group": "ba0d483bad0227fa", 466 "order": 0, 467 "width": 0, 468 "height": 0, 469 "gtype": "gage", 470 "title": "Temperature", 471 "label": "°C", 472 "format": "{{value}}", 473 "min": 0, 474 "max": "70", 475 "colors": [ 476 "#00b500", 477 "#e6e600", 478 "#ca3838" 479 ], 480 "seg1": "", 481 "seg2": "", 482 "diff": false, 483 "className": "", 484 "x": 810, 485 "y": 480, 486 "wires": [] 487 }, 488 { 489 "id": "c263a3a56c11f124", 490 "type": "inject", 491 "z": "b95c86e74dc9406f", 492 "g": "a59f375690802a42", 493 "name": "", 494 "props": [ 495 { 496 "p": "payload" 497 }, 498 { 499 "p": "topic", 500 "vt": "str" 501 } 502 ], 503 "repeat": "1", 504 "crontab": "", 505 "once": false, 506 "onceDelay": 0.1, 507 "topic": "0", 508 "payload": "true", 509 "payloadType": "bool", 510 "x": 140, 511 "y": 620, 512 "wires": [ 513 [ 514 "0159ff1ef70e89d0" 515 ] 516 ] 517 }, 518 { 519 "id": "34bd45f333eab18a", 520 "type": "OpcUa-Client", 521 "z": "b95c86e74dc9406f", 522 "g": "a59f375690802a42", 523 "endpoint": "fd11cd9b5d90099e", 524 "action": "read", 525 "deadbandtype": "a", 526 "deadbandvalue": 1, 527 "time": 10, 528 "timeUnit": "s", 529 "certificate": "n", 530 "localfile": "", 531 "localkeyfile": "", 532 "securitymode": "None", 533 "securitypolicy": "None", 534 "useTransport": false, 535 "maxChunkCount": 1, 536 "maxMessageSize": 8192, 537 "receiveBufferSize": 8192, 538 "sendBufferSize": 8192, 539 "setstatusandtime": false, 540 "keepsessionalive": true, 541 "name": "", 542 "x": 460, 543 "y": 620, 544 "wires": [ 545 [ 546 "0aa25d33b342dc12" 547 ], 548 [], 549 [] 550 ] 551 }, 552 { 553 "id": "0159ff1ef70e89d0", 554 "type": "OpcUa-Item", 555 "z": "b95c86e74dc9406f", 556 "g": "a59f375690802a42", 557 "item": "ns=0;i=50069.Input", 558 "datatype": "Boolean", 559 "value": "", 560 "name": "Input", 561 "x": 290, 562 "y": 620, 563 "wires": [ 564 [ 565 "34bd45f333eab18a" 566 ] 567 ] 568 }, 569 { 570 "id": "0aa25d33b342dc12", 571 "type": "ui_led", 572 "z": "b95c86e74dc9406f", 573 "g": "a59f375690802a42", 574 "order": 0, 575 "group": "bcd73c30755604c7", 576 "width": 0, 577 "height": 0, 578 "label": "Input1", 579 "labelPlacement": "left", 580 "labelAlignment": "left", 581 "colorForValue": [ 582 { 583 "color": "#ff0000", 584 "value": "false", 585 "valueType": "bool" 586 }, 587 { 588 "color": "#008000", 589 "value": "true", 590 "valueType": "bool" 591 } 592 ], 593 "allowColorForValueInMessage": false, 594 "shape": "circle", 595 "showGlow": true, 596 "name": "", 597 "x": 630, 598 "y": 600, 599 "wires": [] 600 }, 601 { 602 "id": "74ee90883bd52478", 603 "type": "ui_chart", 604 "z": "b95c86e74dc9406f", 605 "g": "342ae34d18db20b0", 606 "name": "", 607 "group": "ba0d483bad0227fa", 608 "order": 1, 609 "width": 0, 610 "height": 0, 611 "label": "Temperature Chart", 612 "chartType": "line", 613 "legend": "false", 614 "xformat": "HH:mm:ss", 615 "interpolate": "linear", 616 "nodata": "", 617 "dot": false, 618 "ymin": "", 619 "ymax": "", 620 "removeOlder": 1, 621 "removeOlderPoints": "", 622 "removeOlderUnit": "3600", 623 "cutout": 0, 624 "useOneColor": false, 625 "useUTC": false, 626 "colors": [ 627 "#1f77b4", 628 "#aec7e8", 629 "#ff7f0e", 630 "#2ca02c", 631 "#98df8a", 632 "#d62728", 633 "#ff9896", 634 "#9467bd", 635 "#c5b0d5" 636 ], 637 "outputs": 1, 638 "useDifferentColor": false, 639 "className": "", 640 "x": 830, 641 "y": 420, 642 "wires": [ 643 [] 644 ] 645 }, 646 { 647 "id": "47953c967cba1a58", 648 "type": "function", 649 "z": "b95c86e74dc9406f", 650 "g": "342ae34d18db20b0", 651 "name": "Round", 652 "func": "msg.payload = Number(msg.payload.toFixed(2))\nreturn msg;", 653 "outputs": 1, 654 "timeout": 0, 655 "noerr": 0, 656 "initialize": "", 657 "finalize": "", 658 "libs": [], 659 "x": 630, 660 "y": 460, 661 "wires": [ 662 [ 663 "74ee90883bd52478", 664 "ba4e2ae132449f6a" 665 ] 666 ] 667 }, 668 { 669 "id": "fd11cd9b5d90099e", 670 "type": "OpcUa-Endpoint", 671 "endpoint": "opc.tcp://192.168.1.42:4840", 672 "secpol": "None", 673 "secmode": "None", 674 "none": true, 675 "login": false, 676 "usercert": false, 677 "usercertificate": "", 678 "userprivatekey": "" 679 }, 680 { 681 "id": "85144921.8931b8", 682 "type": "ui_group", 683 "name": "OPTA Outputs", 684 "tab": "440c46c6.769ab8", 685 "order": 2, 686 "disp": true, 687 "width": "6", 688 "collapse": false, 689 "className": "" 690 }, 691 { 692 "id": "f8b97d935f93f052", 693 "type": "ui_group", 694 "name": "OPTA LED", 695 "tab": "440c46c6.769ab8", 696 "order": 3, 697 "disp": true, 698 "width": "6", 699 "collapse": false, 700 "className": "" 701 }, 702 { 703 "id": "ba0d483bad0227fa", 704 "type": "ui_group", 705 "name": "MD02 Sensor", 706 "tab": "440c46c6.769ab8", 707 "order": 5, 708 "disp": true, 709 "width": "6", 710 "collapse": false, 711 "className": "" 712 }, 713 { 714 "id": "bcd73c30755604c7", 715 "type": "ui_group", 716 "name": "OPTA Inputs", 717 "tab": "440c46c6.769ab8", 718 "order": 4, 719 "disp": true, 720 "width": "6", 721 "collapse": false, 722 "className": "" 723 }, 724 { 725 "id": "440c46c6.769ab8", 726 "type": "ui_tab", 727 "name": "Home", 728 "icon": "dashboard", 729 "disabled": false, 730 "hidden": false 731 } 732]
Comments
Only logged in users can leave comments