Sr. Content Developer at Microsoft, working remotely in PA, TechBash conference organizer, former Microsoft MVP, Husband, Dad and Geek.
158907 stories
·
33 followers

GitHub Copilot SDK for Java 1.0.10-preview.0

1 Share

Installation

⚠️ Artifact versioning plan: Releases of this implementation track releases of the reference implementation. For each release of the reference implementation, there may follow a corresponding release of this implementation with the same number as the reference implementation. Release identifiers of the reference implementation are in the form vMaj.Min.Micro. For example v0.1.32. The corresponding maven version for the release will be Maj.Min.Micro-java.N, where Maj, Min and Micro are the corresponding numbers for the reference implementation release, and N is a monotonically increasing sequence number starting with 0 for each release. See the corresponding architectural decision record for more information in the docs/adr directory of the source code.

📦 [View on Maven Central]((central.sonatype.com/redacted)

📖 [Documentation]((github.github.io/redacted) · [Javadoc]((github.github.io/redacted)

Maven

<dependency>
    <groupId>com.github</groupId>
    <artifactId>copilot-sdk-java</artifactId>
    <version>1.0.10-preview.0</version>
</dependency>

Gradle (Kotlin DSL)

implementation("com.github:copilot-sdk-java:1.0.10-preview.0")

Gradle (Groovy DSL)

implementation 'com.github:copilot-sdk-java:1.0.10-preview.0'

Feature: managed permission settings at session startup

Applications can now supply host-managed permission settings at session startup via SessionConfig.setManagedSettings(). The runtime validates and composes this policy with self-fetched and device policy. Re-supply on resume as it is not persisted. (#2139)

SessionConfig config = new SessionConfig()
    .setManagedSettings(new ManagedSettings()
        .setPermissions(new ManagedSettingsPermissions()
            .setFilesystem(PermissionLevel.READ_WRITE)));

Feature: userPromptTransformed hook

A new onUserPromptTransformed hook on SessionHooks lets applications observe (and optionally modify) the prompt text after the runtime transforms it. (#2254)

session.getHooks().setOnUserPromptTransformed((input, ctx) -> {
    System.out.println("Transformed prompt: " + input.getPrompt());
    return CompletableFuture.completedFuture(null);
});

Feature: disable specific MCP servers per session

SessionConfig.setDisabledMcpServers() accepts a list of exact MCP server names to disable for the session. Disabled servers are not started or authenticated on create or cold resume. (#2260)

SessionConfig config = new SessionConfig()
    .setDisabledMcpServers(List.of("my-mcp-server"));

Feature: Tool.isTerminal and history.clearContext

The @CopilotTool annotation gains an isTerminal flag — when true, a successful call to that tool ends the agent turn immediately. The session also gains clearContext() to reset the conversation history. (#2129)

`@CopilotTool`(name = "done", description = "Signal task complete", isTerminal = true)
public void done() { }

Other changes

  • feature: support reasoningEffort: "max" in SessionConfig and ResumeSessionConfig (#2228)
  • bugfix: preserve MCP permission extension data in PermissionRequest serialization (#2276)

Generated by Release Changelog Generator · sonnet46 52.6 AIC · ⌖ 7.17 AIC · ⊞ 8.6K

Read the whole story
alvinashcraft
1 minute ago
reply
Pennsylvania, USA
Share this story
Delete

Creating a fake agile wrapper that is technically agile but is not useful outside its home apartment, part 5

1 Share

Last time, I confessed that I lied when i said that we can’t use std::unique_ptr to manage the registration cookie.

The trick here is that the registration cookie is of type DWORD, which fits in a pointer, so we can smuggle the integer value inside a pointer.

template<typename T>
struct fake_agile_ref
{
private:
    using Smart = std::conditional_t<
        std::is_base_of_v<winrt::Windows::Foundation::IUnknown, T>,
        T, winrt::com_ptr<T>>;

    struct git_deleter                                                                           
    {                                                                                            
        winrt::com_ptr<IGlobalInterfaceTable> m_git;                                             
                                                                                                 
        void operator()(void* p)                                                                 
        {                                                                                        
            m_git->RevokeInterfaceFromGlobal(static_cast<DWORD>(reinterpret_cast<uintptr_t>(p)));
        }                                                                                        
    };                                                                                           

    winrt::com_ptr<IContextCallback> m_context;
    ULONG_PTR m_token = 0;
    std::unique_ptr<void, git_deleter> m_cookie;
    void* m_raw = nullptr;

Our custom deleter holds a pointer to the Global Interface Table and uses it to revoke the cookie on destruction. The cookie is an integer smuggled inside a pointer, so we cast the pointer back to an integer by passing through a uintptr_t to avoid a compiler warning about casting between an integer and pointer of different sizes.

We are relying on the fact that Windows implementations are required to support round-tripping integers through pointers. Macros like MAKEINTRESOURCE rely on it. It’s also codified in Windows with helper functions like PtrToInt and IntToPtr, but I’m writing it out for expository purposes rather than using those helpers.

We can then store the Global Interface Table pointer and the corresponding cookie in the unique_ptr:

    fake_agile_ref(Smart const& p) : m_raw(winrt::get_abi(p))
    {
        if (m_raw) {
            m_context = winrt::capture<IContextCallback>(CoGetObjectContext);
            m_token = get_context_token();
            auto& git = m_cookie.get_deleter().m_git;                                          
            git = winrt::create_instance<IGlobalInterfaceTable>(CLSID_StdGlobalInterfaceTable);
            DWORD cookie;                                                                      
            winrt::check_hresult(git->RegisterInterfaceInGlobal(
                winrt::make<force_marshal<Smart>>(p).get(),
                __uuidof(IUnknown), &m_cookie));
            m_cookie.reset(reinterpret_cast<void*>(static_cast<uintptr_t>(cookie)));
        }
    }

And now that we are letting unique_ptr manage the lifetime of the cookie, we don’t need a custom destructor, which allows us to use the Rule of Zero and simply not have any copy or move constructors or assignment operators.

    // fake_agile_ref(fake_agile_ref&& other) noexcept :
    //     m_context(std::move(other.m_context)),
    //     m_token(std:exchange(other.m_token, 0)),
    //     m_git(std::move(other.m_git)),
    //     m_cookie(std::exchange(other.m_cookie, 0)),
    //     m_raw(other.m_raw)
    // {
    // }

    // fake_agile_ref& operator=(fake_agile_ref&& other) noexcept
    // {
    //     using std::swap;
    //     swap(m_context, other.m_context);
    //     swap(m_token, other.m_token);
    //     swap(m_git, other.m_git);
    //     swap(m_cookie, other.m_cookie);
    //     swap(m_raw, other.m_raw);
    // }

    // ~fake_agile_ref()
    // {
    //     if (m_cookie) {
    //        m_git->RevokeInterfaceFromGlobal(std::exchange(m_cookie, 0));
    //    }
    // }

Since we are storing the cookie in a unique_ptr, we need to adjust the empty method:

    bool empty() const noexcept
    {
        return reinterpret_cast<uintptr_t>(m_cookie.get()) != 0;
    }

Bonus chatter: The Windows Implementation Library (wil) has a class similar to unique_ptr called wil::unique_any that lets you apply cleanup to any data type, not just a pointer.

The post Creating a fake agile wrapper that is technically agile but is not useful outside its home apartment, part 5 appeared first on The Old New Thing.

Read the whole story
alvinashcraft
2 minutes ago
reply
Pennsylvania, USA
Share this story
Delete

7 Powerful Ways to Automate PowerPoint Generation in C#

1 Share

TL;DR: Automating PPTX generation in C# lets you create data-driven PowerPoint presentations using APIs, Excel, and databases without manual edits. By programmatically generating slides, tables, charts, and layouts, you can build scalable reporting workflows that remain consistent, reduce errors, and automatically update with live data, making them ideal for recurring business reports, dashboards, and client-ready presentations.

Every team that deals with recurring reports eventually hits the same wall.

You build a PowerPoint deck once. Then again next month. And again the month after that.

Tables need updates. Charts need new data. Slides need formatting fixes because something shifted. What starts as a simple task slowly turns into hours of repetitive work.

At some point, the question changes from “How do I update this?” to “Why am I still doing this manually?”

That’s where automation comes in.

Instead of treating PowerPoint like a document, you treat it like output from something your code generates. Tools like the Syncfusion® PowerPoint Library make this approach practical by letting you create and manipulate presentations directly from your application code, without relying on Microsoft Office.

Once you set things up, your presentations can be built automatically using live data from Excel files, databases, or APIs.

Experience the magic of Syncfusion’s C# PowerPoint Library. Witness your ideas come to life with its Microsoft PowerPoint-like editing, conversion, and formatting options.

Let’s walk through what that actually looks like in practice.

Start with a simple foundation

Before getting into advanced scenarios, it helps to know how lightweight this actually is.

Creating a presentation programmatically isn’t as complex as it sounds. You define a slide, add text or shapes, and save it. That’s it.

If you’re new to this approach, it’s useful to quickly skim the Syncfusion PowerPoint Library documentation to understand how presentations, slides, and shapes are structured in code. You don’t need to go deep right away, just enough to get familiar with the core objects and how they fit together.

Sample presentation created using Syncfusion PowerPoint Library
Sample presentation created using Syncfusion PowerPoint Library

Once you’ve done that once, everything else becomes an extension of the same idea: define structure → inject data → generate output

From there, things get interesting.

Want to try it instantly? Visit our live demo to generate PPTX files in seconds.

1. Master slide template automation

One of the biggest hidden time drains in PowerPoint is formatting.

Fonts shift. Colors don’t match. Logos end up slightly misaligned across slides.

When you define a master layout in code, all of that disappears.

Instead of styling each slide individually, you define:

  • Background
  • Typography
  • Branding elements
  • Layout structure

With the Syncfusion PowerPoint Library, you can create a custom layout from one of nine predefined slide types, apply your brand colors and shapes directly to the layout, and generate multiple slides that all follow the same standard with zero repetition.

// Create a new PowerPoint presentation
Using (IPresentation presentation = Presentation.Create())
{

    // Add a TitleOnly custom layout to the first master slide
    ILayoutSlide layoutSlide = presentation.Masters[0].LayoutSlides.Add(SlideLayoutType.TitleOnly, "CustomLayout");

    // Set layout background (pale cream) so all slides using this layout inherit it
    layoutSlide.Background.Fill.SolidFill.Color = ColorObject.FromArgb(252, 244, 240);

    // Add a thin terracotta rule under the title area
    layoutSlide.Shapes.AddShape(AutoShapeType.Rectangle, 48, 120, 864, 6).Fill.SolidFill.Color = ColorObject.FromArgb(215, 100, 67);

    // Add one slide using the custom layout
    ISlide slide1 = presentation.Slides.Add(layoutSlide);

    // Populate the title placeholder and apply basic formatting
    IShape? titleShape = slide1.Shapes[0] as IShape;
    var titleParagraph = titleShape.TextBody.AddParagraph("Financial Report \u2014 FY 2024\u20132025");
    titleParagraph.HorizontalAlignment = HorizontalAlignmentType.Center;
    titleParagraph.TextParts[0].Font.FontName = "Calibri";
    titleParagraph.TextParts[0].Font.FontSize = 48;
    titleParagraph.TextParts[0].Font.Color = ColorObject.FromArgb(16, 66, 96);

    // Add a descriptive text box below the title
    IShape descriptionShape = slide1.AddTextBox(50, 140, 874, 120);
    descriptionShape.TextBody.Text = "This report presents a consolidated view of the company's financial performance across FY 2024–2025. It highlights key trends in revenue, expenses, and growth to support informed strategic decisions.";

    //Add image into the slides
    FileStream pictureStream = new FileStream("data/Image.png", FileMode.Open);
    slide1.Shapes.AddPicture(pictureStream, 450, 210, 420, 300);

    //Add second slide using the same custom layout - it automatically inherits the background from the layout slide
    ISlide slide2 = presentation.Slides.Add(layoutSlide);
    ISlide slide3 = presentation.Slides.Add(layoutSlide);
    ISlide slide4 = presentation.Slides.Add(layoutSlide);

    // Save the PowerPoint Presentation
    FileStream outputStream = new FileStream(Path.GetFullPath(@"Output.pptx"), FileMode.Create);
    presentation.Save(outputStream);
    //Release the stream
    outputStream.Dispose();
    presentation.Close();
}
Sample master slide layout created using Syncfusion PowerPoint Library
Sample master slide layout created using Syncfusion PowerPoint Library

For more details on master slides, predefined slides, and slide‑level access, check out our Master slides documentation.

2. Generate tables directly from data sources

Tables are the backbone of most business reports. But rebuilding them in each cycle, copying rows, adjusting column widths, and reformatting cells wastes hours and invites data errors.

The Syncfusion PowerPoint Library lets you generate tables programmatically by loading your data source from CSV, Excel, or a database and injecting the values directly into each cell. The structure stays consistent, and the data stays current, automatically.

//Load the existing PowerPoint presentation from the Data folder
FileStream inputStream = new FileStream(
    Path.GetFullPath(@"Data/Input.pptx"),
    FileMode.Open,
    FileAccess.Read
);

IPresentation presentation = Presentation.Open(inputStream);

// ExcelToPresentationHelper is a custom helper class included in this sample project.
// Include this class in your project to load your Excel data into your presentation slide.
ExcelToPresentationHelper helper = new ExcelToPresentationHelper();

//The helper method will render the external Data and inject the updated table in the presentation
helper.AddFinancialDataToPresentation(presentation);

// Save the PowerPoint Presentation
FileStream outputStream = new FileStream(
    Path.GetFullPath(@"Output.pptx"),
    FileMode.Create
);

presentation.Save(outputStream);

//Release the stream
outputStream.Dispose();
presentation.Close();
Financial report table created using Syncfusion PowerPoint Library
Financial report table created using Syncfusion PowerPoint Library

Want to explore more ways to style tables or inject data automatically? Our table documentation has everything you need.

3. Turn data into charts automatically

Numbers make sense in tables, but trends and comparisons are far easier to communicate in charts. The challenge is that updating charts for every reporting cycle, connecting data ranges, adjusting axes, and fixing labels is slow and repetitive.

Our PowerPoint Library lets you generate charts directly from Excel data with a single API call. You define the data range, chart type, and axis labels in code, and the library handles the entire visualization automatically, creating bar, line, or pie charts based on the type you specify. This ensures your slides always present the latest business values.

//To create a new presentation or load an existing one, add a slide title, and save the presentation, reuse the code demonstrated in the previous section.

// Add chart from Excel (A1:B13 = Month, Profit in Lakhs)
FileStream excelStream = new FileStream(
    Path.GetFullPath(@"Data/Book1.xlsx"),
    FileMode.Open
);

IPresentationChart chart = presentation.Slides[0].Charts.AddChart(excelStream, 1, "A1:B13", new RectangleF(90, 150, 800, 380));

chart.ChartTitle = "Financial Year Profit Visuals";
chart.PrimaryCategoryAxis.Title = "Month";
chart.PrimaryValueAxis.Title = "Profit (Lakhs)";
chart.HasLegend = false;

excelStream.Dispose();
Financial year profit chart created using Syncfusion PowerPoint Library
Financial year profit chart created using Syncfusion PowerPoint Library

Looking for advanced chart types, styling controls, and formatting? Explore the full chart customization guide.

4. Build SmartArt without recreating diagrams

Process flows, approval chains, and org structures are clear when presented visually. Updating SmartArt nodes every time a process changes is surprisingly tedious, but automating SmartArt creation keeps diagrams synchronized with your business model.

The Syncfusion PowerPoint Library lets you create and populate SmartArt programmatically. Load your data from an Excel, CSV, database, or any Data source, insert the SmartArt shape, and fill each node dynamically. No manual editing is needed.

Workflow.txt

Budget Planning
Expenditure Tracking
Variance Analysis
Performance Optimization
Financial Reporting
//To create a new presentation or load an existing one, add a slide title, and save the presentation, reuse the code demonstrated in the previous section.

// Read workflow stages from workflow.txt — each line becomes a SmartArt node
var workflowItems = File.ReadAllLines(Path.GetFullPath(@"Data/workflow.txt"))
    .Select(line => line.Trim())
    .Where(line => !string.IsNullOrEmpty(line))
    .ToList();

// Add SmartArt with workflow data
ISmartArt smartArt = presentation.Slides[0].Shapes.AddSmartArt(SmartArtType.BasicCycle, 100, 150, 750, 350);

for (int i = 0; i < Math.Min(smartArt.Nodes.Count, workflowItems.Count); i++)
    smartArt.Nodes[i].TextBody.AddParagraph(workflowItems[i]);
}
Financial operations SmartArt created using Syncfusion PowerPoint Library
Financial operations SmartArt created using Syncfusion PowerPoint Library

Curious to explore more SmartArt operations and node configuration? Check our PowerPoint SmartArt documentation for more details.

The features of Syncfusion’s PowerPoint Library are documented with clear code examples for multiple scenarios.

5. Insert and update images programmatically

Reports that include logos, product images, or team-specific visuals always look more polished and professional. But changing images slide by slide is time-consuming. Automating image placement solves this by ensuring every slide uses the correct assets with consistent positioning and formatting.

The Syncfusion PowerPoint Library lets you insert or replace images programmatically from any source. Load the presentation, point to the correct image file, and place it precisely on the slide, all in just a few lines of code, with no manual effort.

    
         //To create a new presentation or load an existing one, add a slide title, and save the presentation, reuse the code demonstrated in the previous section.

        // Add picture to slide
        FileStream pictureStream = new FileStream(Path.GetFullPath(@"Data/Image.png"), FileMode.Open);
        IPicture picture = presentation.Slides[0].Pictures.AddPicture(pictureStream, 150, 150, 650, 350);
Business strategy infographic created using Syncfusion PowerPoint Library
Business strategy infographic created using Syncfusion PowerPoint Library

For cropping, replacing existing images, and other image operations, explore the Syncfusion PowerPoint Image documentation.

6. Add animations to highlight key points

Static slides can struggle to hold attention in live presentations. Well-placed animations help keep the audience’s focus on the right data at the right moment.

The Syncfusion PowerPoint Library supports 140+ animation effects, including entrance, emphasis, exit, and motion path types. You can apply them programmatically with full control over timing, triggers, and sequencing, no PowerPoint menus required.

//To create a new presentation or load an existing one, add a slide title, and save the presentation, reuse the code demonstrated in the previous section.

// Get the first slide
ISlide slide = presentation.Slides[0];

// Find the title shape from the slide
IShape? titleShape = null;
foreach (IShape shape in slide.Shapes)
{
    if (shape.TextBody != null && shape.TextBody.Text.Length > 0)
    {
        titleShape = shape;
        break;
    }
}

// Add fly animation to the title (from left to center)
if (titleShape != null)
{
    ISequence sequence = slide.Timeline.MainSequence;
    IEffect effectLeft = sequence.AddEffect(titleShape, EffectType.Fly, EffectSubtype.Left, EffectTriggerType.OnClick);
}
Financial report slide animation created using Syncfusion PowerPoint Library
Financial report slide animation created using Syncfusion PowerPoint Library

Need a deeper exploration of animation options? Dive into our PowerPoint Animation documentation.

7. Clone and merge slides to build full reports

Many reports repeat the same slide across regions, quarters, or product lines. Recreating these blocks manually is time‑consuming and risky. Automated slide cloning ensures perfect consistency, while merging lets you collect content from different presentations into a unified deck.

Our PowerPoint Library lets you clone slides programmatically, producing exact copies complete with layout, shapes, charts, images, and text. You can also merge slides from separate presentations into a single deck, automatically assembling multi-section reports. Together, these two capabilities turn a repetitive manual task into a fast, reliable automated pipeline.

// Open the destination presentation (table.pptx) as the base
IPresentation destinationPresentation = Presentation.Open(new FileStream(Path.GetFullPath(@"Data/table.pptx"), FileMode.Open, FileAccess.Read));

// Open the source presentation (chart.pptx)
IPresentation sourcePresentation = Presentation.Open(new FileStream(Path.GetFullPath(@"Data/chart.pptx"), FileMode.Open, FileAccess.Read));

// Clone and merge all slides from the chart presentation to the table presentation
for (int i = 0; i < sourcePresentation.Slides.Count; i++)
{

    // Clone the slide from the source presentation
    ISlide clonedSlide = sourcePresentation.Slides[i].Clone();

    // Add the cloned slide to the destination presentation with the destination theme
    destinationPresentation.Slides.Add(clonedSlide, PasteOptions.UseDestinationTheme);
}
Consolidated presentation created using Syncfusion PowerPoint Library
Consolidated presentation created using Syncfusion PowerPoint Library

For a complete guide to advanced clone‑and‑merge operations, you can refer to our cloning and merging documentation.

GitHub reference

You can find all the PowerPoint Automation samples in the GitHub repository.

Frequently Asked Questions

Can I protect generated slides from editing?

Yes. Write protection or password protection can be applied at the presentation level via the API.

Does Syncfusion support hyperlinks inside slides?

Yes. Hyperlinks can be inserted into text, shapes, or images with simple API calls.

Can I convert PowerPoint to PDF after inserting charts and animations?

Yes. Animations export as static frames, and charts render cleanly in the generated PDF.

Can I use Syncfusion PowerPoint on Linux or Docker?

Yes. Because it targets .NET Core, the library runs on Linux, Docker containers, and cloud servers.

Can I embed OLE objects like Excel or Word files inside a slide?

Yes. OLE objects, such as embedded Excel sheets or Word documents, can be inserted programmatically.

Does Syncfusion support exporting slides to images?

Yes. Individual slides or entire presentations can be exported to PNG, JPEG, or other image formats.

Can I add or edit comments on slides programmatically?

Yes. The API supports inserting, editing, and removing comments on any slide.

Does Syncfusion support VBA macros in PowerPoint files?

Yes. Syncfusion preserves existing VBA macros in a presentation, though creating new macros programmatically is not supported.

Step into a world of boundlessly creative presentations with Syncfusion’s C# PowerPoint Library!

Conclusion

Automating PPTX generation changes how you think about presentations.

They stop being something you manually assemble and become something your system produces.

You define the structure once. After that, your code takes care of:

  • Updating data
  • Maintaining layout consistency
  • Generating complete presentations

Once this pipeline is in place, the same approach can scale from a single report to hundreds without adding extra effort.

Libraries like the Syncfusion PowerPoint Library make this transition easier by giving you direct control over slides, layouts, and content through code so you can focus less on editing and more on delivering meaningful insights.

Still updating slides manually every reporting cycle? Making this shift can save time, reduce errors, and make your reporting workflow far more reliable.

If you’re a Syncfusion user, you can download the setup from the license and downloads page. Otherwise, you can download a free 30-day trial.

You can also contact us via our support forumssupport portal, or feedback portal for queries. We are always happy to assist you

Read the whole story
alvinashcraft
4 minutes ago
reply
Pennsylvania, USA
Share this story
Delete

Microsoft Hits 40 Million AI Agents: What It Means for Enterprise AI

1 Share

AI agents are beginning to outpace Copilot licenses, signaling that organizations are creating increasingly valuable custom automation across the Microsoft ecosystem.

The post Microsoft Hits 40 Million AI Agents: What It Means for Enterprise AI appeared first on Cloud Wars.

Read the whole story
alvinashcraft
3 hours ago
reply
Pennsylvania, USA
Share this story
Delete

Microsoft and Tech Leaders Push Back on Restrictions for Open-Weight AI Models

1 Share

Microsoft and more than 20 tech companies urge U.S. lawmakers to protect open-weight AI models, arguing openness drives innovation and competition.

The post Microsoft and Tech Leaders Push Back on Restrictions for Open-Weight AI Models appeared first on Cloud Wars.

Read the whole story
alvinashcraft
3 hours ago
reply
Pennsylvania, USA
Share this story
Delete

AI on the Pi: Build Your Own Local Voice Agent

1 Share

As soon as I received my first Raspberry Pi, I knew that it would be a wonderful platform to bring AI into the physical world. Since the initial hardware didn’t have good CPU support for fast arithmetic, I ended up writing code that ran on the GPU so I could get the speed I needed for early deep learning vision models. That was in 2014, and since then the capabilities of both Pis and AI have skyrocketed, and I’m even more convinced that there’s massive potential in combining them. To show you why, I’d like to demonstrate how open source AI running locally on a Pi has solved some practical problems I’ve run into, and hopefully inspire you to build your own projects using the new possibilities.

Pis are great for systems that need to be out in the world, doing specialized jobs. I’ve seen them work well in all sorts of roles, from badge scanners to wildlife cameras. I even run a class that teaches students all about edge AI using the platform. While the boards are generally easy to use, the most frustrating part for the students and instructors is the setup process. While the latest imager makes it straightforward to configure settings like a WiFi network to join or enabling SSH when you’re flashing a card, getting the students to the point where they can connect to their Pi using VS Code from their laptop could often take multiple sessions. The biggest problems were:

  • There were different networks in the lab and in the students’ dorm rooms, so it wasn’t enough to hardcode a single SSID and password on the SD card.
  • You need the local IP address of the Pi to SSH into it from a laptop, but it can change dynamically every session. Using “<Pi name>.local” would sometimes work, but some networks didn’t support this kind of lookup, and even if they did it required coordination between the students to avoid name clashes.
  • It was easy to forget to set the configuration so that WiFi and SSH were available, and since the instructors didn’t always know what network and password they’d be using in the class ahead of time, we couldn’t pre-flash a bunch of cards to speed up student on-boarding.

A lot of these issues were solvable if you plugged the devices into a monitor, mouse, and keyboard, but this has its own problems. It meant we needed to provide that equipment to all students during class, and allow them to take it all home too, so they could update the configuration for their personal networks. It also required an extra power socket per student, for the monitors, which added up in a class where we already had to bring in a cart full of power strips. The monitor connections also weren’t always plug and play, we found we often needed to boot with a screen attached to have the display recognized.

This isn’t just an educational problem either. One of the reasons that I believe the Internet of Things failed is the setup tax involved in getting smart devices running. According to manufacturers I’ve worked with, less than 30% of their smart appliances ever get connected to the internet because the process of downloading an app, setting up an account, connecting over Bluetooth, and then typing in the WiFi name and password takes too long, and is too error prone. Even professional installers sometimes struggle with configuration in enterprise and industrial environments.

So, what can AI do to help? One of the biggest developments in AI over the last few years has been the development of highly accurate open source automatic speech recognition (ASR) models, also known as speech to text (STT). OpenAI was the pioneer in this area, releasing the family of Whisper models in 2022. These offered accuracy that was competitive with the models used internally by large tech companies like Google and Apple. These new models allowed startups to begin building voice applications that had never been possible before, and this led to a new generation of dictation and meeting-note tools like Whispr Flow.

One of my dreams as I dealt with all of the configuration issues was a voice-based system that would allow me to simply plug in a headset and set up everything by talking to a Pi. Whisper made this dream seem more realistic, but as I tried to use the models on local hardware, I realized that they were too slow for any kind of interactive application.

To address that my startup trained new models from the ground up, designed specifically for real-time applications on affordable hardware. These Moonshine models are smaller than Whisper (our high-end model is 250 million parameters versus OpenAI’s 1.5 billion) while offering better accuracy. We also implemented a streaming approach where a lot of the work is done while the user is still talking, so we can return results even faster. This allows us to return more accurate results than Whisper v3 Large, in just 800 milliseconds on a Pi 5, whereas even the less-accurate Whisper Small takes over 10 seconds.

I was excited because this meant I could finally build a responsive voice agent that runs locally on a Pi, something offline-first, and fast and flexible in how it responds. This kind of system needs more than just an STT model, it needs to decide what the user means and respond by taking actions and talking back with a TTS system. The Moonshine Voice framework includes modules for conversation flow and TTS, so I was able to use it to build pi-help-bot, a local voice agent for network configuration on the Pi.

The application listens to the microphone for commands like “What is my IP address?” or “Help me set up the WiFi, please,” figures out what actions to take, and responds appropriately by talking to the user. It’s written as a Python script, and here are some snippets that show how it works.

def report_ip_address(d: Dialog):
        ip = _find_local_ip()
        if ip is None:
            yield d.say("Sorry, I couldn't find a local IP address.")
            return
        speech_ip = re.sub(r"(\d)", r"\1 ", ip.replace(".", " dot "))
        yield d.say([
            f"Okay. Your local IP address is {speech_ip}. ",
            f"To repeat, that's {speech_ip}."
        ])


   dialog_flow.register_flow("What is my IP address?", report_ip_address)

This code is a function that uses the netifaces library to figure out the Pi’s address on the local network, so instead of having to connect a keyboard and display or decode the output of nmap, you can ask the question and hear the result, all in just a few seconds. Unlike older voice interfaces, the phrases the user says don’t have to be exactly the same as the one you register an intent with. Instead the framework matches incoming speech against a small, local LLM, so that variations (“Hey, can you tell me what my IP is?”) work too. This was important to me because one of my biggest frustrations using traditional voice interfaces like Alexa is that they need particular wording to trigger commands, but these wordings aren’t discoverable, so figuring out how to make something happen can require a lot of patience.

The IP address command is the simplest kind of conversational flow, where the user asks a question and the system immediately responds. Not all interactions can be handled as simply as this one though. Here’s another example that shows how to implement something that needs multiple questions, answers, and confirmations, connecting to a new WiFi network.

def connect_to_wifi(d: Dialog):
        input_ssid = yield d.ask("What's the name of your Wi-Fi network? Say list if you want to pick from a list or spell if you want to spell out the start of the name")
        input_ssid = input_ssid.strip()


        networks = _scan_wifi_networks()


        if input_ssid.lower().strip(string.punctuation) == "list":
            yield d.say("Say yes to the network you want to connect to.")
            for network in networks:
                if (yield d.confirm(f"{network}?")):
                    input_ssid = network
                    break
        elif input_ssid.lower().strip(string.punctuation) == "spell":
            input_ssid = yield d.ask("Spell out the start of the network name.", mode=SPELLED)
            print(f"[DEBUG] spelled buffer: {input_ssid!r}", file=sys.stderr)


        found_ssid = fuzzy_match_network(input_ssid, networks)
        if found_ssid is None:
            yield d.say(f"Sorry, I couldn't find a matching network for {input_ssid}.")
            return


        password = yield d.ask(
            f"Please spell the Wi-Fi password for {found_ssid} one character at a time, and say done when finished.",
            mode=SPELLED,
        )


        yield d.say(f"Connecting to {found_ssid}.")
        result = subprocess.run(
            ["sudo", "nmcli", "device", "wifi",
                "connect", found_ssid, "password", password],
            capture_output=True, text=True, timeout=30,
        )
        if result.returncode == 0:
            yield d.say(f"Connected to {found_ssid}.")
        else:
            print(f"[ERROR] nmcli stderr: {result.stderr}", file=sys.stderr)
            yield d.say(
                f"Sorry, I wasn't able to connect to {found_ssid}. "
                "Please check the network name and password and try again."
            )


    dialog_flow.register_flow("Connect to Wi-Fi", connect_to_wifi)

Hopefully you can follow the logic as it walks the user through providing the information required, but you might be wondering about those yield statements. Those hand back control to the dialog controller while the script is waiting for user responses, so the rest of the application isn’t blocked.

The end result is a local voice agent that will listen out for configuration questions and commands, allowing users to set up a Pi for remote access with just a headset. For ease of use, I’ve begun customizing the images I burn to SD cards so that this script automatically starts on boot. This means I can start setting up new devices immediately after powering them on.

I hope this gave you some ideas about how a local voice interface could help with problems you face. For further information check out the Moonshine Voice project on GitHub to see full documentation on the library, and please give us a star while you’re there. It helps us keep working on this project.



Read the whole story
alvinashcraft
3 hours ago
reply
Pennsylvania, USA
Share this story
Delete
Next Page of Stories