Compare commits

...

11 Commits

25 changed files with 1005 additions and 91 deletions

View File

@@ -223,6 +223,8 @@ namespace PepperDash.Essentials
CrestronEnvironment.Sleep(1000);
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Shutting down room");
RunRouteAction("roomOff");
}
@@ -275,8 +277,8 @@ namespace PepperDash.Essentials
// Run this on a separate thread
new CTimer(o =>
{
Debug.Console(1, this, "Run route action '{0}'", routeKey);
var dict = ConfigReader.ConfigObject.GetSourceListForKey(SourceListKey);
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Run route action '{0}'", routeKey);
var dict = ConfigReader.ConfigObject.GetSourceListForKey(SourceListKey);
if(dict == null)
{
Debug.Console(1, this, "WARNING: Config source list '{0}' not found", SourceListKey);

View File

@@ -334,8 +334,11 @@ namespace PepperDash.Essentials
{
// Add Occupancy object from config
if (PropertiesConfig.Occupancy != null)
{
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Setting Occupancy Provider for room");
this.SetRoomOccupancy(DeviceManager.GetDeviceForKey(PropertiesConfig.Occupancy.DeviceKey) as
IOccupancyStatusProvider, PropertiesConfig.Occupancy.TimeoutMinutes);
}
this.LogoUrl = PropertiesConfig.Logo.GetUrl();
this.SourceListKey = PropertiesConfig.SourceListKey;
@@ -359,6 +362,8 @@ namespace PepperDash.Essentials
CrestronEnvironment.Sleep(1000);
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Shutting down room");
RunRouteAction("roomOff");
}
@@ -408,7 +413,7 @@ namespace PepperDash.Essentials
try
{
Debug.Console(1, this, "Run route action '{0}'", routeKey);
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Run route action '{0}'", routeKey);
var dict = ConfigReader.ConfigObject.GetSourceListForKey(SourceListKey);
if (dict == null)
{

View File

@@ -17,15 +17,27 @@ namespace PepperDash.Essentials.Core.Config
[JsonProperty("info")]
public InfoConfig Info { get; set; }
/// <summary>
/// Defines the devices in the system
/// </summary>
[JsonProperty("devices")]
public List<DeviceConfig> Devices { get; set; }
/// <summary>
/// Defines the source list for the system
/// </summary>
[JsonProperty("sourceLists")]
public Dictionary<string, Dictionary<string, SourceListItem>> SourceLists { get; set; }
/// <summary>
/// Defines all the tie lines for system routing
/// </summary>
[JsonProperty("tieLines")]
public List<TieLineConfig> TieLines { get; set; }
/// <summary>
/// Defines any join maps to override the default join maps
/// </summary>
[JsonProperty("joinMaps")]
public Dictionary<string, string> JoinMaps { get; set; }

View File

@@ -11,24 +11,43 @@ using PepperDash.Essentials.Core;
namespace PepperDash.Essentials.Core.Config
{
public class DeviceConfig
public class DeviceConfig : PropertiesConfigBase
{
[JsonProperty("key")]
/// <summary>
/// The unique idendifier for the device
/// </summary>
[JsonProperty("key", Required = Required.Always)]
public string Key { get; set; }
/// <summary>
/// A unique ID for each device instance. Used to differentiate between devices of the same type that may have
/// been added/removed from the system
/// </summary>
[JsonProperty("uid")]
public int Uid { get; set; }
/// <summary>
/// The name of the device
/// </summary>
[JsonProperty("name")]
public string Name { get; set; }
/// <summary>
/// The group for the device
/// </summary>
[JsonProperty("group")]
public string Group { get; set; }
[JsonProperty("type")]
/// <summary>
/// The type of the device to instantiate
/// </summary>
[JsonProperty("type", Required = Required.Always)]
public string Type { get; set; }
[JsonProperty("properties")]
/// <summary>
/// The properties necessary to define the device
/// </summary>
[JsonProperty("properties", Required = Required.Always)]
[JsonConverter(typeof(DevicePropertiesConverter))]
public JToken Properties { get; set; }
}
@@ -59,7 +78,7 @@ namespace PepperDash.Essentials.Core.Config
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException("SOD OFF HOSER");
throw new NotImplementedException("Not Supported");
}
}
}

View File

@@ -4,6 +4,7 @@ using Crestron.SimplSharp;
using Crestron.SimplSharp.CrestronIO;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Schema;
using PepperDash.Core;
using PepperDash.Core.Config;
@@ -91,9 +92,24 @@ namespace PepperDash.Essentials.Core.Config
{
Debug.Console(0, Debug.ErrorLogLevel.Notice, "Loading config file: '{0}'", filePath);
var directoryPrefix = string.Format("{0}Config{1}Schema{1}", Global.ApplicationDirectoryPrefix, Global.DirectorySeparator);
var schemaFilePath = directoryPrefix + "EssentialsConfigSchema.json";
Debug.Console(0, Debug.ErrorLogLevel.Notice, "Loading Schema from path: {0}", schemaFilePath);
var jsonConfig = fs.ReadToEnd();
if(File.Exists(schemaFilePath))
{
// Attempt to validate config against schema
ValidateSchema(jsonConfig, schemaFilePath);
}
else
Debug.Console(0, Debug.ErrorLogLevel.Warning, "No Schema found at path: {0}", schemaFilePath);
if (localConfigFound)
{
ConfigObject = JObject.Parse(fs.ReadToEnd()).ToObject<EssentialsConfig>();
ConfigObject = JObject.Parse(jsonConfig).ToObject<EssentialsConfig>();
Debug.Console(0, Debug.ErrorLogLevel.Notice, "Successfully Loaded Local Config");
@@ -101,7 +117,7 @@ namespace PepperDash.Essentials.Core.Config
}
else
{
var doubleObj = JObject.Parse(fs.ReadToEnd());
var doubleObj = JObject.Parse(jsonConfig);
ConfigObject = PortalConfigReader.MergeConfigs(doubleObj).ToObject<EssentialsConfig>();
// Extract SystemUrl and TemplateUrl into final config output
@@ -129,6 +145,40 @@ namespace PepperDash.Essentials.Core.Config
}
}
/// <summary>
/// Attempts to validate the JSON against the specified schema
/// </summary>
/// <param name="json">JSON to be validated</param>
/// <param name="schemaFileName">File name of schema to validate against</param>
public static void ValidateSchema(string json, string schemaFileName)
{
Debug.Console(0, Debug.ErrorLogLevel.Notice, "Validating Config File against Schema...");
JObject config = JObject.Parse(json);
using (StreamReader fileStream = new StreamReader(schemaFileName))
{
JsonSchema schema = JsonSchema.Parse(fileStream.ReadToEnd());
if (config.IsValid(schema))
Debug.Console(0, Debug.ErrorLogLevel.Notice, "Configuration successfully Validated Against Schema");
else
{
Debug.Console(0, Debug.ErrorLogLevel.Warning, "Validation Errors Found in Configuration:");
config.Validate(schema, Json_ValidationEventHandler);
}
}
}
/// <summary>
/// Event Handler callback for JSON validation
/// </summary>
/// <param name="sender"></param>
/// <param name="args"></param>
public static void Json_ValidationEventHandler(object sender, ValidationEventArgs args)
{
Debug.Console(0, "JSON Validation error at line {0} position {1}: {2}", args.Exception.LineNumber, args.Exception.LinePosition, args.Message);
}
/// <summary>
/// Returns all the files from the directory specified.
/// </summary>

View File

@@ -0,0 +1,181 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"title": "Control Properties",
"$ref": "#/definitions/ControlPropertiesConfig",
"definitions": {
"ControlPropertiesConfig": {
"description": "The method of communicating with the device",
"properties": {
"method": {
"type": "string",
"title": "Communication Method",
"enum": [
"none",
"com",
"ipid",
"ipidtcp",
"ir",
"ssh",
"tcpip",
"telnet",
"cresnet",
"cec",
"udp"
]
},
"tcpSshProperties": {
"$ref":"TcpSshPropertiesConfigSchema.json#definitions/TcpSshPropertiesConfig",
"title": "Properties for IP based communication",
"default": null
},
"comParams": {
"title":"Com Port parameters",
"description": "The parameters to configure the COM port",
"type":"object",
"protocol":{
"title":"Protocol",
"type":"string",
"enum":[
"RS232",
"RS422",
"RS485"
]
},
"baudRate":{
"title":"Baud Rate",
"type":"integer",
"enum":[
300,
600,
1200,
1800,
2400,
3600,
4800,
7200,
9600,
14400,
19200,
28800,
38400,
57600,
115200
]
},
"dataBits":{
"title":"Data Bits",
"type":"integer",
"enum":[
7,
8
]
},
"stopBits":{
"title":"Stop Bits",
"type":"integer",
"enum":[
1,
2
]
},
"parity":{
"title":"Parity",
"type":"string",
"enum":[
"None",
"Even",
"One"
]
},
"softwareHandshake":{
"title":"Software Handshake",
"type":"string",
"enum":[
"None",
"RTS",
"CTS",
"RTSCTS"
]
},
"hardwareHandshake":{
"title":"Hardware Handshake",
"type":"string",
"enum":[
"None",
"XON",
"XONT",
"XONR"
]
},
"pacing":{
"title":"Pacing",
"type":"integer"
}
},
"cresnetId":{
"type": "string",
"title":"Cresnet ID",
"description": "Cresnet ID of the device",
"default": "",
"examples": [
"13",
"03",
"0A",
"F1"
],
"pattern": "^(?!00|01|02|FF)[0-9,A-F,a-f][0-9,A-F,a-f]$"
},
"controlPortDevKey": {
"type": "string",
"title":"Port Device",
"description": "Key of the device where the control port is found",
"examples": [
"processor"
]
},
"controlPortNumber": {
"type": "integer",
"title": "Port Number",
"description": "Control Port Number on the device referenced by controlPortDevKey",
"examples": [
1
]
},
"controlPortName": {
"type": "string",
"title": "Port Name",
"description": "Control Port Name on the device referenced by controlPortDevKey",
"examples": [
"hdmi1"
]
},
"irFile": {
"type": "string",
"title": "IR File",
"description": "IR Filename",
"default": "",
"examples": [
"Comcast Motorola DVR.ir"
],
"pattern": "^(.*).ir$"
},
"ipid": {
"type": "string",
"title": "IPID",
"default": "",
"examples": [
"13",
"03",
"0A",
"F1"
],
"pattern": "^(?!00|01|02|FF)[0-9,A-F,a-f][0-9,A-F,a-f]$"
}
},
"required": [
"method"
]
}
}
}

View File

@@ -0,0 +1,282 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Essentials Configuration File Schema",
"description": "",
"type": "object",
"$ref":"#/definitions/EssentialsConfig",
"definitions": {
"EssentialsConfig": {
"type": "object",
"additionalProperties": false,
"properties": {
"template": {
"$ref": "#/definitions/BasicConfig"
},
"system_url": {
"type": "string",
"format": "uri",
"qt-uri-protocols": [
"https"
]
},
"system": {
"$ref": "#/definitions/BasicConfig"
},
"template_url": {
"type": "string",
"format": "uri",
"qt-uri-protocols": [
"https"
]
}
},
"required": [
"system",
"template"
],
"title": "Essentials Configuration"
},
"BasicConfig": {
"type": "object",
"additionalProperties": false,
"properties": {
"devices": {
"type": "array",
"items": {
"$ref": "#/definitions/Device"
}
},
"rooms": {
"type": "array",
"items": {
"$ref": "#/definitions/Room"
}
},
"info": {
"$ref": "#/definitions/Info"
},
"sourceLists": {
"$ref": "#/definitions/SourceLists"
}
},
"required": [
],
"title": "Basic Config"
},
"Info": {
"type": "object",
"additionalProperties": true,
"properties": {
"name":{
"type":"string"
},
"date":{
"type":"string",
"format": "date-time"
},
"version":{
"type":"string"
},
"runtimeInfo":{
"$ref":"#/definitions/RuntimeInfo"
},
"comment":{
"type":"string"
},
"hostname":{
"type":"string"
},
"appNumber":{
"type":"integer"
},
"lastModifiedDate": {
"type": "string",
"format": "date-time"
},
"lastUid":{
"type":"integer"
},
"processorType":{
"type":"string"
},
"systemType":{
"type":"string"
},
"requiredControlSoftwareVersion":{
"type":"string"
}
},
"required": [
],
"title": "Info"
},
"RuntimeInfo":{
"type":"object",
"additionalProperties": false,
"properties": {
"appName":{
"type":"string"
},
"assemblyVersion": {
"type":"string"
},
"osVersion":{
"type":"string"
}
}
},
"Device": {
"type": "object",
"additionalProperties": true,
"properties": {
"name": {
"type": "string"
},
"group": {
"type": "string"
},
"properties": {
"type":"object"
},
"uid": {
"type": "integer"
},
"key": {
"type": "string"
},
"type": {
"type": "string"
}
},
"required": [
"group",
"key",
"properties",
"type"
],
"title": "Device"
},
"Room": {
"type": "object",
"additionalProperties": false,
"properties": {
"key": {
"type": "string"
},
"name": {
"type": "string"
},
"properties": {
"type": "object"
},
"type": {
"type": "string"
}
},
"required": [
"key",
"name",
"properties",
"type"
],
"title": "Room"
},
"SourceLists": {
"type": "object",
"additionalProperties": true,
"properties": {
"default":{
"$ref":"#/definitions/SourceList"
}
},
"title": "SourceLists"
},
"SourceList": {
"type": "object",
"additionalProperties": true,
"properties": {
},
"title": "Source List"
},
"Source": {
"type": "object",
"additionalProperties": false,
"properties": {
"icon": {
"type": "string"
},
"volumeControlKey": {
"type": "string"
},
"sourceKey": {
"type": "string"
},
"routeList": {
"type": "array",
"items": {
"$ref": "#/definitions/RouteList"
}
},
"includeInSourceList": {
"type": "boolean"
},
"type": {
"type": "string",
"enum":[
"route",
"off",
"other"
]
},
"altIcon": {
"type": "string"
},
"order": {
"type": "integer"
},
"disableCodecSharing": {
"type": "boolean"
}
},
"required": [
"includeInSourceList",
"order",
"routeList",
"sourceKey",
"type",
"volumeControlKey"
],
"title": "Source"
},
"RouteList": {
"type": "object",
"additionalProperties": false,
"properties": {
"destinationKey": {
"type": "string"
},
"sourceKey": {
"type": "string"
},
"type": {
"type": "string",
"enum":[
"audio",
"video",
"audioVideo",
"usbOutput",
"usbInput"
]
}
},
"required": [
"destinationKey",
"sourceKey",
"type"
],
"title": "RouteList"
}
}
}

View File

@@ -0,0 +1,81 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"title": "TcpSshPropertiesConfig",
"$ref": "#/definitions/TcpSshPropertiesConfig",
"definitions": {
"TcpSshPropertiesConfig": {
"properties": {
"username": {
"type": "string",
"title": "Username",
"default":"",
"examples": [
"admin"
],
"pattern": "^(.*)$"
},
"port": {
"type": "integer",
"title": "Port Number",
"minimum": 1,
"maximum": 65535,
"examples": [
22,
23,
1515
]
},
"address": {
"type": "string",
"title": "IP Address or Hostname",
"examples": [
"192.168.99.100",
"myDeviceHostname"
],
"pattern": "^(.*)$"
},
"password": {
"type": "string",
"title": "Password",
"default":"",
"examples": [
"password"
],
"pattern": "^(.*)$"
},
"autoReconnect": {
"type": "boolean",
"title": "Auto Reconnect",
"description": "Indicates if automatic attemtps to reconnect should be made if communication is lost with the device",
"default": true,
"examples": [
true
]
},
"autoReconnectIntervalMs": {
"type": "integer",
"title": "Auto Reconnect Interval (Milliseconds)",
"description": "If Auto Reconnect is enabled, how often should reconnect attempts be made",
"default": 5000,
"examples": [
2000
]
},
"bufferSize": {
"type": "integer",
"title": "Buffer Size",
"description": "The size of the receive buffer to use",
"default": 32768,
"examples": [
32768
]
}
},
"required": [
"port",
"address"
]
}
}
}

View File

@@ -0,0 +1,40 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
using Crestron.SimplSharpPro;
using Crestron.SimplSharpPro.GeneralIO;
using PepperDash.Core;
namespace PepperDash.Essentials.Core
{
/// <summary>
/// Wrapper class for CEN-IO-DIGIN-104 digital input module
/// </summary>
public class CenIoDigIn104Controller : Device, IDigitalInputPorts
{
public CenIoDi104 Di104 { get; private set; }
public CenIoDigIn104Controller(string key, string name, CenIoDi104 di104)
: base(key, name)
{
Di104 = di104;
}
#region IDigitalInputPorts Members
public CrestronCollection<DigitalInput> DigitalInputPorts
{
get { return Di104.DigitalInputPorts; }
}
public int NumberOfDigitalInputPorts
{
get { return Di104.NumberOfDigitalInputPorts; }
}
#endregion
}
}

View File

@@ -10,7 +10,9 @@ using PepperDash.Essentials.Core.Config;
namespace PepperDash.Essentials.Core.Devices
{
/// <summary>
///
/// This class should be inherited from when the configuration can be modified at runtime from another source other than the configuration file.
/// It contains the necessary properties, methods and events to allot the initial device configuration to be overridden and then notifies the
/// ConfigWriter to write out the new values to a local file to be read on next boot.
/// </summary>
public abstract class ReconfigurableDevice : Device
{

View File

@@ -67,34 +67,64 @@ namespace PepperDash.Essentials.Core
[JsonProperty("name")]
public string Name { get; set; }
/// <summary>
/// The icon to display
/// </summary>
[JsonProperty("icon")]
public string Icon { get; set; }
/// <summary>
/// Alternate icon to display
/// </summary>
[JsonProperty("altIcon")]
public string AltIcon { get; set; }
/// <summary>
/// Indicates if the source should be included in the list
/// </summary>
[JsonProperty("includeInSourceList")]
public bool IncludeInSourceList { get; set; }
/// <summary>
/// Determines the order the source appears in the list (ascending)
/// </summary>
[JsonProperty("order")]
public int Order { get; set; }
/// <summary>
/// Key of the volume control device for the source
/// </summary>
[JsonProperty("volumeControlKey")]
public string VolumeControlKey { get; set; }
/// <summary>
/// The type of source list item
/// </summary>
[JsonProperty("type")]
[JsonConverter(typeof(StringEnumConverter))]
public eSourceListItemType Type { get; set; }
/// <summary>
/// The list of routes to run when source is selected
/// </summary>
[JsonProperty("routeList")]
public List<SourceRouteListItem> RouteList { get; set; }
/// <summary>
/// Indicates if this source should be disabled for sharing via codec content
/// </summary>
[JsonProperty("disableCodecSharing")]
public bool DisableCodecSharing { get; set; }
/// <summary>
/// Indicates if this source should be disabled for local routing
/// </summary>
[JsonProperty("disableRoutedSharing")]
public bool DisableRoutedSharing { get; set; }
/// <summary>
/// The list of valid destination types for this source
/// </summary>
[JsonProperty("destinations")]
public List<eSourceListItemDestinationTypes> Destinations { get; set; }

View File

@@ -52,6 +52,13 @@ namespace PepperDash.Essentials.Core
Debug.Console(1, "Factory Attempting to create new Generic Comm Device");
return new GenericComm(dc);
}
else if (typeName == "ceniodigin104")
{
var control = CommFactory.GetControlPropertiesConfig(dc);
var ipid = control.CresnetIdInt;
return new CenIoDigIn104Controller(key, name, new Crestron.SimplSharpPro.GeneralIO.CenIoDi104(ipid, Global.ControlSystem));
}
// then check for types that have been added by plugin dlls.
if (FactoryMethods.ContainsKey(typeName))

View File

@@ -9,8 +9,14 @@ using PepperDash.Essentials.License;
namespace PepperDash.Essentials.Core
{
/// <summary>
/// Global application properties
/// </summary>
public static class Global
{
/// <summary>
/// The control system the application is running on
/// </summary>
public static CrestronControlSystem ControlSystem { get; set; }
public static LicenseManager LicenseManager { get; set; }
@@ -31,6 +37,19 @@ namespace PepperDash.Essentials.Core
}
}
/// <summary>
/// The file path prefix to the folder containing the application files (including embedded resources)
/// </summary>
public static string ApplicationDirectoryPrefix
{
get
{
string fmt = "00.##";
var appNumber = InitialParametersClass.ApplicationNumber.ToString(fmt);
return string.Format("{0}{1}Simpl{1}app{2}{1}", Crestron.SimplSharp.CrestronIO.Directory.GetApplicationRootDirectory(), Global.DirectorySeparator,appNumber );
}
}
/// <summary>
/// Wildcarded config file name for global reference
/// </summary>

View File

@@ -62,6 +62,10 @@
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\..\..\..\..\..\..\..\..\ProgramData\Crestron\SDK\SSPDevices\Crestron.SimplSharpPro.Fusion.dll</HintPath>
</Reference>
<Reference Include="Crestron.SimplSharpPro.GeneralIO, Version=1.0.0.0, Culture=neutral, PublicKeyToken=1099c178b3b54c3b, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\..\..\..\..\ProgramData\Crestron\SDK\SSPDevices\Crestron.SimplSharpPro.GeneralIO.dll</HintPath>
</Reference>
<Reference Include="Crestron.SimplSharpPro.Remotes, Version=1.0.0.0, Culture=neutral, PublicKeyToken=1099c178b3b54c3b, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\..\..\..\..\..\..\..\..\ProgramData\Crestron\SDK\SSPDevices\Crestron.SimplSharpPro.Remotes.dll</HintPath>
@@ -114,6 +118,7 @@
<Compile Include="Config\Essentials\ConfigWriter.cs" />
<Compile Include="Config\Essentials\EssentialsConfig.cs" />
<Compile Include="Config\SourceDevicePropertiesConfigBase.cs" />
<Compile Include="Crestron IO\Inputs\CenIoDigIn104Controller.cs" />
<Compile Include="Crestron IO\Inputs\GenericDigitalInputDevice.cs" />
<Compile Include="Crestron IO\Inputs\GenericVersiportInputDevice.cs" />
<Compile Include="Crestron IO\Inputs\IDigitalInput.cs" />
@@ -262,6 +267,15 @@
<Compile Include="SmartObjects\SubpageReferencList\SubpageReferenceList.cs" />
<Compile Include="SmartObjects\SubpageReferencList\SubpageReferenceListItem.cs" />
<None Include="app.config" />
<EmbeddedResource Include="Config\Schema\EssentialsConfigSchema.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</EmbeddedResource>
<EmbeddedResource Include="Config\Schema\ControlPropertiesConfigSchema.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</EmbeddedResource>
<EmbeddedResource Include="Config\Schema\TcpSshPropertiesConfigSchema.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</EmbeddedResource>
<None Include="Properties\ControlSystem.cfg" />
</ItemGroup>
<ItemGroup>

View File

@@ -100,7 +100,8 @@ namespace PepperDash.Essentials.Core
ShutdownPromptTimer.HasFinished += (o, a) => Shutdown(); // Shutdown is triggered
ShutdownPromptSeconds = 60;
ShutdownVacancySeconds = 120;
ShutdownVacancySeconds = 120;
ShutdownType = eShutdownType.None;
RoomVacancyShutdownTimer = new SecondsCountdownTimer(Key + "-vacancyOffTimer");
@@ -140,7 +141,7 @@ namespace PepperDash.Essentials.Core
case eVacancyMode.InShutdownWarning:
{
StartShutdown(eShutdownType.Vacancy);
Debug.Console(0, this, "Shutting Down due to vacancy.");
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Shutting Down due to vacancy.");
break;
}
default:
@@ -163,7 +164,7 @@ namespace PepperDash.Essentials.Core
ShutdownType = type;
ShutdownPromptTimer.Start();
Debug.Console(0, this, "ShutdwonPromptTimer Started. Type: {0}. Seconds: {1}", ShutdownType, ShutdownPromptTimer.SecondsToCount);
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "ShutdownPromptTimer Started. Type: {0}. Seconds: {1}", ShutdownType, ShutdownPromptTimer.SecondsToCount);
}
public void StartRoomVacancyTimer(eVacancyMode mode)
@@ -175,7 +176,7 @@ namespace PepperDash.Essentials.Core
VacancyMode = mode;
RoomVacancyShutdownTimer.Start();
Debug.Console(0, this, "Vacancy Timer Started. Mode: {0}. Seconds: {1}", VacancyMode, RoomVacancyShutdownTimer.SecondsToCount);
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Vacancy Timer Started. Mode: {0}. Seconds: {1}", VacancyMode, RoomVacancyShutdownTimer.SecondsToCount);
}
/// <summary>
@@ -211,6 +212,9 @@ namespace PepperDash.Essentials.Core
return;
}
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Room Occupancy set to device: '{0}'", (statusProvider as Device).Key);
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Timeout Minutes from Config is: {0}", timeoutMinutes);
// If status provider is fusion, set flag to remote
if (statusProvider is Core.Fusion.EssentialsHuddleSpaceFusionSystemControllerBase)
OccupancyStatusProviderIsRemote = true;
@@ -218,16 +222,14 @@ namespace PepperDash.Essentials.Core
if(timeoutMinutes > 0)
RoomVacancyShutdownSeconds = timeoutMinutes * 60;
Debug.Console(1, this, "RoomVacancyShutdownSeconds set to {0}", RoomVacancyShutdownSeconds);
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "RoomVacancyShutdownSeconds set to {0}", RoomVacancyShutdownSeconds);
RoomOccupancy = statusProvider;
OnRoomOccupancyIsSet();
RoomOccupancy.RoomIsOccupiedFeedback.OutputChange -= RoomIsOccupiedFeedback_OutputChange;
RoomOccupancy.RoomIsOccupiedFeedback.OutputChange += RoomIsOccupiedFeedback_OutputChange;
Debug.Console(0, this, "Room Occupancy set to device: '{0}'", (statusProvider as Device).Key);
OnRoomOccupancyIsSet();
}
void OnRoomOccupancyIsSet()
@@ -252,13 +254,13 @@ namespace PepperDash.Essentials.Core
{
if (RoomOccupancy.RoomIsOccupiedFeedback.BoolValue == false)
{
Debug.Console(1, this, "Notice: Vacancy Detected");
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Notice: Vacancy Detected");
// Trigger the timer when the room is vacant
StartRoomVacancyTimer(eVacancyMode.InInitialVacancy);
}
else
{
Debug.Console(1, this, "Notice: Occupancy Detected");
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Notice: Occupancy Detected");
// Reset the timer when the room is occupied
RoomVacancyShutdownTimer.Cancel();
}

View File

@@ -17,21 +17,42 @@ namespace PepperDash.Essentials.DM.Config
[JsonProperty("control")]
public ControlPropertiesConfig Control { get; set; }
/// <summary>
/// The available volume controls
/// </summary>
[JsonProperty("volumeControls")]
public Dictionary<uint, DmCardAudioPropertiesConfig> VolumeControls { get; set; }
/// <summary>
/// The input cards
/// </summary>
[JsonProperty("inputSlots")]
public Dictionary<uint, string> InputSlots { get; set; }
/// <summary>
/// The output cards (each card represents a pair of outputs)
/// </summary>
[JsonProperty("outputSlots")]
public Dictionary<uint, string> OutputSlots { get; set; }
/// <summary>
/// The names of the Inputs
/// </summary>
[JsonProperty("inputNames")]
public Dictionary<uint, string> InputNames { get; set; }
/// <summary>
/// The names of the Outputs
/// </summary>
[JsonProperty("outputNames")]
public Dictionary<uint, string> OutputNames { get; set; }
/// <summary>
/// The string to use when no route is set for a given output
/// </summary>
[JsonProperty("noRouteText")]
public string NoRouteText { get; set; }
[JsonProperty("inputSlotSupportsHdcp2")]
public Dictionary<uint, bool> InputSlotSupportsHdcp2 { get; set; }
@@ -46,9 +67,15 @@ namespace PepperDash.Essentials.DM.Config
/// </summary>
public class DmCardAudioPropertiesConfig
{
/// <summary>
/// The level to set on the output
/// </summary>
[JsonProperty("outLevel")]
public int OutLevel { get; set; }
/// <summary>
/// Defines if this level is adjustable or not
/// </summary>
[JsonProperty("isVolumeControlPoint")]
public bool IsVolumeControlPoint { get; set; }
}

View File

@@ -20,6 +20,9 @@ namespace PepperDash.Essentials.DM.Config
[JsonProperty("outputNames")]
public Dictionary<uint, string> OutputNames { get; set; }
[JsonProperty("noRouteText")]
public string NoRouteText { get; set; }
public DmpsRoutingPropertiesConfig()
{
InputNames = new Dictionary<uint, string>();

View File

@@ -0,0 +1,74 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "DmChassisController Properties Config Schema",
"description": "",
"$ref":"EssentialsConfigSchema.json/definitions/Device",
"properties":{
"properties":{
"$ref":"#/definitions/propertiesConfig"
}
},
"definitions": {
"propertiesConfig": {
"type":"object",
"additionalProperties": true,
"properties": {
"control":{
"type":"object",
"$ref":"ControlPropertiesConfigSchema.json#definitions/ControlPropertiesConfig"
},
"volumeControls":{
"type":"object",
"additionalProperties": {
"type":"object",
"$ref": "#definitions/dmAudioCardPropertiesConfig"
}
},
"inputSlots":{
"type":"object",
"additionalProperties": {
"type":"string"
}
},
"outputSlots":{
"type":"object",
"additionalProperties": {
"type":"string"
}
},
"inputNames":{
"type":"object",
"additionalProperties": {
"type":"string"
}
},
"outputNames":{
"type":"object",
"additionalProperties": {
"type":"string"
}
},
"noRouteText":{
"type":"string"
},
"inputSlotSupportsHdcp2":{
"type":"object",
"additionalProperties": {
"type":"boolean"
}
}
}
},
"dmAudioCardPropertiesConfig":{
"type":"object",
"properties": {
"OutLevel":{
"type":"integer"
},
"isVolumeControlPoint":{
"type":"boolean"
}
}
}
}
}

View File

@@ -66,6 +66,11 @@ namespace PepperDash.Essentials.DM
public const int RouteOffTime = 500;
Dictionary<PortNumberType, CTimer> RouteOffTimers = new Dictionary<PortNumberType, CTimer>();
/// <summary>
/// Text that represents when an output has no source routed to it
/// </summary>
public string NoRouteText = "";
/// <summary>
/// Factory method to create a new chassis controller from config data. Limited to 8x8 right now
/// </summary>
@@ -128,6 +133,10 @@ namespace PepperDash.Essentials.DM
controller.InputNames = properties.InputNames;
controller.OutputNames = properties.OutputNames;
if (!string.IsNullOrEmpty(properties.NoRouteText))
controller.NoRouteText = properties.NoRouteText;
controller.PropertiesConfig = properties;
return controller;
}
@@ -217,7 +226,7 @@ namespace PepperDash.Essentials.DM
}
else
{
return "";
return NoRouteText;
}
});
OutputAudioRouteNameFeedbacks[tempX] = new StringFeedback(() =>
@@ -228,7 +237,7 @@ namespace PepperDash.Essentials.DM
}
else
{
return "";
return NoRouteText;
}
});

View File

@@ -48,6 +48,11 @@ namespace PepperDash.Essentials.DM
public const int RouteOffTime = 500;
Dictionary<PortNumberType, CTimer> RouteOffTimers = new Dictionary<PortNumberType, CTimer>();
/// <summary>
/// Text that represents when an output has no source routed to it
/// </summary>
public string NoRouteText = "";
public static DmpsRoutingController GetDmpsRoutingController(string key, string name,
DmpsRoutingPropertiesConfig properties)
{
@@ -67,6 +72,9 @@ namespace PepperDash.Essentials.DM
controller.InputNames = properties.InputNames;
controller.OutputNames = properties.OutputNames;
if (!string.IsNullOrEmpty(properties.NoRouteText))
controller.NoRouteText = properties.NoRouteText;
return controller;
}
@@ -191,7 +199,7 @@ namespace PepperDash.Essentials.DM
}
else
{
return "";
return NoRouteText;
}
});
OutputAudioRouteNameFeedbacks[outputCard.Number] = new StringFeedback(() =>
@@ -202,7 +210,7 @@ namespace PepperDash.Essentials.DM
}
else
{
return "";
return NoRouteText;
}
});

View File

@@ -103,10 +103,10 @@
<Compile Include="Chassis\DmpsRoutingController.cs" />
<Compile Include="Chassis\HdMdNxM4kEController.cs" />
<Compile Include="IDmSwitch.cs" />
<Compile Include="Config\DmpsRoutingConfig.cs" />
<Compile Include="Chassis\Config\DmpsRoutingConfig.cs" />
<Compile Include="Config\DmRmcConfig.cs" />
<Compile Include="Config\DmTxConfig.cs" />
<Compile Include="Config\DMChassisConfig.cs" />
<Compile Include="Chassis\Config\DMChassisConfig.cs" />
<Compile Include="Config\HdMdNxM4kEPropertiesConfig.cs" />
<Compile Include="Config\InputPropertiesConfig.cs" />
<Compile Include="DmPortName.cs" />
@@ -148,6 +148,9 @@
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="VideoStatusHelpers.cs" />
<None Include="app.config" />
<EmbeddedResource Include="Chassis\Config\Schema\DmChassisControllerPropertiesConfigSchema.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</EmbeddedResource>
<None Include="Properties\ControlSystem.cfg" />
</ItemGroup>
<ItemGroup>

View File

@@ -167,84 +167,91 @@ namespace PepperDash.Essentials.Devices.Displays
/// <param name="sender"></param>
void Communication_BytesReceived(object sender, GenericCommMethodReceiveBytesArgs e)
{
// This is probably not thread-safe buffering
// Append the incoming bytes with whatever is in the buffer
var newBytes = new byte[IncomingBuffer.Length + e.Bytes.Length];
IncomingBuffer.CopyTo(newBytes, 0);
e.Bytes.CopyTo(newBytes, IncomingBuffer.Length);
if (Debug.Level == 2) // This check is here to prevent following string format from building unnecessarily on level 0 or 1
Debug.Console(2, this, "Received:{0}", ComTextHelper.GetEscapedText(newBytes));
// Need to find AA FF and have
for (int i = 0; i < newBytes.Length; i++)
try
{
if (newBytes[i] == 0xAA && newBytes[i + 1] == 0xFF)
// This is probably not thread-safe buffering
// Append the incoming bytes with whatever is in the buffer
var newBytes = new byte[IncomingBuffer.Length + e.Bytes.Length];
IncomingBuffer.CopyTo(newBytes, 0);
e.Bytes.CopyTo(newBytes, IncomingBuffer.Length);
if (Debug.Level == 2) // This check is here to prevent following string format from building unnecessarily on level 0 or 1
Debug.Console(2, this, "Received:{0}", ComTextHelper.GetEscapedText(newBytes));
// Need to find AA FF and have
for (int i = 0; i < newBytes.Length; i++)
{
newBytes = newBytes.Skip(i).ToArray(); // Trim off junk if there's "dirt" in the buffer
// parse it
// If it's at least got the header, then process it,
while (newBytes.Length > 4 && newBytes[0] == 0xAA && newBytes[1] == 0xFF)
if (newBytes[i] == 0xAA && newBytes[i + 1] == 0xFF)
{
var msgLen = newBytes[3];
// if the buffer is shorter than the header (3) + message (msgLen) + checksum (1),
// give and save it for next time
if (newBytes.Length < msgLen + 4)
break;
newBytes = newBytes.Skip(i).ToArray(); // Trim off junk if there's "dirt" in the buffer
// Good length, grab the message
var message = newBytes.Skip(4).Take(msgLen).ToArray();
// At this point, the ack/nak is the first byte
if (message[0] == 0x41)
// parse it
// If it's at least got the header, then process it,
while (newBytes.Length > 4 && newBytes[0] == 0xAA && newBytes[1] == 0xFF)
{
switch (message[1]) // type byte
var msgLen = newBytes[3];
// if the buffer is shorter than the header (3) + message (msgLen) + checksum (1),
// give and save it for next time
if (newBytes.Length < msgLen + 4)
break;
// Good length, grab the message
var message = newBytes.Skip(4).Take(msgLen).ToArray();
// At this point, the ack/nak is the first byte
if (message[0] == 0x41)
{
case 0x00: // General status
//UpdatePowerFB(message[2], message[5]); // "power" can be misrepresented when the display sleeps
switch (message[1]) // type byte
{
case 0x00: // General status
//UpdatePowerFB(message[2], message[5]); // "power" can be misrepresented when the display sleeps
// Handle the first power on fb when waiting for it.
if (IsPoweringOnIgnorePowerFb && message[2] == 0x01)
IsPoweringOnIgnorePowerFb = false;
// Ignore general-status power off messages when powering up
if (!(IsPoweringOnIgnorePowerFb && message[2] == 0x00))
UpdatePowerFB(message[2]);
UpdateVolumeFB(message[3]);
UpdateMuteFb(message[4]);
UpdateInputFb(message[5]);
break;
// Handle the first power on fb when waiting for it.
if (IsPoweringOnIgnorePowerFb && message[2] == 0x01)
IsPoweringOnIgnorePowerFb = false;
// Ignore general-status power off messages when powering up
if (!(IsPoweringOnIgnorePowerFb && message[2] == 0x00))
UpdatePowerFB(message[2]);
UpdateVolumeFB(message[3]);
UpdateMuteFb(message[4]);
UpdateInputFb(message[5]);
break;
case 0x11:
UpdatePowerFB(message[2]);
break;
case 0x11:
UpdatePowerFB(message[2]);
break;
case 0x12:
UpdateVolumeFB(message[2]);
break;
case 0x12:
UpdateVolumeFB(message[2]);
break;
case 0x13:
UpdateMuteFb(message[2]);
break;
case 0x13:
UpdateMuteFb(message[2]);
break;
case 0x14:
UpdateInputFb(message[2]);
break;
case 0x14:
UpdateInputFb(message[2]);
break;
default:
break;
default:
break;
}
}
// Skip over what we've used and save the rest for next time
newBytes = newBytes.Skip(5 + msgLen).ToArray();
}
// Skip over what we've used and save the rest for next time
newBytes = newBytes.Skip(5 + msgLen).ToArray();
}
break; // parsing will mean we can stop looking for header in loop
}
}
// Save whatever partial message is here
IncomingBuffer = newBytes;
break; // parsing will mean we can stop looking for header in loop
}
}
// Save whatever partial message is here
IncomingBuffer = newBytes;
}
catch (Exception err)
{
Debug.Console(2, this, "Error parsing feedback: {0}", err);
}
}
/// <summary>
@@ -256,6 +263,7 @@ namespace PepperDash.Essentials.Devices.Displays
if (newVal != _PowerIsOn)
{
_PowerIsOn = newVal;
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Feedback Power State: {0}", _PowerIsOn);
PowerIsOnFeedback.FireUpdate();
}
}
@@ -364,6 +372,8 @@ namespace PepperDash.Essentials.Devices.Displays
/// </summary>
public override void PowerOn()
{
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Powering On Display");
IsPoweringOnIgnorePowerFb = true;
//Send(PowerOnCmd);
SendBytes(new byte[] { 0xAA, 0x11, 0x00, 0x01, 0x01, 0x00 });
@@ -387,6 +397,8 @@ namespace PepperDash.Essentials.Devices.Displays
/// </summary>
public override void PowerOff()
{
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Powering Off Display");
IsPoweringOnIgnorePowerFb = false;
// If a display has unreliable-power off feedback, just override this and
// remove this check.

View File

@@ -0,0 +1,29 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "SamsungMDC Properties Config Schema",
"description": "",
"$ref":"EssentialsConfigSchema.json#/definitions/Device",
"properties":{
"properties":{
"$ref":"#/definitions/propertiesConfig"
}
},
"definitions": {
"propertiesConfig": {
"type":"object",
"additionalProperties": true,
"properties": {
"control":{
"type":"object",
"$ref":"ControlPropertiesConfigSchema.json#definitions/ControlPropertiesConfig"
},
"id":{
"title":"Display ID",
"description": "This must match the ID set in the display's on screen menu",
"type":"string",
"pattern": "^(?!FF)[0-9,A-F,a-f][0-9,A-F,a-f]$"
}
}
}
}
}

View File

@@ -182,6 +182,9 @@
<Compile Include="VideoCodec\ZoomRoom\ZoomRoom.cs" />
<Compile Include="VideoCodec\ZoomRoom\ZoomRoomCamera.cs" />
<Compile Include="VideoCodec\ZoomRoom\ZoomRoomPropertiesConfig.cs" />
<EmbeddedResource Include="Display\Schema\SamsungMDCPropertiesConfigSchema.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</EmbeddedResource>
<None Include="Properties\ControlSystem.cfg" />
</ItemGroup>
<ItemGroup>