AVCoders.Core
2026.11.1
Prefix Reserved
dotnet add package AVCoders.Core --version 2026.11.1
NuGet\Install-Package AVCoders.Core -Version 2026.11.1
<PackageReference Include="AVCoders.Core" Version="2026.11.1" />
<PackageVersion Include="AVCoders.Core" Version="2026.11.1" />
<PackageReference Include="AVCoders.Core" />
paket add AVCoders.Core --version 2026.11.1
#r "nuget: AVCoders.Core, 2026.11.1"
#:package AVCoders.Core@2026.11.1
#addin nuget:?package=AVCoders.Core&version=2026.11.1
#tool nuget:?package=AVCoders.Core&version=2026.11.1
AVCoders.Core
Foundation for the AV Coders device library — every driver package builds on it. Targets .NET 8.0 and .NET 10.0.
Install
dotnet add package AVCoders.Core
Published to nuget.org. See the repository README for details.
What's inside
LogBase— logging through theMicrosoft.Extensions.Logging(MEL) abstraction (no hard dependency on any logging framework), an in-memory ring buffer of recentEvents/Errors, and a staticActivitySourcefor tracing.DeviceBase— power and communication state, implementsIDevice.VolumeControl— volume/mute state for drivers that need it.SupportsLevelandSupportsMute(bothtrueunless the driver overrides them) say which halves a control has;RestoreLevelskips a half it doesn't have.SaveLevelsaves only what the device has reported: a level once a volume report has arrived, a mute state when it isn'tUnknown;RestoreLevelsends nothing for a half that had no report.CommunicationClient— transport abstraction (concrete clients live inAVCoders.CommunicationClients), withConnectionReasonbesideConnectionState: see Why a client isn't connected.SentCommandTracker<TCommand>— the commands a driver has sent, in send order, so each reply or error can be matched to the command it answers: see Matching replies to sent commands.ExtronSis— what Extron SIS error codes mean, shared by the Extron drivers in every package:MeansNotSupported(error)is true forE10(invalid command),E13(invalid parameter) andE14(not valid for this configuration).Printable(command)shows a command with its escape asEscand without its trailing carriage return.- Shared enums, handler delegates and
ThreadWorker.
Logging
Wire a logger factory once at startup. Until you do, logging is silently discarded:
LogBase.LoggerFactory = LoggerFactory.Create(builder => builder.AddConsole());
Any MEL provider works (Serilog via SerilogLoggerFactory, etc.). Each log line is scoped with the instance's Class, InstanceName, InstanceUid and the calling Method. Spans are emitted under LogBase.ActivitySourceName ("AVCoders.Core") and are zero-cost when no listener is registered.
LogException(e, message) writes one Error line with the exception attached. When the exception has an inner exception, that line's message also carries the inner exception's type and message: {Context} ({InnerExceptionType}: {InnerExceptionMessage}). LogException(e, level, message) writes the same line at another level, such as Debug for an expected connection failure. Either way, the exception is added to Errors.
See the repository README for the full logging, tracing and diagnostics-buffer guide.
Why a client isn't connected
CommunicationClient.ConnectionReason sits beside ConnectionState, which keeps its values and when they're set. It's null while nothing is wrong. Otherwise it holds:
Category—TimedOut,Refused,HostNotFound,Unreachable,CredentialsRejected,ClosedByDevice,RequestedorOther.Description— the text of theconnectionissue, such as "10.0.0.1:23 refused the connection".RawException— the exception's type and message, each inner exception after--->, or null when there was no exception.Since— when this category was first seen. A later failure with the same category keeps it.
ConnectionReasonHandlers is raised with the new reason, or null, each time it changes. A handler that throws is logged.
For transport writers:
ReportConnectionFailure(exception)sets the reason and raises theconnectionissue, both fromExplainConnectionError(exception). It maps the socket errorsTimedOut,ConnectionRefused,HostNotFound,HostUnreachableandNetworkUnreachableto their categories andConnectionResettoClosedByDevice. ATimeoutExceptionorOperationCanceledException(the client's own connect timeout) isTimedOut, an exception wrapping aSocketExceptiontakes the socket error's category, and anything else isOther. A transport overridesExplainConnectionErrorfor its own exceptions and falls back to the base.DescribeConnectionError(exception)returns just the description.ReportConnectionFailure(category, description, exception)is for a failure with its own category, such as an MQTT result code.ReportConnectionFailure(description)isOther.SetConnectionReason(category, description, exception)sets the reason without raising the issue, such as for a REST 401 while the client stays Connected.ClearConnectionFailures(), which the change to Connected calls, clears the reason.- While
DesiredConnectionStateis Disconnected, the reason isRequested("Disconnected on request") and failures don't replace it. SettingDesiredConnectionStateto anything else clearsRequested. MirrorConnectionReason(reason)copies another client's reason, for a client that wraps a transport.
Matching replies to sent commands
SentCommandTracker<TCommand> keeps every command a driver sends, in send order, with the Trigger.Id it was sent under. TCommand is the driver's own description of a command, such as a record holding its text and the reply tag it expects. Construct one per connection with the reply timeout, and an optional TimeProvider (the real clock by default).
Add(command)— record a command as it is sent.Add(command, lateErrorWindow)lets it take an error for that long after its normal reply, like a save that reports its failure later. EachAddmoves the commands past their reply timeout to the missed list thatTakeMissed()returns. The tracker holds at mostMaxCommands(256) commands, the missed ones included. Above that,Adddrops the oldest, a missed one first, and returns it, so the driver can log a Warning with the dropped command andMaxCommands. Otherwise it returns null.TakeReply(answers)— a reply arrived. Returns the oldest command still awaiting a reply for whichanswers(command)is true, and removes it. A command with a late-error window stays, but takes no second reply. Returns null when no command matches, such as for a verbose-mode change notice or a reply to another controller's command. Nothing is consumed then, and the driver decides how to log it.TakeError(canReceive)— an error arrived that names no command, such as a bare SISE13, an NEC NULL message or a Philips NACK. Returns the oldest command still awaiting a reply for whichcanReceiveis true (any command when it is null). If no command is awaiting a reply, it returns the oldest command in its late-error window instead. Returns null when nothing can take the error.TakeMissed()— removes and returns, in send order, every command that wasn't answered within the reply timeout. A command past its timeout takes no reply or error even before this is called. Call it from the poll so a missed reply is always reported. Late-error windows that have closed are dropped without being reported.IsAwaitingReply(matches)— whether a matching command is still awaiting its reply, to avoid sending a query twice.Clear()— forget every command, without reporting any as missed, such as on a reconnect.
Resume the returned TriggerId with using (Trigger.Resume(sent.TriggerId)) so the reply's log lines join the action that sent the command. All members are thread-safe. The answers, canReceive and matches functions run under the tracker's lock, so keep them to comparisons on the command.
private readonly SentCommandTracker<ExtronSisQuery> _sent = new(QueryReplyTimeout, timeProvider);
_sent.Add(query);
CommunicationClient.Send(query.Command);
if (_sent.TakeError() is { } rejected)
using (Trigger.Resume(rejected.TriggerId))
LogInformation("{Query} was rejected with {Error}", rejected.Command.Name, line);
Audit: who triggered an action
Actor— who triggered the current action:Kind(Browser,Mcp,Panel),Ip,UserAgent,HostName(null until known) andConnectionId.ToString()givesBrowser 10.1.2.3 (LAPTOP-12), orMcp 10.1.2.3without a host name.Actor.Current— the ambient actor, held in anAsyncLocal. Set it where a request enters (a Blazor circuit, an MCP tool call, a SignalR hub call) and it flows through the awaits of that call.ThreadWorkerloops never inherit it, so polling and receive loops are not attributed to whoever calledConnect().Actor(LogBase.ActorProperty) — whileActor.Currentis set, everyLogBaselog scope, tracing span andEventsentry carriesActor=Actor.Current.ToString().Audit.Record(target, action, detail)— records anAuditEntry(timestamp,Actor.Current,Trigger.Id, target, action, detail). The lastAudit.EntryLimit(1000) entries are kept in memory and returned oldest first byAudit.GetEntries(). There is no clear. Each entry is also logged at Information under theAudit.Category("AVCoders.Audit") logger, with the structured propertiesAuditTarget,AuditAction,AuditDetail,ActorKind(Unknownwithout an actor),ClientIp,ClientHostName,ClientUserAgent,ClientConnectionIdandTriggerId, so remote log servers receive it. If the host raises the minimum level forAVCoders.*, keepAVCoders.Auditat Information.Audit.Recorded— raised after each entry is stored. Each subscriber runs in its own try/catch; a throwing subscriber is logged as a Warning and the others still run.HostNames.Get(IPAddress)— the reverse-DNS name for an address, or null. It never blocks: a miss returns null and starts one background lookup, so the name is there from a later call. Lookups run one at a time. Results, failures included, are cached for 10 minutes, since DHCP can hand the address to another machine; after that the last known name is returned while a fresh lookup runs. Call it each time you build anActor.
Actor.Current = new Actor(ActorKind.Browser, ip.ToString(), userAgent, HostNames.Get(ip), circuitId);
Audit.Record("Projector", "Power", "On");
Trigger id: which action
Trigger.Id is the current action's id, 8 lowercase hex characters (a3f9c2e1), held in an AsyncLocal separate from Actor.Current: the actor is who, the trigger is which action, and a flow a device starts has an id but no actor. Trigger.Start() sets a new id and returns it. Synchronous callbacks on threads that outlive the call, such as Crestron SDK event threads, use using (Trigger.Begin()), which starts a new id and restores the previous one on dispose. Each ThreadWorker pass starts its own, so every poll cycle, reconnect attempt and received chunk has one; browser events, MCP tool calls and SignalR hub calls start theirs in the UI and SignalR packages. Audit.Record stamps the current id and never starts one. Work that runs later on another thread, such as a queued command when it is sent or a device reply matched to the command that caused it, resumes the id it was given with using (Trigger.Resume(id)) { ... }, which restores the previous id on dispose. A null id, from a command sent outside any trigger such as at program startup, keeps the current id, so a matched reply stays under the receive's id. While an id is set, every LogBase log scope, tracing span and Events entry carries TriggerId (LogBase.TriggerIdProperty), next to Actor.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Microsoft.Extensions.Logging.Abstractions (>= 8.0.2)
-
net8.0
- Microsoft.Extensions.Logging.Abstractions (>= 8.0.2)
NuGet packages (33)
Showing the top 5 NuGet packages that depend on AVCoders.Core:
| Package | Downloads |
|---|---|
|
AVCoders.MediaPlayer
Media player, recorder and IPTV drivers for the AV Coders device library, including Lumens, Extron SMP, TriplePlay, Vitec and Exterity. |
|
|
AVCoders.CommunicationClients
Transport clients for the AV Coders device library: TCP, UDP, SSH, MQTT, REST, multicast and TCP server connections, plus SNMPv3 and Wake-on-LAN. |
|
|
AVCoders.Display
Display, projector and LED-wall drivers for the AV Coders device library, including PjLink, Samsung MDC, Sony, NEC, Philips SICP, LG, CEC, NovaStar and Colorlight. |
|
|
AVCoders.Dsp
Audio DSP drivers for the AV Coders device library, including Biamp Tesira, QSC Q-SYS, Bose ControlSpace and a CEC audio system (soundbar) volume control. |
|
|
AVCoders.Camera
PTZ camera and auto-tracking drivers for the AV Coders device library, including Sony and AVer VISCA, Lumens and 1 Beyond AutomateVX. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 2026.11.1 | 0 | 11/12/2026 |
| 2026.10.5 | 469 | 10/21/2026 |
| 2026.10.4 | 433 | 10/21/2026 |
| 2026.10.3 | 749 | 10/14/2026 |
| 2026.10.2 | 426 | 10/13/2026 |
| 2026.10.1 | 424 | 10/6/2026 |
| 2026.9.8 | 428 | 9/29/2026 |
| 2026.9.7 | 424 | 9/24/2026 |
| 2026.9.6 | 423 | 9/18/2026 |
| 2026.9.5 | 428 | 9/17/2026 |
| 2026.9.4 | 723 | 9/14/2026 |
| 2026.9.2 | 432 | 9/2/2026 |
| 2026.9.1 | 419 | 9/2/2026 |
| 2026.8.5 | 407 | 8/27/2026 |
| 2026.8.4 | 410 | 8/26/2026 |
| 2026.8.3 | 404 | 8/26/2026 |
| 2026.8.2 | 466 | 8/24/2026 |
| 2026.8.1 | 574 | 8/11/2026 |
| 2026.7.3 | 486 | 7/22/2026 |
| 2026.7.2 | 669 | 7/21/2026 |