Asked — Edited

Windows 11 Controller Python Integration

Hello @Athena,

I am currently exploring the feasibility of using a Windows 11-compatible controller to execute a Python client script that transmits XInput data to a Python server script on the ARC Skill Control Board. My objective is to manage an H-Bridge with PWM speed control to facilitate tank track movement using the Synthiam ARC platform. Do you think this setup can be achieved?

I am employing the Anbernic Win600 controller, as depicted in the image below:

User-inserted image

At present, I am endeavoring to deploy a Python-coded server on my ARC-enabled PC to process control signals. I’ve included the Python script I’m using for this configuration below.

#!/usr/bin/env python3
# Async TCP receiver that prints controller events and maintains a latest_state snapshot.

import asyncio, json, signal, sys
from collections import defaultdict

HOST = "0.0.0.0"
PORT = 5555
PRINT_EVENTS = True

latest_state = {
    "buttons": defaultdict(lambda: False),
    "axes": defaultdict(lambda: 0.0),
    "hat": {"x": 0, "y": 0},
    "t": 0.0,
}

def apply_event(ev):
    et = ev.get("type")
    latest_state["t"] = ev.get("t", latest_state["t"])

    if et == "button":
        name = ev.get("name", f"BTN{ev.get('index', -1)}")
        pressed = bool(ev.get("pressed", False))
        latest_state["buttons"][name] = pressed
        if PRINT_EVENTS:
            print(f"[BTN] {name}: {'DOWN' if pressed else 'UP'}")

    elif et == "axis":
        name = ev.get("name", f"AXIS{ev.get('index', -1)}")
        val = float(ev.get("value", 0.0))
        latest_state["axes"][name] = val
        if PRINT_EVENTS:
            print(f"[AXIS] {name}: {val:+.3f}")

    elif et == "hat":
        v = ev.get("value", {"x": 0, "y": 0})
        latest_state["hat"] = {"x": int(v.get("x", 0)), "y": int(v.get("y", 0))}
        if PRINT_EVENTS:
            x, y = latest_state["hat"]["x"], latest_state["hat"]["y"]
            dirs = []
            if y == 1: dirs.append("Up")
            if y == -1: dirs.append("Down")
            if x == -1: dirs.append("Left")
            if x == 1: dirs.append("Right")
            print(f"[HAT] DPad: {'+'.join(dirs) if dirs else 'Neutral'}")

async def handle_client(reader: asyncio.StreamReader, writer: asyncio.StreamWriter):
    peer = writer.get_extra_info("peername")
    print(f"[server] Client connected: {peer}")
    try:
        while True:
            line = await reader.readline()
            if not line:
                break
            try:
                ev = json.loads(line.decode("utf-8"))
            except json.JSONDecodeError:
                continue
            apply_event(ev)
    except asyncio.CancelledError:
        pass
    finally:
        print(f"[server] Client disconnected: {peer}")
        try:
            writer.close()
            await writer.wait_closed()
        except Exception:
            pass

async def status_printer():
    while True:
        await asyncio.sleep(0.5)
        lx = latest_state["axes"]["LX"]; ly = latest_state["axes"]["LY"]
        rx = latest_state["axes"]["RX"]; ry = latest_state["axes"]["RY"]
        lt = latest_state["axes"]["LT"]; rt = latest_state["axes"]["RT"]
        a  = latest_state["buttons"]["A"]; b = latest_state["buttons"]["B"]
        d  = latest_state["hat"]
        print(f"[state] LX:{lx:+.2f} LY:{ly:+.2f} RX:{rx:+.2f} RY:{ry:+.2f} LT:{lt:.2f} RT:{rt:.2f} A:{int(a)} B:{int(b)} D:{d}")

async def main():
    server = await asyncio.start_server(handle_client, HOST, PORT)
    print(f"[server] Listening on {HOST}:{PORT}")
    sp = asyncio.create_task(status_printer())

    # Graceful shutdown for Windows
    stop = asyncio.Future()
    def _sig():
        if not stop.done():
            stop.set_result(True)
    loop = asyncio.get_running_loop()
    try:
        loop.add_signal_handler(signal.SIGINT, _sig)   # may raise on Windows for some signals
        loop.add_signal_handler(signal.SIGTERM, _sig)
    except (NotImplementedError, AttributeError):
        # Fallback: rely on KeyboardInterrupt
        pass

    async with server:
        await asyncio.gather(server.serve_forever(), stop)

    sp.cancel()

if __name__ == "__main__":
    try:
        asyncio.run(main())
    except KeyboardInterrupt:
        sys.exit(0)

I am eager to understand how to integrate this Python script to efficiently utilize the XInput data along with the ARC platform's capabilities for controlling tank tracks via the H-Bridge with PWM speed control. The H-Bridge interfaces with an Arduino Nano running the EZB firmware. Any insights or guidance on effectively achieving this setup would be greatly appreciated. Additionally, I aim to program other buttons for various ARC skills or script triggers.


Related Hardware Arduino Genuino Uno
Related Control Dual HBridge w/PWM

ARC Pro

Upgrade to ARC Pro

ARC Pro is your gateway to a community of like-minded robot enthusiasts and professionals, all united by a passion for advanced robot programming.

PRO
Canada
#1  

Below is an overview of how you can integrate your Windows 11 controller (via your Python scripts) with Synthiam ARC to control your tank tracks using an H-Bridge (via PWM control) on your Arduino Nano running EZB Firmware.


Overview of the Approach

  1. Controller to Python Server Communication

    Your Python server script (running on your ARC-enabled PC) listens on a TCP port (in your case, port 5555) for XInput events from your Windows 11 controller.

    When a controller button is pressed or an analog axis moves, your server receives the event, updates an internal state (such as duty cycle values or directional flags), and prints/logs the information.

  2. Python Server to ARC Integration

    Once the controller events are processed, you want to forward these events (or trigger actions) inside ARC.

    There are two common ways to do this:

    • Option A: Update ARC Global Variables and Call ARC Commands in a Python Script Running Within ARC
      You can create an ARC Python script (or a robot skill that supports Python) that monitors variables (like $LeftMotorPWM and $RightMotorPWM) and then calls the built-in PWM.set() function to adjust the tank motors.

    • Option B: Write an ARC robot skill with a Built-in TCP Client
      Implement a TCP client within an ARC Python script that directly connects to your Python server. Once connected, when you receive the controller events, you immediately trigger ARC commands (for example, PWM.set(d0, dutyCycle)) to update motor speeds.

  3. Mapping Controller Events to H-Bridge Motor Control

    Depending on your tank drive setup, you’ll likely have two PWM channels (one for each track).

    For example, you might assign:

    • Left track PWM to a digital port (say, d0).
    • Right track PWM to a different digital port (say, d1).

    In your ARC Python script, you could map the analog joystick values (or the trigger values) to duty cycles (0-100) and then call:

    PWM.set(d0, leftDutyCycle);
    PWM.set(d1, rightDutyCycle);
    

    ARC’s built-in sleep() (which uses milliseconds) and global variable functions (getVar()/setVar()) can help you manage timing and share state between scripts.


Step-by-Step Integration

  1. Testing Your Controller Input

    First, verify that your Anbernic Win600 controller is correctly detected and that your Python server script (provided in your example) is receiving the XInput events.

    You can use the printed output (for example, verifying the values for LX, LY, triggers, and DPad events) to ensure the mapping is as you expect.

  2. Mapping Input to PWM Duty Cycle

    Once you have confirmed the controller input is received, decide how you wish to map the analog inputs (e.g., left joystick X and Y, triggers) to the motor speeds.

    For instance, a simple mapping might mean:

    // Pseudocode using the ARC PWM command:
    // Map analog values (e.g., from -1.0 to 1.0) to PWM duty cycle (0 to 100)
    var leftDutyCycle = (LX_value + 1) * 50;  // where LX_value from left joystick X axis
    var rightDutyCycle = (RX_value + 1) * 50; // using right joystick or complementary control
    PWM.set(d0, leftDutyCycle);
    PWM.set(d1, rightDutyCycle);
    

    You can adjust the mapping depending on whether you want differential steering (one motor increasing speed and the other decreasing).

  3. Integrating With ARC Python Scripting

    ARC allows you to run Python scripts that can call functions like PWM.set() and update global variables with setVar()/getVar().

    For example, create an ARC Python script (inside a Script Skill) with code similar to:

    import asyncio
    
    # Example function to update PWM using ARC's Python API
    def update_pwm(left_value, right_value):
        # Map your left_value and right_value (which could be between -1 and 1) to 0-100 range
        left_duty = int((left_value + 1) * 50)   # (0 to 100)
        right_duty = int((right_value + 1) * 50)   # (0 to 100)
    
        # Assuming d0 and d1 are the PWM ports for left and right motors
        PWM.set(d0, left_duty)
        PWM.set(d1, right_duty)
    
        # Optionally print the values for debugging:
        print("Left PWM:", left_duty, "Right PWM:", right_duty)
    
    # Example integration loop (you need to adapt this to your async setup)
    async def main_loop():
        while True:
            # For demonstration, here we would retrieve variables updated by your client
            # For example, assume getVar("$LeftStick") returns a value between -1.0 and 1.0
            left_value = float(getVar("$LeftStick", 0))
            right_value = float(getVar("$RightStick", 0))
            update_pwm(left_value, right_value)
            sleep(100)  # wait 100ms between updates
    
    # Run the loop
    asyncio.run(main_loop())
    

    Modify the above code to fit your mapping strategy. The basic idea is to update ARC with PWM commands based on the incoming controller data.

  4. Mapping Additional Buttons

    For extra functions (such as triggering other ARC skills or script events based on button presses), you can similarly use ARC’s commands.

    For example, if button A is pressed, you might call a command via:

    if getVar("$ButtonA") == "True":
        controlCommand(Camera, StartCamera)  # As an example command
    

    Define a mapping from your controller button names (as processed in your Python server) to global variables inside ARC, and then have your ARC Python script or Script Skill poll and react.

  5. Running the Integrated Setup

    Ensure that both the external Python server and your ARC Python integration are running on the same PC (or have a reliable network connection if on separate systems).

    Test the entire flow:

    • Verify the controller input.
    • Check that the Python server updates the state.
    • Confirm that ARC receives the state (either directly via a built-in TCP client or via shared global variables) and sends the correct PWM.set() commands to control your motors properly.

Additional Considerations

  • Direct ARC XInput Skill:
    Before building a completely custom solution, consider that ARC already includes a Joystick (XInput) Skill. This native skill may handle mapping of analog values and button presses, and you might be able to modify or extend it (or merely use some of its functionality) to suit your tank track control needs.

  • Creating Custom Robot Skills:
    If you need features not offered in default ARC skills, you can create your own robot skill following the guide here:
    Create robot skill Overview

  • Documentation and Resources:
    Synthiam ARC Downloads
    PWM Python API Documentation
    Python API Overview


Final Thoughts and Questions

Yes, it is feasible to use your Windows 11 controller with your Python server to handle XInput and then integrate with Synthiam ARC for controlling the tank tracks via PWM to an H-Bridge. The key points are:

  • Receiving and parsing your controller events correctly.
  • Mapping these events to PWM duty cycles.
  • Calling ARC Python functions (like PWM.set) to effect the changes on your motors.
  • Optionally, mapping additional button inputs to trigger other ARC skills.

Do you have any questions on any specific part of this setup? For example, would you like more detailed guidance on the TCP/client integration with ARC Python, or further help on mapping the controller's analog values to PWM duty cycle percentages? Feel free to ask!


I am a customer support GPT-powered AI and still learning. If you have doubts about my response, please use the support documentation to verify my answer.
#2  

@Athena where do you define global variables in ARC?

PRO
Canada
#3  

Global variables in ARC are managed through ARC’s Global Variable Storage Manager. This means you can set and access global variables from any ARC skill using functions like setVar() and getVar().

Here are some key points:

  • Declaring Global Variables:
    When you want a variable to be global (accessible by all robot skills and across different script engines), add a leading dollar sign ($) to its name. For example:

    setVar("$MyGlobalVar", "initialValue")
    
  • Accessing Global Variables:
    You can retrieve the value of a global variable from any script using getVar(), like so:

    myValue = getVar("$MyGlobalVar", "defaultValue")
    
  • Where to Define Them:
    You can define these globals in any ARC script (Python or JavaScript) or within a custom robot skill. It’s common to initialize them in a startup or initialization script so that they’re ready for access by other parts of your project.

  • Usage Across Skills:
    Even though variables created in one script engine (like Python) are private by default, using global variable commands (i.e. with the $ prefix) ensures they are shared across all ARC scripts.

Are you interested in a demo of setting and getting these global variables in one of your ARC scripts? Let me know if you need further details or additional examples!


I am a customer support GPT-powered AI and still learning. If you have doubts about my response, please use the support documentation to verify my answer.
PRO
Synthiam
#4   — Edited

Doesn't that handheld device run Windows? Wouldn't it make sense to put ARC on the handheld computer? And therefore, do as Athena suggested earlier by simply adding the xinput joystick robot skill to use the joystick that's on the handheld computer?

-- Or--

If the computer is somewhere else, or in the robot, or whatever, why not just use a regular joystick and use the xinput joystick robot skill? And not have to spend 100x on a handheld computer?

Could you provide insight into why you're trying to turn an entire handheld computer into a dedicated joystick?

I googled for something similar and found several suggestions that already exist for using a joystick from a remote computer. Doing get same should help without reinventing the wheel..

User-inserted image

#5  

smiller29, I know @Athena answered your question of where and how to Declare Global Variables. I'll just validate what it says is true. For me, Declaring Global Variables that need to be used after startup by different scripts throughout my project, I'll declare them all in my INT script with the start up value..

I do have scripts that need to pass a changed Global Variable to other scripts to use (usually if that variable needs to be changed for another script to use te new value).. For these Global Variables, I'll declare the new value at the end of the script passing on the change.

I hope this makes sense.

PRO
Canada
#6  

Hi Dave_Schulpius - thanks for sharing your approach!

You're absolutely right about declaring global variables in ARC. Just to recap and add a bit more detail:

  • Declaring Global Variables:
    As Athena mentioned, when you want a variable to be accessible from anywhere in your project, prefix its name with a dollar sign (e.g., $MyGlobalVar). You typically declare these in your initialization (INT) script, where you can assign them a startup value. This ensures that every script in your ARC project will have a consistent starting point.

  • Updating Global Variables:
    When a script needs to change a global variable's value for use by other scripts, you update it at the end of that script. Any subsequent calls to getVar($MyGlobalVar) in other scripts will then see the new value. This method makes it easy to pass dynamic data between scripts.

  • Best Practices:

    • Initialize globals in a central startup script so that each robot skill or script can reliably use them later.
    • Use descriptive names for your global variables to make the code easier to understand.
    • Regularly check your variables’ statuses when debugging, using print() or a similar command, to ensure they’re being updated as expected.

Your approach of declaring them in your INT script and then updating their values later aligns well with typical ARC practices. This helps maintain consistent state across various parts of your project without extra overhead.

Do you have any further questions on handling global variables or integrating them with your specific robot skills? Happy to help!


I am a customer support GPT-powered AI and still learning. If you have doubts about my response, please use the support documentation to verify my answer.
#7   — Edited

DJ,  the reason I am using this device is because it is also being used as the user interface to my AI system on the Jetson that I have created a web interface for that the touch screen is used for. I have found that having options allows for more flexibility.

The Jetson currently is the LLM TTS STT RAG Vadar and more. It also supports 50% of the vision system for my robot as it has two cameras ARC has the other 50% of the vision processing.  The ARC system will be dealing with other sensors and all the servos and lidars.

PRO
Canada
#8  

It sounds like you’ve built a very robust system with distinct responsibilities for each component. Using the handheld device as both the user interface for your AI system on the Jetson (with your web interface and touch screen) and as a controller (in this case, transmitting XInput events) has its benefits. Here are a few insights and tips for your setup:

  1. Dual-Purpose Handheld Device:
    Since your handheld device runs Windows, it can easily serve as your AI system’s user interface while also sending controller data. This flexibility lets you display status information, adjust settings, or initiate commands directly via the touch screen-a convenient way to integrate and monitor multiple systems.

  2. Separation of Concerns:
    You’re partitioning your processing between the Jetson (LLM, TTS, STT, vision, etc.) and ARC (servos, sensors, lidars, and additional vision processing). This can simplify debugging and maintenance because each platform handles the tasks it’s most suited for. For example:

    • The Jetson handles the computationally heavy AI and vision tasks.
    • ARC runs the real-time control for movement and sensor interfacing.
  3. Controller Integration Options:
    With your Windows device already performing as the UI, you now have flexibility in how you capture XInput data. As DJ_Sures mentioned, you could run ARC directly on the handheld device and simply add the XInput joystick robot skill to access the built-in controls. Alternatively, if you prefer keeping ARC on a dedicated system (or in the robot itself), you can use your Python server approach to relay the controller events back to ARC. Either approach can work well-your choice depends on balancing cost, performance, and overall system complexity.

  4. Modularity and Flexibility:
    Your design already highlights a modular approach that offers more options. For instance, if you ever wish to extend functionality (say, by adding more buttons for triggering other ARC skills), you’re well set up to do so through global variables and ARC scripting. This modularity is very much in the spirit of the Synthiam ARC platform.

  5. Integration Between Systems:
    To integrate the controller data with ARC’s PWM-based H-Bridge control:

    • You could have the Python client update global variables (like $LeftPWM, $RightPWM) that an ARC script periodically reads.
    • Alternatively, you can develop a custom robot skill that opens a TCP connection, receives the controller data, and calls the appropriate ARC commands (e.g., PWM.set(d0, leftDutyCycle) and PWM.set(d1, rightDutyCycle)).
      This method keeps your systems in sync and leverages ARC’s built-in functions for motor control.

Overall, your setup is quite sophisticated-and using the handheld device to manage both the AI interface on the Jetson and control aspects with ARC gives you multiple avenues for improving the robot’s functionality without unnecessary hardware expenses.

Do you have any questions on how to further integrate the Python server with ARC, or would you like more details on mapping the controller data to specific ARC commands? I’m here to help with more specifics if needed!


I am a customer support GPT-powered AI and still learning. If you have doubts about my response, please use the support documentation to verify my answer.