/** * @defgroup lessons Lessons * @ingroup guide
In this section a simple application example is described: Switching LED on an off, toggling if requested so via the serial interface, using the timer, and sending and receiving packets.
\section first_lesson Switching LED on and offHave a look at the following lines of code:
\code uint8_t counter; void toggleLED() { if (counter == 1) { Data_enable(0, 0, NULL); // enable the red LED counter = 0; } else { counter = 1; Data_disable(0); // disable the red LED } Timers_add(1024, toggleLED, (void*) 0xFFFF); } void Process_init() { […] counter = 0; Timers_add(1024, toggleLED, (void*) 0xFFFF); } \endcodeThis code is easy to understand: It toggles the red LED; i.e. switches it on and off in regular intervals. Try it out!
\section second_lesson Toggling on commandNext, what you will need quite often are new terminal commands for testing. For now, try to write a terminal command called “tog” that has as the only argument the number of times the LED should toggle. So if you enter “tog 5” it toggles five times and so on.
Therefore, you first of all have to add the “tog” command to the list of known and supported commands. This can be done quite easily using a macro:
\code COMMAND(tog, 0, cmdargs) { uint16_t iterations = 0; iterations = String_parseUint16(cmdargs->args, NULL); // parse an intenger printf("%u iterations\r\n", iterations); // print over serial term_toggle(iterations); } \endcodeThe function String_parseUint16 reads from the command-arguments-array (cmdargs) where all the arguments of the command are buffered. In this case, it reads beginning from position 0 (C begins counting at zero).
The next thing you need is to implement the toggling itself. Look at the straightforward solution here:
\code void term_toggle(uint16_t iterations) { uint16_t i = 0; for (i = 0; i < iterations; i++) { Data_enable(0, 0, NULL); System_wait(10000); // block for 10,000 nops Data_disable(0); System_wait(10000); // block for 10,000 nops } } \endcodeTry it out, it works! But: It works only for small numbers, so if you type in “tog 2” you will see it toggling twice, but “tog 19” will most likely not bring the expected results. The serial output of the node will give you a hint on what happens instead (also lookup the implementation of System_wait()). A better idea is to use the timer as described in the next section.
\section third_lesson Using the TimerThe better way to implement this kind of long-term, returning action is to use timers. Using timers, you would once switch on the LED, switch it off immediately and then set the timer for the next time it should toggle again.
The only function you need to know for this is the function bool Timers_add(uint32_t t, fp_timer fp, void* data) that is defined in ScatterWeb.Timers.c. If you are not used to C-style function pointers, this might be a bit tricky, but can be copied from the example given in the following.
Imagine a revised toggle-command that calls term_toggle2() implemented as follows:
\code void term_toggle2(void* iterations) { if ((uint16_t) iterations <= 0) return; Data_enable(0, 0, NULL); System_wait(10000); Timers_add(500, term_toggle2, (void*) --iterations); // call the function Data_disable(0); } COMMAND (tog, 0, cmdargs) { uint16_t iterations = 0; iterations = String_parseUint16(cmdargs->args, NULL); printf("%u iterations\r\n", iterations); term_toggle2((void*) iterations); } \endcodeThe function Timers_add is passed as first argument the value 500. That means after ca. 500 ticks (1024 ticks = 1 second) the timer calls the function that is passed as second argument. This function, term_toggle2 in our example, needs an argument. This argument is the third parameter of the Timers_add-function. So in this example if term_toggle2(4) is called, after a time the function term_toggle2(3) will be called and so on, until the iterations variable is zero or less. Hint for the C programmers: Think (before testing) if iteration-- would work as well! Play a little bit with these values; you could for example easily implement a countdown that ticks faster as nearer it comes to its end.
\section fourth_lesson Sending and receiving packetsLet us consider the case that you want to carry out simple connectivity tests: You send a command to a sensor node, this triggers a broadcast packet sent, and all nodes that receive this packet switch on their red LED.
\code COMMAND (scp, 0, cmdargs) { sendConnectivityTestPacket(); } \endcodeThen you could for example implement the function sendConnectivityTest Packet() directly above the command as follows:
\code void sendConnectivityTestPacket() { struct ConnectivityTestPacketStruct ConnectivityTestPacket; // define a payload ConnectivityTestPacket.type = 0; // fill the payload ConnectivityTestPacket.src = Configuration.id; netpacket_send_args_t npsargs; //define a packet Net_sendArgsInit(&npsargs); // fill the packet npsargs.netheader.to = NETADDR_BROADCAST; npsargs.netheader.type = CONNECTIVITY_TEST_PACKET; npsargs.netheader.flags = 0; npsargs.payload[0].buffer = (uint8_t*) &ConnectivityTestPacket; npsargs.payload[0].size = sizeof(ConnectivityTestPacket); Net_send(&npsargs); // sends a packet } \endcodeYou basically have to define a structure of the data part (payload) of your packet, define your own packet type (in this example CONNECTIVITY_TEST _PACKET) and send it over the air. The structure of the data part could be defined as follows in ScatterWeb.Process.c:
\code struct ConnectivityTestPacketStruct { uint8_t type; uint16_t src; }; \endcodeWhen receiving packets, all you have to do is very simple: Look for the packet type you just defined (CONNECTIVITY_TEST_PACKET in our example), interpret the payload of the packet as structured as you defined it at the sender side (in our example, as defined in struct ConnectivityTestPacketStruct in ScatterWeb.Process.c) and act accordingly.
Packets that the system layer does not filter are passed to the application, where the appropriate action according to the packet type can be chosen in the switch (args->netheader->type)-construction in the packet handler function bool Process_radioHandler(struct netpacket_handler_args* args).
Insert a new switch/case-statement into the if-statement of broadcasted packets for example as follows:
\code bool Process_radioHandler(struct netpacket_handler_args* args) { […] } else if ( args->cpaResult == ADRCOMP_BROADCAST ) { […] switch(args->netheader->type) { case CONNECTIVITY_TEST_PACKET: printf("Connectivity test packet received\r\n"); handleConnectivityTestPacket(args->netheader->from, args->payload); break; } else { […] } \endcodeWell, this only filters out the case that a CONNECTIVITY_TEST_PACKET was received. Everything further is done in the handler function:
\code void handleConnectivityTestPacket(uint16_t from, const void* payload) { struct ConnectivityTestPacketStruct* ConnectivityTestPacket; ConnectivityTestPacket = (struct ConnectivityTestPacketStruct*) payload; printf("Conn.test packet from: "); printf("%u", from); printf(",from field in payload: "); printf("%u\r\n",ConnectivityTestPacket->src); Data_enable(0, 0, NULL); } \endcodeThe casting of the payload to the structure is a bit tricky here. For the beginning you should keep strictly to this scheme, especially if you are a beginner with the C programming language.
\section fifth_lesson Now it is your taskMake a real two-way connectivity test by sending back a CONNECTIVITY _TEST_RESPONSE packet to the sender of a CONNECTIVITY_TEST _PACKET. But be careful: Doing it that simple straightforward way will result in a lot of simultaneous answers of different nodes, resulting in packet collisions and traffic jam. Therefore you should send packets to a specific node, no broadcast. Therefore you need to implement:
- A new terminal command “cnt” (cnt stands for connectivity test) that takes the destination node ID as argument
- A modified handler of the CONNECTIVITY_TEST_PACKET that sends back the CONNECTIVITY_TEST_RESPONSE packet
- A new handler that handles the reception of a CONNECTIVITY_TEST _RESPONSE packet and writes to the serial port which node answered the test packet
Basically, that’s all. Feel free to play around with this and find new combinations. For the rest you will have to read the documentation or the source code. Avoid changing the firmware, there is definitely no need for that and it makes portability of implementations much easier if the firmware is everywhere the same. For further information take a look at http://cst.mi.fu-berlin.de/projects/ScatterWeb.
Have Fun!
*/