From 2471cb331b44c4a300a038b02c08e4fa2dd04fc8 Mon Sep 17 00:00:00 2001 From: Tristan Imken Date: Sun, 24 Apr 2022 11:02:54 +0200 Subject: [PATCH 1/4] Feature Control the LEDs via COAP --- Nodes/LED_control/.DS_Store | Bin 0 -> 6148 bytes Nodes/LED_control/Makefile | 50 +++++ Nodes/LED_control/README.md | 321 ++++++++++++++++++++++++++++++ Nodes/LED_control/client.c | 236 ++++++++++++++++++++++ Nodes/LED_control/diagram.jpg | Bin 0 -> 15994 bytes Nodes/LED_control/gcoap_example.h | 50 +++++ Nodes/LED_control/main.c | 34 ++++ Nodes/LED_control/server.c | 91 +++++++++ 8 files changed, 782 insertions(+) create mode 100644 Nodes/LED_control/.DS_Store create mode 100644 Nodes/LED_control/Makefile create mode 100644 Nodes/LED_control/README.md create mode 100644 Nodes/LED_control/client.c create mode 100644 Nodes/LED_control/diagram.jpg create mode 100644 Nodes/LED_control/gcoap_example.h create mode 100644 Nodes/LED_control/main.c create mode 100644 Nodes/LED_control/server.c diff --git a/Nodes/LED_control/.DS_Store b/Nodes/LED_control/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..957845c803cd29ad5395978befb6f3631d1bd210 GIT binary patch literal 6148 zcmeHK%TB{E5FD2ZQuRQOT#)hwmH2~DwFeGd-~*sdN_nIYm4`Uy&W~_F;uFj4+O$au zS5%>0$vcj-vm1}2I0oQ)&)Esk0T8hXHd=H)nB14PWsNX$Ky;d8h9`89p5^m&E7}Hj zQ2|-I2q|*hVT{uHjq>@3_vFPmW$Uig*aOBH@hRqH zEO5nIugYDp@|YQJt88~%WAC%hkX2{j@7Jc1d#lApIA`nw^WKob{MFn#pO4afTDZV~ zqkZxQ%s)j|W$4qqd&X+TY7ripeTf(K;f>!s5oGS=3cNHk1xx``V22doo-G!&J(@QK zOaW71t$=(VVm84fVBt|e9dvpGAT~Ly#=iVA3MUSj1S~xA4$XNg(NkSGVmMFdcpT!A zfQ3g-hYN=f7b?4OLUCH1`C|=-OFWu41x$ge0(<_lBm4j0_w#?1WLKtuDe$ipaLxW@ zzsD(sy>)AHve(9J*KA@ES9n}g*wL++v9c8(v8i!9mJTrqSa{?J&3*(_26LvspDOSL DVD)XX literal 0 HcmV?d00001 diff --git a/Nodes/LED_control/Makefile b/Nodes/LED_control/Makefile new file mode 100644 index 0000000..7eb351d --- /dev/null +++ b/Nodes/LED_control/Makefile @@ -0,0 +1,50 @@ +# name of your application +APPLICATION = coap-example + +# If no BOARD is found in the environment, use this default: +BOARD ?= pba-d-01-kw2x + +# This has to be the absolute path to the RIOT base directory: +RIOTBASE ?= $(CURDIR)/../RIOT + +# enable default networking devides in the platform +USEMODULE += netdev_default + +# automatically initialize network interfaces for enabled devices +USEMODULE += auto_init_gnrc_netif + +# add minimal IPv6 support +USEMODULE += gnrc_ipv6_default + +# add ICMPv6 support (ping) +USEMODULE += gnrc_icmpv6_echo + +# add CoAP module +USEMODULE += gcoap + +# object dump allows use to print streams of bytes +USEMODULE += od + +# fmt allows use to format strings +USEMODULE += fmt + +USEMODULE += netutils +# Add also the shell, some shell commands +USEMODULE += shell +USEMODULE += shell_commands +USEMODULE += ps + +# Comment this out to disable code in RIOT that does safety checking +# which is not needed in a production environment but helps in the +# development process: +DEVELHELP ?= 1 + +# The default size of GCOAP_PDU_BUF_SIZE can be too small for the response +# from the RD server. Use a larger value if the client drops response packet +# from RD server. +CFLAGS += -DCONFIG_GCOAP_PDU_BUF_SIZE=512 + +# Change this to 0 show compiler invocation lines by default: +QUIET ?= 1 + +include $(RIOTBASE)/Makefile.include diff --git a/Nodes/LED_control/README.md b/Nodes/LED_control/README.md new file mode 100644 index 0000000..9764c71 --- /dev/null +++ b/Nodes/LED_control/README.md @@ -0,0 +1,321 @@ +# CoAP basic example + +The Constrained Application Protocol (CoAP) is a specialized web transfer +protocol for use with constrained nodes and constrained (e.g., low-power, lossy) +networks. It has been standardized by the IETF in +[RFC 7252](https://datatracker.ietf.org/doc/html/rfc7252). + +Nodes can act both as clients (performing requests) and as servers (serving +requests). In this example the node will expose one resource at `/riot/board`, +which accepts GET requests and returns the board name in plain text. + +It also can perform CoAP requests to other nodes. The application exposes a +`coap` shell command to interact with these functionalities. + +We will be using the gcoap module from RIOT. For more detailed information and +advanced usages, check +[the documentation](https://doc.riot-os.org/group__net__gcoap.html). + +## Task 1 + +Verify that your device is exposing the resource correctly. + +**1. Build and flash the application. Open a serial communication:** +```sh +$ make all flash term +``` + +**2. On the RIOT shell, find your node IPv6 address by using the `ifconfig` command:** +```sh +> ifconfig +``` + +**The response should be similar to:** +``` +2022-03-30 13:51:14,836 # ifconfig +2022-03-30 13:51:14,840 # Iface 6 HWaddr: 3E:66 Channel: 26 NID: 0x23 +2022-03-30 13:51:14,844 # Long HWaddr: 5F:0F:7B:9D:AE:49:3E:E6 +2022-03-30 13:51:14,848 # TX-Power: 0dBm State: IDLE +2022-03-30 13:51:14,851 # AUTOACK ACK_REQ AUTOCCA +2022-03-30 13:51:14,856 # L2-PDU:102 MTU:1280 HL:64 6LO IPHC +2022-03-30 13:51:14,859 # Source address length: 8 +2022-03-30 13:51:14,862 # Link type: wireless +2022-03-30 13:51:14,867 # inet6 addr: fe80::5d0f:7b9d:ae49:3ee6 scope: link VAL +2022-03-30 13:51:14,874 # inet6 addr: 2001:db8::5d0f:7b9d:ae49:3ee6 scope: global VAL +2022-03-30 13:51:14,876 # inet6 group: ff02::1 +2022-03-30 13:51:14,877 # +``` + +**You should see one wireless interface.** +**Among other things, it has a MAC address (Long HWaddr), a maximum transmission unit (MTU) and one or more IPv6 addresses.** +**They are denoted `inet6 addr`. You may not see the one with `scope:global`.** +**In this case, one of the node's IPv6 addresses is `2001:db8::5d0f:7b9d:ae49:3ee6`.** + +**3. Make a coap GET request to your own node. Use your IP address from the previous step.** +**You can check how to use the `coap` shell command by typing:** +```sh +> coap help +``` + +**The default UDP port for CoAP servers is `5683`:** +```sh +> coap get 2001:db8::5d0f:7b9d:ae49:3ee6 5683 /.well-known/core +``` + +**You should get a response with `code 2.05` and the paths of the resources.** +``` +2022-03-30 19:44:17,534 # gcoap_cli: sending msg ID 37913, 23 bytes +2022-03-30 19:44:17,539 # gcoap: response Success, code 2.05, 13 bytes +2022-03-30 19:44:17,540 # +``` + +**4. Try to get the board name from the `/riot/board` resource, sending a GET request.** +**The command should look almost as the one in step 3, but with a different path at the end.** + +## Task 2 + +Add a new CoAP resource to the server to interact with LEDs on the board. Upon a +GET request it should return the status of the specific LED. When a PUT request +is received, the payload should used to set the new status of the LED. + +**NOTE: The CoAP server is implemented in `server.c`, that's where you are going** +**to work.** + +**1. Declare an array of GPIOs to control the LEDs:** +```C +static const gpio_t leds[] = { + LED0_PIN, + LED1_PIN, + LED2_PIN, +}; +``` +**2. Initialize the LED GPIOs inside the `server_init` function.** +```C +/* initialize LEDs and turn them off */ +for (unsigned i = 0; i < ARRAY_SIZE(leds); i++) { + gpio_init(leds[i], GPIO_OUT); + gpio_set(leds[i]); +} +``` + +**3. Register a new CoAP resource in the `_resources` array.** +**It should accept GET and PUT requests.** +**It should also match all requests to paths starting with `/led/`:** +```C +{ "/led/", COAP_GET | COAP_PUT | COAP_MATCH_SUBTREE, _led_handler, NULL }, +``` + +**4. Implement the resource handler function.** + +**Start by defining the function with the correct** +**[signature](https://doc.riot-os.org/group__net__gcoap.html#ga8f62887693fa63a7595565e44156806d):** +```C +static ssize_t _led_handler(coap_pkt_t *pdu, uint8_t *buf, size_t len, void *ctx) +{ + (void) ctx; /* argument not used */ + + /* implement your handler here */ + + return 0; +} +``` + +**First we need to parse the URI, to know which LED was requested:** +```C +char uri[CONFIG_NANOCOAP_URI_MAX] = { 0 }; +/* get the request path, to know which LED is being requested */ +if (coap_get_uri_path(pdu, (uint8_t *)uri) <= 0) { + /* reply with an error if we could not parse the URI */ + return gcoap_response(pdu, buf, len, COAP_CODE_BAD_REQUEST); +} + +/* find the LED number, the URI should be /led/ */ +char *led_str = uri + strlen("/led/"); +unsigned led_number = atoi(led_str); + +/* verify that the number is valid, respond with an error otherwise */ +if (led_number >= ARRAY_SIZE(leds)) { + return gcoap_response(pdu, buf, len, COAP_CODE_BAD_REQUEST); +} +``` + +**Now we need to determine the type of request (GET or PUT):** +```C +ssize_t resp_len = 0; +int led_status = 0; +unsigned method = coap_method2flag(coap_get_code_detail(pdu)); + +switch (method) { +case COAP_PUT: /* on PUT, we set the status of the LED based on the payload */ + +case COAP_GET: /* on GET, we return the status of the LED in plain text */ + +} +``` + +**Let's implement the PUT request first:** +```C +case COAP_PUT: /* on PUT, we set the status of the LED based on the payload */ + /* check if there is a payload with a LED status */ + if (pdu->payload_len) { + led_status = atoi((char *)pdu->payload); + } else { + return gcoap_response(pdu, buf, len, COAP_CODE_BAD_REQUEST); + } + + if (led_status) { + gpio_clear(leds[led_number]); + } else { + gpio_set(leds[led_number]); + } + return gcoap_response(pdu, buf, len, COAP_CODE_CHANGED); + +``` + +**Now the response to a GET request:** +```C +case COAP_GET: /* on GET, we return the status of the LED in plain text */ + /* initialize the CoAP response */ + gcoap_resp_init(pdu, buf, len, COAP_CODE_CONTENT); + + /* set the content format to plain text */ + coap_opt_add_format(pdu, COAP_FORMAT_TEXT); + + /* finish the options indicating that we will include a payload */ + resp_len = coap_opt_finish(pdu, COAP_OPT_FINISH_PAYLOAD); + + /* get the current status of the LED, which is the inverted value of the GPIO */ + led_status = !gpio_read(leds[led_number]); + + /* based on the status, write the value of the payload to send */ + if (led_status) { + pdu->payload[0] = '1'; + } else { + pdu->payload[0] = '0'; + } + resp_len++; + return resp_len; +``` + +**5. Declare your handler function's prototype before `_resources` is declared:** +```C +static ssize_t _led_handler(coap_pkt_t *pdu, uint8_t *buf, size_t len, void *ctx); +``` + +**6. Build and flash your application. Open the serial communication.** + +You should be able to interact with your own LEDs by sending messages to your +own IP address (`coap put`)...but that's not fun... + +## Task 3 + +Ask someone around for their IP address and try to turn some other board's LEDs +on. + +**1. Turn the LED 0 of some other board on:** +```sh +> coap put 2001:db8::814c:35fc:fd31:5fde 5683 /led/0 1 +``` +--- + +## CoRE Link Format (RFC 6690) + +[RFC 6690](https://datatracker.ietf.org/doc/html/rfc6690) defines a format to +express web linking for constrained nodes. It is usually utilized for resource +discovery (e.g. via the `/.well-known/core` resource or a resource directory). + +Let's see an example of a link format response (the line breaks are included +for clarity): + +``` +;ct=40;title="Sensor Index", +;rt="temperature-c";if="sensor", +;rt="light-lux";if="sensor";obs +``` + +This list of links contains 3 elements. Link in a list are separated by `,`. A +link is composed by a URI (enclosed in `< >`) and zero or more attributes. Each +attribute is separated by `;`. An attribute may or may not have a value. The key +and the value are separated by `=`. Attributes are key/values, expressing extra +information about the link. Some examples of attributes are: content type +(`ct`), resource type (`rt`), or the interface (`if`). + +--- + +**The next two tasks involve multiple nodes and a resource directory. The** +**complete diagram looks like the following:** + +![](diagram.jpg) + +## Task 4 + +Use the information from the `/.well-known/core` to find resources exposing +temperature and humidity readings of the room. + +**1. Using the provided IP address, perform a GET request to the nodes** +**`/.well-known/core` resource to find which resources it exposes.** +**Use the `coap` shell command as in task 1.** + +**2. Once you have found the resources, try getting the current temperature** +**and humidity values.** + +## Task 5 + +Discover the hidden sensor. Perform a lookup on a resource directory to find a +node that is exposing pressure and magnetic readings in the room. + +**1. Using the provided resource directory IP address, perform a GET request to** +**its `/.well-known/core` resource. The response will be in Link Format.** +**You need to find in the list of links, a resource where to perform a lookup.** +**According to the specification, the resource type of the lookup resource** +**should be `rt=core.rd-lookup-res`** + +**2. Once you have found the lookup resource, perform a GET request to it.** +**It should reply with the hidden sensor information.** + +**3. Finally, get the current pressure and magnetic values from the hidden sensor.** + +## Task 6 + +Make a pull request on GitHub with your changes. + +**1. Make sure you saved all changes. Switch to a new git branch:** +```sh +$ git checkout -b pr/add_my_sensors +``` + +**2. Configure your name and email, replace with your information:** +```sh +$ git config --global user.name "FIRST_NAME LAST_NAME" +$ git config --global user.email "MY_NAME@example.com" +``` + +**3. Stage your changes to be committed:** +```sh +$ git add . +``` + +**4. Add a new commit with the change. Adapt the commit message with your name:** +```sh +$ git commit -m "Add sensor CoAP resources" +``` + +**5. Make a fork of [the project](https://github.com/smartuni/exercises)** +**on your own account, using GitHub's website.** + +**6. Add the new remote to your repository. Replace with the correct username** +```sh +$ git remote add upstream https://github.com//exercises.git +``` + +**7. You will need to generate a token to login while you push your changes.** +**Generate a new token following** +**[this guide](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token). COPY IT SOMEWHERE!** + +**7. Push the new branch to your repository:** +```sh +$ git push upstream pr/add_my_sensors +``` +**When prompted for a password, use your token.** + +**8. Create a new ull request using GitHub website** diff --git a/Nodes/LED_control/client.c b/Nodes/LED_control/client.c new file mode 100644 index 0000000..06902bb --- /dev/null +++ b/Nodes/LED_control/client.c @@ -0,0 +1,236 @@ +/* + * Copyright (c) 2015-2017 Ken Bannister. All rights reserved. + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup examples + * @{ + * + * @file + * @brief gcoap CLI support + * + * @author Ken Bannister + * @author Hauke Petersen + * + * @} + */ + +#include +#include +#include +#include + +#include "shell.h" +#include "fmt.h" +#include "net/gcoap.h" +#include "net/utils.h" +#include "od.h" + +#include "gcoap_example.h" + +uint16_t req_count = 0; + +/* + * Response callback. + */ +static void _resp_handler(const gcoap_request_memo_t *memo, coap_pkt_t* pdu, + const sock_udp_ep_t *remote) +{ + (void)remote; /* not interested in the source currently */ + + if (memo->state == GCOAP_MEMO_TIMEOUT) { + printf("gcoap: timeout for msg ID %02u\n", coap_get_id(pdu)); + return; + } + else if (memo->state != GCOAP_MEMO_RESP) { + printf("gcoap: error in response\n"); + printf("state: %d\n", memo->state); + return; + } + + char *class_str = (coap_get_code_class(pdu) == COAP_CLASS_SUCCESS) ? "Success" : "Error"; + + printf("gcoap: response %s, code %1u.%02u", class_str, + coap_get_code_class(pdu), + coap_get_code_detail(pdu)); + if (pdu->payload_len) { + unsigned content_type = coap_get_content_type(pdu); + if (content_type == COAP_FORMAT_TEXT + || content_type == COAP_FORMAT_LINK + || coap_get_code_class(pdu) == COAP_CLASS_CLIENT_FAILURE + || coap_get_code_class(pdu) == COAP_CLASS_SERVER_FAILURE) { + /* Expecting diagnostic payload in failure cases */ + printf(", %u bytes\n%.*s\n", pdu->payload_len, pdu->payload_len, + (char *)pdu->payload); + } + else { + printf(", %u bytes\n", pdu->payload_len); + od_hex_dump(pdu->payload, pdu->payload_len, OD_WIDTH_DEFAULT); + } + } + else { + printf(", empty payload\n"); + } +} + +static bool _parse_endpoint(sock_udp_ep_t *remote, + const char *addr_str, const char *port_str) +{ + netif_t *netif; + + /* parse hostname */ + if (netutils_get_ipv6((ipv6_addr_t *)&remote->addr, &netif, addr_str) < 0) { + puts("gcoap_cli: unable to parse destination address"); + return false; + } + remote->netif = netif ? netif_get_id(netif) : SOCK_ADDR_ANY_NETIF; + remote->family = AF_INET6; + + /* parse port */ + remote->port = atoi(port_str); + if (remote->port == 0) { + puts("gcoap_cli: unable to parse destination port"); + return false; + } + + return true; +} + +static size_t _send(uint8_t *buf, size_t len, char *addr_str, char *port_str) +{ + size_t bytes_sent; + sock_udp_ep_t remote; + + if (!_parse_endpoint(&remote, addr_str, port_str)) { + return 0; + } + + bytes_sent = gcoap_req_send(buf, len, &remote, _resp_handler, NULL); + if (bytes_sent > 0) { + req_count++; + } + return bytes_sent; +} + +static int _print_usage(char **argv) +{ + printf("usage: %s info\n", argv[0]); + printf(" %s [%%iface] [data]\n",argv[0]); + return 1; +} + +static int _coap_info_cmd(void) +{ + uint8_t open_reqs = gcoap_op_state(); + + printf("CoAP server is listening on port %u\n", CONFIG_GCOAP_PORT); + printf(" CLI requests sent: %u\n", req_count); + printf("CoAP open requests: %u\n", open_reqs); + return 0; +} + +/* map a string to a coap method code */ +int _method_str_to_code(const char *method) +{ + if (!strcmp(method, "get")) { + return COAP_METHOD_GET; + } + else if (!strcmp(method, "post")) { + return COAP_METHOD_POST; + } + else if (!strcmp(method, "put")) { + return COAP_METHOD_PUT; + } + else if (!strcmp(method, "delete")) { + return COAP_METHOD_DELETE; + } + + printf("Unknown method: %s\n", method); + return -1; +} + +int gcoap_cli_cmd(int argc, char **argv) +{ + + uint8_t buf[CONFIG_GCOAP_PDU_BUF_SIZE]; + coap_pkt_t pdu; + size_t len; + int position = 1; + + /* parse user input */ + if (argc == 1) { + /* invalid number of arguments, show help for the command */ + return _print_usage(argv); + } + + if (strcmp(argv[position], "info") == 0) { + return _coap_info_cmd(); + } + + if ((argc != 5) && (argc != 6)) { + /* invalid number of arguments, show help for the command */ + return _print_usage(argv); + } + + /* determine which method to use */ + int code = _method_str_to_code(argv[position]); + if (code < 0) { + /* unknown method, show help */ + return _print_usage(argv); + } + + position ++; + + char *addr = argv[position++]; + char *port = argv[position++]; + char *path = argv[position++]; + + /* simple check for the path */ + if (!path || path[0] != '/') { + return _print_usage(argv); + } + + char *data = NULL; + size_t data_len = 0; + if (argc == 6) { + data = argv[position++]; + data_len = strlen(data); + } + printf("%s\n", path); + /* initialize the CoAP request */ + gcoap_req_init(&pdu, buf, CONFIG_GCOAP_PDU_BUF_SIZE, code, path); + + /* send a confirmable message */ + coap_hdr_set_type(pdu.hdr, COAP_TYPE_CON); + + /* if there is data, we specify the plain text format and copy the payload */ + if (data_len) { + coap_opt_add_format(&pdu, COAP_FORMAT_TEXT); + + len = coap_opt_finish(&pdu, COAP_OPT_FINISH_PAYLOAD); + if (pdu.payload_len >= data_len) { + memcpy(pdu.payload, data, data_len); + len += data_len; + } + else { + puts("The buffer is too small, reduce the message length"); + return 1; + } + } else { + len = coap_opt_finish(&pdu, COAP_OPT_FINISH_NONE); + } + + printf("gcoap_cli: sending msg ID %u, %u bytes\n", coap_get_id(&pdu), (unsigned) len); + if (!_send(buf, len, addr, port)) { + puts("gcoap_cli: msg send failed"); + return -1; + } + return 0; +} + +/* define CoAP shell command */ +SHELL_COMMAND(coap, "Perform CoAP operations", gcoap_cli_cmd); diff --git a/Nodes/LED_control/diagram.jpg b/Nodes/LED_control/diagram.jpg new file mode 100644 index 0000000000000000000000000000000000000000..793684c85d882e44a19bff037b89ba165cc6234f GIT binary patch literal 15994 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 DRg9h> literal 0 HcmV?d00001 diff --git a/Nodes/LED_control/gcoap_example.h b/Nodes/LED_control/gcoap_example.h new file mode 100644 index 0000000..aa4f7e8 --- /dev/null +++ b/Nodes/LED_control/gcoap_example.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2020 Otto-von-Guericke-Universität Magdeburg + * + * This file is subject to the terms and conditions of the GNU Lesser General + * Public License v2.1. See the file LICENSE in the top level directory for more + * details. + */ + +/** + * @ingroup examples + * @{ + * + * @file + * @brief gcoap example + * + * @author Ken Bannister + */ + +#ifndef GCOAP_EXAMPLE_H +#define GCOAP_EXAMPLE_H + +#include +#include +#include +#include + +#include "fmt.h" +#include "net/gcoap.h" +#include "net/utils.h" +#include "od.h" + +#ifdef __cplusplus +extern "C" { +#endif + +extern uint16_t req_count; /**< Counts requests sent by CLI. */ + +/** + * @brief Registers the CoAP resources exposed in the example app + * + * Run this exactly one during startup. + */ +void server_init(void); + +#ifdef __cplusplus +} +#endif + +#endif /* GCOAP_EXAMPLE_H */ +/** @} */ diff --git a/Nodes/LED_control/main.c b/Nodes/LED_control/main.c new file mode 100644 index 0000000..48f9161 --- /dev/null +++ b/Nodes/LED_control/main.c @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2015-2016 Ken Bannister. All rights reserved. + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +#include +#include "msg.h" + +#include "net/gcoap.h" +#include "shell.h" + +#include "gcoap_example.h" + +#define MAIN_QUEUE_SIZE (4) +static msg_t _main_msg_queue[MAIN_QUEUE_SIZE]; + +int main(void) +{ + puts("CoAP example application"); + /* for the thread running the shell */ + msg_init_queue(_main_msg_queue, MAIN_QUEUE_SIZE); + server_init(); + + /* start shell */ + puts("All up, running the shell now"); + char line_buf[SHELL_DEFAULT_BUFSIZE]; + shell_run(NULL, line_buf, SHELL_DEFAULT_BUFSIZE); + + /* should never be reached */ + return 0; +} diff --git a/Nodes/LED_control/server.c b/Nodes/LED_control/server.c new file mode 100644 index 0000000..6a9e01c --- /dev/null +++ b/Nodes/LED_control/server.c @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2015-2017 Ken Bannister. All rights reserved. + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +#include +#include +#include +#include + +#include "fmt.h" +#include "net/gcoap.h" +#include "net/utils.h" +#include "od.h" + +#include "gcoap_example.h" + +#include "periph/gpio.h" +#include "board.h" + +static ssize_t _riot_board_handler(coap_pkt_t* pdu, uint8_t *buf, size_t len, void *ctx); + +/* [TASK 2: add the prototype of your resource handler here] */ + +/* [TASK 2: declare the array of LEDs here] */ + +/* CoAP resources. Must be sorted by path (ASCII order). */ +static const coap_resource_t _resources[] = { + /* [TASK 2: register your CoAP resource here] */ + { "/riot/board", COAP_GET, _riot_board_handler, NULL }, +}; + +/* a gcoap listener is a collection of resources. Additionally we can specify + * custom functions to: + * - list our resources on the /.well-known/core + * - how our resources are matched on an incoming request (simple string + * comparison is the default) + */ +static gcoap_listener_t _listener = { + _resources, + ARRAY_SIZE(_resources), + GCOAP_SOCKET_TYPE_UDP, + NULL, + NULL, + NULL +}; + +void server_init(void) +{ + gcoap_register_listener(&_listener); + + /* [TASK 2: initialize the GPIOs here] */ +} + +/* [TASK 2: implement the LED handler here] */ + +/* + * Server callback for /riot/board. Accepts only GET. + * + * GET: Returns the name of the board in plain text + */ +static ssize_t _riot_board_handler(coap_pkt_t *pdu, uint8_t *buf, size_t len, void *ctx) +{ + (void)ctx; + + /* initialize a new coap response */ + gcoap_resp_init(pdu, buf, len, COAP_CODE_CONTENT); + + /* first we set all the options */ + /* set the format option to "plain text" */ + coap_opt_add_format(pdu, COAP_FORMAT_TEXT); + + /* finish the options sections */ + /* it is important to keep track of the amount of used bytes (resp_len) */ + size_t resp_len = coap_opt_finish(pdu, COAP_OPT_FINISH_PAYLOAD); + + /* write the RIOT board name in the response buffer */ + if (pdu->payload_len >= strlen(RIOT_BOARD)) { + memcpy(pdu->payload, RIOT_BOARD, strlen(RIOT_BOARD)); + return resp_len + strlen(RIOT_BOARD); + } + else { + /* in this case we use a simple convenience function to create the + * response, it only allows to set a payload and a response code. */ + puts("gcoap_cli: msg buffer too small"); + return gcoap_response(pdu, buf, len, COAP_CODE_INTERNAL_SERVER_ERROR); + } +} From 3f1bf3ed3d4a44794a6278181b897405435479b2 Mon Sep 17 00:00:00 2001 From: Tristan Imken Date: Sun, 24 Apr 2022 11:06:08 +0200 Subject: [PATCH 2/4] Fix delete Mac file --- Nodes/LED_control/.DS_Store | Bin 6148 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 Nodes/LED_control/.DS_Store diff --git a/Nodes/LED_control/.DS_Store b/Nodes/LED_control/.DS_Store deleted file mode 100644 index 957845c803cd29ad5395978befb6f3631d1bd210..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHK%TB{E5FD2ZQuRQOT#)hwmH2~DwFeGd-~*sdN_nIYm4`Uy&W~_F;uFj4+O$au zS5%>0$vcj-vm1}2I0oQ)&)Esk0T8hXHd=H)nB14PWsNX$Ky;d8h9`89p5^m&E7}Hj zQ2|-I2q|*hVT{uHjq>@3_vFPmW$Uig*aOBH@hRqH zEO5nIugYDp@|YQJt88~%WAC%hkX2{j@7Jc1d#lApIA`nw^WKob{MFn#pO4afTDZV~ zqkZxQ%s)j|W$4qqd&X+TY7ripeTf(K;f>!s5oGS=3cNHk1xx``V22doo-G!&J(@QK zOaW71t$=(VVm84fVBt|e9dvpGAT~Ly#=iVA3MUSj1S~xA4$XNg(NkSGVmMFdcpT!A zfQ3g-hYN=f7b?4OLUCH1`C|=-OFWu41x$ge0(<_lBm4j0_w#?1WLKtuDe$ipaLxW@ zzsD(sy>)AHve(9J*KA@ES9n}g*wL++v9c8(v8i!9mJTrqSa{?J&3*(_26LvspDOSL DVD)XX From bc4030f68b4d23e71cfb9f2ef5decd474daf1edc Mon Sep 17 00:00:00 2001 From: TristanImkn Date: Mon, 30 May 2022 14:09:15 +0200 Subject: [PATCH 3/4] Changed goal weight and tolerance, added print --- Nodes/puzzle_weight/Makefile | 2 +- Nodes/puzzle_weight/main.c | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Nodes/puzzle_weight/Makefile b/Nodes/puzzle_weight/Makefile index c062eaf..f3a77e6 100644 --- a/Nodes/puzzle_weight/Makefile +++ b/Nodes/puzzle_weight/Makefile @@ -2,7 +2,7 @@ APPLICATION = WeightPuzzle # If no BOARD is found in the environment, use this default: -BOARD ?= native +BOARD ?= pba-d-01-kw2x # This has to be the absolute path to the RIOT base directory: RIOTBASE ?= ../RIOT diff --git a/Nodes/puzzle_weight/main.c b/Nodes/puzzle_weight/main.c index 5975312..36443b2 100644 --- a/Nodes/puzzle_weight/main.c +++ b/Nodes/puzzle_weight/main.c @@ -28,8 +28,8 @@ #include "puzzle_coap.h" -#define weight 55 /*wanted weight*/ -#define tolerance 0.1f /*tolerance of wanted weight*/ +#define weight 422 /*wanted weight*/ +#define tolerance 0.2f /*tolerance of wanted weight*/ #define STARTUP_DELAY (1U) #define MAIN_QUEUE_SIZE (4) @@ -48,7 +48,7 @@ static const puzzle_t puzzle = { .get_ready_handler = _ready, .set_ready_handler = _set_ready, .name = "Weight puzzle", - .resource_dir_uri = "coap://[fd00:dead:beef::1]", + .resource_dir_uri = "coap://[fd00:dead:beef::1]:5555", }; static bool _is_solved = false; @@ -59,7 +59,7 @@ void *meassure_weight(void *arg) (void) arg; int cnt = 0; /*counter*/ static hx711_t dev; - uint8_t times = 10; + uint8_t times = 5; puts("In thread"); /*initialize LED*/ @@ -81,7 +81,7 @@ void *meassure_weight(void *arg) while (1) { /* int32_t value = hx711_get_value(&dev, times); */ int32_t units = hx711_get_units(&dev, times); - + printf("%ld\n", units); /*check if measurement is within the borders*/ if ((units > weight - weight * tolerance) && (units < weight + weight * tolerance)) { cnt = cnt + 1; From b2c8c62400ead5578f1156463cb8451bd2ed7b3d Mon Sep 17 00:00:00 2001 From: TristanImkn Date: Mon, 30 May 2022 18:00:57 +0200 Subject: [PATCH 4/4] Updated README --- Nodes/puzzle_weight/README.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Nodes/puzzle_weight/README.md b/Nodes/puzzle_weight/README.md index b9eed9b..886e313 100644 --- a/Nodes/puzzle_weight/README.md +++ b/Nodes/puzzle_weight/README.md @@ -1,4 +1,10 @@ # Weight puzzle -something. something, weight, weight -(to be filled by Tristan) +This program measures the ouput of a load cell and converts it to a weight value. When the value is within the borders of a set goal for 2.5 seconds an LED will turn on, the puzzle is solved. The wanted value is 400g (water) + 22g weight of the bottle. When starting the puzzle the bottle cannot be on top of the weigh. + +Connection: +VDD - 3.3V +VCC - 5V +DAT - D6 +CLK - D2 +GND - GND