Wiznet makers

ShebinJoseJacob

Published August 20, 2023 © 3-Clause BSD License(BSD-3-Clause)

0 UCC

0 VAR

1 Contests

0 Followers

0 Following

Modular IoT Kit

No Code, No Limits: Effortless Setup, Endless Possibilities With Modular IoT

COMPONENTS Hardware components

WIZnet - W5300-TOE-Shield

x 1


ST - NUCLEO-F429ZI

x 1


WIZnet - WizFi360-EVB-Pico

x 1

Software Apps and online services

Arduino - Arduino IDE

x 1


microsoft - VS Code

x 1


Autodesk - Fusion 360

x 1


PROJECT DESCRIPTION
In the ever-evolving landscape of the Internet of Things (IoT), where devices interconnect and communicate seamlessly, a new frontier has emerged with the Modular IoT Kit. As we navigate the rapid strides of technological advancement, this cutting-edge kit stands as a beacon of innovation, empowering individuals and businesses to effortlessly tap into the boundless possibilities of IoT.
 
Picture a world where your surroundings are not just inanimate objects, but a symphony of interconnected nodes, collecting, transmitting, and analyzing data in real-time. The Modular IoT Kit is your gateway to this connected realm, where the fusion of smart devices and intelligent systems is more than a vision – it's a tangible reality. Imagine a reality where the intricate web of connectivity is at your fingertips, regardless of your technical expertise. Welcome to a realm where anyone – from tech enthusiasts to homeowners, from industry pioneers to everyday users – can effortlessly set up their own IoT network, ushering in the era of smart living, smart industries, and beyond.
 
Central to the Modular IoT Kit's allure is its unparalleled simplicity. Gone are the days of convoluted programming and the need for technical experts. With this kit, the barriers to entry are shattered. Unbox the potential and let your journey into IoT commence. The gateway, designed for utmost ease, derives power from a mere USB cable, instantly putting you in control. Meanwhile, the sensors, operating independently with battery power, stand as sentinels ready to capture the world around them.
 
Here lies the true magic – the setup. Even if coding is a foreign language to you, fear not. The Modular IoT Kit has reimagined the process. Imagine effortlessly connecting the gateway to your router, as if connecting the dots of an image that comes into focus. The sensors, each with their innate intelligence, initiate a user-friendly provisioning process. With a simple turn of the switch, they spring to life, creating a configuration portal. Your smartphone seamlessly connects, paving the way for an intuitive configuration via a browser. It's like painting with technology, and the canvas is your environment.
 
Notably, the Modular IoT Kit champions independence. Each node, with its built-in identity, can be placed wherever your imagination takes you. This isn't a symphony conducted by experts; it's your composition. Imagine a smart home, where lights adjust to your presence and energy consumption adapts to your schedule. Envision a smart industry, where machines communicate their needs and productivity is optimized at your fingertips. The nodes are your instruments, and the symphony is yours to orchestrate.
 
And here's where the magic truly happens: the Modular IoT Kit's intuitive dashboard. Imagine a central hub where you can not only monitor the intricate web of data streaming in from your sensors but also exert control over these devices with a mere touch. This dashboard empowers you to visualize the heartbeat of your connected space, offering insights that transcend the realm of mere numbers. Widgets and tools are at your disposal, enabling you to craft an experience tailored to your needs.
 
In this new world, expertise is a mindset, not a requirement. With the Modular IoT Kit, setting up a smart home, revolutionizing an industry, or bringing an idea to life no longer hinges on specialized knowledge. Simplicity and power converge, and the once-imposing IoT landscape becomes your playground. Welcome to a future where innovation is unbounded, and connectivity is second nature. Welcome to the Modular IoT Kit. Your gateway to a smarter, tomorrow.
 
Modular IoT Kit: Unbox And Setup

Features Of Modular IoT Kit

  • Seamless Setup with Zero Coding: Just unbox and create your IoT network
  • Open Source; all of the assets are open-sourced, and developers are welcome to contribute
  • Rechargeable Battery Powered Nodes; with self-sustaining battery power experience the freedom of placing nodes anywhere
  • IoT for everyone: The kit is designed in such a way to use it for all different purposes from Smart Home to Smart Industries.

Setting Up IoT Network With Modular IoT Kit: Users' Guide

The Modular IoT Kit is an innovative solution that enables users to create a customizable Internet of Things (IoT) network. The kit includes a gateway and sensors, with the gateway being powered via USB and the sensors operating on battery power. This guide outlines the step-by-step process for setting up the Modular IoT Kit and its accompanying intuitive dashboard.

Setting Up The Gateway

  • Unboxing and Initial Setup: Begin by unboxing the Modular IoT Kit and locating the gateway. Power the gateway by connecting it to a power source using the provided USB cable.
  • Connecting to the Internet: Use the included Ethernet cable to connect the gateway to your Wi-Fi router. This step establishes internet connectivity for the gateway, allowing it to communicate with other devices and the online dashboard.

Setting Up The Nodes

  • Node Activation: Each node in the kit features an integrated battery and an on/off switch. Activate the node by turning on the switch, initiating the provisioning process.
  • Connecting to the Node's Access Point: Once the node is powered on, it will create an Access Point (AP) named "wiznet". Connect your smartphone to this AP.
  • Accessing the Configuration Page: Open a web browser on your smartphone and navigate to "http://192.168.2.100" This will direct you to a configuration webpage for the node.
  • Configuring Wi-Fi Settings: On the configuration webpage, provide the SSID and Password of your Wi-Fi network. Submit the information to complete the provisioning process for the node.
  • Repeating the Process: Repeat the above steps for each node in the kit, ensuring that all nodes are connected to the designated network.

Setting Up The Dashboard

  • Accessing the Modular IoT Dashboard: Open a web browser and navigate to the Modular IoT Dashboard. Upon arrival, you will see a blank dashboard interface.
  • Adding Widgets to the Dashboard: Utilize the '+' icon on the landing page to add widgets to your dashboard. Widgets are graphical elements that display data from the connected nodes.
  • Customizing Widgets: The dashboard widgets are resizable and draggable, offering versatility in design. Click the edit icon to link widgets to specific nodes and configure their appearance.
  • Linking Widgets to Nodes: In the edit mode, you'll find a link icon. Click on this icon to open a form. Enter the display name and the unique Node ID for the respective node. Repeat this process for each widget, linking them to the appropriate nodes.
  • Associating the Dashboard with the Kit: Click on the user icon on the dashboard to access a form. Enter your name and the unique 'Kit ID' associated with your kit. This step links your dashboard to your Modular IoT Kit.
By following these steps, users can seamlessly set up their Modular IoT Kit, connect the nodes to the dashboard, and begin monitoring data from nodes and control them in a user-friendly and intuitive manner.

Build Your Modular IoT Kit: Developer's Guide

1. General Overview Of The System

The modular IoT system comprises two integral components: a gateway and multiple nodes. The gateway consists of a Nucleo F429ZI microcontroller combined with a W5300-TOE-Shield module, establishing a connection to the router. This guide provides a clear and comprehensive explanation of the system's components, their functionalities, and their interaction.
 
Gateway Component:
The gateway is constructed by integrating the Nucleo F429ZI microcontroller with the W5300-TOE-Shield module. This configuration enables the gateway to connect to the router and perform two distinct roles:
  • Ethernet Server: The gateway consists of an Ethernet server, designed to accept incoming connections from nodes within the network. This server establishes a fixed address, based on the gateway's IP address. For instance, if the gateway's IP address is 192.168.1.1, the Ethernet server is located at 192.168.1.6:5000. This positioning enables efficient communication with connected nodes.
  • Ethernet Client with PubSub Integration: The gateway also consists of an Ethernet client, encapsulated within a PubSubClient. This client encapsulation facilitates the transmission of data received from nodes to an MQTT broker. Data acquired from nodes is processed by the Ethernet client, then forwarded to the MQTT broker for wider dissemination.
Node Component:
Nodes are constructed around the WizFi360-EVB-PICO module, often paired with either a sensor or an actuator. The behavior of nodes can be divided into two categories:
  • Sensor Nodes: Nodes equipped with sensors operate as Wi-Fi clients, actively searching for the Ethernet server on the connected network. Upon locating the server, a connection is established, and sensor data is transmitted to the server. This connection is temporary and is re-established at predetermined intervals, ensuring consistent data flow.
  • Actuator Nodes: Nodes featuring actuators maintain a lifelong connection with the Ethernet server. This persistent connection enables real-time interaction with the server, facilitating immediate response to commands or instructions issued by the server.
Dashboard Integration:
The system's dashboard is linked to the MQTT broker via a JavaScript-based MQTT client. This connection permits real-time interaction with the data originating from nodes:
  • Data Publication and Subscription: Whenever new data is collected from the nodes, it's formatted as "nodeID: value" and published to a subscribed topic corresponding to the kit's unique identifier (Kit ID). This arrangement ensures that the dashboard receives the latest data updates from the MQTT broker
  • Widget Update: The dashboard features widgets designed to display specific data. Upon receiving updated data via MQTT, the relevant widget, identified by the associated node ID, is promptly updated with the new value. This mechanism enables users to visualize real-time changes in data from the connected nodes.

2. Topology And Data Flow

DFD Level 0
DFD Level 1
DFD Level 2

Dashboard

We have developed a sophisticated yet user-friendly dashboard that serves as a powerful visualization and control hub for our IoT nodes. This dashboard represents a dynamic canvas where multiple widgets can be seamlessly added, resized, and aligned, granting users the creative freedom to compose their own intricate IoT experience.

Constructed entirely through the utilization of HTML, CSS, and JavaScript, our dashboard exemplifies a commitment to modern web technologies. The integration of the Interact JS library facilitates a seamless drag-and-drop canvas, enabling users to arrange widgets to their preferences intuitively. Moreover, the application's state is preserved using Local Storage, ensuring that the dashboard configuration remains intact even after a page reload, providing an uninterrupted experience.

A cornerstone of the dashboard's functionality lies in its ability to establish an MQTT connection between the browser and our MQTT broker. We've harnessed the power of the MQTT js library to facilitate this connection, fostering real-time data exchange and communication between the dashboard and the IoT nodes. This two-way conduit ensures that information flows seamlessly, enabling users to interact with their nodes in real-time through the familiar web browser interface.

Gateway

1. W5300 TOE Shield 

The W5300 TOE (TCP/IP Offload Engine) shield is a crucial cornerstone of gateway systems. This specialized component is pivotal in optimizing network communication processes, specifically those related to TCP/IP protocols. By offloading these intricate tasks from the main microcontroller, the shield significantly enhances network performance and responsiveness, ultimately leading to more efficient data transfers and reduced latency.
 
It offers seamless compatibility with a range of STM32 Nucleo-144 boards, including models like NUCLEO-F207ZG, NUCLEO-F429ZI, NUCLEO-F439ZI, NUCLEO-F722ZE, NUCLEO-F756ZG, and NUCLEO-F767ZI. This shield supports both 16-bit and 8-bit data bus widths, allowing for versatile data handling. With the capability to manage up to 8 independent sockets simultaneously, it enables efficient multitasking in network communication applications.
 
A standout feature of the W5300 TOE Shield is its user-friendly design, eliminating the need for additional hardware design involving the W5300 chip, transformer, and RJ-45 connector. The shield is pre-equipped with a built-in 25 MHz crystal oscillator, ensuring high-precision timing for network communication processes.
 
The shield's compatibility extends to the Arduino ecosystem, making it Arduino pin compatible. This means it seamlessly integrates with Arduino projects, simplifying the development process and providing access to the Arduino library ecosystem. In summary, the W5300 TOE Shield streamlines network communication for STM32 Nucleo-144 boards, offering efficient socket management, versatile data bus widths, and an integrated crystal oscillator, all while being Arduino pin compatible for enhanced development convenience.
 
The Shield is an add-on board that integrates the W5300 chip onto a convenient form factor compatible with the STM32 Nucleo 144.
 
Let's delve into the technical details of the W5300 TOE Shield and the W5300 chip:
 
W5300 TOE Shield Overview:
It enables the gateway to establish a TCP/IP server and manage multiple client connections using the 8 independent hardware sockets provided by the W5300 chip.
 
W5300 Chip Overview:
The W5300 is a specialized chip designed for TCP/IP offload. It can handle the complexities of network communication, freeing up the STM32 Nucleo's resources for other tasks. Here's an overview of the key features and components of the W5300 chip:
  • 8 Independent Hardware Sockets: The W5300 chip provides 8 independent hardware sockets, allowing the STM32 Nucleo to establish and manage up to 8 simultaneous TCP connections. This is crucial for this project, as each client can be allocated a separate socket for communication.
  • TCP/IP Offload Engine: The W5300 includes a TCP/IP Offload Engine that handles low-level networking tasks, such as segmenting data into packets, managing acknowledgments, and handling retransmissions. This offload engine ensures efficient and reliable communication between your server and clients.
  • Ethernet Interface: The W5300 integrates an Ethernet MAC (Media Access Control) interface, which allows the STM32 Nucleo to connect to an Ethernet network. The Ethernet interface handles the physical layer communication, including the encoding and decoding of data onto the network medium.
  • Memory Management: The W5300 features internal memory buffers that store incoming and outgoing data. These buffers temporarily hold data as it's being transmitted or received. Efficient memory management is essential for smooth communication.

2. STM32 F429ZI

The STM32 Nucleo-144 F429ZI board boasts several advantages, including a high-performance ARM Cortex-M4 core coupled with an extensive set of peripherals, enabling versatile and powerful embedded applications. Its Nucleo form factor offers compatibility with a wide range of expansion boards, facilitating rapid prototyping and development. The onboard integrated ST-LINK debugger/programmer simplifies debugging and programming tasks. Additionally, the board's large memory capacity and connectivity options make it well-suited for complex projects requiring substantial computational resources and communication capabilities.
STM32duino
STM32duino is an open-source project that aims to bring Arduino compatibility to STM32 microcontrollers. It allows developers to program STM32 microcontrollers using the Arduino IDE, which is a popular and user-friendly development environment often associated with Arduino boards. Here I used STM32duino for programming my board.
To get started with this just have a look here. This document also shows how to setup the ST-LINK pin which was changed due to overlapping use of the FMC(Flexible Memory controller) data pin to control the W5300 built in the W5300 TOE Shield and the ST-LINK pin of the STM32 Nucleo-144 board.
Wire Bridge For Serial Debugging
Case

 

The case for the Gateway was designed in Fusion 360 and printed with PLA.
A 5V 2A power brick is used to power the controller unit.

Firmware

#include <Arduino.h>
#include <Ethernet.h>
#include <PubSubClient.h>
#include <SPI.h>

/* Network */
#define SERVER_PORT 5000
#define MAX_CLIENT  8
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };

const char* UID="KIT ID";

EthernetServer server(SERVER_PORT);
void print_network_info(void);

EthernetClient ethClient;
PubSubClient mqttClient(ethClient);

const char* mqttServer = "test.mosquitto.org";
const int mqttPort = 1883;
const char* mqttClientId = "myClientIDs";

// Array to store connected clients
EthernetClient clients[MAX_CLIENT];
EthernetClient client;

void setup() {
  Serial.begin(115200);
  Serial3.setRx(PC11);
  Serial3.setTx(PC10);

  Ethernet.begin(mac);
  IPAddress localIP = Ethernet.localIP();
  IPAddress modifiedIP(localIP[0], localIP[1], localIP[2], 6);
  Ethernet.begin(mac, modifiedIP);
  print_network_info();

  server.begin();
  Serial.print("Server is at ");
  Serial.println(Ethernet.localIP());

  mqttClient.setServer(mqttServer, mqttPort);
  mqttClient.setCallback(subscribeReceive);

  connectToMQTTServer();
}

void loop() {
  if (!mqttClient.connected()) {
    reconnectToMQTTServer();
  }
  mqttClient.loop();

  // Handle new client connections
  EthernetClient newClient = server.accept();
  if (newClient) {
    for (byte i = 0; i < MAX_CLIENT; i++) {
      if (!clients[i]) {
        Serial.printf("We have a new client # %d\r\n", i);
        clients[i] = newClient;
        break;
      }
    }
  }

  // Handle client data and publish to MQTT
  for (byte i = 0; i < MAX_CLIENT; i++) {
    if (clients[i] && clients[i].available() > 0) {
      byte buffer[80];
      int count = clients[i].read(buffer, 80);
      //Serial.write(buffer, count);

      if (count>0) {
        String topic = UID;
        String datum = String((char*)buffer);
        datum.trim();
        Serial.println(datum);
        if (mqttClient.publish(topic.c_str(), datum.c_str())) {
            //Serial.println(String((char*)buffer)+":");
            Serial.println("Publish message success");
            //break;
          } else {
            Serial.println("Publish message failed");
            //break;
          }
      }
    }

  }


  // Check for disconnected clients and remove them
  for (byte i = 0; i < MAX_CLIENT; i++) {
    if (clients[i] && !clients[i].connected()) {
      Serial.printf("Client #%d disconnected\r\n", i);
      clients[i].stop();
    }
  }
}

void connectToMQTTServer() {
  while (!mqttClient.connected()) {
    Serial.print("Connecting to MQTT server...");
    if (mqttClient.connect(mqttClientId)) {
      Serial.println("Connected");
      mqttClient.subscribe(UID);
    } else {
      Serial.print("Failed, retrying in 5 seconds...");
      delay(5000);
    }
  }
}

void reconnectToMQTTServer() {
  if (!mqttClient.connected()) {
    Serial.println("MQTT connection lost. Reconnecting...");
    connectToMQTTServer();
  }
}

void print_network_info(void) {
  byte print_mac[] = { 0, };
  Serial.println("\r\n-------------------------------------------------");
  Serial.printf("MAC        : ");
  Ethernet.MACAddress(print_mac);
  for (byte i = 0; i < 6; i++) {
    Serial.print(print_mac[i], HEX);
    if (i < 5) {
      Serial.print(":");
    }
  }
  Serial.println();
  Serial.printf("IP         : ");
  Serial.print(Ethernet.localIP());
  Serial.printf(": %d\r\n", SERVER_PORT);
  Serial.println("-------------------------------------------------");
}

void subscribeReceive(char* topic, byte* payload, unsigned int length) {
    for (byte i = 0; i < MAX_CLIENT; i++) {
      client = clients[i];
      if (client && client.connected()) {
        client.write(payload, length);
      }
    }
}

Node

1. WizFi360-EVB-Pico

 

WizFi360-EVB-Pico is the Controller unit used in the Node. It is a development board based on Raspberry Pi RP2040 and adds Wi-Fi connectivity using WizFi360. These features collectively make the WizFi360-EVB-Pico a versatile and powerful platform for Wi-Fi-enabled projects, combining the capabilities of the RP2040 microcontroller with seamless wireless connectivity.
  • Microcontroller: RP2040 with 2MByte Flash, dual-core Cortex-M0+ up to 133MHz, 264kByte SRAM, and Quad-SPI Flash (XIP).
  • Wi-Fi Connectivity: Integrated WizFi360-PA module supporting 2.4GHz Wi-Fi (802.11 b/g/n).
  • Operating Modes: Supports Station, SoftAP, SoftAP+Station, data pass-through, and AT command data transfer.
  • Networking Modes: TCP Server, TCP Client, and UDP modes for versatile communication.
  • Wireless Configuration: Channel selection (0-13), auto bandwidth (20MHz/40MHz), WPA_PSK/WPA2_PSK encryption.
  • Industrial Grade: Operating temperature -40°C to 85°C, CE and FCC certified.
  • Memory: Includes 16M-bit Flash Memory, micro-USB B port for power and data.
  • Form Factor: 40-pin 21x51 'DIP' style PCB, 3-pin ARM SWD port, built-in LDO for voltage regulation.

2. Sensors or Actuators

A sensor is a device that detects or measures physical changes in its environment and converts them into a signal that can be understood by humans or other systems. Sensors are like the "senses" of electronic devices. Just as our eyes see light, our skin feels touch, and our ears hear sound, sensors enable electronic devices to perceive the world around them.
An actuator is a device that takes a signal or command from a control system (such as a computer or a sensor) and converts it into physical action or movement. In essence, an actuator is what causes something to move, change position, or perform a specific action based on an input signal.
Any sensor or actuator can be connected to the EVB pico.
3. LiPo Battery
A Lipo battery having a voltage of 3.7V and a capacity of 500mAh will be used to power the WizFi360-EVB-Pico through the Main system input voltage(VSYS). The decision to choose a LiPo battery for the device was driven by its approximately fourfold higher energy density compared to nickel-cadmium or nickel metal hydride batteries, as well as its lightweight and flexible characteristics.
4. TP4056 Charging Module
TP4056 Charging controller is used to safely charge and discharge the lipo battery. This module offers a 1-ampere charging current by default.
So I reduced the charging current by changing R3(Rprog) resistor to 4.7K by checking the datasheet.
5. Switch
A 4mm SPDT 1P2T Slide Switch is used to ON and OFF the Node. It also hinders the load while charging.
6. Case
All the components will be enclosed in the case. The case design may vary depending on the sensor or actuator used.
A PCB will be used to mount all the components. This is an outline of the PCB in the Node.
Note: WizFi360-EVB-Pico is a common component in all Nodes. All other components except this thing will change according to the sensor or actuator.
The node will consist of two parts, the upper part and the backplate. M3x10mm screws are used to secure these two.
Firmware

A sample code for a node to send data to ethernet server is shown below

#include "WizFi360.h"
#include <DHT.h>

// setup according to the device you use
#define WIZFI360_EVB_PICO

// Emulate Serial1 on pins 6/7 if not present
#ifndef HAVE_HWSERIAL1
#include "SoftwareSerial.h"
#if defined(ARDUINO_MEGA_2560)
SoftwareSerial Serial1(6, 7); // RX, TX
#elif defined(WIZFI360_EVB_PICO)
SoftwareSerial Serial2(6, 7); // RX, TX
#endif
#endif

/* Baudrate */
#define SERIAL_BAUDRATE   115200
#if defined(ARDUINO_MEGA_2560)
#define SERIAL1_BAUDRATE  115200
#elif defined(WIZFI360_EVB_PICO)
#define SERIAL2_BAUDRATE  115200
#endif

/* Wi-Fi info */
char ssid[] = "";       // your network SSID (name)
char pass[] = "";   // your network password

int status = WL_IDLE_STATUS;  // the Wifi radio's status

unsigned long lastConnectionTime = 0;         // last time you connected to the server, in milliseconds
const unsigned long postingInterval = 1000L; // delay between updates, in milliseconds

// Sensor
#define DHT_PIN 28
DHT dht(DHT_PIN, DHT11);

// Initialize the Ethernet client object
WiFiClient client;

void setup() {
  // initialize serial for debugging
  Serial.begin(SERIAL_BAUDRATE);
  // initialize serial for WizFi360 module
#if defined(ARDUINO_MEGA_2560)
  Serial1.begin(SERIAL1_BAUDRATE);
#elif defined(WIZFI360_EVB_PICO)
  Serial2.begin(SERIAL2_BAUDRATE);
#endif
  // initialize WizFi360 module
#if defined(ARDUINO_MEGA_2560)
  WiFi.init(&Serial1);
#elif defined(WIZFI360_EVB_PICO)
  WiFi.init(&Serial2);
#endif

  // check for the presence of the shield
  if (WiFi.status() == WL_NO_SHIELD) {
    Serial.println("WiFi shield not present");
    // don't continue
    while (true);
  }

  // attempt to connect to WiFi network
  while (status != WL_CONNECTED) {
    Serial.print("Attempting to connect to WPA SSID: ");
    Serial.println(ssid);
    // Connect to WPA/WPA2 network
    status = WiFi.begin(ssid, pass);
  }

  Serial.println("You're connected to the network");

  printWifiStatus();

  dht.begin();
}

void loop() {
  // if there's incoming data from the net connection send it out the serial port
  // this is for debugging purposes only
  while (client.available()) {
    char c = client.read();
    Serial.write(c);
  }

  // if 10 seconds have passed since your last connection,
  // then connect again and send data
  if (millis() - lastConnectionTime > postingInterval) {
    httpRequest();
  }
}

// this method makes an HTTP connection to the server
void httpRequest() {
  IPAddress server = WiFi.localIP();
  server[3] = 6;
  Serial.println(server);
  Serial.println();
  float temperature = dht.readTemperature();
  float humidity = dht.readHumidity();
  Serial.print("Temp: ");
  Serial.print(temperature);
  Serial.println();

  // close any connection before sending a new request
  // this will free the socket on the WiFi shield
  //client.stop();

  // if there's a successful connection
  if (client.connect(server, 5000)) {
    Serial.println("Connected");
    String TempData = "TEMP:"+String(temperature);
    String HumidityData = "HUMIDITY:"+String(humidity);
    Serial.println(TempData);
    // send the HTTP PUT request
    client.print(TempData);
    client.println();
    client.print(HumidityData);
    Serial.println("Message Sent");
    client.stop();

    // note the time that the connection was made
    lastConnectionTime = millis();
  }
  else {
    // if you couldn't make a connection
    Serial.println("Connection failed");
    delay(100);
  }
}

void printWifiStatus() {
  // print the SSID of the network you're attached to
  Serial.print("SSID: ");
  Serial.println(WiFi.SSID());

  // print your WiFi shield's IP address
  IPAddress ip = WiFi.localIP();
  Serial.print("IP Address: ");
  Serial.println(ip);

  // print the received signal strength
  long rssi = WiFi.RSSI();
  Serial.print("Signal strength (RSSI):");
  Serial.print(rssi);
  Serial.println(" dBm");
}
The basic code used for WiFi provisioning is shown below.
#include "WizFi360.h"
#include "provision.h"
#include <EEPROM.h>

// setup according to the device you use
#define WIZFI360_EVB_PICO

// Emulate Serial1 on pins 6/7 if not present
#ifndef HAVE_HWSERIAL1
#include "SoftwareSerial.h"
#if defined(ARDUINO_MEGA_2560)
SoftwareSerial Serial1(6, 7); // RX, TX
#elif defined(WIZFI360_EVB_PICO)
SoftwareSerial Serial2(6, 7); // RX, TX
#endif
#endif
/* Baudrate */
#define SERIAL_BAUDRATE   115200
#if defined(ARDUINO_MEGA_2560)
#define SERIAL1_BAUDRATE  115200
#elif defined(WIZFI360_EVB_PICO)
#define SERIAL2_BAUDRATE  115200
#endif

char ap_ssid[] = "wiznet";
char ap_pass[] = "0123456789";

int status = WL_IDLE_STATUS;

WiFiServer server(80);

char ssid[32]; // Max SSID length
char pass[64]; // Max password length
const int ssidAddress = 0;
const int passAddress = ssidAddress + sizeof(ssid);


void setup() {
  // initialize serial for debugging
  Serial.begin(SERIAL_BAUDRATE);
  // initialize serial for WizFi360 module
#if defined(ARDUINO_MEGA_2560)
  Serial1.begin(SERIAL1_BAUDRATE);
#elif defined(WIZFI360_EVB_PICO)
  Serial2.begin(SERIAL2_BAUDRATE);
#endif
  // initialize WizFi360 module
#if defined(ARDUINO_MEGA_2560)
  WiFi.init(&Serial1);
#elif defined(WIZFI360_EVB_PICO)
  WiFi.init(&Serial2);
#endif


  if (WiFi.status() == WL_NO_SHIELD) {
    Serial.println("WiFi shield not present");
    while (true);
  }

  EEPROM.get(ssidAddress, ssid);
  EEPROM.get(passAddress, pass);

  Serial.print("Attempting to start AP ");
  Serial.println(ssid);

  if (ssid[0] == '\0' || pass[0] == '\0') {
    Serial.println(ssid);
    startAPMode();
  } else {
    connectToWiFi();
  }
  printWifiStatus();

  server.begin();
  Serial.println("Server started");
}

void loop() {
  WiFiClient client = server.available();

  if (client) {
    Serial.println("New client");
    String request = "";
    while (client.connected()) {
      if (client.available()) {
        char c = client.read();
        request += c;

        if (request.endsWith("\r\n\r\n")) {
          if (request.indexOf("GET /submit") != -1) {
            handleFormSubmission(client, request);
          } else {
            sendHtmlForm(client);
          }
          break;
        }
      }
    }

    delay(10);
    client.stop();
    Serial.println("Client disconnected");
  }
}

void sendHtmlForm(WiFiClient client) {
  client.print(
    "HTTP/1.1 200 OK\r\n"
    "Content-Type: text/html\r\n"
    "Connection: close\r\n"
    "\r\n");
  client.println(home_0);
  client.println(home_1);
  client.println(home_2);
  client.println(home_3);

}

void handleFormSubmission(WiFiClient client, String request) {
  String ssidValue, passwordValue;
  getSsidAndPasswordFromRequest(request, ssidValue, passwordValue);

  Serial.println("Entered SSID: " + ssidValue);
  Serial.println("Entered Password: " + passwordValue);

  // Store credentials in EEPROM
  ssidValue.toCharArray(ssid, sizeof(ssid));
  passwordValue.toCharArray(pass, sizeof(pass));
  Serial.println(ssid);
  Serial.println(pass);
  EEPROM.put(ssidAddress, ssid);
  EEPROM.put(passAddress, pass);
  EEPROM.commit();

  Serial.println("Values in EEPROM:");
  Serial.println("SSID: " + String(ssid));
  Serial.println("Password: " + String(pass));

  client.print(
    "HTTP/1.1 200 OK\r\n"
    "Content-Type: text/html\r\n"
    "Connection: close\r\n"
    "\r\n");
  client.print(response_0);

  //resetWizFi360();
  delay(5000);

resetWiFiModule();
  delay(1000);
  connectToWiFi();
}

void startAPMode() {
  // Start access point and set up web server
  WiFi.beginAP(ap_ssid, 10, ap_pass, ENC_TYPE_WPA2_PSK);
  IPAddress localIp(192, 168, 2, 100);
  WiFi.configAP(localIp);
  
  // Set up the web server for configuration
  server.begin();
  Serial.println("Access point started");
  Serial.println("Server started");
}

void connectToWiFi() {
  WiFi.disconnect(); // Disconnect from any previous connections
  Serial.println("SSID: "+String(ssid));
  Serial.println("Password: "+String(pass));
  WiFi.begin(ssid, pass);
  int retry = 0;
  while (WiFi.status() != WL_CONNECTED && retry < 10) {
    delay(1000);
    Serial.println("Connecting to WiFi...");
    retry++;
  }

  if (WiFi.status() == WL_CONNECTED) {
    Serial.println("Connected to WiFi");
  } else {
    Serial.println("Failed to connect to WiFi");
    startAPMode(); // Enter AP mode if failed to connect
  }
}

void getSsidAndPasswordFromRequest(String request, String &ssidValue, String &passwordValue) {
  int ssidIndex = request.indexOf("ssid=");
  int passwordIndex = request.indexOf("password=");
  int spaceIndex = request.indexOf("HTTP/1.1");

  if (ssidIndex != -1 && passwordIndex != -1 && spaceIndex != -1) {
    ssidValue = request.substring(ssidIndex + 5, passwordIndex - 1); // 5 is the length of "ssid="
    passwordValue = request.substring(passwordIndex + 9, spaceIndex-1); // 9 is the length of "password="
  } else {
    ssidValue = "";
    passwordValue = "";
  }
}


void printWifiStatus() {
  // print the SSID of the network you're attached to
  Serial.print("SSID: ");
  Serial.println(WiFi.SSID());

  // print your WiFi shield's IP address
  IPAddress ip = WiFi.localIP();
  Serial.print("IP Address: ");
  Serial.println(ip);

  // print the received signal strength
  long rssi = WiFi.RSSI();
  Serial.print("Signal strength (RSSI):");
  Serial.print(rssi);
  Serial.println(" dBm");
}

void resetWiFiModule() {
  WiFi.disconnect();
  delay(1000); // Wait for module to fully disconnect
  WiFi.init(&Serial2); // Initialize WiFi module again

  Serial.println("WiFi module reset");
}
Sample Nodes
1. Air Quality
This node is based on the MQ2 gas sensor. 
 
The MQ2 gas sensor is a versatile module known for detecting flammable gases and smoke using a metal oxide semiconductor sensing mechanism. It provides an analog voltage output that varies with the concentration of the detected gas, and its sensitivity can be adjusted using a potentiometer.
 
Here the node will calculate Air Quality Index(AQI) and pass it to the gateway.
2. Temperature & Humidity
This node is based on the DHT11 sensor. 
 
The DHT11 sensor is a basic digital temperature and humidity sensor that provides accurate measurements of these two environmental parameters. It utilizes a capacitive humidity sensor and a thermistor for temperature measurement.
This node will measure the temperature and humidity and pass it to the gateway.
3. Smart Plug
This node is based on the Relay. 
 
A relay is an electrically operated switch that uses an electromagnet to mechanically open or close its contacts, allowing it to control the flow of current in one circuit by applying a voltage to another.
So any actuators which can't be directly controlled with the controller can be controlled using this smart plug.
Apart from the other nodes, It has some variations. First, is the outline and the second is the controller is directly powered through the micro USB. There are no batteries and the charge controller.
You can easily connect any device through its female adapter. In the demo video, a zero-watt bulb is attached to it via the Parallel Adapter with Plug and Light Socket.
4. Motion detector
This node is based on the PIR motion sensor. 
The Passive Infrared (PIR) motion sensor is a widely used device for detecting human movement by sensing changes in infrared radiation emitted by warm objects. It contains a pyroelectric sensor that generates electrical signals when exposed to variations in infrared radiation within its field of view. PIR sensors are commonly integrated into security systems, lighting controls, and automated appliances to trigger actions based on detected motion, making them essential components for energy-efficient and security-conscious environments.
 
This node will give an alert to the user if there any human movements are detected.

Deploying Modular IoT in the Low Energy Realm with BLE

In our relentless pursuit of innovation, we have extended the boundaries of the Modular IoT Kit to a BLE (Bluetooth Low Energy) variant, ushering in a new era of efficiency and longevity within the realm of low-energy IoT applications. With an emphasis on prolonging the lifespan of battery-powered nodes, while optimizing their size, our BLE variant stands as a testament to our commitment to cutting-edge solutions.

One of the cornerstones of this evolution is the strategic incorporation of BLE technology. Designed to minimize power consumption, BLE ensures that nodes equipped with smaller batteries can operate significantly longer, extending their operational lifetime and minimizing maintenance requirements. This optimization paves the way for smaller node designs, seamlessly merging energy efficiency with compact form factors.

Architecturally distinct from its predecessor, the BLE variant unveils a refined architecture to cater to its unique demands. At the heart of the gateway lies the W5300 chipset, synergistically coupled with the STM32 F429ZI microcontroller. Serially integrated with the Seeed Studio Xiao ESP32C3, this combination enables BLE connectivity to the gateway, thereby enriching the interaction possibilities. Notably, the ESP32C3 assumes the role of a multi-connect BLE server, accommodating simultaneous connections from multiple BLE clients—a pivotal enhancement in scalability.

Within this architecture, data flow is meticulously orchestrated. Data received from BLE clients is seamlessly relayed to the STM32F429ZI microcontroller through a serial port connection. Subsequently, this data is directed to the W5300 chipset, where it is encapsulated for MQTT transmission. The 16-bit bus in W5300 TOE Shield enables the real-time transfer of data between STM32 F429ZI and W5300 without any delays. Leveraging the ethernet client encapsulated with the pub-sub client, data is then efficiently transmitted to the MQTT broker—a seamless journey from BLE to MQTT.

Turning our attention to the node side, the Xiao ESP32C3 once again takes center stage. Here, it operates in BLE client mode, effectively facilitating bidirectional data exchange with the server. This communication dynamic underscores the interoperability of our ecosystem, where the BLE-enabled nodes actively engage in data exchange with the gateway.

Undoubtedly, the smaller form factor and optimized energy consumption of the Xiao ESP32C3 emerge as pivotal features on the node side. These attributes collectively translate into nodes that are not only compact but also demonstrate an extended operational lifespan—a synthesis of technology and innovation.

By integrating BLE technology, we introduce a new chapter in low-energy IoT landscapes, exemplifying our commitment to innovation.

Topology and Data Flow

Gateway

In the Bluetooth variant also the gateway is comprised of a W5300 TOE shield and STM32 Nucleo 144 F429ZI. In addition to that ESP32 C3 will be added to the gateway for giving Bluetooth capability.

The ESP32C3 serially communicates with Nucleo 144 to pass and retrieve data from Nucleo 144. The USART2 (PA3 & PD5) of the STM32 Nucleo 144 is used for communication. The communication speed of the W5300 TOE (TCP/IP Offload Engine) shield is a significant advantage in this scenario, particularly in the context of high-speed data exchange between the STM32 Nucleo 144 and the W5300 TOE Shield.

Firmware

We have developed the code for STM32 F429ZI coupled with W5300 to create an ethernet client and to serially read from the USART2 port of STM32 F429ZI.

#include <Ethernet.h>
#include <PubSubClient.h>
#include <SPI.h>
#include <HardwareSerial.h>

HardwareSerial Serial2(PA3,PD5);

cons char* UID = "KIT ID";

/* Network */
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };

EthernetClient ethClient;
PubSubClient mqttClient(ethClient);

const char* mqttServer = "test.mosquitto.org";
const int mqttPort = 1883;
const char* mqttClientId = "myClientIDs";


void connectToMQTTServer() {
  while (!mqttClient.connected()) {
    Serial.print("Connecting to MQTT server...");
    if (mqttClient.connect(mqttClientId)) {
      Serial.println("Connected");
      mqttClient.subscribe(UID);
    } else {
      Serial.print("Failed, retrying in 5 seconds...");
      delay(5000);
    }
  }
}

void reconnectToMQTTServer() {
  if (!mqttClient.connected()) {
    Serial.println("MQTT connection lost. Reconnecting...");
    connectToMQTTServer();
  }
}

void print_network_info(void) {
  byte print_mac[] = { 0, };
  Serial.println("\r\n-------------------------------------------------");
  Serial.printf("MAC        : ");
  Ethernet.MACAddress(print_mac);
  for (byte i = 0; i < 6; i++) {
    Serial.print(print_mac[i], HEX);
    if (i < 5) {
      Serial.print(":");
    }
  }
  Serial.println();
  Serial.printf("IP         : ");
  Serial.print(Ethernet.localIP());
  Serial.println("-------------------------------------------------");
}


void setup() {
  Serial.begin(115200);
  Serial3.setRx(PC11);
  Serial3.setTx(PC10);
  Serial2.begin(9600);

  Ethernet.begin(mac);
  IPAddress localIP = Ethernet.localIP();
  IPAddress modifiedIP(localIP[0], localIP[1], localIP[2], 5);
  Ethernet.begin(mac, modifiedIP);
  print_network_info();

  mqttClient.setServer(mqttServer, mqttPort);
  //mqttClient.setCallback(subscribeReceive);
  mqttClient.subscribe(UID);
  connectToMQTTServer();
}

void loop() {
  if (!mqttClient.connected()) {
    reconnectToMQTTServer();
  }
  mqttClient.loop();
  
  if (Serial2.available() > 0) {
        String sensorData = Serial2.readStringUntil('\n');
        sensorData.trim(); // Remove leading/trailing whitespace characters
        String topic = UID;
        String datum = sensorData;
        
        if (sensorData.length() > 0) {
          if (mqttClient.publish(topic.c_str(), datum.c_str())) {
            Serial.println(datum.c_str());
            Serial.println("Publish message success");
          } else {
            Serial.println("Publish message failed");
          }
        }
      }
    delay(1);
  }

Also, we have XIAO ESP32C3 that act as a multi-connect BLE Server, that receives data from the nodes and sends it serially to STM32 F429ZI. The code for ESP32C3 is shown below.

#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>

BLEServer* pServer = NULL;
BLECharacteristic* pCharacteristic = NULL;
bool deviceConnected = false;
bool oldDeviceConnected = false;
uint32_t value = 0;

#define SERVICE_UUID        "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"

class MyServerCallbacks: public BLEServerCallbacks {
  void onConnect(BLEServer* pServer) {
    deviceConnected = true;
    BLEDevice::startAdvertising();
  }

  void onDisconnect(BLEServer* pServer) {
    deviceConnected = false;
  }
};

class MyCharacteristicCallbacks : public BLECharacteristicCallbacks {
  void onWrite(BLECharacteristic* pCharacteristic) {
    std::string value = pCharacteristic->getValue();
    Serial.print("Received message from BLE Client: ");
    Serial1.println(value.c_str());
    Serial.println(value.c_str());
  }
};

void setup() {
  Serial.begin(115200);
  Serial1.begin(9600, SERIAL_8N1, 4,5);
  
  BLEDevice::init("ESP32");

  pServer = BLEDevice::createServer();
  pServer->setCallbacks(new MyServerCallbacks());

  BLEService *pService = pServer->createService(SERVICE_UUID);

  pCharacteristic = pService->createCharacteristic(
    CHARACTERISTIC_UUID,
    BLECharacteristic::PROPERTY_READ |
    BLECharacteristic::PROPERTY_WRITE |
    BLECharacteristic::PROPERTY_NOTIFY |
    BLECharacteristic::PROPERTY_INDICATE
  );

  pCharacteristic->addDescriptor(new BLE2902());
  pCharacteristic->setCallbacks(new MyCharacteristicCallbacks());
  pCharacteristic->setValue("Hello World");
  pService->start();

  BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
  pAdvertising->addServiceUUID(SERVICE_UUID);
  pAdvertising->setScanResponse(false);
  pAdvertising->setMinPreferred(0x0);
  BLEDevice::startAdvertising();
  Serial.println("Characteristic defined! Now you can read and write it from your phone!");
}

void loop() {

  if (!deviceConnected && oldDeviceConnected) {
    delay(500);
    pServer->startAdvertising();
    Serial.println("Start advertising");
    oldDeviceConnected = deviceConnected;
  }

  if (deviceConnected && !oldDeviceConnected) {
    oldDeviceConnected = deviceConnected;
  }
}

Node

The key component in the Node is also a Seeed Studio XIAO ESP32C3 with a sensor. One of the key things about the XIAO ESP32C3 is that it can be easily charged via its USB C port because it contains its battery charger, we don't need any extra charger. Here is the general outline of the Node.

Due to its smaller size, we used a small battery with a capacity of 150 mah, and this is the design of the Node.

Firmware

On the node side, we have developed the firmware in such a way that the ESP32C3 will act as a BLE client and send the sensor data to the multiconnect BLE server. A sample code is shown below.



#include "BLEDevice.h"
//#include "BLEScan.h"

// The remote service we wish to connect to.
static BLEUUID serviceUUID("4fafc201-1fb5-459e-8fcc-c5c9c331914b");
// The characteristic of the remote service we are interested in.
static BLEUUID charUUID("beb5483e-36e1-4688-b7f5-ea07361b26a8");

static boolean doConnect = false;
static boolean connected = false;
static boolean doScan = false;
static BLERemoteCharacteristic* pRemoteCharacteristic;
static BLEAdvertisedDevice* myDevice;

int tempPin = 2;

static void notifyCallback(
  BLERemoteCharacteristic* pBLERemoteCharacteristic,
  uint8_t* pData,
  size_t length,
  bool isNotify) {
    Serial.print("Notify callback for characteristic ");
    Serial.print(pBLERemoteCharacteristic->getUUID().toString().c_str());
    Serial.print(" of data length ");
    Serial.println(length);
    Serial.print("data: ");
    Serial.write(pData, length);
    Serial.println();
}

class MyClientCallback : public BLEClientCallbacks {
  void onConnect(BLEClient* pclient) {
  }

  void onDisconnect(BLEClient* pclient) {
    connected = false;
    Serial.println("onDisconnect");
  }
};

bool connectToServer() {
    Serial.print("Forming a connection to ");
    Serial.println(myDevice->getAddress().toString().c_str());
    
    BLEClient*  pClient  = BLEDevice::createClient();
    Serial.println(" - Created client");

    pClient->setClientCallbacks(new MyClientCallback());

    // Connect to the remove BLE Server.
    pClient->connect(myDevice);  // if you pass BLEAdvertisedDevice instead of address, it will be recognized type of peer device address (public or private)
    Serial.println(" - Connected to server");
    pClient->setMTU(517); //set client to request maximum MTU from server (default is 23 otherwise)
  
    // Obtain a reference to the service we are after in the remote BLE server.
    BLERemoteService* pRemoteService = pClient->getService(serviceUUID);
    if (pRemoteService == nullptr) {
      Serial.print("Failed to find our service UUID: ");
      Serial.println(serviceUUID.toString().c_str());
      pClient->disconnect();
      return false;
    }
    Serial.println(" - Found our service");


    // Obtain a reference to the characteristic in the service of the remote BLE server.
    pRemoteCharacteristic = pRemoteService->getCharacteristic(charUUID);
    if (pRemoteCharacteristic == nullptr) {
      Serial.print("Failed to find our characteristic UUID: ");
      Serial.println(charUUID.toString().c_str());
      pClient->disconnect();
      return false;
    }
    Serial.println(" - Found our characteristic");

    // Read the value of the characteristic.
    if(pRemoteCharacteristic->canRead()) {
      std::string value = pRemoteCharacteristic->readValue();
      Serial.print("The characteristic value was: ");
      Serial.println(value.c_str());
    }

    if(pRemoteCharacteristic->canNotify())
      pRemoteCharacteristic->registerForNotify(notifyCallback);

    connected = true;
    return true;
}

class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks {

  void onResult(BLEAdvertisedDevice advertisedDevice) {
    Serial.print("BLE Advertised Device found: ");
    Serial.println(advertisedDevice.toString().c_str());


    if (advertisedDevice.haveServiceUUID() && advertisedDevice.isAdvertisingService(serviceUUID)) {

      BLEDevice::getScan()->stop();
      myDevice = new BLEAdvertisedDevice(advertisedDevice);
      doConnect = true;
      doScan = true;

    } 
  } 
}; 


void setup() {
  pinMode(tempPin, INPUT);
  Serial.begin(115200);
  Serial.println("Starting Arduino BLE Client application...");
  BLEDevice::init("Client");


  BLEScan* pBLEScan = BLEDevice::getScan();
  pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
  pBLEScan->setInterval(1349);
  pBLEScan->setWindow(449);
  pBLEScan->setActiveScan(true);
  pBLEScan->start(5, false);
} 

void loop() {

  if (doConnect == true) {
    if (connectToServer()) {
      Serial.println("We are now connected to the BLE Server.");
    } else {
      Serial.println("We have failed to connect to the server; there is nothin more we will do.");
    }
    doConnect = false;
  }

  float temp = analogRead(tempPin)*0.1039;

  if (connected) {
    String newValue = "TEMP:" + String(temp);
    Serial.println(newValue);
    

    pRemoteCharacteristic->writeValue(newValue.c_str(), newValue.length());
  }else if(doScan){
    BLEDevice::getScan()->start(0); 
  }
  
  delay(1000); // Delay a second between loops.
} 

Here are the sample nodes.

1. Temperature Node

This node is based on the LM35 sensor. It is used to send the temperature value to the cloud.

2. Light Intensity Node

This node is based on the LDR. It will update the light intensity to the cloud.

Conclusion

Modular IoT Kit is not just a product; it's a paradigm shift. Its user-first approach tears down the complexity barriers, making IoT accessible to all, regardless of technical prowess.

The dashboard serves as a digital canvas, where data comes alive through widgets you can effortlessly add, resize, and arrange. The dashboard's creation relies on familiar web technologies—HTML, CSS, JS—enhanced by the Interact JS library for seamless interaction. MQTT JS ensures real-time connectivity, cementing the dashboard's role as a control center.

Enter the BLE-enabled variant, a marvel in efficiency. With Bluetooth Low Energy, nodes thrive on minimal power, expanding their lifespan remarkably. The gateway's architecture orchestrates intricate data flow, connecting BLE clients to MQTT brokers. It's a symphony of innovation, scaling connections effortlessly.

Looking ahead, the Modular IoT Kit redefines possibility. It's more than technology; it's empowerment. As everyday individuals harness the potential of IoT, industries are poised for transformation. This democratization of innovation is set to revolutionize how we live and work, ushering in an era where connected devices shape a smarter, more efficient world.

In essence, the Modular IoT Kit isn't just shaping IoT; it's shaping the future—yours, mine, and everyone's. It's an invitation to create, innovate, and connect, marking a turning point where technology bows to simplicity and everyone holds the reins of progress. Welcome to the revolution.

Documents
  • Code

  • Schematics

  • BOM

Comments Write