16 Commits

Author SHA1 Message Date
jwetzell ceb18307f6 Update min and target android version 2023-11-25 23:31:47 -06:00
jwetzell 2f5ce281d3 cleanup logging 2023-11-25 23:26:07 -06:00
jwetzell 7b6ed09ced rework TCPClient yet again to try to nail down this SLIP framing problem 2023-11-25 22:46:06 -06:00
jwetzell 486af45d53 Make SlipFrame constants public 2023-11-25 22:45:10 -06:00
jwetzell e5d09538ab add in status/data and connection event 2022-11-08 15:41:27 -06:00
jwetzell 1a0ccc8763 Update publish.yml 2022-11-05 15:03:51 -05:00
jwetzell fce3eab8d7 bump version number 2022-11-05 14:58:58 -05:00
jwetzell 2176ca4488 TCP Work, Log cleanup, Message parsing 2022-11-05 13:50:27 -05:00
jwetzell 57ca80df77 Fix QLab Message parsing for reply formats 2022-11-05 13:23:14 -05:00
jwetzell 974d14156f Update dependencies 2022-10-11 18:00:32 -05:00
jwetzell 92a44d95d4 Create LICENSE 2022-07-25 10:06:15 -05:00
jwetzell 498d2a41f9 Update README.md 2022-07-23 13:56:56 -05:00
jwetzell 730e7bc554 Update dependencies 2022-07-23 13:10:25 -05:00
jwetzell 1eb8e0c42b Cue properties clean up 2022-07-23 09:57:47 -05:00
jwetzell 69b8210362 switch to simpletcp library 2022-07-23 09:57:33 -05:00
jwetzell 1deca3e7f3 Change Log Levels 2022-07-23 09:57:06 -05:00
26 changed files with 460 additions and 304 deletions
+1 -1
View File
@@ -18,7 +18,7 @@ jobs:
- name: Publish NuGet
# You may pin to the exact commit or the version.
# uses: brandedoutcast/publish-nuget@c12b8546b67672ee38ac87bea491ac94a587f7cc
uses: brandedoutcast/publish-nuget@v2.5.5
uses: alirezanet/publish-nuget@v3.0.4
with:
# Filepath of the project to be packaged, relative to root of repository
PROJECT_FILE_PATH: QControlKit/QControlKit.csproj
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 Joel Wetzell
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+11
View File
@@ -289,6 +289,17 @@
public const string Error = "error";
}
public static class QConnectionRole
{
public const string View = "view";
public const string ViewEdit = "view|edit";
public const string ViewEditControl = "view|edit|control";
public const string ViewControl = "view|control";
public const string EditControl = "edit|control";
public const string Edit = "edit";
public const string Control = "control";
}
public static class QColorName
{
public const string Red = "red";
+3 -1
View File
@@ -48,7 +48,8 @@ namespace QControlKit.Events
public class QWorkspaceConnectedArgs : EventArgs
{
public string status { get; set; }
public string data { get; set; }
}
public class QWorkspaceDisconnectedArgs : EventArgs
@@ -59,6 +60,7 @@ namespace QControlKit.Events
public class QWorkspaceConnectionErrorArgs : EventArgs
{
public string status { get; set; }
public string data { get; set; }
}
public class QQLabPreferencesUpdatedArgs : EventArgs
{
+8 -8
View File
@@ -13,6 +13,8 @@ namespace QControlKit
{
public class QBrowser
{
private ILogger _log = Log.Logger.ForContext<QBrowser>();
public ObservableCollection<QServer> servers = new ObservableCollection<QServer>();
public event QServerFoundHandler ServerFound;
@@ -26,17 +28,16 @@ namespace QControlKit
public async void ProbeForQLabInstances()
{
Log.Debug("[qbrowser] probing for instances");
_log.Debug("probing for instances");
IReadOnlyList<IZeroconfHost> results = await
ZeroconfResolver.ResolveAsync(QBonjour.TCPService,TimeSpan.FromSeconds(3));
foreach (var zeroconfHost in results)
{
Log.Debug($"[qbrowser] found host {zeroconfHost.IPAddress}:{zeroconfHost.DisplayName}");
_log.Debug($"found host {zeroconfHost.IPAddress}:{zeroconfHost.DisplayName}");
foreach (var service in zeroconfHost.Services)
{
Log.Debug($"[qbrowser] found {service.Key}:{service.Value}");
if (service.Key.Equals(QBonjour.TCPService))
{
@@ -44,7 +45,7 @@ namespace QControlKit
if (server == null)
{
Log.Information($"[qbrowser] Found {zeroconfHost.DisplayName} : {zeroconfHost.IPAddress} : {service.Value.Port}");
_log.Information($"Found {zeroconfHost.DisplayName} : {zeroconfHost.IPAddress} : {service.Value.Port}");
QServer serverToAdd = new QServer(zeroconfHost.IPAddress, service.Value.Port);
serverToAdd.name = zeroconfHost.DisplayName;
serverToAdd.zeroconfHost = zeroconfHost;
@@ -70,10 +71,9 @@ namespace QControlKit
if (found == null)
{
Log.Information($"[qbrowser] Lost {server.name} : {server.host} : {server.port}");
_log.Information($"Lost {server.name} : {server.host} : {server.port} disconnecting");
server.disconnect();
Log.Information($"[qbrowser] after server disconnect()");
_log.Verbose($"after server disconnect()");
servers.Remove(server);
OnServerLost(server);
}
@@ -123,7 +123,7 @@ namespace QControlKit
public void Close()
{
Log.Information($"[qbrowser] Close requested");
_log.Information($"Close requested");
foreach (var server in servers)
{
server.disconnect();
+32 -33
View File
@@ -12,6 +12,8 @@ namespace QControlKit
{
public class QClient
{
private ILogger _log = Log.Logger.ForContext<QClient>();
TCPClient tcpClient;
public event QWorkspacesUpdatedHandler WorkspacesUpdated;
@@ -35,7 +37,7 @@ namespace QControlKit
public QClient(string host, int port)
{
tcpClient = new TCPClient(host, port);
Log.Debug($"[client] setup connection to: <{host}:{port}>");
_log.Debug($"setup connection to: <{host}:{port}>");
tcpClient.MessageReceived += ProcessMessage;
}
@@ -52,15 +54,14 @@ namespace QControlKit
public void disconnect()
{
Log.Information($"[client] disconnecting from {tcpClient.Address}");
_log.Information($"disconnecting from {tcpClient.Address}");
tcpClient.Close();
}
public void sendMessage(string address, params object[] args)
{
tcpClient.QueueForSending(new OscMessage(address, args));
Log.Information($"[client] send message {address} : {args}");
tcpClient.Send(new OscMessage(address, args));
}
private void ProcessMessage(object source, MessageEventArgs args)
@@ -70,7 +71,7 @@ namespace QControlKit
if (message.IsReply)
{
JToken data = message.response;
JToken data = message.data;
//special case, want to update cue properties
if (message.IsReplyFromCue)
@@ -84,26 +85,25 @@ namespace QControlKit
string property = message.AddressParts.Last();
if (property == null)
return;
if (property == QOSCKey.PlaybackPositionId)
{
OnCueListChangedPlaybackPosition(message.cueID, data.ToString());
return;
}
//create object manually since single value replies don't have dictionaries
JObject properties = new JObject();
properties.Add(property, data);
OnCueUpdated(message.cueID, properties);
if (property == QOSCKey.PlaybackPositionId)
{
OnCueListChangedPlaybackPosition(message.cueID, data.ToString());
}
}
else
{
Log.Error($"[client] unhandled reply from cue: Type: {data.Type} value: {message.response}");
_log.Error($"unhandled reply from cue: Type: {data.Type} value: {message.data}");
}
}
else if (message.IsReplyFromCueLists)
{
OnCueListsUpdated(message.response);
OnCueListsUpdated(message.data);
}
else if (message.IsWorkspacesInfo)
{
@@ -111,14 +111,14 @@ namespace QControlKit
}
else if (message.IsConnect)
{
if (message.response.ToString() == "ok")
OnWorkspaceConnected();
if (message.status.ToString().Equals("ok"))
OnWorkspaceConnected(message.status.ToString(),message.data.ToString());
else
OnWorkspaceConnectionError(message.response.ToString());
OnWorkspaceConnectionError(message.status.ToString(), message.data.ToString());
}
else
{
Log.Error($"[client] unhandled reply message: {message.address}");
_log.Error($"unhandled reply message: {message.address}");
}
}
else if(message.IsUpdate) {
@@ -150,7 +150,6 @@ namespace QControlKit
else if ( message.IsPreferencesUpdate)
{
//need to do checks for 4.2 or newer
string key = message.AddressParts.Last();
if (key == null)
return;
@@ -162,76 +161,76 @@ namespace QControlKit
}
else
{
Log.Error($"[client] unhandled update message: {message.address}");
_log.Error($"unhandled update message: {message.address}");
}
}
else
{
Log.Error($"[client] unhandled message: {message.address}");
_log.Error($"unhandled message: {message.address}");
}
}
protected virtual void OnCueUpdated(string cueID, JToken properties)
{
Log.Debug($"[client] cue updated: {cueID}");
_log.Debug($"cue updated: {cueID}");
CueUpdated?.Invoke(this, new QCueUpdatedArgs { cueID = cueID, data = properties });
}
protected virtual void OnCueNeedsUpdated(string cueID)
{
Log.Debug($"[client] cue needs updated: {cueID}");
_log.Debug($"cue needs updated: {cueID}");
CueNeedsUpdated?.Invoke(this, new QCueNeedsUpdatedArgs { cueID = cueID });
}
protected virtual void OnCueListsUpdated(JToken response)
{
Log.Debug($"[client] Cue Lists Updated");
_log.Debug("Cue Lists Updated");
CueListsUpdated?.Invoke(this, new QCueListsUpdatedArgs { data = response });
}
protected virtual void OnCueListChangedPlaybackPosition(string cueListID, string cueID)
{
Log.Debug($"[client] CueList <{cueListID}> Playback Position Changed to <{cueID}>");
_log.Debug($"CueList <{cueListID}> Playback Position Changed to <{cueID}>");
CueListChangedPlaybackPosition?.Invoke(this, new QCueListChangedPlaybackPositionArgs { cueListID = cueListID, cueID = cueID });
}
protected virtual void OnWorkspaceUpdated()
{
//Log.Debug($"[client] Workspace Updated");
_log.Debug($"Workspace Updated");
WorkspaceUpdated?.Invoke(this, new QWorkspaceUpdatedArgs());
}
protected virtual void OnWorkspaceSettingsUpdated(string settingsType)
{
Log.Debug($"[client] Workspace Settings Updated");
_log.Debug($"Workspace Settings Updated");
WorkspaceSettingsUpdated?.Invoke(this, new QWorkspaceSettingsUpdatedArgs { settingsType = settingsType });
}
protected virtual void OnWorkspaceLightDashboardUpdated()
{
Log.Debug($"[client] Workspace Light Dashboard Updated");
_log.Debug("Workspace Light Dashboard Updated");
WorkspaceLightDashboardUpdated?.Invoke(this, new QWorkspaceLightDashboardUpdatedArgs());
}
protected virtual void OnQLabPreferencesUpdated(string key)
{
Log.Debug($"[client] QLab Preferences Updated");
_log.Debug("QLab Preferences Updated");
QLabPreferencesUpdated?.Invoke(this, new QQLabPreferencesUpdatedArgs { key = key });
}
protected virtual void OnWorkspaceDisconnected()
{
Log.Debug($"[client] Workspace Disconnected");
_log.Debug("Workspace Disconnected");
WorkspaceDisconnected?.Invoke(this, new QWorkspaceDisconnectedArgs());
}
protected virtual void OnWorkspaceConnected()
protected virtual void OnWorkspaceConnected(string status, string data)
{
WorkspaceConnected?.Invoke(this, new QWorkspaceConnectedArgs());
WorkspaceConnected?.Invoke(this, new QWorkspaceConnectedArgs { status = status, data = data });
}
protected virtual void OnWorkspaceConnectionError(string status)
protected virtual void OnWorkspaceConnectionError(string status, string data)
{
WorkspaceConnectionError?.Invoke(this, new QWorkspaceConnectionErrorArgs { status = status });
WorkspaceConnectionError?.Invoke(this, new QWorkspaceConnectionErrorArgs { status = status, data = data });
}
protected virtual void OnWorkspacesUpdated(QMessage message)
@@ -239,7 +238,7 @@ namespace QControlKit
if(WorkspacesUpdated != null)
{
List<QWorkspaceInfo> workspaces = new List<QWorkspaceInfo>();
foreach (var item in message.response)
foreach (var item in message.data)
{
QWorkspaceInfo workspacefound = JsonConvert.DeserializeObject<QWorkspaceInfo>(item.ToString());
workspaces.Add(workspacefound);
+4 -3
View File
@@ -6,7 +6,7 @@
<Authors>Joel Wetzell</Authors>
<Version>1.0.0-devel</Version>
<PackOnBuild>true</PackOnBuild>
<PackageVersion>0.0.24</PackageVersion>
<PackageVersion>0.0.33</PackageVersion>
<PackageId>QControlKit</PackageId>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<Summary>Unofficial C# port of Figure53's QLabKit.objc</Summary>
@@ -15,10 +15,10 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Serilog" Version="2.10.0" />
<PackageReference Include="Serilog" Version="2.12.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="TcpSharp" Version="1.2.0" />
<PackageReference Include="Zeroconf" Version="3.5.11" />
<PackageReference Include="SuperSimpleTcp" Version="2.6.0" />
</ItemGroup>
<ItemGroup>
<Folder Include="Constants\" />
@@ -32,5 +32,6 @@
<None Remove="SharpOSC\" />
<None Remove="SimpleTCP" />
<None Remove="SuperSimpleTcp" />
<None Remove="WatsonTcp" />
</ItemGroup>
</Project>
+14 -11
View File
@@ -12,6 +12,7 @@ namespace QControlKit
{
public class QCue : IComparable
{
private ILogger _log = Log.Logger.ForContext<QCue>();
public QWorkspace workspace;
public Dictionary<string, object> cueData;
@@ -45,8 +46,6 @@ namespace QControlKit
init();
this.workspace = workspace;
updatePropertiesWithDictionary(dict);
workspace.fetchBasicPropertiesForCue(this);
}
public QCue(JToken dict, QWorkspace workspace, int nestLevel)
@@ -55,7 +54,6 @@ namespace QControlKit
this.workspace = workspace;
this.nestLevel = nestLevel + 1;
updatePropertiesWithDictionary(dict);
workspace.fetchBasicPropertiesForCue(this);
}
public void init()
@@ -165,7 +163,6 @@ namespace QControlKit
QCue cue = childCuesUIDMap[aUid];
//Log.Debug($"Removing Child Cue From {listName} : {cue.uid} + {cue.listName}");
childCues.Remove(cue);
childCuesUIDMap.Remove(aUid);
}
@@ -650,14 +647,12 @@ namespace QControlKit
JToken value = obj.Value;
if (obj.Key.Equals(QOSCKey.Cues))
{
//Log.Debug($"Cues OSC Key found in update message...updating child cues");
if (value.Type != JTokenType.Array)
continue;
updateChildCuesWithPropertiesArray(value, false);
}
else if(obj.Key.Equals(QOSCKey.Children) && IsGroup)
{
//Log.Debug($"Children OSC Key found in update message for {uid} ...updating child and removing deleted ones.");
if (value.Type != JTokenType.Array)
continue;
updateChildCuesWithPropertiesArray(value, true);
@@ -671,7 +666,6 @@ namespace QControlKit
{
cueUpdated = true;
propertiesUpdated.Add(obj.Key);
//Log.Debug($"[cue] cue property {obj.Key} updated with {obj.Value}");
}
}
}
@@ -806,7 +800,7 @@ namespace QControlKit
if (propertyForKey(key) != null)
{
object prop = propertyForKey(key);
//Log.Warning($"{listName} IsBroken: {propertyForKey(QOSCKey.IsBroken).ToString()} {prop.GetType().ToString()}");
//_log.Warning($"{listName} IsBroken: {propertyForKey(QOSCKey.IsBroken).ToString()} {prop.GetType().ToString()}");
if(prop.GetType() == typeof(Boolean)){
return (bool)prop;
}
@@ -896,7 +890,7 @@ namespace QControlKit
}
else if (key.Equals(QOSCKey.Children))
{
Log.Debug($"Children Cues property updated for: {this.displayName} : {this.uid}");
_log.Debug($"Children Cues property updated for: {this.displayName} : {this.uid}");
}
else if(key.Equals(QOSCKey.PlaybackPositionId))
{
@@ -995,9 +989,18 @@ namespace QControlKit
#region Event Handling
void OnCuePropertiesUpdated(List<string> properties)
{
_log.Verbose("OnCuePropertiesUpdated");
if(parentID != null && parentID != "")
{
workspace.fetchBasicPropertiesForCue(workspace.cueWithID(parentID));
QCue parentCue = workspace.cueWithID(parentID);
if(parentCue != null)
{
workspace.fetchBasicPropertiesForCue(workspace.cueWithID(parentID));
}
else
{
_log.Error($"parent cue with id: {parentID} can't be found this could be a problem");
}
}
CuePropertiesUpdated?.Invoke(this, new QCuePropertiesUpdatedArgs { properties = properties });
}
@@ -1014,7 +1017,7 @@ namespace QControlKit
{
string indent = new string(' ', level*2);
Log.Information($"{indent}\u00b7{displayName} - {uid}");
_log.Information($"{indent}\u00b7{displayName} - {uid}");
if (IsGroup)
{
level++;
+50 -4
View File
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using Newtonsoft.Json.Linq;
using SharpOSC;
@@ -28,7 +29,32 @@ namespace QControlKit
public bool IsReply { get { return OSCMessage.Address.StartsWith("/reply"); } }
// /reply/cue_id/1/action
public bool IsReplyFromCue { get{ return OSCMessage.Address.StartsWith("/reply/cue_id"); } }
public bool IsReplyFromCue {
get {
if (!IsReply)
{
return false;
}
//return OSCMessage.Address.StartsWith("/reply/cue_id");
if (AddressParts.Length > 5)
{
// /reply/workspace/*/cue_id/*/*
if (AddressParts[1].Equals("workspace") && AddressParts[3].Equals("cue_id")){
return true;
}
}
if(AddressParts.Length > 2)
{
// /reply/cue_id/*/*
if (AddressParts[1].Equals("cue_id"))
{
return true;
}
}
return false;
}
}
public bool IsReplyFromCueLists { get { return IsReply && OSCMessage.Address.EndsWith("cueLists"); } }
public bool IsUpdate { get{ return OSCMessage.Address.StartsWith("/update"); } }
@@ -115,12 +141,25 @@ namespace QControlKit
public string[] AddressParts { get { return address.Split(new char[] { '/' },StringSplitOptions.RemoveEmptyEntries); } }
public JToken response
public JToken data
{
get
{
JObject responseObj = JObject.Parse((string)arguments[0]);
return responseObj["data"];
JObject dataObj = JObject.Parse((string)arguments[0]);
return dataObj["data"];
}
}
public JToken status
{
get
{
if (arguments != null && arguments.Count > 0)
{
JObject statusObj = JObject.Parse((string)arguments[0]);
return statusObj["status"];
}
return null;
}
}
@@ -136,6 +175,13 @@ namespace QControlKit
//TODO: check string cast
return arguments.Count > 0 ? (string)arguments[0] : null;
} else if (IsReplyFromCue) {
// /reply/workspace/*/cue_id/*/*
if (AddressParts.Length > 5)
{
return AddressParts[4];
}
// /reply/cue_id/*/*
return AddressParts[2];
} else {
return null;
+11 -6
View File
@@ -10,11 +10,13 @@ namespace QControlKit
{
public class QServer
{
private ILogger _log = Log.Logger.ForContext<QServer>();
public event QServerUpdatedHandler ServerUpdated;
public event QServerWorkspaceAddedHandler WorkspaceAdded;
public event QServerWorkspaceRemovedHandler WorkspaceRemoved;
private QClient client;
private QClient _client;
public string host { get; set; }
public int port { get; set; }
@@ -28,18 +30,21 @@ namespace QControlKit
this.host = host;
this.port = port;
client = new QClient(host, port);
_client = new QClient(host, port);
client.WorkspacesUpdated += OnServerWorkspacesUpdated;
if (!client.connect())
{
Log.Error($"[server] unable to connect to QLab Server: {host}:{port}");
_log.Error($"unable to connect to QLab Server: {host}:{port}");
}
}
public string description { get { return $"{name} - {host} - {port}"; } }
public QClient client {
get { return _client; }
}
public void refreshWorkspaces()
{
client.sendMessage("/workspaces");
@@ -111,12 +116,12 @@ namespace QControlKit
public void disconnect()
{
Log.Information($"[server] disconnect requested for server <{name}>");
_log.Information($"disconnect requested for server <{name}>");
foreach (var workspace in workspaces)
{
if (workspace.connected)
{
Log.Debug($"[server] Closing workspace <{workspace.name}> still connected to {name}");
_log.Debug($"Closing workspace <{workspace.name}> still connected to {name}");
workspace.disconnect();
}
}
+33 -25
View File
@@ -10,6 +10,7 @@ namespace QControlKit
{
public class QWorkspace: IEquatable<QWorkspace>
{
private ILogger _log = Log.Logger.ForContext<QWorkspace>();
private QServer server;
private QClient client;
@@ -64,7 +65,7 @@ namespace QControlKit
updateWithWorkspaceInfo(workspaceInfo);
client = new QClient(server.host, server.port);
client = server.client;
client.WorkspaceConnected += OnWorkspaceConnected;
client.WorkspaceConnectionError += OnWorkspaceConnectionError;
@@ -75,7 +76,7 @@ namespace QControlKit
client.CueUpdated += OnCueUpdated;
this.server = server;
Log.Debug($"[workspace] <{name}> on <{server.name}> initialized.");
_log.Debug($"<{name}> on <{server.name}> initialized.");
}
//updateWithDictionary
@@ -159,24 +160,26 @@ namespace QControlKit
public string[] versionParts { get { return version.Split('.'); } }
public bool connectedToQLab3 { get { return versionParts[0] == "3"; } }
public bool connectedToQLab4 { get { return versionParts[0] == "4"; } }
public bool connectedToQLab5 { get { return versionParts[0] == "5"; } }
#region Connection/reconnection
public void connect(string passcode = null)
{
Log.Information($"[workspace] connecting to <{name}> @ {this.server.host}:{this.server.port}");
_log.Information($"connecting to <{name}> @ {this.server.host}:{this.server.port}");
if(hasPasscode && passcode == null)
{
Log.Error($"[workspace] *** workspace <{name}> requires a passcode but none was supplied.");
OnWorkspaceConnectionError(this, new QWorkspaceConnectionErrorArgs { status = QConnectionStatus.BadPass });
_log.Error($"*** workspace <{name}> requires a passcode but none was supplied.");
OnWorkspaceConnectionError(this, new QWorkspaceConnectionErrorArgs { status = QConnectionStatus.BadPass, data = "badpass" });
return;
}
if (!client.connect())
{
Log.Error($"[workspace] *** couldn't connect to server client is not connected.");
OnWorkspaceConnectionError(this, new QWorkspaceConnectionErrorArgs { status = QConnectionStatus.Error });
_log.Error($"*** couldn't connect to server client is not connected.");
OnWorkspaceConnectionError(this, new QWorkspaceConnectionErrorArgs { status = QConnectionStatus.Error, data = "error" });
return;
}
@@ -210,7 +213,7 @@ namespace QControlKit
public void disconnect()
{
Log.Information($"[workspace] disconnecting from <{name}>");
_log.Information($"disconnecting from <{name}>");
if (!connected)
return;
stopReceivingUpdates();
@@ -319,17 +322,12 @@ namespace QControlKit
{
List<string> keys = new List<string> {
QOSCKey.UID,
QOSCKey.Number,
QOSCKey.Name,
QOSCKey.ListName,
QOSCKey.Type,
QOSCKey.ColorName,
QOSCKey.Flagged,
QOSCKey.Armed,
QOSCKey.IsRunning,
QOSCKey.IsBroken,
QOSCKey.IsLoaded,
QOSCKey.Parent,
QOSCKey.Notes
};
fetchPropertiesForCue(cue, keys.ToArray(), false);
@@ -422,27 +420,27 @@ namespace QControlKit
{
//clear passcode if there was one set in the connect() method
this.passcode = null;
Log.Error($"[workspace] *** Password for workspace <{name}> was incorrect!");
_log.Error($"*** Password for workspace <{name}> was incorrect!");
}
else
{
Log.Error($"[workspace] *** Unable to connect to workspace: <{name}> on server: <{server.name}>");
_log.Error($"*** Unable to connect to workspace: <{name}> on server: <{server.name}>");
}
WorkspaceConnectionError?.Invoke(this, new QWorkspaceConnectionErrorArgs { status = args.status });
WorkspaceConnectionError?.Invoke(this, args);
}
private void OnWorkspaceConnected(object source, QWorkspaceConnectedArgs args)
{
Log.Information($"[workspace] Connection to <{name}> successful, finishing things up.");
WorkspaceConnected?.Invoke(this, new QWorkspaceConnectedArgs());
_log.Information($"Connection to <{name}> successful, finishing things up.");
WorkspaceConnected?.Invoke(this, args);
finishConnection();
}
private void OnWorkspaceDisconnected(object source, QWorkspaceDisconnectedArgs args)
{
//this might not be called with TCP?
Log.Warning($"[workspace] *** Workspace has indicated it is disconnecting");
_log.Warning($"*** Workspace has indicated it is disconnecting");
WorkspaceDisconnected?.Invoke(this, new QWorkspaceDisconnectedArgs());
}
@@ -511,7 +509,7 @@ namespace QControlKit
//add Event handled? use CueUpdated one?
}
Log.Debug($"[workspace] cueLists finished processing. root updated? {rootCueUpdated}");
_log.Debug($"cueLists finished processing. root updated? {rootCueUpdated}");
OnWorkspaceUpdated();
@@ -529,7 +527,7 @@ namespace QControlKit
cueList.setProperty(args.cueID, QOSCKey.PlaybackPositionId, false);
}
Log.Information($"[workspace] cue list <{args.cueListID}> playback position changed to <{args.cueID}>");
_log.Information($"cue list <{args.cueListID}> playback position changed to <{args.cueID}>");
CueListChangedPlaybackPosition?.Invoke(this, new QCueListChangedPlaybackPositionArgs { cueListID = args.cueListID, cueID = args.cueID });
}
@@ -543,7 +541,7 @@ namespace QControlKit
{
if (args.cueID.Equals(QIdentifiers.RootCueUpdate))
{
Log.Debug("[workspace] root cue update requested, updating all cue lists");
_log.Debug("root cue update requested, updating all cue lists");
foreach (var cuelist in this.cueLists)
{
if (!cuelist.uid.Equals(QIdentifiers.ActiveCues))
@@ -565,10 +563,20 @@ namespace QControlKit
private void OnCueUpdated(object source, QCueUpdatedArgs args)
{
_log.Verbose($"OnCueUpdated: {args.cueID}");
QCue cue = cueWithID(args.cueID);
if (cue == null || cue.ignoreUpdates)
if(cue == null)
{
_log.Error($"Could not find cue to update, this is likely a problem.");
return;
}
if (cue.ignoreUpdates)
{
return;
}
cue.updatePropertiesWithDictionary(args.data);
}
@@ -583,7 +591,7 @@ namespace QControlKit
#region Printing
public void Print()
{
Log.Information($"[workspace] {name}");
_log.Information($"{name}");
foreach (var cueList in root.cues)
{
cueList.Print();
-2
View File
@@ -26,8 +26,6 @@ namespace SharpOSC
private static OscMessage parseMessage(byte[] msg)
{
int index = 0;
//Log.Debug("Raw ASCII DATA: " + System.Text.Encoding.ASCII.GetString(msg));
//Log.Debug("Raw UTF-8 DATA: " + System.Text.Encoding.UTF8.GetString(msg));
string address = null;
char[] types = new char[0];
List<object> arguments = new List<object>();
+5 -4
View File
@@ -4,10 +4,10 @@ namespace SharpOSC
{
public static class SlipFrame
{
static readonly byte END = 0xc0;
static readonly byte ESC = 0xdb;
static readonly byte ESC_END = 0xDC;
static readonly byte ESC_ESC = 0xDD;
public static readonly byte END = 0xc0;
public static readonly byte ESC = 0xdb;
public static readonly byte ESC_END = 0xDC;
public static readonly byte ESC_ESC = 0xDD;
public static List<byte[]> Decode(byte[] data)
{
@@ -26,6 +26,7 @@ namespace SharpOSC
buffer.Add(data[i]);
}
}
return messages;
}
+74 -69
View File
@@ -1,8 +1,9 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Linq;
using System.Text;
using Serilog;
using SuperSimpleTcp;
using TcpSharp.Events;
namespace SharpOSC
{
@@ -17,6 +18,8 @@ namespace SharpOSC
public class TCPClient
{
private ILogger _log = Log.Logger.ForContext<TCPClient>();
public int Port
{
get { return _port; }
@@ -30,96 +33,96 @@ namespace SharpOSC
public delegate void MessageReceivedHandler(object source, MessageEventArgs args);
public event MessageReceivedHandler MessageReceived;
private Queue<OscPacket> SendQueue = new Queue<OscPacket>();
private Thread sendThread;
string _address;
SimpleTcpClient simpleClient;
TcpSharp.TcpSharpSocketClient tcpClient;
private List<byte> frameStream = new List<byte>();
public TCPClient(string address, int port)
{
_port = port;
_address = address;
simpleClient = new SimpleTcpClient(address, port);
simpleClient.Events.Connected += Connected;
simpleClient.Events.Disconnected += Disconnected;
simpleClient.Events.DataReceived += DataReceived;
simpleClient.Logger = Log.Debug;
}
public static string ByteArrayToString(byte[] ba)
{
return BitConverter.ToString(ba).Replace("-", "");
}
private void DataReceived(object sender, DataReceivedEventArgs e)
{
List<byte[]> messages = SlipFrame.Decode(e.Data);
for(int i = 0; i < messages.Count; i++)
{
OscPacket packet = OscPacket.GetPacket(messages[i]);
OscMessage responseMessage = (OscMessage)packet;
OnMessageReceived(responseMessage);
}
}
private void Disconnected(object sender, ConnectionEventArgs e)
{
Log.Debug("[tcpClient] connection to " + e.IpPort + " was disconnected");
}
private void Connected(object sender, ConnectionEventArgs e)
{
Log.Debug($"[tcpclient] connected to <{e.IpPort}>");
}
public bool Connect()
{
try
{
simpleClient.Connect();
sendThread = new Thread(SendLoop);
sendThread.IsBackground = true;
sendThread.Start();
tcpClient = new TcpSharp.TcpSharpSocketClient(Address, Port);
tcpClient.OnConnected += ClientConnected;
tcpClient.OnDataReceived += DataReceived;
tcpClient.OnDisconnected += ClientDisconneted;
tcpClient.OnError += ClientError;
tcpClient.Connect();
return true;
}
}
catch (Exception e)
{
Log.Error(e.Message);
_log.Error(e.Message);
return false;
}
}
private void ClientError(object sender, OnClientErrorEventArgs e)
{
_log.Error("client error");
_log.Error(e.Exception.ToString());
}
public void QueueForSending(OscPacket packet)
private void ClientDisconneted(object sender, OnClientDisconnectedEventArgs e)
{
SendQueue.Enqueue(packet);
_log.Information($"client disconnected: {e.Reason}");
Close();
}
private void SendLoop()
private void DataReceived(object sender, OnClientDataReceivedEventArgs e)
{
while (simpleClient != null && simpleClient.IsConnected)
{
if(SendQueue.Count > 0)
{
OscPacket packet = SendQueue.Dequeue();
Send(packet);
}
frameStream.AddRange(e.Data);
int i = 0;
int frameEnd = frameStream.FindIndex(1, frameByte=>frameByte.Equals(SlipFrame.END));
while(frameEnd > 0)
{
List<byte> frame = frameStream.GetRange(0, frameEnd+1);
frameStream.RemoveRange(0, frameEnd + 1);
List<byte[]> messages = SlipFrame.Decode(frame.ToArray());
foreach (var message in messages)
{
try
{
OscPacket packet = OscPacket.GetPacket(message);
OscMessage responseMessage = (OscMessage)packet;
if (packet == null)
{
_log.Error("packet is null");
}
if (responseMessage == null)
{
_log.Error("responeMessage is null");
}
OnMessageReceived(responseMessage);
}
catch (Exception ex)
{
_log.Error($"Exception parsing OSC message: {ex.ToString()}");
}
}
frameEnd = frameStream.FindIndex(0, frameByte => frameByte.Equals(SlipFrame.END));
}
}
private void ClientConnected(object sender, OnClientConnectedEventArgs e)
{
_log.Debug($"connected to <{Address}:{Port}>");
}
public void Send(byte[] message)
{
byte[] slipData = SlipFrame.Encode(message);
simpleClient.Send(slipData);
tcpClient.SendBytes(slipData.ToArray());
}
public void Send(OscPacket packet)
@@ -132,22 +135,24 @@ namespace SharpOSC
{
get
{
if (simpleClient == null)
if (tcpClient == null)
return false;
else
return simpleClient.IsConnected;
return tcpClient.Connected;
}
}
public void Close()
{
Log.Information("[tcpclient] Close() was requested");
if (simpleClient != null)
if (tcpClient != null)
{
if (simpleClient.IsConnected)
tcpClient.OnConnected -= ClientConnected;
tcpClient.OnDataReceived -= DataReceived;
tcpClient.OnDisconnected -= ClientDisconneted;
if (tcpClient.Connected)
{
Log.Information($"[tcpClient] closing connection to {Address}");
simpleClient.Disconnect();
_log.Debug($"closing connection to {Address}");
tcpClient.Disconnect();
}
}
}
@@ -157,4 +162,4 @@ namespace SharpOSC
MessageReceived?.Invoke(this, new MessageEventArgs() { Message = msg });
}
}
}
}
-2
View File
@@ -100,7 +100,6 @@ namespace SharpOSC
Log.Error(e.ToString());
// If there is an error reading the packet, null is sent to the callback
}
//Log.Debug("Raw UDP In: " + System.Text.Encoding.ASCII.GetString(bytes));
OscPacketCallback(packet);
}
else
@@ -151,7 +150,6 @@ namespace SharpOSC
{
byte[] bytes = queue.Dequeue();
var packet = OscPacket.GetPacket(bytes);
//Log.Debug("Raw UDP In: " + System.Text.Encoding.ASCII.GetString(bytes));
return packet;
}
else
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="com.jwetzell.qcontrolkitxamdemo">
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="29" />
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="33" />
<application android:label="QControlKitXamDemo.Android"></application>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
</manifest>
@@ -1,113 +1,113 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{4F7F13C7-85EF-431F-AB03-B2494F0F4C7F}</ProjectGuid>
<ProjectTypeGuids>{EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<TemplateGuid>{c9e5eea5-ca05-42a1-839b-61506e0a37df}</TemplateGuid>
<OutputType>Library</OutputType>
<RootNamespace>QControlKitXamDemo.Droid</RootNamespace>
<AssemblyName>QControlKitXamDemo.Android</AssemblyName>
<Deterministic>True</Deterministic>
<AndroidApplication>True</AndroidApplication>
<AndroidResgenFile>Resources\Resource.designer.cs</AndroidResgenFile>
<AndroidResgenClass>Resource</AndroidResgenClass>
<AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest>
<MonoAndroidResourcePrefix>Resources</MonoAndroidResourcePrefix>
<MonoAndroidAssetsPrefix>Assets</MonoAndroidAssetsPrefix>
<TargetFrameworkVersion>v10.0</TargetFrameworkVersion>
<AndroidEnableSGenConcurrent>true</AndroidEnableSGenConcurrent>
<AndroidUseAapt2>true</AndroidUseAapt2>
<AndroidHttpClientHandlerType>Xamarin.Android.Net.AndroidClientHandler</AndroidHttpClientHandlerType>
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>portable</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug</OutputPath>
<DefineConstants>DEBUG;</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AndroidLinkMode>None</AndroidLinkMode>
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>portable</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release</OutputPath>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AndroidManagedSymbols>true</AndroidManagedSymbols>
<AndroidUseSharedRuntime>false</AndroidUseSharedRuntime>
</PropertyGroup>
<ItemGroup>
<Reference Include="Mono.Android" />
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Xml" />
<Reference Include="System.Numerics" />
<Reference Include="System.Numerics.Vectors" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Serilog.Sinks.Xamarin">
<Version>0.2.0.64</Version>
</PackageReference>
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2337" />
<PackageReference Include="Xamarin.Essentials" Version="1.7.1" />
</ItemGroup>
<ItemGroup>
<Compile Include="MainActivity.cs" />
<Compile Include="Resources\Resource.designer.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="Resources\AboutResources.txt" />
<None Include="Assets\AboutAssets.txt" />
<None Include="Properties\AndroidManifest.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\layout\Tabbar.xml" />
<AndroidResource Include="Resources\layout\Toolbar.xml" />
<AndroidResource Include="Resources\values\styles.xml" />
<AndroidResource Include="Resources\values\colors.xml" />
<AndroidResource Include="Resources\mipmap-anydpi-v26\icon.xml" />
<AndroidResource Include="Resources\mipmap-anydpi-v26\icon_round.xml" />
<AndroidResource Include="Resources\mipmap-hdpi\icon.png" />
<AndroidResource Include="Resources\mipmap-hdpi\launcher_foreground.png" />
<AndroidResource Include="Resources\mipmap-mdpi\icon.png" />
<AndroidResource Include="Resources\mipmap-mdpi\launcher_foreground.png" />
<AndroidResource Include="Resources\mipmap-xhdpi\icon.png" />
<AndroidResource Include="Resources\mipmap-xhdpi\launcher_foreground.png" />
<AndroidResource Include="Resources\mipmap-xxhdpi\icon.png" />
<AndroidResource Include="Resources\mipmap-xxhdpi\launcher_foreground.png" />
<AndroidResource Include="Resources\mipmap-xxxhdpi\icon.png" />
<AndroidResource Include="Resources\mipmap-xxxhdpi\launcher_foreground.png" />
</ItemGroup>
<ItemGroup>
<Folder Include="Resources\drawable\" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\QControlKit\QControlKit.csproj">
<Project>{51cd2579-79c9-49a9-b68d-93cbe878baa1}</Project>
<Name>QControlKit</Name>
</ProjectReference>
<ProjectReference Include="..\QControlKitXamDemo\QControlKitXamDemo.csproj">
<Project>{20D2D0D8-A518-42DC-BAB8-CF098CDF03C4}</Project>
<Name>QControlKitXamDemo</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<AndroidAsset Include="Assets\materialdesignicons-webfont.ttf" />
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
<ProjectExtensions>
<VisualStudio>
<UserProperties XamarinHotReloadDebuggerTimeoutExceptionQControlKitXamDemoAndroidHideInfoBar="True" />
</VisualStudio>
</ProjectExtensions>
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{4F7F13C7-85EF-431F-AB03-B2494F0F4C7F}</ProjectGuid>
<ProjectTypeGuids>{EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<TemplateGuid>{c9e5eea5-ca05-42a1-839b-61506e0a37df}</TemplateGuid>
<OutputType>Library</OutputType>
<RootNamespace>QControlKitXamDemo.Droid</RootNamespace>
<AssemblyName>QControlKitXamDemo.Android</AssemblyName>
<Deterministic>True</Deterministic>
<AndroidApplication>True</AndroidApplication>
<AndroidResgenFile>Resources\Resource.designer.cs</AndroidResgenFile>
<AndroidResgenClass>Resource</AndroidResgenClass>
<AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest>
<MonoAndroidResourcePrefix>Resources</MonoAndroidResourcePrefix>
<MonoAndroidAssetsPrefix>Assets</MonoAndroidAssetsPrefix>
<TargetFrameworkVersion>v13.0</TargetFrameworkVersion>
<AndroidEnableSGenConcurrent>true</AndroidEnableSGenConcurrent>
<AndroidUseAapt2>true</AndroidUseAapt2>
<AndroidHttpClientHandlerType>Xamarin.Android.Net.AndroidClientHandler</AndroidHttpClientHandlerType>
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>portable</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug</OutputPath>
<DefineConstants>DEBUG;</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AndroidLinkMode>None</AndroidLinkMode>
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>portable</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release</OutputPath>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AndroidManagedSymbols>true</AndroidManagedSymbols>
<AndroidUseSharedRuntime>false</AndroidUseSharedRuntime>
</PropertyGroup>
<ItemGroup>
<Reference Include="Mono.Android" />
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Xml" />
<Reference Include="System.Numerics" />
<Reference Include="System.Numerics.Vectors" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Serilog.Sinks.Xamarin">
<Version>1.0.0</Version>
</PackageReference>
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2622" />
<PackageReference Include="Xamarin.Essentials" Version="1.8.0" />
</ItemGroup>
<ItemGroup>
<Compile Include="MainActivity.cs" />
<Compile Include="Resources\Resource.designer.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="Resources\AboutResources.txt" />
<None Include="Assets\AboutAssets.txt" />
<None Include="Properties\AndroidManifest.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\layout\Tabbar.xml" />
<AndroidResource Include="Resources\layout\Toolbar.xml" />
<AndroidResource Include="Resources\values\styles.xml" />
<AndroidResource Include="Resources\values\colors.xml" />
<AndroidResource Include="Resources\mipmap-anydpi-v26\icon.xml" />
<AndroidResource Include="Resources\mipmap-anydpi-v26\icon_round.xml" />
<AndroidResource Include="Resources\mipmap-hdpi\icon.png" />
<AndroidResource Include="Resources\mipmap-hdpi\launcher_foreground.png" />
<AndroidResource Include="Resources\mipmap-mdpi\icon.png" />
<AndroidResource Include="Resources\mipmap-mdpi\launcher_foreground.png" />
<AndroidResource Include="Resources\mipmap-xhdpi\icon.png" />
<AndroidResource Include="Resources\mipmap-xhdpi\launcher_foreground.png" />
<AndroidResource Include="Resources\mipmap-xxhdpi\icon.png" />
<AndroidResource Include="Resources\mipmap-xxhdpi\launcher_foreground.png" />
<AndroidResource Include="Resources\mipmap-xxxhdpi\icon.png" />
<AndroidResource Include="Resources\mipmap-xxxhdpi\launcher_foreground.png" />
</ItemGroup>
<ItemGroup>
<Folder Include="Resources\drawable\" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\QControlKit\QControlKit.csproj">
<Project>{51cd2579-79c9-49a9-b68d-93cbe878baa1}</Project>
<Name>QControlKit</Name>
</ProjectReference>
<ProjectReference Include="..\QControlKitXamDemo\QControlKitXamDemo.csproj">
<Project>{20D2D0D8-A518-42DC-BAB8-CF098CDF03C4}</Project>
<Name>QControlKitXamDemo</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<AndroidAsset Include="Assets\materialdesignicons-webfont.ttf" />
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
<ProjectExtensions>
<VisualStudio>
<UserProperties XamarinHotReloadDebuggerTimeoutExceptionQControlKitXamDemoAndroidHideInfoBar="True" />
</VisualStudio>
</ProjectExtensions>
</Project>
@@ -5,6 +5,8 @@ using System.Linq;
using Foundation;
using UIKit;
using Serilog;
using Serilog.Events;
namespace QControlKitXamDemo.iOS
{
// The UIApplicationDelegate for the application. This class is responsible for launching the
@@ -23,7 +25,7 @@ namespace QControlKitXamDemo.iOS
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
global::Xamarin.Forms.Forms.Init();
Log.Logger = new LoggerConfiguration().WriteTo.NSLog().MinimumLevel.Information().CreateLogger();
Log.Logger = new LoggerConfiguration().WriteTo.NSLog(outputTemplate: "[{Level}] ({SourceContext}) {Message}{NewLine}{Exception}").MinimumLevel.Verbose().CreateLogger();
LoadApplication(new App());
return base.FinishedLaunching(app, options);
@@ -126,15 +126,15 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Serilog">
<Version>2.10.0</Version>
<Version>2.12.0</Version>
</PackageReference>
<PackageReference Include="Serilog.Sinks.Xamarin">
<Version>0.2.0.64</Version>
<Version>1.0.0</Version>
</PackageReference>
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2337" />
<PackageReference Include="Xamarin.Essentials" Version="1.7.1" />
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2515" />
<PackageReference Include="Xamarin.Essentials" Version="1.7.3" />
<PackageReference Include="SuperSimpleTcp">
<Version>2.6.0</Version>
<Version>3.0.0.2</Version>
</PackageReference>
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Xamarin\iOS\Xamarin.iOS.CSharp.targets" />
@@ -7,6 +7,7 @@ using Xamarin.Forms;
using QControlKit.Events;
using QControlKit.Constants;
using Serilog;
namespace QControlKitXamDemo
{
@@ -20,25 +21,44 @@ namespace QControlKitXamDemo
connectedWorkspace = workspace;
connectedWorkspace.WorkspaceUpdated += Workspace_WorkspaceUpdated;
connectedWorkspace.WorkspaceConnectionError += ConnectedWorkspace_WorkspaceConnectionError;
connectedWorkspace.WorkspaceConnected += ConnectedWorkspace_WorkspaceConnected; ;
connectedWorkspace.connect(passcode);
connectedWorkspace.defaultSendUpdatesOSC = true;
connectedWorkspace.CueListChangedPlaybackPosition += ConnectedWorkspace_CueListChangedPlaybackPosition;
}
protected override void OnDisappearing()
{
base.OnDisappearing();
connectedWorkspace.disconnect();
connectedWorkspace.WorkspaceUpdated -= Workspace_WorkspaceUpdated;
connectedWorkspace.WorkspaceConnectionError -= ConnectedWorkspace_WorkspaceConnectionError;
connectedWorkspace.WorkspaceConnected -= ConnectedWorkspace_WorkspaceConnected;
connectedWorkspace.CueListChangedPlaybackPosition -= ConnectedWorkspace_CueListChangedPlaybackPosition;
}
private async void ConnectedWorkspace_WorkspaceConnected(object source, QWorkspaceConnectedArgs args)
{
if (args.status.Equals("ok") && connectedWorkspace.connectedToQLab5)
{
if (!args.data.Contains(QConnectionRole.View))
{
string passcode = await DisplayPromptAsync("PIN does not have view permissions.", "Try Again", maxLength: 4, keyboard: Keyboard.Numeric);
if (passcode != null)
{
connectedWorkspace.connect(passcode);
}
}
}
}
private async void ConnectedWorkspace_WorkspaceConnectionError(object source, QWorkspaceConnectionErrorArgs args)
{
if (args.status.Equals("badpass"))
{
string passcode = await DisplayPromptAsync("Passcode was incorrect.", "Try Again", maxLength: 4, keyboard: Keyboard.Numeric);
string passcode = await DisplayPromptAsync("PIN is incorrect.", "Try Again", maxLength: 4, keyboard: Keyboard.Numeric);
if (passcode != null)
{
connectedWorkspace.connect(passcode);
@@ -53,7 +73,7 @@ namespace QControlKitXamDemo
QCue selectedCue = connectedWorkspace.cueWithID(args.cueID);
if(selectedCue != null) //TODO: I think this being null is the result of a race condition?
{
connectedWorkspace.fetchDefaultPropertiesForCue(selectedCue);
connectedWorkspace.fetchBasicPropertiesForCue(selectedCue);
selectedCueGrid.BindingContext = new QCueViewModel(selectedCue, false);
if (cueGridDict.ContainsKey(args.cueID))
{
@@ -64,6 +84,18 @@ namespace QControlKitXamDemo
});
}
void updateCuePropertes(QCue cue)
{
cue.workspace.fetchDefaultPropertiesForCue(cue);
if (cue.cues.Count > 0)
{
foreach (QCue childCue in cue.cues)
{
childCue.workspace.fetchDefaultPropertiesForCue(childCue);
}
}
}
void Workspace_WorkspaceUpdated(object source, QWorkspaceUpdatedArgs args)
{
if(connectedWorkspace.cueLists.Count > 0)
@@ -71,6 +103,7 @@ namespace QControlKitXamDemo
List<Task> cueAddTasks = new List<Task>();
foreach(var aCue in connectedWorkspace.cueLists)
{
updateCuePropertes(aCue);
Grid cueGrid = cueToGrid(aCue);
cueGridDict.Add(aCue.uid, cueGrid);
MainThread.InvokeOnMainThreadAsync(() =>
@@ -95,7 +128,7 @@ namespace QControlKitXamDemo
HorizontalOptions = LayoutOptions.StartAndExpand,
VerticalTextAlignment = TextAlignment.Center,
};
cueLabel.SetBinding(Label.TextProperty, "status", BindingMode.OneWay);
cueLabel.SetBinding(Label.TextProperty, "name", BindingMode.OneWay);
var cueBackground = new Frame
{
BindingContext = qCueViewModel,
@@ -30,7 +30,7 @@ namespace QControlKitXamDemo
qBrowserViewModel.autoUpdate = false;
QWorkspace selectedWorkspace = (e.SelectedItem as QWorkspaceViewModel).workspace;
Log.Debug($"[demo] workspace: {selectedWorkspace.nameWithoutPathExtension} has been selected");
Log.Debug($"workspace: {selectedWorkspace.nameWithoutPathExtension} has been selected");
((ListView)sender).SelectedItem = null;
if (selectedWorkspace.hasPasscode)
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
@@ -11,10 +11,10 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Serilog" Version="2.10.0" />
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2337" />
<PackageReference Include="Xamarin.Essentials" Version="1.7.1" />
<PackageReference Include="Acr.UserDialogs" Version="7.2.0.562" />
<PackageReference Include="Serilog" Version="2.12.0" />
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2622" />
<PackageReference Include="Xamarin.Essentials" Version="1.8.0" />
<PackageReference Include="Acr.UserDialogs" Version="7.2.0.564" />
</ItemGroup>
<ItemGroup>
@@ -13,11 +13,15 @@ namespace QControlKitXamDemo.ViewModels
{
public class QBrowserViewModel : INotifyPropertyChanged
{
private ILogger _log = Log.Logger.ForContext<QBrowserViewModel>();
QBrowser browser;
public event PropertyChangedEventHandler PropertyChanged;
public bool autoUpdate = false;
public ObservableCollection<QServerViewModel> ServersGrouped { get; set; }
bool connected = false;
public QBrowserViewModel(QBrowser browser)
{
this.browser = browser;
@@ -31,9 +35,10 @@ namespace QControlKitXamDemo.ViewModels
{
if (autoUpdate)
{
_log.Verbose($"Auto Update is enabled running probe");
Device.BeginInvokeOnMainThread(() =>
{
Log.Debug("[QBrowserViewModel] QBrowser probe triggered");
_log.Debug("QBrowser probe triggered");
this.browser.ProbeForQLabInstances();
});
}
@@ -47,7 +52,7 @@ namespace QControlKitXamDemo.ViewModels
{
Device.BeginInvokeOnMainThread(() =>
{
Log.Debug($"[QBrowserViewModel] adding server: {args.server.description}");
_log.Debug($"adding server: {args.server.description}");
ServersGrouped.Add(new QServerViewModel(args.server));
});
}
@@ -70,7 +75,7 @@ namespace QControlKitXamDemo.ViewModels
{
Device.BeginInvokeOnMainThread(() =>
{
Log.Debug($"[QBrowserViewModel] removing server: {args.server.description}");
_log.Debug($"removing server: {args.server.description}");
ServersGrouped.Remove(serverToRemove);
});
}
@@ -85,7 +90,7 @@ namespace QControlKitXamDemo.ViewModels
{
if (!autoUpdate)
{
Log.Debug("[QBrowserViewModel] Manual Scan Initiated");
_log.Debug("Manual Scan Initiated");
browser.ProbeForQLabInstances();
}
}
@@ -11,6 +11,8 @@ namespace QControlKitXamDemo.ViewModels
{
public class QCueViewModel : INotifyPropertyChanged
{
private ILogger _log = Log.Logger.ForContext<QCueViewModel>();
QCue cue;
bool isSelected = false;
public event PropertyChangedEventHandler PropertyChanged;
@@ -18,16 +20,20 @@ namespace QControlKitXamDemo.ViewModels
public QCueViewModel(QCue cue, bool checkPlayback)
{
this.cue = cue;
if(checkPlayback)
if (checkPlayback)
{
this.cue.workspace.CueListChangedPlaybackPosition += Workspace_CueListChangedPlaybackPosition;
}
this.cue.CuePropertiesUpdated += OnCuePropertiesUpdated;
}
private void OnCuePropertiesUpdated(object source, QCuePropertiesUpdatedArgs args)
{
foreach(var property in args.properties)
_log.Debug($"properties updated from cue");
foreach (var property in args.properties)
{
Log.Debug($"[cueviewmodel] property <{property}> has been updated for cue {name}.");
_log.Debug($"property <{property}> has been updated for cue {name}.");
if (property.Equals(QOSCKey.Name) || property.Equals(QOSCKey.ListName))
{
OnPropertyChanged("name");
@@ -5,11 +5,14 @@ using Xamarin.Forms;
using QControlKit;
using QControlKit.Events;
using Serilog;
namespace QControlKitXamDemo.ViewModels
{
public class QServerViewModel : ObservableCollection<QWorkspaceViewModel>, INotifyPropertyChanged
{
private ILogger _log = Log.Logger.ForContext<QServerViewModel>();
QServer server;
public new event PropertyChangedEventHandler PropertyChanged;
public string name
+9
View File
@@ -2,6 +2,15 @@
This is an unofficial .NET library port of Figure53's [QLabKit](https://github.com/Figure53/QLabKit.objc).
## Components
- **QBrowser**: Searches for an maintains a list of QLab "instances" using bonjour
- **QClient**: Wrapper over underlying TCP/UDP connection as well as providing event handlers for common QLab replies (i.e. cue needs updated, workspace need updated, etc.)
- **QCue**: The most basic object in QLab, contains shared properties and methods for a cue in a cuelist (cuelists are also a cue) is extended for other cue types (Text, Midi, etc.)
- **QMessage**: Wrapper over underlying OSC Message, has methods to quickly determine the message type and parts (Workspace info, address, arguments, etc.)
- **QServer**: This class represents a computer that is running QLab, it has host information (IP, Port) as well as discovered workspaces on that computer.
- **QWorkspace**: This class represents a workspace on a QServer, it contains methods and properties to interact with a workspace as well as access the underlying QCues and QClient that is facilitating communication
## Todo: give a basic example of the workflow