Most basic R4 to Python over WiFi

I see this one come up fairly often and I want to show how to connect the two but I always have too much other code around it. I wanted to try something super minimal.

The intent is to send an analog read to python and display it on the screen to demonstrate Arduino to Python, and send on and off times for the blinking from Python to Arduino.

So here's the Arduino side. It's going to run blink without delay with different on and off times and set up a WiFi server to listen to a socket from the python script. The client code is a version of @Robin2's code with the Serial swapped out for a WiFi client.

It will receive packets with < and > end markers. The first letter determines the intent and the rest is data. H sets the HIGH period and L sets the LOW period for the blink. So H50 set's the HIGH period to 50ms.

Also every 1 second it sends an analogRead from pin A0 back to the python code.



#include "WiFiS3.h"
#include "arduino_secrets.h"

const int ledPin = 13;

/// Blink variables
unsigned long lastMillis = 0;
unsigned long onTime = 500;
unsigned long offTime = 500;

// @Robin2 Receive with start and end markers variables
const byte numChars = 32;
char receivedChars[numChars];

// Variables for the WiFi Access Point
// char ssid[] = "WiFi_Test_AP";
// char pass[] = "";

WiFiServer server(2080);
IPAddress ipAddress(192, 168, 1, 77);

WiFiClient client;

void setup() {

  Serial.begin(115200);
  while (!Serial)
    ;
  Serial.println("\n\n *** " __FILE__ " ***\n\n");

  pinMode(ledPin, OUTPUT);

  startWifi();
  // Start the server
  server.begin();
  printWiFiStatus();
}

void loop() {
  blink();
  recvWithStartEndMarkers();
}


void blink() {
  static unsigned long last = millis();
  unsigned long now = millis();
  static bool ledOn = false;
  if (now - last >= (ledOn ? onTime : offTime)) {
    last = now;
    ledOn = !ledOn;
    digitalWrite(ledPin, (ledOn ? HIGH : LOW));
  }
}

//  @Robin2  see: https://forum.arduino.cc/t/serial-input-basics-updated/382007
void recvWithStartEndMarkers() {
  static boolean recvInProgress = false;
  static byte ndx = 0;
  char startMarker = '<';
  char endMarker = '>';
  char rc;

  static unsigned long lastSend = millis();

  if (client && client.connected()) {
    while (client.available() > 0) {
      rc = client.read();

      if (recvInProgress == true) {
        if (rc != endMarker) {
          receivedChars[ndx] = rc;
          ndx++;
          if (ndx >= numChars) {
            ndx = numChars - 1;
          }
        } else {
          receivedChars[ndx] = '\0';  // terminate the string
          recvInProgress = false;
          ndx = 0;
          processReceivedChars();
        }
      }

      else if (rc == startMarker) {
        recvInProgress = true;
      }
    }
    if(client.connected() && (millis() - lastSend >= 1000)) {
      lastSend = millis();
      int ana = analogRead(A0);
      client.print("<A");
      client.print(ana);
      client.print(">");
      Serial.println(ana);
    }
  }
  else {
    client = server.available();
  }
}

void processReceivedChars() {
  // print the command to be sure what we got
  Serial.print("New Command : ");
  Serial.println(receivedChars);

  unsigned long newVal = 0;
  // The first character is the command and the new value starts after that.
  switch (receivedChars[0]) {
    case 'H':  // Set the HIGH - ON time
      // strtoul converts ascii text to an unsigned long
      newVal = strtoul(receivedChars + 1, NULL, DEC);
      onTime = newVal;
      break;

    case 'L':  // Set the LOW - OFF time
      newVal = strtoul(receivedChars + 1, NULL, DEC);
      offTime = newVal;
      break;

    default:
      Serial.println("Unknown Command");
  }
}

// From AP_SimpleWebServer example in Arduino IDE (setup function)
void startWifi() {
  // check for the WiFi module:
  if (WiFi.status() == WL_NO_MODULE) {
    Serial.println("Communication with WiFi module failed!");
    // don't continue
    while (true)
      ;
  }
  // check module firmware version
  if (WiFi.firmwareVersion() < WIFI_FIRMWARE_LATEST_VERSION) {
    Serial.println("Please upgrade the firmware");
  }
  WiFi.config(ipAddress);
  int status = WL_IDLE_STATUS;
  // attempt to connect to WiFi network:
  while (status != WL_CONNECTED) {
    Serial.print("Attempting to connect to Network named: ");
    Serial.println(ssid);                   // print the network name (SSID);

    // Connect to WPA/WPA2 network. Change this line if using open or WEP network:
    status = WiFi.begin(ssid, pass);
    // wait 10 seconds for connection:
    delay(10000);
  }

}

// From AP_SimpleWebServer example in Arduino IDE
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);
}

The Python side is all console. It's going to receive the data and every 100ms clear the console screen and print a 2 line UI. I've also got a TK version I'll post in a few.

To make input you Control-C to get a keyboard interrupt and that will pause the display and let you make input.

import socket, time
import threading, queue
from os import system


def main():
    ###  Create the socket and connect 
    socketArgs = ('192.168.1.77', 2080)
    arduino_socket = socket.socket(socket.AF_INET , socket.SOCK_STREAM)
    arduino_socket.connect(socketArgs)

    ### A queue to hold data from the socket
    sock_queue = queue.Queue()

    ### define a function to read from the socket
    def readFromSocket():
        while True:
            c = arduino_socket.recv(1)
            sock_queue.put(c)

    ### Create a thread to run the above function and start it. 
    sock_thread = threading.Thread(target=readFromSocket, args=(), daemon=True)
    sock_thread.start()


    input_string = ""
    parsing = False
    analog_read = ""

    ### This will be the main program loop
    while (True):
        ### The try will let us catch a keyboard interrupt (Ctrl-C)
        ### to stop the screen from clearing so you can input
        try:
            ### Read characters from the queue and process
            ### ala @Robin2
            while(sock_queue.qsize() > 0):
                c = sock_queue.get().decode("utf-8")
                if(c == '<'):
                    parsing = True
                    input_string = ""
                elif(c == '>'):
                    parsing = False
                    if input_string[0] == 'A':
                        analog_read = input_string[1:]
                elif parsing:
                    input_string += c
                
            ### Pause for 100ms so the screen doesnt flicker
            time.sleep(0.100)
            ###  Clear the console window
            system('clear')
            ###  Print the UI
            print("Easy Console App\n\n")
            print(analog_read)
        ###  Catch the keyboard interrupt and take input
        except KeyboardInterrupt:
            ###  Add the start and end markers to the input
            output_string = '<' + input("???") + '>'
            ###  Send the input to the arduino
            arduino_socket.send(output_string.encode("utf-8"))


if (__name__ == '__main__'):
    main()

It's about as bare bones as it gets.

Here's a version using Tk that creates a little window with the analog reading constantly updating and a place to put your input. It's a little more complicated, but a lot more usable.


import tkinter as tk
import threading, queue
import socket



def main():
    ###  Create a socket and a queue to hold the characters from it
    socketArgs = ('192.168.1.77', 2080)
    arduino_socket = socket.socket(socket.AF_INET , socket.SOCK_STREAM)   
    sock_queue = queue.Queue()

    main.stop_flag = False

    ### A function to read from the socket
    def readFromSocket():
        while not main.stop_flag:
            try:        
                arduino_socket.settimeout(2.0)
                c = arduino_socket.recv(1)
                if(c):
                    sock_queue.put(c)
            except TimeoutError:
                pass
    
    ### A thread to run the above function
    sock_thread = threading.Thread(target=readFromSocket, args=(), daemon=True)    


    ###  Create a tk window
    root = tk.Tk()
    root.title("Arduino R4-WiFi Communication")

    ### A function to connect the socket
    def connect():
        ### Connect the socket
        arduino_socket.connect(socketArgs)
        arduino_socket.setblocking(0)
        ### Start the thread that reads from the socket
        sock_thread.start()       
        ### call handleSocketInput after 10ms. 
        root.after(10, handleSocketInput)
        print("Connected") 

    ### A button to call the above function
    connect_button = tk.Button(root, text="Connect", command=connect)
    ### pack at the top and anchor to the left side
    connect_button.pack(anchor=tk.W)

    ### A frame to hold the entry and send button side by side
    input_frame = tk.Frame(root)
    ### An entry box for the user to type a command
    entry = tk.Entry(input_frame, width = 40)

    ### A function to send the contents of the entry to the Arduino
    def sendOutput():
        outStr = '<' + entry.get() + '>'
        arduino_socket.send(outStr.encode("utf-8"))

    ### A button to call the above function
    send_button = tk.Button(input_frame, text="Send", command=sendOutput)
    
    ### pack the entry and send buttons side by side
    entry.pack(side=tk.LEFT)
    send_button.pack(side=tk.LEFT)
    ### pack the frame containing the entry and send button below the connect button
    input_frame.pack()

    ### A frame to hold the data from the Arduino
    log_frame = tk.Frame(root)
    ### A label as a label and a label to hold the analog value. 
    analog_label = tk.Label(log_frame, text = "Analog_Reading :")
    reading_label = tk.Label(log_frame, text="0")
    ### Pack them side by side in the frame
    analog_label.pack(side=tk.LEFT)
    reading_label.pack(side=tk.LEFT)
    ### Pack the frame with the analog return under the input frame and anchor left. 
    log_frame.pack(anchor = tk.W)


    ### A function to gracefully close the connection and end
    def exitFunc():
        main.stop_flag = True
        ### wait for the thread to finish
        sock_thread.join()
        ### close the connection on the Arduino end
        arduino_socket.shutdown(socket.SHUT_RDWR)
        ### close the connection on the Python side
        arduino_socket.close()
        ### close the window
        root.destroy()

    ### A button to call the above function
    exit_button = tk.Button(root, text="Exit", command=exitFunc)
    exit_button.pack()

    ### A function to handle input from Arduino
    main.input_string = ""
    main.parsing = False
    def handleSocketInput():
        if(sock_queue.qsize() > 0):
            line = sock_queue.get()
            for c in line.decode("utf-8"):
                if (c == '<'):
                    main.input_string = ""
                    main.parsing = True
                elif (c == '>'):                    
                    reading_label.config(text=main.input_string[1:])
                    main.parsing = False
                elif main.parsing:
                    main.input_string = main.input_string + c
        ### call this function again after 10ms
        root.after(10, handleSocketInput) 
    root.mainloop()


if (__name__ == "__main__"):
    main()