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

C♯ 14 Extension Types and Static Members - Next-Gen Extension Architecture for Clean .NET 10 Code

1 Share

Ever since C# 3.0 introduced extension methods alongside LINQ, developers have relied on the this parameter modifier on static methods to augment existing types without altering their source code or inheriting from them. While this approach revolutionized fluent collection querying, it remained confined strictly to instance methods, leaving properties, indexers, static factories, and user-defined operators out of reach.

 

With the release of C# 14 running on the .NET 10 LTS runtime—and early previews of C# 15 exploring extension indexers on .NET 11—Microsoft has delivered a comprehensive paradigm shift: Extension Member Declarations. In this architectural guide, we explore the new extension block syntax, implement extension properties and static factory members, examine Roslyn Intermediate Language (IL) emission, and demonstrate how this next-generation capability refactors enterprise domain logic into clean, decoupled architectures.

 

C# 14 Extension Types and Static Members in .NET 10
Mastering C# 14 extension declarations, extension properties, and static members for clean architecture in .NET 10.

 

Table of Contents

 

  • Beyond Extension Methods: C# 14 introduces first-class extension blocks, allowing developers to define instance extension properties, instance extension methods, static member methods, static properties, and overloaded operators within a unified declaration scope.
  • Dedicated Receiver Scoping: The new extension(Type receiver) syntax declares the target type and receiver parameter once for the entire block, eliminating repetitive this Type parameter signatures across multiple method declarations.
  • Static Member Invocation: For the first time in C#, developers can attach static extension methods and properties directly to existing types (e.g., calling IEnumerable<int>.Generate(1, 10, 2) or string.EmptyGuid) without needing instance references.
  • 100% Binary Compatibility: C# 14 extension members compile down to standard CLI static methods and property accessors decorated with compiler attributes, guaranteeing seamless source and binary interoperability with earlier C# versions.
  • Future-Ready Language Roadmap: Modern enterprise codebases targeting .NET 10 LTS can adopt C# 14 extension members immediately, with preview support in C# 15 / .NET 11 expanding into extension indexers (this[int index]).

 

The Evolution of Extensions - From C# 3.0 Methods to C# 14 Member Declarations

For nearly two decades, C# extension methods served as an essential syntactic sugar mechanism. By declaring a static method inside a nongeneric static class and decorating the first parameter with the this keyword, the compiler allowed developers to invoke the method using instance invocation syntax (object.ExtensionMethod()).

 

However, as software architecture evolved toward domain-driven design, immutable records, and expression-bodied functional pipelines—as demonstrated in our deep dive on C♯ 14 field-backed properties and enhanced pattern matching and high-performance C♯ zero-allocation memory practices—the limitations of classic extension methods became a frequent friction point:

 

// -------------------------------------------------------------
// BEFORE C# 14: Repetitive Static Class & 'this' Parameter Noise
// -------------------------------------------------------------
public static class LegacyStringExtensions
{
    // Method verb used where a property noun is naturally expected
    public static bool IsBlank(this string? source) 
        => string.IsNullOrWhiteSpace(source);

    public static string Truncate(this string source, int maxLength) 
        => source.Length <= maxLength ? source : source[..maxLength];

    // Cannot define static factory methods on 'string' directly!
    // Cannot define extension properties!
}

// -------------------------------------------------------------
// AFTER C# 14: Clean, Unified Extension Block Declaration
// -------------------------------------------------------------
public static class ModernStringExtensions
{
    extension(string source)
    {
        // 1. Natural Instance Extension Property
        public bool IsBlank => string.IsNullOrWhiteSpace(source);

        // 2. Instance Extension Method (source is automatically in scope)
        public string Truncate(int maxLength) 
            => source.Length <= maxLength ? source : source[..maxLength];
    }

    // 3. Static Type Extension (no instance receiver needed)
    extension(string)
    {
        public static string RandomNonce => Guid.NewGuid().ToString("N");
    }
}

 

In C# 14, extension members elevate extensions from an ad-hoc method hack into a structured type-extension architecture, allowing developers to extend external libraries, BCL interfaces, and immutable data contracts with full syntactic parity.

 

 

Anatomy of C# 14 Extension Blocks - Receivers, Properties, and Syntax

Starting with C# 14 in .NET 10, top-level nongeneric static classes can host one or more extension blocks. An extension block declares the target type and binds a receiver parameter that remains in scope across all instance members declared within that block.

 

Let us examine an architectural implementation extending collections with statistical domain properties, calculation methods, and user-defined operators:

 

using System;
using System.Collections.Generic;
using System.Linq;

/// <summary>
/// Contains C# 14 extension members for numeric collections.
/// </summary>
public static class NumericSequenceExtensions
{
    /// <summary>
    /// Defines instance extensions for integer sequences.
    /// </summary>
    /// <param name="sequence">The sequence receiver instance.</param>
    extension(IEnumerable<int> sequence)
    {
        /// <summary>
        /// Extension property calculating median value on demand.
        /// </summary>
        public int Median
        {
            get
            {
                var sorted = sequence.OrderBy(x => x).ToList();
                if (sorted.Count == 0) return 0;
                int mid = sorted.Count / 2;
                return (sorted.Count % 2 == 0) ? (sorted[mid - 1] + sorted[mid]) / 2 : sorted[mid];
            }
        }

        /// <summary>
        /// Extension property checking emptiness without LINQ Any() overhead.
        /// </summary>
        public bool IsEmpty => !sequence.Any();

        /// <summary>
        /// Instance extension method applying an addition scalar.
        /// </summary>
        public IEnumerable<int> AddScalar(int scalar)
        {
            foreach (var item in sequence)
            {
                yield return item + scalar;
            }
        }

        /// <summary>
        /// User-defined static operator method concatenating two integer sequences.
        /// </summary>
        public static IEnumerable<int> operator +(IEnumerable<int> left, IEnumerable<int> right)
            => left.Concat(right);
    }
}

 

Notice how consumer code interacts with these members. To the caller, Median and IsEmpty feel like native instance properties, while the + operator allows natural mathematical syntax:

 

// Consuming C# 14 Extension Members
IEnumerable<int> scores = [85, 92, 78, 95, 88];

// 1. Calling Extension Property
int medianScore = scores.Median; // Evaluates smoothly like a native property

// 2. Calling Extension Method
var adjustedScores = scores.AddScalar(5);

// 3. Using Overloaded Operator Extension
IEnumerable<int> bonusScores = [100, 98];
var allScores = scores + bonusScores; // Concatenates sequences using '+'

 

 

Static Extension Members and User-Defined Operators Directly on Types

One of the most consequential enhancements in C# 14 is the ability to declare static extension members that are invoked directly on the extended type itself, rather than requiring an instantiated object.

 

When declaring static-only extensions, the receiver parameter name is omitted from the extension declaration because no instance receiver is needed. You declare factory methods, identity elements, and singleton constants directly on the type:

 

public static class CollectionTypeExtensions
{
    /// <summary>
    /// Provides static factory extensions directly on the IEnumerable<int> interface type.
    /// </summary>
    extension(IEnumerable<int>)
    {
        /// <summary>
        /// Static factory method generating a stepped arithmetic progression.
        /// </summary>
        public static IEnumerable<int> Generate(int start, int count, int step)
        {
            for (int i = 0; i < count; i++)
            {
                yield return start + (i * step);
            }
        }

        /// <summary>
        /// Static extension property providing an empty identity sequence.
        /// </summary>
        public static IEnumerable<int> Identity => Enumerable.Empty<int>();
    }
}

// -------------------------------------------------------------
// Usage: Invoked directly on the type itself!
// -------------------------------------------------------------
var progression = IEnumerable<int>.Generate(start: 10, count: 5, step: 2);
// Output: 10, 12, 14, 16, 18

var empty = IEnumerable<int>.Identity;

 

This capability solves a long-standing architectural limitation in C#. Previously, if you wanted a factory method for an interface like IRepository<T> or IValidator<T>, you had to invent arbitrary helper classes like RepositoryFactory or ValidatorHelper. In C# 14, factory methods can live on the interface or class type itself.

 

 

Generic Extension Blocks and Type Parameter Scoping Rules

In enterprise systems, extension members must handle generic types with precision. C# 14 establishes clear scoping rules for generic type parameters:

 

  • Receiver Type Parameters: Defined on the extension<TReceiver> declaration itself when the type parameter participates in the receiver type definition.
  • Member Type Parameters: Defined on the specific member signature when the type parameter is auxiliary and distinct from the receiver.
  • No Redundant Re-declaration: A member cannot re-declare a type parameter already scoped by its containing extension block.

 

public static class GenericPipelineExtensions
{
    /// <summary>
    /// Generic extension block scoped to any IEnumerable<TSource>.
    /// </summary>
    extension<TSource>(IEnumerable<TSource> source)
    {
        // 1. Member using the receiver's scoped type parameter TSource
        public IEnumerable<TSource> Slice(int skip, int take) 
            => source.Skip(skip).Take(take);

        // 2. Member introducing an auxiliary type parameter TResult for transformation
        public IEnumerable<TResult> Transform<TResult>(Func<TSource, TResult> selector)
        {
            foreach (var item in source)
            {
                yield return selector(item);
            }
        }

        // 3. Static extension property for any generic closed type
        public static IEnumerable<TSource> Empty => Enumerable.Empty<TSource>();
    }
}

// -------------------------------------------------------------
// Usage across diverse enterprise domain models
// -------------------------------------------------------------
List<string> names = ["Kunal", "Chowdhury", "Enterprise", "DotNet"];
var sliced = names.Slice(skip: 1, take: 2);
var lengths = names.Transform(name => name.Length);

var defaultStringSequence = IEnumerable<string>.Empty;

 

 

Enterprise Clean Architecture - Domain Modeling Without DTO Pollution

In clean and hexagonal software architectures, keeping core domain entities and data transfer objects (DTOs) decoupled from presentation formatting, UI bindings, and transport protocols is essential.

 

Frequently, engineering teams corrupt pure domain records by adding serialization helpers, currency formatters, and status calculation properties. With C# 14 extension members, you can keep domain entities strictly focused on state, while cleanly attaching context-specific properties in separate architectural layers:

 

// Core Domain Layer (Pure, lightweight record DTO)
namespace Enterprise.Domain.Orders;

public record Order(
    Guid Id,
    string CustomerName,
    decimal TotalAmount,
    OrderStatus Status,
    DateTime CreatedAtUtc
);

public enum OrderStatus { Pending, Processing, Shipped, Delivered, Cancelled }

// =============================================================
// Presentation / API Gateway Layer (Extensions decoupled from Domain)
// =============================================================
namespace Enterprise.Api.Presenters;

public static class OrderPresentationExtensions
{
    extension(Order order)
    {
        /// <summary>
        /// Extension property formatting localized financial currency.
        /// </summary>
        public string FormattedTotal => $"₹{order.TotalAmount:N2}";

        /// <summary>
        /// Extension property evaluating business SLAs.
        /// </summary>
        public bool IsOverdue => 
            order.Status != OrderStatus.Delivered && 
            DateTime.UtcNow - order.CreatedAtUtc > TimeSpan.FromDays(3);

        /// <summary>
        /// Extension property generating a deterministic UI badge color.
        /// </summary>
        public string StatusBadgeColor => order.Status switch
        {
            OrderStatus.Delivered => "badge-emerald",
            OrderStatus.Cancelled => "badge-rose",
            OrderStatus.Processing => "badge-amber",
            _ => "badge-slate"
        };
    }
}

 

This design maintains absolute domain purity. The Order entity remains lightweight, serialization-safe, and independent of external presentation libraries.

 

 

IL Emission, Binary Compatibility, and Zero-Cost Runtime Performance

A critical concern when adopting new language syntax in high-throughput enterprise systems is runtime overhead. Does C# 14 introduce virtual dispatch tables, delegate allocation, or reflection?

 

The answer is zero runtime overhead. The Roslyn compiler compiles extension blocks directly down into standard static methods in the generated Intermediate Language (IL). An extension property getter is emitted as a standard static method with an [Extension] metadata attribute, identical to classic C# extension methods.

 

// Conceptual C# Equivalent of Roslyn IL Emission

// C# 14 Source:
// public bool IsBlank => string.IsNullOrWhiteSpace(source);

// Decompiled IL Equivalent:
[Extension]
[CompilerGenerated]
public static bool get_IsBlank(string source)
{
    return string.IsNullOrWhiteSpace(source);
}

 

Because the generated bytecode consists of direct static call opcodes (call rather than callvirt), the .NET 10 Tier-1 JIT compiler can inline extension properties and methods with zero pointer indirection. As discussed in our benchmarks on modern C♯ lock objects and params collections and building productivity tools with Visual Studio 2026 developer productivity tools, extension members achieve complete zero-cost abstraction.

 

 

Frequently Asked Questions (FAQ)

1. What are C# 14 extension members?

C# 14 extension members expand C#'s extension capabilities beyond static methods, allowing developers to define instance extension properties, static methods, static properties, and overloaded operators inside dedicated extension blocks.

 

2. How do you declare an extension block in C# 14?

Inside a top-level nongeneric static class, you declare an extension block using the syntax extension(Type receiverName) { ... } for instance members, or extension(Type) { ... } for static members.

 

3. Can C# 14 extension properties have both getters and setters?

Yes. Extension properties support get-only accessors, expression-bodied getters, and full get/set accessor pairs, provided the setter modifies reachable state on the receiver.

 

4. Are C# 14 extension members backward compatible with older C# code?

Yes. Extension members compile to standard static methods in the generated IL. Callers using earlier C# versions can invoke them as conventional static methods without breaking binary compatibility.

 

5. Can I define static factory methods on interfaces using C# 14?

Yes. Using static extension blocks extension(IMyInterface), you can attach static factory methods and properties that are invoked directly on the interface type (e.g., IEnumerable<int>.Generate(...)).

 

6. What is the performance cost of using C# 14 extension properties?

There is zero runtime overhead. Extension properties compile to static method calls that the .NET JIT compiler easily inlines, avoiding heap allocations or virtual dispatch overhead.

 

7. Does C# 14 support extension indexers?

Initial C# 14 releases focused on properties, methods, and static operators. Extension indexers (public T this[int index]) are part of the active C# 15 preview language specification in .NET 11.

 

8. Can extension members access private or protected fields of the extended class?

No. Extension members obey standard accessibility rules and can only access public or internal members exposed by the target type.

 

9. How does Visual Studio 2026 support C# 14 extension members?

Visual Studio 2026 provides native IntelliSense, code completion, Roslyn refactoring tools to convert legacy extension methods into extension blocks, and full debugger integration.

 

10. Can extension blocks be used on open generic types?

Yes. You can declare generic extension blocks such as extension<T>(IEnumerable<T> source), allowing members to operate fluently across all closed implementations.

 

 

End Note

The introduction of extension members in C# 14 marks a significant milestone in the language's ongoing quest for expressive, clean, and mechanical sympathetic syntax. By providing a natural syntax for extension properties, static members, and overloaded operators, C# eliminates decades-old workarounds and boilerplate code.

 

For software architects and enterprise engineering teams building upon .NET 10 LTS, adopting extension blocks allows clean domain-driven boundaries to flourish without sacrificing runtime throughput or memory efficiency.

 

As you refactor existing utilities and design new microservices, leveraging C# 14 extension declarations will ensure your codebase remains maintainable, performant, and aligned with modern .NET best practices.

 

Read the whole story
alvinashcraft
just a second ago
reply
Pennsylvania, USA
Share this story
Delete

How we got from no spaces between words to the interrobang, with Florence Hazrat

1 Share

1217. This week, we talk to Florence Hazrat, author of "On the Mark," about how punctuation evolved over 2,000 years. We look at why ancient Greek and Roman writing had no spaces between words, how a librarian in Alexandria introduced the first marks to help readers, and why the Renaissance brought a burst of new punctuation that then mostly stopped. Then we look at why a period can make a short text message feel aggressive, whether emoji count as punctuation, and how failed inventions like the interrobang tried to join the ranks. 

Find Florence Hazrat on Instagram or her website.


A hearty thank-you to our Keepers of the Commas on Patreon. We appreciate your support!

  • Larry Rosenblum
  • Mahala Russell
  • George Wilson
  • Laurel Paul
  • Linda Cox
  • Birna Anna Björnsdóttir


🔗 Join the Grammar Girl Patreon.

🔗 Share your familect recording in Speakpipe or by leaving a voicemail at 833-214-GIRL (833-214-4475)

🔗 Watch my LinkedIn Learning writing courses.

🔗 Subscribe to the newsletter.

🔗 Find an edited transcript.

🔗 Get Grammar Girl books.

| HOST: Mignon Fogarty

| Grammar Girl is part of the Quick and Dirty Tips podcast network.

  • Audio Engineer: Dan Feierabend
  • Director of Podcast: Holly Hutchings
  • Creative Partnerships & Business Development: Morgan Christianson
  • Marketing and Video: Nat Hoopes, Rebekah Sebastian
  • Podcast Associate: Maram Elnagheeb

| Theme music by Catherine Rannus.

| Grammar Girl Social Media: YouTubeTikTokFacebookThreadsInstagramLinkedInMastodonBluesky.


Hosted on Acast. See acast.com/privacy for more information.





Download audio: https://sphinx.acast.com/p/open/s/69c1476c007cdcf83fc0964b/e/6a91e68553508d8b54cbe032/media.mp3
Read the whole story
alvinashcraft
14 seconds ago
reply
Pennsylvania, USA
Share this story
Delete

Less about Models; More about Architecture

1 Share

As AI moves from experimentation to enterprise deployment, are organizations thinking too much about models and not enough about architecture? In this episode, Daniel and Chris talk with Chetan Gupta, Chief AI Officer at Rackspace, about the evolution from industrial AI and physical AI to today’s enterprise AI landscape. Discover how organizations can navigate the complex AI landscape responsibly and effectively as they think about AI architecture, model deployment, AI governance, and AI sovereignty.

Featuring: 

Sponsors:

  • Midwest AI Summit: Join AI practitioners on October 15 in Indianapolis for practical sessions, hands-on discussions, and real-world AI solutions. Use code PracticalAI20 to save 20% on your registration. https://midwestaisummit.com/#tickets
  • Prediction Guard: A self-hosted AI control plane for running agents in high impact environments. predictionguard.com/practicalai

Resources and Events:





Download audio: https://pscrb.fm/rss/p/dts.podtrac.com/redirect.mp3/media.transistor.fm/ec79b4ac/efba87b4.mp3
Read the whole story
alvinashcraft
20 seconds ago
reply
Pennsylvania, USA
Share this story
Delete

Build an AI agent without leaving VS Code

1 Share
From: Microsoft Developer
Duration: 6:55
Views: 146

https://aka.ms/foundry-portal
https://aka.ms/InsideMicrosoftFoundryPlaylist

Build the Sparkles agent using the Microsoft Foundry Toolkit. Configure the deployed model and MCP connection, invoke the agent from the development environment, and verify that it can use the shop’s prompts and live ordering tools. Store traces, evaluation datasets, and results with the project for repeatable testing.

0:00 - Build Production-Ready AI Agents
0:43 - Create a Foundry Project
1:10 - Select and Deploy GPT-5.4
1:44 - Configure the Prompt Agent
2:17 - Connect the MCP Server
2:50 - Test the Cupcake Ordering Flow
3:58 - Inspect Observability Traces
4:31 - Set Up Agent Evaluations
5:05 - Generate Test Cases with Copilot
5:39 - Run the Evaluation Metrics
6:11 - Review Results and Wrap Up

Subscribe to Microsoft Developer for more practical tutorials about AI agents, developer tools, and building production-ready applications.

#MicrosoftFoundry #AIAgents #BuildanAgent #VSCode

Read the whole story
alvinashcraft
27 seconds ago
reply
Pennsylvania, USA
Share this story
Delete

Why Fable 5.1 Is Worth the Upgrade

1 Share
From: AIDailyBrief
Duration: 28:07
Views: 4,042

Fable 5.1 takes the top spot across nearly every benchmark while Anthropic pushes hard on cost, safeguards, and zero data retention for enterprises. NLW breaks down what independent evals actually found on token burn, why the right question is no longer whether to switch models but where each one fits in a personal model stack, and why building your own benchmark matters. In the headlines: Astra hitting OpenAI's critical cyber threshold, the recurrent depth monitorability debate, Gemini 3.8 Flash, and World Labs' Atlas.

The AI Daily Brief helps you understand the most important news and discussions in AI.
Subscribe to the podcast version of The AI Daily Brief wherever you listen: https://pod.link/1680633614
Get it ad free at http://patreon.com/aidailybrief
Learn more about the show https://aidailybrief.ai/

Read the whole story
alvinashcraft
40 seconds ago
reply
Pennsylvania, USA
Share this story
Delete

Build an internet radio

1 Share

This tutorial features in Raspberry Pi Press’ book, Simple electronics with GPIO Zero. Updated for the latest Raspberry Pi devices, this book has all the info you need to start creating electronic projects using Raspberry Pi’s GPIO pins. Coded in Python with the GPIO Zero library, projects include LED lights, a motion-sensing alarm, a rangefinder, a laser-powered tripwire, and a Raspberry Pi robot.

In Raspberry Pi Official Magazine #167, we used a trick with a capacitor to get a reading from an analogue sensor. The best — and most reliable — way for Raspberry Pi to detect analogue inputs is by using an analogue-to-digital converter (ADC) chip, such as the MCP3008, which offers eight input channels to connect sensors and other analogue inputs. This not only eliminates the need for the capacitor, but it allows you to get more precise and instant readings across the entire range of the sensor’s outputs.

In this tutorial, we’ll hook up a potentiometer to an MCP3008 to control the brightness of an LED by turning the knob. We’ll then add a second potentiometer and create an internet radio, using the two potentiometers to switch the station and adjust the volume.

The final circuit with two pots

Enable SPI

The analogue values from the ADC chip will be communicated to the Raspberry Pi using the SPI protocol. While this will work in GPIO Zero out of the box, you may get better results if you enable full SPI support. First, make sure the Python spidev package is installed (it should be by default). Open a terminal window and enter:

$ sudo apt install python3-spidev

Next, click the Raspberry Pi menu, choose Preferences, and open the Control Centre, then enable SPI in the Interfaces section. Click OK and reboot your Raspberry Pi. You can also use the command-line tool by running sudo raspi-config from the command prompt, going to Interface Options, enabling SPI, and rebooting your Raspberry Pi.

Connect the ADC

As usual, you need to turn off the Raspberry Pi while creating the circuit. As you can see from Figure 1, there’s quite a lot of wiring required to connect the MCP3008 ADC to Raspberry Pi’s GPIO pins. 

First, place the MCP3008 in the middle of the breadboard, straddling its central groove. Now connect the jumper wires as in the diagram. Two go to the ‘+’ power rail, connected to a 3V3 pin; two others are connected to a GND pin via the ‘–’ rail. The four middle legs of the ADC are connected to GPIO 8 (CE0), 10 (MOSI), 9 (MISO), and 11 (SCLK).

Figure 1: Wiring up the ADC on a breadboard

Read the value

With the ADC connected to Raspberry Pi, you can wire devices to its eight input channels (numbered 0 to 7). Here, we’ll connect the first (leftmost) potentiometer, which is a variable resistor: as you turn its rotary knob, Raspberry Pi reads the voltage (from 0V to 3.3V). We can use this for control of other components, such as an LED. As shown in Figure 1, connect one outer leg of the potentiometer (bottom-left) to the ‘+’ power rail, the other side to the ‘–’ ground rail, and the middle leg to the first input of the MCP3008: channel 0.

We can now read the potentiometer’s value in Python. Create a new file, then save the following code as test_pot.py, and run it.

from gpiozero import MCP3008

pot = MCP3008(channel=0)

while True:

    print(pot.value)

At the top we import the MCP3008 class from GPIO Zero, then set the pot variable to the ADC’s channel 0. A while True: loop then continuously displays the potentiometer’s value (from 0 to 1) on the screen; try turning it as the code runs to see the number change. Press CTRL+C to exit.

Light an LED

Next, we’ll add an LED to the circuit as in Figure 2, connecting its longer (positive) leg to GPIO 21, and its shorter leg via a resistor to the ‘–’ ground rail. Create a new file, enter the following code, and save the program as source_values.py.

from gpiozero import MCP3008, PWMLED

from signal import pause

pot = MCP3008(0)

led = PWMLED(21)

led.source = pot.values

pause()

The code imports the MCP3008 and PWMLED classes, as well as the signal module’s pause function. The MCP3008 class enables us to control the brightness of an LED using pulse-width modulation (PWM). We create a PWMLED object on GPIO 21, assigning it to the led variable. We assign our potentiometer to channel 0, as before. Finally, we use GPIO Zero’s clever source and values system to pair the potentiometer with the LED, to continuously set the latter’s brightness level to the former’s value. Run the code and turn the knob to adjust the LED’s brightness. Press CTRL+C to quit the program.

Figure 2: The LED and pots added to the breadboard

Add a second pot

If you haven’t already, add a second potentiometer to our circuit as in Figure 2, with its middle leg connected to channel 1 of the MCP3008. We’ll now use both potentiometers to control our LED’s blink rate. In Thonny, create a new file, enter the following code and save it as two_pots.py.

from gpiozero import MCP3008, PWMLED

from signal import pause

pot = MCP3008(0)

led = PWMLED(21)

led.source = pot.values

pause()

Here, we create two separate pot1 and pot2 variables, assigned to the ADC’s channels 0 and 1 respectively. In a while True: loop, we then print the two values on the screen and make the LED blink, with its on and off times affected by our two potentiometers. Run the code and twist both knobs to see how it changes.

Install VLC

We’ll use the same circuit to create a simple internet radio, with one potentiometer used to switch the station and the other to adjust the volume. If it’s not installed by default, you’ll need to install the VLC media player to be able to play M3U internet radio streams. Open a terminal window and enter:

$ sudo apt install vlc

Make the radio

Create a new file, enter the code shown in the radio_new.py listing above, and save it under that name. 

At the start, we import the MCP3008 class, along with Popenrun, and time; Popen will enable us to start and stop VLC. We create variables for the station and volume dials, on ADC channels 0 and 1 respectively. We then assign variables to two radio stream URLs (we’ve used SomaFM Groove Salad and Indie Pop in this example).

Next, we create a couple of functions. The first, set_volume, uses wpctl (the command-line control tool for WirePlumber, the PipeWire session manager) to control the volume. The second,  change_station, includes an if condition so it only triggers when the station set by the first potentiometer position is different from the currently selected one (current_station). If so, it stops the current stream and starts playing the new one, before reassigning the current_station variable to it.

Finally, in a while True: loop, inside a try … except block, we read each potentiometer in turn and use if conditional statements to call the related functions, set_volume and change_station, when required.

Run the code and try turning both potentiometers to switch the station and adjust the volume. To keep things simple, we’ve only used two radio stations in this example, but you could easily add more, adjusting the station.dial.value thresholds accordingly.

Thanks to the try … except block, pressing CTRL+C stops the radio stream before exiting the program.

Download the full code.

Simple electronics with GPIO Zero

This tutorial features in Raspberry Pi Press’ book, Simple electronics with GPIO Zero. Updated for the latest Raspberry Pi devices, this book has all the info you need to start creating electronic projects using Raspberry Pi’s GPIO pins. Coded in Python with the GPIO Zero library, projects include LED lights, a motion-sensing alarm, a rangefinder, a laser-powered tripwire, and a Raspberry Pi robot.

The post Build an internet radio appeared first on Raspberry Pi.

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