📜 ⬆️ ⬇️

Alljoyn: embedded view of the developer. Part 3: Porting on MK SAMD21


In previous articles, we dealt with the basics of Alljoyn and the tools that help debugging. It's time to write code for the microcontroller. Briefly recall the architecture LSF (Lighting Software Framework).
There are three entities in the LSF library:

Thin-bulb is the part that “rotates” directly in the microcontroller of our smart light bulb. That is what we are doing today. The rest was described in great detail earlier; we will not stop again.

The main thing is that all the actors are in the same local network, and the Router is “correct” (see the first part of the cycle ).

Our hardware configuration

In the role of a “light bulb” (physically), there is a debug board with samd21 and a winc 1500 wifi module, while the role of Router is a program in Ubuntu lighting controller service . As the controlling application, we will use the sample app LSF on the Android phone, which can be downloaded from the Allseen website of the alliance from the page of the respective working group .

The code for our debugging was written on the basis of the code for arduino (Thin Core) and the open code on the github for the “light bulb” . As it seemed to us then, this is the closest example to pure C and the absence of an operating system. But as it turned out later, this code is largely not completed and does not even perform the basic functions of a Thin device. So I had to finish / redo a lot.
All code is divided into 3 parts: Thin Core Alljoyn support, LSF “lamp” support, and everything else (hal level, own functions).
')
Thin Core Code

The first thing to do is implement a hal level to be able to connect to the network. In our case, it consists in raising a connection via UDP (for primary detection of a Thin device in the Alljoyn network using mdns), sending / receiving data via UDP, as well as raising the connection via TCP and receiving / sending data for basic communication on the network.
All these functions are written in the file aj_net.s.
The main problem was that the functions of interaction with the network in the arduino work on the flag, and in the library for winc interrupt, as well as for the library to work for winc, it is mandatory to constantly call the auxiliary function of polling flags set by the module. In detail about work with winc1500 we wrote in one of our previous articles .
The easiest way to begin to modify the hal level is with the connection setup functions via UDP and TCP. They must prescribe the necessary lines for establishing a connection directly and configure the structure corresponding to the connection: specify the receive / transmit functions and receive / transmit buffers.
Code for UDP (connection is established in main)
AJ_Status AJ_Net_MCastUp(AJ_NetSocket* netSock) { uint8_t ret = 1; if (ret != 1) { return AJ_ERR_READ; } else { netSock->rx.bufStart = udp_data_rx; netSock->rx.bufSize = sizeof(udp_data_rx); netSock->rx.readPtr = udp_data_rx; netSock->rx.writePtr = udp_data_rx; netSock->rx.direction = AJ_IO_BUF_RX; netSock->rx.recv = AJ_Net_RecvFrom; netSock->tx.bufStart = udp_data_tx; netSock->tx.bufSize = sizeof(udp_data_tx); netSock->tx.readPtr = udp_data_tx; netSock->tx.writePtr = udp_data_tx; netSock->tx.direction = AJ_IO_BUF_TX; netSock->tx.send = AJ_Net_SendTo; } return AJ_OK; } int main(void) { ... // Initialize socket address structure. addr.sin_family = AF_INET; addr.sin_port = _htons(MAIN_WIFI_M2M_SERVER_PORT); addr.sin_addr.s_addr = _htonl(MAIN_WIFI_M2M_SERVER_IP); src_addr.sin_family = AF_INET; src_addr.sin_port = _htons(MAIN_WIFI_M2M_SERVER_PORT); //_htons(52148); src_addr.sin_addr.s_addr = _htonl(MAIN_WIFI_M2M_SERVER_IP); // Initialize Wi-Fi parameters structure. memset((uint8_t *)¶m, 0, sizeof(tstrWifiInitParam)); // Initialize Wi-Fi driver with data and status callbacks. param.pfAppWifiCb = wifi_cb; ret = m2m_wifi_init(¶m); if (M2M_SUCCESS != ret) { printf("main: m2m_wifi_init call error!(%d)\r\n", ret); while (1); } // Initialize socket module socketInit(); registerSocketCallback(socket_cb, NULL); // Connect to router. m2m_wifi_connect((char *)MAIN_WLAN_SSID, sizeof(MAIN_WLAN_SSID), MAIN_WLAN_AUTH, (char *)MAIN_WLAN_PSK, M2M_WIFI_CH_ALL); printf("m2m_wifi_connect!\r\n"); ... } 


TCP code
 AJ_Status AJ_Net_Connect(AJ_BusAttachment* bus, const AJ_Service* service) { int ret; if (!(service->addrTypes & AJ_ADDR_TCP4)) { return AJ_ERR_CONNECT; } printf("AJ_Net_Connect()\n"); addr.sin_port = _htons(service->ipv4port); addr.sin_addr.s_addr = _htonl(service->ipv4); printf("AJ_Net_Connect(): ipv4= %x, port = %d\n",addr.sin_addr.s_addr, addr.sin_port); tcp_client_socket = socket(AF_INET, SOCK_STREAM, 0); ret=connect(tcp_client_socket, (struct sockaddr *)&addr, sizeof(struct sockaddr_in)); printf("AJ_Net_Connect(): connect\n"); while(tcp_ready_to_send==0) { m2m_wifi_handle_events(NULL); } printf("AJ_Net_Connect(): connect OK\n"); if (ret == -1) { return AJ_ERR_CONNECT; } else { bus->sock.rx.bufStart = AJ_in_data_tcp; bus->sock.rx.bufSize = sizeof(AJ_in_data_tcp); bus->sock.rx.readPtr = AJ_in_data_tcp; bus->sock.rx.writePtr = AJ_in_data_tcp; bus->sock.rx.direction = AJ_IO_BUF_RX; bus->sock.rx.recv = AJ_Net_Recv; bus->sock.tx.bufStart = tcp_data_tx; bus->sock.tx.bufSize = sizeof(tcp_data_tx); bus->sock.tx.readPtr = tcp_data_tx; bus->sock.tx.writePtr = tcp_data_tx; bus->sock.tx.direction = AJ_IO_BUF_TX; bus->sock.tx.send = AJ_Net_Send; printf("AJ_Net_Connect(): connect() success: status=AJ_OK\n"); return AJ_OK; } printf("AJ_Net_Connect(): connect() failed: %d: status=AJ_ERR_CONNECT\n", ret); return AJ_ERR_CONNECT; } 


Over UDP, we actually only need to send mdns requests and receive an answer to them. Upon receipt of the parcel is checked whether there is something to send. If so, it is sent, after which the auxiliary flag handling function is called. If the successful send flag is set (it is set in the callback), the function completes its work successfully, otherwise it returns a write error.
 AJ_Status AJ_Net_SendTo(AJ_IOBuffer* buf) { int ret; uint32_t tx = AJ_IO_BUF_AVAIL(buf); if (tx > 0) { ret = sendto(rx_socket, buf->readPtr, tx, 0, (struct sockaddr *)&addr, sizeof(addr)); m2m_wifi_handle_events(NULL); if (sock_tx_state != 1) { return AJ_ERR_WRITE; } buf->readPtr += ret; } AJ_IO_BUF_RESET(buf); return AJ_OK; } 

When received in a loop with an exit by time-out or receiving a parcel, the receive handler and the auxiliary flag processing function are called. Arduino connoisseurs will notice that I use the millis function (it was rewritten according to our realities). If the exit from the wait cycle of the parcel occurred on a timeout, then a read error is returned, otherwise the status is AJ_OK.
 AJ_Status AJ_Net_RecvFrom(AJ_IOBuffer* buf, uint32_t len, uint32_t timeout) { AJ_Status status = AJ_OK; int ret; uint32_t rx = AJ_IO_BUF_SPACE(buf); unsigned long Recv_lastCall = millis(); while ((sock_rx_state==0) && (millis() - Recv_lastCall < timeout)) { recv(rx_socket, udp_data_rx, MAIN_WIFI_M2M_BUFFER_SIZE, 0); m2m_wifi_handle_events(NULL); } ret=sock_rx_state; if (ret == -1) { printf("AJ_Net_RecvFrom(): read() fails. status=AJ_ERR_READ\n"); status = AJ_ERR_READ; } else { if (ret != -1) { AJ_DumpBytes("AJ_Net_RecvFrom", buf->writePtr, ret); } buf->writePtr += ret; status = AJ_OK; } printf("AJ_Net_RecvFrom(): status=%s\n", AJ_StatusText(status)); return status; } 

We now turn to the implementation of the reception / transmission of TCP. Transmission is not much different from UDP transmission.
 AJ_Status AJ_Net_Send(AJ_IOBuffer* buf) { uint32_t ret; uint32_t tx = AJ_IO_BUF_AVAIL(buf); printf("AJ_Net_Send(buf=0x%p)\n", buf); if (tx > 0) { send(tcp_client_socket, buf->readPtr, tx, 0); buf->readPtr += tcp_tx_ready; tcp_tx_ready=0; } AJ_IO_BUF_RESET(buf); return AJ_OK; } 

But with the reception all a little more interesting. The general approach, of course, is the same as in UDP, but the source codes still had a lot of checks, I couldn’t figure out what they were doing and how much they needed them, so most of them left that code.

TCP reception
 AJ_Status AJ_Net_Recv(AJ_IOBuffer* buf, uint32_t len, uint32_t timeout) { AJ_Status status = AJ_ERR_READ; uint32_t ret; uint32_t rx = AJ_IO_BUF_SPACE(buf); uint32_t recvd = 0; unsigned long Recv_lastCall = millis(); // first we need to clear out our buffer uint32_t M = 0; if (rxLeftover != 0) { // there was something leftover from before, M = min(rx, rxLeftover); memcpy(buf->writePtr, rxDataStash, M); // copy leftover into buffer. buf->writePtr += M; // move the data pointer over memmove(rxDataStash, rxDataStash + M, rxLeftover - M); // shift left-overs toward the start. rxLeftover -= M; recvd += M; // we have read as many bytes as we can // higher level isn't requesting any more if (recvd == rx) { return AJ_OK; } } if ((M != 0) && (rxLeftover != 0)) { printf("AJ_Net_REcv(): M was: %d, rxLeftover was: %d\n", M, rxLeftover); } while ((tcp_rx_ready==0) && (millis() - Recv_lastCall < timeout)) { recv(tcp_client_socket, tcp_data_rx, sizeof(tcp_data_rx), 0); m2m_wifi_handle_events(NULL); } if (tcp_rx_ready==0) { printf("AJ_Net_Recv(): timeout. status=AJ_ERR_TIMEOUT\n"); status = AJ_ERR_TIMEOUT; } else { memcpy(AJ_in_data_tcp, tcp_data_rx,tcp_rx_ready); uint32_t askFor = rx; askFor -= M; ret=tcp_rx_ready; if (askFor < ret) { printf("AJ_Net_Recv(): BUFFER OVERRUN: askFor=%u, ret=%u\n", askFor, ret); } if (ret == -1) { printf("AJ_Net_Recv(): read() failed. status=AJ_ERR_READ\n"); status = AJ_ERR_READ; } else { AJ_DumpBytes("Recv", buf->writePtr, ret); if (ret > askFor) { printf("AJ_Net_Recv(): new leftover %d\n", ret - askFor); // now shove the extra into the stash memcpy(rxDataStash + rxLeftover, buf->writePtr + askFor, ret - askFor); rxLeftover += (ret - askFor); buf->writePtr += rx; } else { buf->writePtr += ret; } status = AJ_OK; } } tcp_rx_ready=0; return status; } 


Another important point is LocalGUID - a unique device identifier (see the second part of the article ), which we honestly borrowed (with minor changes) from a light bulb implemented on Linux.
One of the important corrections: in the source code in the mdns request, the ip address of the light bulb is set explicitly. If you do not want to rewrite it for each device with pens, then you need to add the reading of the assigned ip address (we will use dhcp to get the address on the network) and its entry into the packet. This is done in the file: aj_disco.c in the function: ComposeMDnsReq (...).

Code for "light bulb"

We start the implementation of the "light bulb" in terms of LSF. To indicate operation as light bulbs, we will use a custom LED on the debug board. Accordingly, in the hardware we will have only the ability to turn on / off the LED.

During the implementation of this part of the program, quite a lot of changes were made that made it work. Enumerate them all here will not, we note only the important points.

HAL level of work with the "light bulb" we implemented in the function OEM_LS_TransitionStateFields , the file OEM_LS_Code.c . It could have been done less locally, but since the hal level only supports on / off, they did not spend much time and effort on it.
 LampResponseCode OEM_LS_TransitionStateFields(LampStateContainer* newStateContainer, uint64_t timestamp, uint32_t transitionPeriod) { //OEMs should do the following operations just before transitioning the state LampState state; /* Retrieve the current state of the Lamp */ LAMP_GetState(&state); /* Update the requisite fields to new values */ if (newStateContainer->stateFieldIndicators & LAMP_STATE_ON_OFF_FIELD_INDICATOR) { state.onOff = newStateContainer->state.onOff; printf("%s: Updating OnOff to %u\n", __func__, state.onOff); printf("----------------state.onOff=%d-----------------------\n",state.onOff); if (state.onOff==1) { port_pin_set_output_level(LED_0_PIN, LED_0_ACTIVE); } else { port_pin_set_output_level(LED_0_PIN, LED_0_INACTIVE); } } ... } 

If you want to change the company name of the device manufacturer, device name, supported languages ​​and other parameters, then you need to change the corresponding lines at the beginning of the OEM_LS_Provisioning.c file.

All settings are initially taken and, when changed, are written to some NVRAM. We didn’t think up how to implement this in our case, so everywhere where read / write from NVRAM was used, we changed the code to work in our realities.

It is simply impossible to list all the “rakes” that we were attacking (there were also dead-end branches, some were forgotten). Therefore, we mentioned the main methods and their “catching” - take obviously working applications (for example, under linux), and look at the WireShark exchange, which we should try to repeat.
But when all the “rakes” are passed, the working system causes affection with its forethought and, actually, work (finally!). You can watch the video at the beginning of the article.

Project code posted on githab

Source: https://habr.com/ru/post/278363/


All Articles