This shows you the differences between two versions of the page.
|
iothings:laboratoare:2025:lab4 [2025/10/21 11:36] dan.tudose [CoAP Server] |
iothings:laboratoare:2025:lab4 [2025/10/28 11:29] (current) dan.tudose [CoAP Server] |
||
|---|---|---|---|
| Line 80: | Line 80: | ||
| On the CoAP side, the library handles all the protocol details—message type, message ID, token management, options, and parsing—so your sketch only deals with high-level actions. You issue a client request with coap.get(remoteIP, 5683, ""), and when the server replies, the callback receives a parsed CoapPacket. The code prints the CoAP code (e.g., 2.xx success), basic header info, and the payload as text. | On the CoAP side, the library handles all the protocol details—message type, message ID, token management, options, and parsing—so your sketch only deals with high-level actions. You issue a client request with coap.get(remoteIP, 5683, ""), and when the server replies, the callback receives a parsed CoapPacket. The code prints the CoAP code (e.g., 2.xx success), basic header info, and the payload as text. | ||
| - | + | [[iothings:laboratoare:2025_code:lab4_1|Click here to get the code example]] | |
| - | <code C main.cpp> | + | |
| - | #include <Arduino.h> | + | |
| - | #include <WiFi.h> | + | |
| - | #include <WiFiUdp.h> | + | |
| - | #include <coap-simple.h> // hirotakaster/CoAP simple library | + | |
| - | + | ||
| - | // ===== Wi-Fi creds ===== | + | |
| - | const char* WIFI_SSID = "UPB-Guest"; | + | |
| - | const char* WIFI_PASS = ""; | + | |
| - | + | ||
| - | // ===== CoAP server (Californium demo) ===== | + | |
| - | const char* COAP_HOST = "californium.eclipseprojects.io"; | + | |
| - | const uint16_t COAP_PORT = 5683; // RFC 7252 default port | + | |
| - | + | ||
| - | // UDP + CoAP objects | + | |
| - | WiFiUDP udp; | + | |
| - | Coap coap(udp); | + | |
| - | + | ||
| - | // ----- Response callback: prints payload as text ----- | + | |
| - | void onCoapResponse(CoapPacket &packet, IPAddress ip, int port) { | + | |
| - | // Copy payload into a printable buffer | + | |
| - | const size_t n = packet.payloadlen; | + | |
| - | String from = ip.toString() + ":" + String(port); | + | |
| - | Serial.printf("[CoAP] Response from %s code=%u (0x%02X) len=%u\n", | + | |
| - | from.c_str(), (packet.code >> 5), packet.code, (unsigned)n); | + | |
| - | + | ||
| - | if (n > 0) { | + | |
| - | // Make it printable; not all payloads are ASCII | + | |
| - | size_t m = n; | + | |
| - | if (m > 1023) m = 1023; // clamp for serial | + | |
| - | char buf[1024]; | + | |
| - | memcpy(buf, packet.payload, m); | + | |
| - | buf[m] = '\0'; | + | |
| - | Serial.println(F("[CoAP Payload]")); | + | |
| - | Serial.println(buf); | + | |
| - | } | + | |
| - | } | + | |
| - | + | ||
| - | // Re-resolves hostname and sends GET to "/" | + | |
| - | bool coapGetRoot() { | + | |
| - | IPAddress remote; | + | |
| - | if (!WiFi.hostByName(COAP_HOST, remote)) { | + | |
| - | Serial.println(F("[CoAP] DNS failed")); | + | |
| - | return false; | + | |
| - | } | + | |
| - | Serial.printf("[CoAP] GET coap://%s:%u/\n", COAP_HOST, COAP_PORT); | + | |
| - | // Path is "" for "/", per library examples | + | |
| - | int msgid = coap.get(remote, COAP_PORT, ""); | + | |
| - | (void)msgid; // not used further in this demo | + | |
| - | return true; | + | |
| - | } | + | |
| - | + | ||
| - | unsigned long lastGetMs = 0; | + | |
| - | const unsigned long GET_PERIOD_MS = 10000; | + | |
| - | + | ||
| - | void setup() { | + | |
| - | Serial.begin(115200); | + | |
| - | delay(200); | + | |
| - | Serial.println(F("\n== CoAP client (Californium demo) ==")); | + | |
| - | + | ||
| - | // Wi-Fi | + | |
| - | WiFi.mode(WIFI_STA); | + | |
| - | WiFi.begin(WIFI_SSID, WIFI_PASS); | + | |
| - | Serial.print(F("Connecting")); | + | |
| - | while (WiFi.status() != WL_CONNECTED) { delay(300); Serial.print('.'); } | + | |
| - | Serial.println(); | + | |
| - | Serial.print(F("Wi-Fi OK. IP: ")); Serial.println(WiFi.localIP()); | + | |
| - | + | ||
| - | // Bind UDP so we can receive responses (use standard CoAP port or 0 for ephemeral) | + | |
| - | udp.begin(5683); | + | |
| - | + | ||
| - | // Register response handler and start CoAP | + | |
| - | coap.response(onCoapResponse); | + | |
| - | coap.start(); | + | |
| - | + | ||
| - | // First GET right away | + | |
| - | coapGetRoot(); | + | |
| - | lastGetMs = millis(); | + | |
| - | } | + | |
| - | + | ||
| - | void loop() { | + | |
| - | // Let the library process incoming UDP (and fire onCoapResponse) | + | |
| - | coap.loop(); | + | |
| - | + | ||
| - | // Periodic GET | + | |
| - | unsigned long now = millis(); | + | |
| - | if (now - lastGetMs >= GET_PERIOD_MS) { | + | |
| - | lastGetMs = now; | + | |
| - | coapGetRoot(); | + | |
| - | } | + | |
| - | } | + | |
| - | + | ||
| - | </code> | + | |
| After building, you should get a similar print-out in the console: | After building, you should get a similar print-out in the console: | ||
| Line 216: | Line 123: | ||
| </note> | </note> | ||
| - | <code C main.cpp> | + | [[iothings:laboratoare:2025_code:lab4_2|Click here to get the code example.]] |
| - | #include <Arduino.h> | + | |
| - | #include <WiFi.h> | + | |
| - | #include <WiFiUdp.h> | + | |
| - | #include <Adafruit_NeoPixel.h> | + | |
| - | #include <coap-simple.h> // hirotakaster CoAP simple library | + | |
| - | + | ||
| - | // ===== Wi-Fi creds ===== | + | |
| - | //const char* WIFI_SSID = "UPB-Guest"; | + | |
| - | //const char* WIFI_PASS = ""; | + | |
| - | // ===== UDP + CoAP objects ===== | ||
| - | WiFiUDP udp; | ||
| - | Coap coap(udp, 256); | ||
| - | |||
| - | // ===== On-board NeoPixel (GPIO 3) ===== | ||
| - | constexpr uint8_t kNeoPixelPin = 3; | ||
| - | constexpr uint8_t kNeoPixelCount = 1; | ||
| - | constexpr uint8_t kNeoPixelBrightness = 128; // 50% to limit power draw | ||
| - | |||
| - | Adafruit_NeoPixel g_pixel(kNeoPixelCount, kNeoPixelPin, NEO_GRB + NEO_KHZ800); | ||
| - | bool g_pixelReady = false; | ||
| - | |||
| - | // ===== Simple LED state ===== | ||
| - | static bool g_ledOn = false; | ||
| - | |||
| - | void applyLedState() { | ||
| - | if (!g_pixelReady) { | ||
| - | return; | ||
| - | } | ||
| - | if (g_ledOn) { | ||
| - | g_pixel.setPixelColor(0, g_pixel.Color(255, 255, 255)); | ||
| - | } else { | ||
| - | g_pixel.setPixelColor(0, 0, 0, 0); | ||
| - | } | ||
| - | g_pixel.show(); | ||
| - | } | ||
| - | |||
| - | // ----- helper: send text/plain 2.05 Content ----- | ||
| - | static void coapSendText(IPAddress ip, int port, const CoapPacket& req, | ||
| - | const char* text) { | ||
| - | Serial.printf("[CoAP] Replying to %s:%d mid=%u code=%u tokensz=%u\n", | ||
| - | ip.toString().c_str(), port, req.messageid, req.code, req.tokenlen); | ||
| - | coap.sendResponse( | ||
| - | ip, port, req.messageid, | ||
| - | text, strlen(text), | ||
| - | COAP_CONTENT, // 2.05 Content | ||
| - | COAP_TEXT_PLAIN, | ||
| - | req.token, req.tokenlen | ||
| - | ); | ||
| - | } | ||
| - | |||
| - | // ----- /sys/uptime (GET) ----- | ||
| - | void h_sys_uptime(CoapPacket &packet, IPAddress ip, int port) { | ||
| - | unsigned long secs = millis() / 1000UL; | ||
| - | char buf[32]; | ||
| - | snprintf(buf, sizeof(buf), "%lu", (unsigned long)secs); | ||
| - | coapSendText(ip, port, packet, buf); | ||
| - | } | ||
| - | |||
| - | // ----- /net/rssi (GET) ----- | ||
| - | void h_net_rssi(CoapPacket &packet, IPAddress ip, int port) { | ||
| - | long rssi = WiFi.RSSI(); | ||
| - | char buf[16]; | ||
| - | snprintf(buf, sizeof(buf), "%ld", rssi); | ||
| - | coapSendText(ip, port, packet, buf); | ||
| - | } | ||
| - | |||
| - | // ----- /led (GET state, PUT/POST "on" or "off") ----- | ||
| - | void h_led(CoapPacket &packet, IPAddress ip, int port) { | ||
| - | if (packet.code == COAP_GET) { | ||
| - | coapSendText(ip, port, packet, g_ledOn ? "on" : "off"); | ||
| - | return; | ||
| - | } | ||
| - | |||
| - | if (packet.code == COAP_PUT || packet.code == COAP_POST) { | ||
| - | String body; | ||
| - | body.reserve(packet.payloadlen + 1); | ||
| - | for (size_t i = 0; i < packet.payloadlen; ++i) body += (char)packet.payload[i]; | ||
| - | body.trim(); | ||
| - | if (body.equalsIgnoreCase("on")) g_ledOn = true; | ||
| - | if (body.equalsIgnoreCase("off")) g_ledOn = false; | ||
| - | |||
| - | applyLedState(); | ||
| - | |||
| - | // Acknowledge change (2.04 Changed) and return new state | ||
| - | const char* state = g_ledOn ? "on" : "off"; | ||
| - | coap.sendResponse( | ||
| - | ip, port, packet.messageid, | ||
| - | state, strlen(state), | ||
| - | COAP_CHANGED, // 2.04 Changed | ||
| - | COAP_TEXT_PLAIN, | ||
| - | packet.token, packet.tokenlen | ||
| - | ); | ||
| - | return; | ||
| - | } | ||
| - | |||
| - | // Method not allowed | ||
| - | coap.sendResponse( | ||
| - | ip, port, packet.messageid, | ||
| - | "Method Not Allowed", 18, | ||
| - | COAP_METHOD_NOT_ALLOWD, | ||
| - | COAP_TEXT_PLAIN, | ||
| - | packet.token, packet.tokenlen | ||
| - | ); | ||
| - | } | ||
| - | |||
| - | // ----- /echo (POST/PUT echoes payload; GET returns hint) ----- | ||
| - | void h_echo(CoapPacket &packet, IPAddress ip, int port) { | ||
| - | if (packet.payloadlen == 0 || packet.code == COAP_GET) { | ||
| - | const char* hint = "POST/PUT a payload and I'll echo it."; | ||
| - | coapSendText(ip, port, packet, hint); | ||
| - | return; | ||
| - | } | ||
| - | coap.sendResponse( | ||
| - | ip, port, packet.messageid, | ||
| - | (const char*)packet.payload, packet.payloadlen, | ||
| - | COAP_CONTENT, // 2.05 Content | ||
| - | COAP_TEXT_PLAIN, | ||
| - | packet.token, packet.tokenlen | ||
| - | ); | ||
| - | } | ||
| - | |||
| - | // ----- /.well-known/core (RFC 6690 CoRE Link Format) ----- | ||
| - | void h_wkc(CoapPacket &packet, IPAddress ip, int port) { | ||
| - | Serial.printf("[CoAP] Discovery hit from %s:%d type=%u code=%u accept=%s\n", | ||
| - | ip.toString().c_str(), | ||
| - | port, | ||
| - | packet.type, | ||
| - | packet.code, | ||
| - | packet.optionnum ? "yes" : "no"); | ||
| - | // Advertise resources and basic attributes | ||
| - | const char* linkfmt = | ||
| - | "</sys/uptime>;rt=\"sys.uptime\";if=\"core.a\";ct=0," | ||
| - | "</net/rssi>;rt=\"net.rssi\";if=\"core.a\";ct=0," | ||
| - | "</led>;rt=\"dev.led\";if=\"core.a\";ct=0," | ||
| - | "</echo>;rt=\"util.echo\";if=\"core.p\";ct=0"; | ||
| - | |||
| - | coap.sendResponse( | ||
| - | ip, port, packet.messageid, | ||
| - | linkfmt, strlen(linkfmt), | ||
| - | COAP_CONTENT, | ||
| - | COAP_APPLICATION_LINK_FORMAT, // ct=40 | ||
| - | packet.token, packet.tokenlen | ||
| - | ); | ||
| - | } | ||
| - | |||
| - | void setup() { | ||
| - | Serial.begin(115200); | ||
| - | delay(200); | ||
| - | Serial.println("\n== CoAP RESTful mapping & discovery (server-only) =="); | ||
| - | |||
| - | // Wi-Fi | ||
| - | WiFi.mode(WIFI_STA); | ||
| - | WiFi.begin(WIFI_SSID, WIFI_PASS); | ||
| - | Serial.print("Connecting"); | ||
| - | while (WiFi.status() != WL_CONNECTED) { delay(300); Serial.print("."); } | ||
| - | Serial.println(); | ||
| - | Serial.print("Wi-Fi OK. IP: "); Serial.println(WiFi.localIP()); | ||
| - | Serial.printf("Listening for CoAP on udp/%d\n", COAP_DEFAULT_PORT); | ||
| - | |||
| - | g_pixel.begin(); | ||
| - | g_pixel.setBrightness(kNeoPixelBrightness); | ||
| - | g_pixel.clear(); | ||
| - | g_pixel.show(); | ||
| - | g_pixelReady = true; | ||
| - | applyLedState(); | ||
| - | |||
| - | // Start CoAP server on udp/5683 | ||
| - | udp.begin(5683); | ||
| - | |||
| - | // Register resources | ||
| - | coap.server(h_sys_uptime, "sys/uptime"); | ||
| - | coap.server(h_net_rssi, "net/rssi"); | ||
| - | coap.server(h_led, "led"); | ||
| - | coap.server(h_echo, "echo"); | ||
| - | coap.server(h_wkc, ".well-known/core"); // discovery | ||
| - | |||
| - | // Start CoAP stack | ||
| - | coap.start(); | ||
| - | } | ||
| - | |||
| - | void loop() { | ||
| - | // Process incoming CoAP requests | ||
| - | coap.loop(); | ||
| - | } | ||
| - | |||
| - | </code> | ||
| Test it from your computer with the following commands, where esp-ip is the IP the node prints after connecting in the serial terminal: | Test it from your computer with the following commands, where esp-ip is the IP the node prints after connecting in the serial terminal: | ||
| Line 426: | Line 147: | ||
| <note>**Assignment 2:** | <note>**Assignment 2:** | ||
| - | Build a web page that acts as a GUI for your coap-client queries (e.g. you can do discovery, get and put/post from the page. | + | Build a web page that acts as a GUI for your coap-client queries (e.g. you can do discovery, get and put/post from the page. You will probably need a proxy to translate coap datagrams to WS or HTTP. |
| </note> | </note> | ||
| + | |||
| <hidden> | <hidden> | ||