Compare commits

..

363 Commits

Author SHA1 Message Date
Neil Dorin
ac393c4885 refactor(force-patch): Remove obsolete GenericHttpClient class in favor of built-in HttpClient 2026-03-10 17:35:23 -06:00
Neil Dorin
346a5e9e57 refactor: Refactor DeviceManager and related classes to improve thread safety and performance
- Replaced CCriticalSection with lock statements in DeviceManager for better thread management.
- Updated AddDevice and RemoveDevice methods to use Monitor for locking.
- Enhanced event handling for device activation and registration.
- Modified FileIO class to utilize Task for asynchronous file operations instead of CrestronInvoke.
- Improved feedback mechanisms in FeedbackBase and SystemMonitorController using Task.Run.
- Refactored GenericQueue to remove Crestron threading dependencies and utilize System.Threading.
- Updated BlueJeansPc and VideoCodecBase classes to use Task for asynchronous operations.
- Cleaned up unnecessary critical sections and improved code documentation across various files.
2026-03-10 17:30:59 -06:00
Nick Genovese
426ef4ad6b fix: add error handling when saving debug settings 2026-03-10 00:35:18 -06:00
Nick Genovese
bcb65c7fa4 ci(force-patch): force build 2026-03-10 00:35:18 -06:00
Andrew Welker
c2ff65dce3 build: remove unneeded references from PD Core project 2026-03-10 00:35:18 -06:00
Andrew Welker
3feab2593d fix: use correct line endings for verbatim strings 2026-03-10 00:35:18 -06:00
Andrew Welker
5d90fafbd7 chore: remove duplication namespace declaration
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-10 00:31:20 -06:00
Andrew Welker
7f60dcb4cf chore: update local build version 2026-03-10 00:30:57 -06:00
Andrew Welker
574e4dfb0f feat: modify factory loading
Updating IDeviceFactory to resolve [FEATURE]-Refactor Plugin loading mechanism  #1065.
This change should be backwards-compatible with existing plugins that use the EssentialsPluginDeviceFactory<T> class,
as the interfaces are implemented by the various base classes.

In addition, the correct assembly name is now printed when a type is loaded.
2026-03-10 00:30:36 -06:00
Andrew Welker
76759d35cc feat: remove context file for storing values 2026-03-10 00:22:13 -06:00
Andrew Welker
3ece4f0b7b chore: move all files to file-scoped namespace 2026-03-10 00:21:06 -06:00
Andrew Welker
aaa5b0532b feat: modify plugin loading process 2026-03-06 10:34:53 -07:00
Andrew Welker
04c4557528 docs: update XML docs 2026-03-05 17:19:20 -07:00
Andrew Welker
9febbf2557 chore: fix issues related to remving crestron usings 2026-03-05 15:51:29 -07:00
Andrew Welker
eafade9569 docs: add XML comments 2026-03-05 15:43:42 -07:00
Andrew Welker
4e5b8f3897 wip: package updates 2026-03-05 14:54:54 -07:00
jtalborough
7bd3ccd54b fix: adjust installation steps for prerequisites based on environment detection 2026-03-05 14:23:45 -07:00
jtalborough
790b5a6fb9 fix: update condition for CPZ copy target and remove obsolete workflows 2026-03-05 14:23:45 -07:00
jtalborough
fbcd9c84da wip: address performance issues in plugin loading and versioning 2026-03-05 14:22:53 -07:00
jtalborough
4a2c9fe311 fix: update target framework to net8 and bump PepperDashCore version to 2.0.0-alpha-462
BREAKING CHANGE: Target Framework is now .NET 8:
2026-03-05 13:10:22 -07:00
jtalborough
bdd398e2e6 feat: implement WebSocket classes and update culture settings; bump PepperDashCore version 2026-03-05 13:10:22 -07:00
jtalborough
8be5481ac9 fix: update target frameworks and package references; change culture to InvariantCulture 2026-03-05 13:09:14 -07:00
jtalborough
dbab427d69 fix: update package references and clean up unused imports 2026-03-05 12:00:35 -07:00
Andrew Welker
db4f540710 Merge pull request #1394 from PepperDash/temp
merge temp into dev
2026-03-03 13:39:46 -05:00
Andrew Welker
b64f63ac6b Merge pull request #1389 from PepperDash/copilot/fix-crestrononvif-dll-error
fix(hotfix): skip CrestronOnvif.dll during assembly scanning at startup
2026-03-03 12:38:34 -05:00
Nick Genovese
b21be608f0 Update src/PepperDash.Essentials.Core/Factory/DeviceFactory.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-01 14:38:07 -05:00
copilot-swe-agent[bot]
6ab3ed9911 fix(hotfix): skip CrestronOnvif.dll when scanning assemblies at startup
Co-authored-by: ngenovese11 <23391587+ngenovese11@users.noreply.github.com>
2026-03-01 19:27:22 +00:00
copilot-swe-agent[bot]
a9fdf30880 Initial plan 2026-03-01 19:23:14 +00:00
Neil Dorin
963925ffef Merge pull request #1388 from PepperDash/feature/add-IHasWebViewWithPwaMode
Feature/add i has web view with pwa mode
2026-02-24 15:36:59 -07:00
Neil Dorin
e162ed208b Update src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IHasWebView.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-24 14:53:31 -07:00
Neil Dorin
bd38475b10 Update src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IHasWebView.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-24 14:53:12 -07:00
Neil Dorin
a704e48ae6 feat: add IsInPwaModeFeedback property to IHasWebViewWithPwaMode interface 2026-02-24 14:48:21 -07:00
Neil Dorin
b40200ecae feat: add IHasWebViewWithPwaMode interface with PWA navigation methods 2026-02-24 11:52:08 -07:00
Neil Dorin
9de14da201 Merge pull request #1386 from PepperDash/docfx-docs
docs: fix capitalization error in toc
2026-02-09 14:16:08 -07:00
Andrew Welker
633608c8c6 docs: fix capitalization error in toc 2026-02-09 14:53:04 -06:00
Neil Dorin
f8a81de177 Merge pull request #1385 from PepperDash/docfx-docs 2026-02-09 13:48:27 -07:00
Neil Dorin
39ebf6f63d Merge pull request #1377 from PepperDash/feature/circuittype-property-versiport 2026-02-09 13:47:36 -07:00
Andrew Welker
3ed0bd52d8 Merge branch 'main' into feature/circuittype-property-versiport 2026-02-09 14:58:06 -05:00
Andrew Welker
afc5cf1a14 docs: update landing page and getting started 2026-02-09 13:57:24 -06:00
Andrew Welker
8ff9de817c docs: reorganize and add article on working with docFx 2026-02-09 13:31:42 -06:00
Andrew Welker
b35d2b47cf Merge pull request #1379 from PepperDash/xml-docs-devices
feat: complete XML documentation in Devices.Common
2026-02-09 10:04:25 -05:00
Andrew Welker
10579f1424 Merge pull request #1380 from PepperDash/xml-docs-essentials-core
Xml docs essentials core
2026-02-09 10:04:07 -05:00
Andrew Welker
f88bb13367 docs: update XML comments for Essentials Core 2026-02-09 08:58:52 -06:00
Erik Meyer
2924ce6916 docs: complete XML documentation in Devices.Common 2026-02-09 08:55:08 -06:00
Andrew Welker
4437074f07 fix: implement copilot suggestions 2026-02-09 08:45:28 -06:00
Andrew Welker
0764685c51 Merge pull request #1378 from PepperDash/xml-docs
feat: complete XML documentation in PD Core
2026-02-09 09:33:24 -05:00
Andrew Welker
1fb5d3e5ee fix: make registering for push schedule updates optional
The default config for a Fusion room will now NOT subscribe for Fusion schedule push updates unless explicitly
requested.
2026-02-09 08:09:18 -06:00
Neil Dorin
1404899342 Merge pull request #1383 from onryigit/fix/case-insensitive-files 2026-02-03 13:51:56 -07:00
Yiğit Öner
a4759c3e67 fix case sensitivity issue for file searches
Updated logic to use case-insensitive filtering
2026-02-03 23:11:19 +03:00
Erik Meyer
a1029cd7c7 fix: add param info, clean up trailing whitespace and minor formatting 2026-01-27 14:39:50 -05:00
Erik Meyer
b1a5136ec6 fix: remove buildFile.txt 2026-01-27 13:53:26 -05:00
Erik Meyer
a7c4e2fd60 feat: complete XML documentation in PD Core 2026-01-27 13:22:27 -05:00
equinoy
c0971a4f8e Merge remote-tracking branch 'origin/screen-lift-controller-mute-logic' into feature/circuittype-property-versiport 2026-01-22 15:36:27 -06:00
equinoy
8bc6d4392b feat: add circuitType property to IOPortConfig and implement state inversion in digital input devices 2026-01-22 15:04:25 -06:00
Neil Dorin
4fa7a42330 Merge pull request #1376 from PepperDash/client-id-issues
fix: handle subsequent join calls and clientid/websocket client mismatches
2026-01-21 14:59:22 -07:00
Andrew Welker
9bad3ae21b fix: handle subsequent join calls and clientid/websocket client mismatches 2026-01-21 15:20:27 -06:00
Nick Genovese
f49901d3fa fix: Improve status messages in StatusMonitorCollection
Enhanced error and warning message generation to use monitor names when available, include counts, proper pluralization, and append "Offline" when issues are present. Avoided multiple enumerations by converting to lists. "Room Ok." is shown when no issues are detected.
2026-01-01 18:01:37 -06:00
Nick Genovese
7910b7931e feat: Add mute logic to ScreenLiftController
- adds a config value that mutes the display when the screen is in the up position
- screens will now mute/unmute based on their position if the config is set
2026-01-01 09:02:04 -06:00
Nick Genovese
3fb30d5561 Merge pull request #1373 from PepperDash/matrix-routing-isonline
Multiple fixes
2025-12-31 15:19:31 -05:00
Andrew Welker
57cd77f019 fix: implement IKeyName for DspControlPoint 2025-12-31 14:04:04 -06:00
Andrew Welker
7f2bb078c8 fix: revert prop name to inUpPosition for screenlift messenger
- refactor volume interfaces into separate files
- IBasicVolumeControl implements IKeyName
2025-12-31 12:20:40 -06:00
Andrew Welker
316bb849b4 fix: update matrix routing inputs if endpoint online status changes 2025-12-30 16:58:36 -06:00
Andrew Welker
a983e2c87f fix: save config only when values change 2025-12-30 14:34:11 -06:00
Nick Genovese
babb9a77df fix: a few logging updates 2025-12-29 16:48:13 -06:00
Nick Genovese
7629921113 fix: a few logging updates 2025-12-29 15:34:14 -06:00
Andrew Welker
3878c85a7a fix: use correct property name for isInUpPosition 2025-12-29 13:36:47 -06:00
Andrew Welker
7ad8218af0 fix: fusion controller now sets only associated room custom values 2025-12-29 13:03:47 -06:00
Andrew Welker
0c4aec14d1 fix: use .NET timers instead of CTimer 2025-12-29 11:53:52 -06:00
Andrew Welker
7d3f871460 Merge branch 'copilot/featureadd-raise-lower-time' into mc-connection-issues 2025-12-29 09:03:09 -06:00
Andrew Welker
78c9381108 fix: add clientId as qp for websocket for MC 2025-12-29 08:57:52 -06:00
Erik Meyer
a7ff2e8903 fix: move isInUpPosition to momvement complete method, remove conditionlal logic on raise/lower commands 2025-12-27 17:09:56 -06:00
copilot-swe-agent[bot]
ae0b2fe086 Refactor timer disposal and improve code readability
Co-authored-by: erikdred <88980320+erikdred@users.noreply.github.com>
2025-12-27 20:17:59 +00:00
copilot-swe-agent[bot]
bd11c827da Split movement time into separate raise/lower times and remove timing from latched mode
Co-authored-by: erikdred <88980320+erikdred@users.noreply.github.com>
2025-12-27 20:14:39 +00:00
copilot-swe-agent[bot]
7ea1efbabf Add raise/lower movement time configuration and banked command support
Co-authored-by: erikdred <88980320+erikdred@users.noreply.github.com>
2025-12-27 20:09:31 +00:00
copilot-swe-agent[bot]
9d6aaa2a0e Initial plan 2025-12-27 20:02:52 +00:00
Andrew Welker
53e7a30224 fix: handle threading issues for concurrent clients joining 2025-12-26 12:34:31 -06:00
Neil Dorin
ce8b08e312 Merge pull request #1371 from PepperDash/temp-to-dev 2025-12-23 12:14:03 -05:00
Neil Dorin
39c1f60a4d Merge pull request #1370 from PepperDash/udp-eisc 2025-12-23 12:10:37 -05:00
Andrew Welker
2cc37a4e40 fix: ExecuteJoinAction now uses the correct Sig collections 2025-12-23 11:06:52 -06:00
Andrew Welker
076e5dfa9d chore: update comments, debug methods, and mark some things obsolete 2025-12-23 10:25:53 -06:00
Andrew Welker
092896bb25 fix: support for legacy UDP EISC 2025-12-23 09:57:49 -06:00
Neil Dorin
7c8f0586e6 Merge pull request #1369 from PepperDash/volume-messenger-issue 2025-12-16 17:15:46 -05:00
Andrew Welker
c5b0872a4c fix: DeviceVolumeMessenger only sends rawValue when device implements it 2025-12-16 15:41:33 -06:00
Andrew Welker
a7b88ec38d Merge pull request #1368 from PepperDash/generic-sink
generic sink
2025-12-16 11:09:40 -05:00
Andrew Welker
210b398a13 fix: implement PR Review suggestions 2025-12-16 10:04:01 -06:00
Nick Genovese
bce1e3610e fix: copy dictionaries
- fixed multiple enumeration exception
2025-12-10 16:58:41 -06:00
Andrew Welker
13e833b797 Merge pull request #1366 from PepperDash/main
Main -> Development
2025-12-09 15:48:09 -05:00
Andrew Welker
6a33e7c99d fix: initialize current sources dictionaries 2025-12-05 16:26:08 -06:00
Andrew Welker
2048e3f65d fix: GenericSink implements ICurrentSources 2025-12-05 16:26:02 -06:00
Neil Dorin
81df2738de Merge pull request #1363 from PepperDash/feature/add-on-off-dsp-preset-keys-to-room-config
feat: Add on/off dsp keys to EssentialsAvRoomPropertiesConfig
2025-11-26 18:00:18 -05:00
Neil Dorin
08fbec416f Update src/PepperDash.Essentials.Core/Room/Config/EssentialsRoomConfig.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-26 15:53:45 -07:00
Neil Dorin
7594b22096 Update src/PepperDash.Essentials.Core/Room/Config/EssentialsRoomConfig.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-26 15:53:39 -07:00
Neil Dorin
d1babf6b9b Update src/PepperDash.Essentials.Core/Room/Config/EssentialsRoomConfig.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-26 15:53:31 -07:00
Neil Dorin
2187c9fb0d Update src/PepperDash.Essentials.Core/Devices/SourceListItem.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-26 15:53:22 -07:00
Neil Dorin
a5e6059160 Update src/PepperDash.Essentials.Core/Room/Config/EssentialsRoomConfig.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-26 15:53:14 -07:00
Neil Dorin
9ef4aedcce Update src/PepperDash.Essentials.Core/Room/Config/EssentialsRoomConfig.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-26 15:53:07 -07:00
Neil Dorin
f7c7160bf0 feat: Add on/off dsp keys to EssentialsAvRoomPropertiesConfig
clean up XML comments and improve property definitions in EssentialsRoomConfig
2025-11-26 15:48:14 -07:00
Neil Dorin
dbf5740841 Merge pull request #1362 from PepperDash/feature/add-IHasFeedback-to-IEssentialsRoomFusionController
fix: ensure proper disposal of help request timeout timer and improve…
2025-11-26 13:28:55 -05:00
Neil Dorin
c07e099a79 feat: add logging for help request timeout events in IEssentialsRoomFusionController 2025-11-26 11:10:21 -07:00
Neil Dorin
06cb508f3a Update src/PepperDash.Essentials.Core/Fusion/IEssentialsRoomFusionController.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-26 11:09:07 -07:00
Neil Dorin
e93b5b34cc Update src/PepperDash.Essentials.Core/Fusion/IEssentialsRoomFusionController.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-26 11:09:02 -07:00
Neil Dorin
4f5d4ef87a fix: ensure proper disposal of help request timeout timer and improve logging 2025-11-26 11:02:31 -07:00
Andrew Welker
636da8cc8c Merge pull request #1361 from PepperDash/feature/add-IHasFeedback-to-IEssentialsRoomFusionController
feat: add help request timeout functionality to IEssentialsRoomFusion…
2025-11-26 12:34:48 -05:00
Neil Dorin
5de1e2d7bb feat: add help request timeout functionality to IEssentialsRoomFusionController 2025-11-26 10:26:41 -07:00
Andrew Welker
2be078da18 Merge pull request #1360 from PepperDash/temp-to-dev
Temp to dev
2025-11-25 13:42:11 -05:00
Andrew Welker
03bbb84894 Merge pull request #1359 from PepperDash/feature/add-IHasFeedback-to-IEssentialsRoomFusionController
feat: implement IHasFeedback interface in IEssentialsRoomFusionContro…
2025-11-25 13:37:31 -05:00
Neil Dorin
d17394cdd7 feat: add logging to ExecuteSwitch method in GenericSink 2025-11-25 11:06:48 -07:00
Neil Dorin
8467afde38 Update src/PepperDash.Essentials.Core/Fusion/IEssentialsRoomFusionController.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-24 17:23:38 -07:00
Neil Dorin
5c016fb4b8 feat: implement IHasFeedback interface in IEssentialsRoomFusionController 2025-11-24 17:14:34 -07:00
Neil Dorin
2fbc32947c Merge pull request #1357 from PepperDash/url-parsing
fix: check for multiple URL patterns for both template & system URLS
2025-11-18 14:14:16 -05:00
Andrew Welker
c06b57a5f9 fix: check for multiple URL patterns for both template & system URLS 2025-11-18 12:30:54 -06:00
Neil Dorin
6d64fffc50 Merge pull request #1356 from PepperDash/mc-subscription-logging
Multiple Fixes
2025-11-14 15:44:58 -05:00
Andrew Welker
0c4ebdaf1d fix: change how subscription state is logged to reduce traffic to console 2025-11-13 09:56:29 -06:00
Andrew Welker
2c49fb9321 fix: parse current Portal URLS for system and template UUIDs correctly 2025-11-12 16:58:23 -06:00
Neil Dorin
c55de61da9 fix: enhance COM port registration logging and update GenericSink class for input switching 2025-11-05 14:43:42 -07:00
Andrew Welker
42444ede0a Merge pull request #1354 from PepperDash/comport-controller-updates
fix: improve logging for COM port registration and configuration
2025-11-04 10:55:44 -05:00
jkdevito
3d50f5f5ac fix: improve logging for COM port registration and configuration 2025-11-04 09:37:14 -06:00
Nick Genovese
11d62aebe1 Merge pull request #1353 from PepperDash/mc-subscription-concurrency
fix: make subscriber functionality thread-safe
2025-11-03 17:26:31 -04:00
Andrew Welker
edc10a9c2a fix: make subscriberIds private & check for add failure 2025-11-03 15:19:17 -06:00
Andrew Welker
9be5823956 Merge branch 'main' into mc-subscription-concurrency 2025-11-03 15:18:45 -06:00
Andrew Welker
35371dde22 fix: make subscriber functionality thread-safe 2025-11-03 15:13:48 -06:00
Jason DeVito
d3ceb4d7e7 Merge pull request #1352 from PepperDash/theme-saving
fix: use correct overload for PostStatusMessage
2025-11-03 15:08:28 -06:00
Andrew Welker
a782b57100 fix: use correct overload for PostStatusMessage 2025-11-03 14:00:51 -06:00
Neil Dorin
1360de599f Merge pull request #1351 from PepperDash/stream-debugging
fix: centralize debug printing into extension methods
2025-11-03 12:19:05 -05:00
Andrew Welker
fd95f5fed1 docs: update XML docs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-03 11:11:00 -06:00
Andrew Welker
9426dff5df fix: copilot suggestions from PR review 2025-11-03 11:02:39 -06:00
Andrew Welker
0d083e63c6 Merge commit '314570d6c3d78c7a92a362f3ec3a4a06bdbebd28' into stream-debugging 2025-11-03 10:56:53 -06:00
Andrew Welker
6ed7c96ec7 fix: centralize debug printing into extension methods
Stream debugging now uses CrestronConsole instead of debug methods, so that the debug statements will be printed regardless of console debug level. This also means that comm debug statements will NOT be in the Crestron Error log or in the files created by the logging system
2025-11-03 10:53:21 -06:00
Andrew Welker
314570d6c3 fix: change number of retained files to 7 instead of 30 for processors 2025-10-31 13:11:50 -05:00
Andrew Welker
ff609dfecd fix: add config option to turn echo off for SSH
In addition, removed CTimer in favor of System.Threading.Timers Timer in the SSH class, and modified the class to be thread-safe.
2025-10-31 09:01:54 -05:00
Andrew Welker
666be5ae59 Merge pull request #1345 from PepperDash/mc-touchpanel-key
mc touchpanel key
2025-10-30 17:11:15 -04:00
Andrew Welker
bfc9b7e7fa fix: logging & ternary changes 2025-10-30 16:07:26 -05:00
Andrew Welker
b02e952765 Merge remote-tracking branch 'origin/com102-comspec' into mc-touchpanel-key 2025-10-30 15:58:36 -05:00
Andrew Welker
72861a5097 Merge remote-tracking branch 'origin/feature/fusion-help-request' into mc-touchpanel-key 2025-10-30 15:58:26 -05:00
Andrew Welker
44ed067f4d chore: update local build version 2025-10-30 15:57:37 -05:00
Andrew Welker
faa2169baf fix: send correct URL to panel 2025-10-30 15:57:28 -05:00
Andrew Welker
fd40b0c6d1 fix: send all status messages with ClientId 2025-10-30 15:57:15 -05:00
Andrew Welker
afcddad1cc fix: remove async on task, as it's unnecessary 2025-10-30 15:56:50 -05:00
Andrew Welker
c4cf8f13e9 fix: register panel in post phase rather than activation cycle 2025-10-30 15:56:28 -05:00
Neil Dorin
c852f87a27 refactor: Comment out logging statements in help request handling 2025-10-30 14:33:58 -06:00
Neil Dorin
071174fa7d feat: Implement help request status tracking in Fusion system 2025-10-30 14:13:02 -06:00
Neil Dorin
da0f28a10c feat: Enhance IEssentialsRoomFusionController with additional properties and logging 2025-10-29 16:47:18 -06:00
Andrew Welker
0538a304ed Merge branch 'main' into mc-touchpanel-key 2025-10-29 15:13:53 -05:00
Andrew Welker
705c5db237 Merge pull request #1347 from PepperDash/json-console
fix: print json response to console using \r\n instead of \n
2025-10-29 16:12:49 -04:00
Neil Dorin
2e95f5337e feat: Add IEssentialsRoomFusionController and related configurations
- Introduced IEssentialsRoomFusionControllerFactory for creating Fusion Room Controller devices.
- Added IEssentialsRoomFusionControllerPropertiesConfig to define configuration properties for the Fusion Room Controller.
- Updated IFusionHelpRequest interface to include methods for cancelling and toggling help requests.
- Refactored RoomOnToDefaultSourceWhenOccupied to use IEssentialsRoomFusionController instead of EssentialsHuddleSpaceFusionSystemControllerBase.
- Modified EssentialsRoomBase to check for IEssentialsRoomFusionController in occupancy status provider.
2025-10-28 16:49:29 -06:00
jkdevito
ba576180a7 refactor: remove unused using directives in ComPortController 2025-10-28 09:05:56 -05:00
jkdevito
92c9db8237 refactor: improve logging messages in ComPort registration and configuration 2025-10-28 09:04:15 -05:00
jkdevito
c4d064965f refactor: enhance ComPortController with additional event documentation and logging improvements 2025-10-28 08:40:29 -05:00
Neil Dorin
f27965ac29 feat: Add help request functionality to Fusion system controller
Introduce `IFusionHelpRequest` interface for managing help requests, including `HelpRequestResponseFeedback` property and `SendHelpRequest` method. Enhance `EssentialsHuddleSpaceFusionSystemControllerBase` with new properties and methods to support help request tracking and management. Improve code organization and documentation throughout the affected files.
2025-10-27 17:35:38 -06:00
jkdevito
3ce282e2db refactor: comment out debug logging for ComPort registration 2025-10-27 17:33:06 -05:00
jkdevito
32a332a6e2 refactor: update TODO comment for Port registration verification 2025-10-27 17:32:40 -05:00
jkdevito
151a90c9be refactor: streamline ComPort registration and configuration logic with enhanced logging 2025-10-27 17:13:43 -05:00
jkdevito
89e893b3b7 fix: improve formatting and logging in ComPortController 2025-10-27 16:46:09 -05:00
jkdevito
d01eb03890 feat: enhance ComPortController with detailed logging on configuration changes 2025-10-27 14:06:20 -05:00
Andrew Welker
16c92afabb fix: print json response to console using \r\n instead of \n 2025-10-27 09:21:51 -05:00
Andrew Welker
dff5d2d32e Merge pull request #1346 from PepperDash/feature/add-infinetEx-comm-method 2025-10-23 18:50:06 -04:00
Neil Dorin
317bde3814 fix: add InfinetEx to eControlMethods enum 2025-10-23 15:22:46 -06:00
Andrew Welker
8bab3dc966 fix: send touchpanelKey message with all room combiner checks 2025-10-23 11:21:17 -05:00
Andrew Welker
514ac850ca fix: send touchpanel key correctly 2025-10-23 10:01:05 -05:00
Andrew Welker
44432f7a41 fix: send touchpanel key to client when client joins direct server 2025-10-23 09:54:03 -05:00
Andrew Welker
99253b30c2 fix: send touchpanel key to client when client joins 2025-10-23 09:49:45 -05:00
Andrew Welker
bf248fe33e Merge pull request #1343 from PepperDash/device-interface-system
device interface system
2025-10-23 10:40:56 -04:00
cdenig
2f44040e4f Merge pull request #1344 from PepperDash/feature/allow-config-tool-v2-structure
Feature/allow config tool v2 structure
2025-10-23 10:15:03 -04:00
Neil Dorin
10399a1be8 Update src/PepperDash.Core/Config/PortalConfigReader.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-10-22 13:54:08 -06:00
Neil Dorin
5409db193c Update src/PepperDash.Essentials.Core/Config/Essentials/EssentialsConfig.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-10-22 13:53:23 -06:00
Neil Dorin
f1ce54a524 Merge branch 'main' into feature/allow-config-tool-v2-structure 2025-10-22 13:48:22 -06:00
Andrew Welker
7c72a0d905 fix: send device interface list on client join
In order to avoid updating the MC Edge API to send device interfaces, one of the responses to the clientJoined message will now be a message with the type `/system/deviceInterfaces`. This has a corresponding handler in the core library to put the interfaces in the correct location.
2025-10-21 18:19:09 -05:00
Andrew Welker
5d5e78629e chore: update local build version to 2.18.2-local 2025-10-21 18:17:25 -05:00
Neil Dorin
dab5484d6e Merge pull request #1342 from PepperDash/wsdebug-persistence
Unique Client IDs
2025-10-15 15:16:46 -04:00
Andrew Welker
5c35a3be45 fix: catch exceptions in handlers directly
Previously, any exceptions that were occuring in a hander's action were being swalled due to being off on another thread. Now, those exceptions are caught and printed out.
2025-10-15 14:03:17 -05:00
Andrew Welker
6cb98e12fa fix: use correct collection for program stop 2025-10-15 14:02:06 -05:00
Andrew Welker
608601990b docs: fix Copilot comments 2025-10-15 12:54:14 -05:00
Andrew Welker
3e0f318f7f fix: update log methods to all be consistent 2025-10-15 12:36:42 -05:00
Andrew Welker
98d0cc8fdc docs: add missing XML comments for Mobile Control Project 2025-10-15 12:26:57 -05:00
Andrew Welker
c557c6cdd6 fix: mobileinfo & CWS info call report the correct data 2025-10-15 11:49:06 -05:00
Andrew Welker
8525134ae7 fix: direct server clients now have unique client IDs
Using the generated security token as an ID was presenting problems with duplicate connections using the same ID and trying to figure out where to send the messages. Now, the clients have a unique ID that's an increasing integer that restarts at 1 when the program restarts.
2025-10-15 09:50:12 -05:00
Neil Dorin
7330ae2e30 Merge pull request #1330 from PepperDash/temp-to-dev 2025-10-10 10:32:48 -04:00
Andrew Welker
a57dddba5e Merge pull request #1341 from PepperDash/main
Update temp-to-dev branch
2025-10-10 10:31:28 -04:00
Andrew Welker
1197b15a33 Merge pull request #1340 from PepperDash/display-bridging
Display bridging, Docs, Appdebug
2025-10-10 10:21:35 -04:00
Andrew Welker
ea6a7568fc Merge remote-tracking branch 'origin/feature/add-IKeyed-to-interfaces' into display-bridging 2025-10-09 16:08:17 -05:00
Andrew Welker
3a2a059ce1 fix: appdebug now has option for setting all sink levels 2025-10-09 16:07:25 -05:00
Andrew Welker
f9d9df9d5c docs: XML comments for Devices.Common 2025-10-09 13:18:36 -05:00
Neil Dorin
c284c4275f feat: Enhance UDPServer initialization in Connect method
Updated the `Connect` method in `GenericUdpServer.cs` to include error handling for hostname parsing. The method now attempts to create a `UDPServer` instance with specified parameters and falls back to default initialization if an error occurs. This improves flexibility and robustness in server setup.
2025-10-09 11:31:38 -06:00
Neil Dorin
0418f8a7cc fix: Fix typos and enhance item selection handling
Corrected parameter name in GenericUdpServer constructor.
Added new action for item selection in ISelectableItemsMessenger
with error handling for missing or invalid keys.
Updated SetItems method to improve clarity and ensure proper
clearing and re-adding of item actions.
2025-10-09 09:40:46 -06:00
Andrew Welker
a5d409e93a fix: analog input check uese correct comparison
input selection coming from SIMPL is 1-based, not 0-based. Comparison to input port array length needs to be <= instead of <
2025-10-09 08:36:26 -05:00
Andrew Welker
59944c0e2f refactor: split up classes to separate files 2025-10-09 08:33:22 -05:00
Neil Dorin
419177ccd5 fix: Add item management and update handling in messenger
Introduces a new private field `_itemKeys` to store item keys. Adds the `SetItems` method to manage item actions and update events, ensuring proper registration and cleanup. The `SendFullStatus` method is now invoked from a dedicated event handler `LocalItem_ItemUpdated`, improving the handling of item updates.
2025-10-08 12:24:17 -06:00
Neil Dorin
bd01e2bacc fix: Add KeyName class and update camera messaging
This commit introduces a new `KeyName` class implementing the `IKeyName` interface, enhancing the representation of camera data. The `CameraController_CameraSelected` and `SendFullStatus` methods are updated to utilize `KeyName` instances for selected and listed cameras, improving data encapsulation and consistency in the `IHasCamerasWithControlsStateMessage`. Additionally, new using directives for logging and core functionalities are added.
2025-10-07 17:10:11 -06:00
Neil Dorin
2928c5cf94 feat: Enhance camera capabilities and messaging structure
- Introduced `ICameraCapabilities` interface and `CameraCapabilities` class for defining camera features like pan, tilt, zoom, and focus.
- Modified `IHasCameras` interface to include a list of `IHasCameraControls` objects for improved camera management.
- Refactored `CameraBaseMessenger` to be generic, enhancing flexibility and type safety.
- Updated `SendCameraFullMessageObject` to include detailed camera capabilities in status messages.
- Added `CameraStateMessage` class to encapsulate camera state, including control support and capabilities.
- Updated `IHasCamerasWithControlMessenger` to use `IKeyName` for camera list and selected camera properties, improving type consistency.
- Enhanced `MobileControlSystemController` to manage devices implementing `IHasCameraControls`, creating appropriate messengers for different device types.
2025-10-07 15:37:31 -06:00
Neil Dorin
82b5dc96c1 feat: Deprecate IHasCamerasMessenger; introduce new controls
Mark IHasCamerasMessenger as obsolete and replace it with
IHasCamerasWithControlMessenger, which adds functionality
for devices with camera controls. A new state message class,
IHasCamerasWithControlsStateMessage, has been added to
encapsulate camera state. Update MobileControlSystemController
to use the new messenger implementation.
2025-10-07 12:00:01 -06:00
Neil Dorin
37cea8a11c fix: Refactor camera control interfaces and event arguments
Significantly restructure camera control interfaces and event arguments.
Removed obsolete interfaces like `IHasCameras` and `CameraSelectedEventArgs`,
and introduced generic event argument classes for improved type safety.
Added `IHasCamerasWithControls` for better management of camera controls.
Corrected the `IHasCameraMuteWithUnmuteReqeust` interface name.
Reintroduced the `eCameraControlMode` enum to define camera control modes.
These changes enhance the organization, clarity, and functionality of the camera control system.
2025-10-07 11:52:07 -06:00
Neil Dorin
a3f0901fa0 Merge pull request #1335 from PepperDash/mc-messenger-improvements
feat: unique status requests for messengers
2025-09-30 14:33:07 -06:00
Neil Dorin
f91f435768 fix: Add null check for CurrentScenario in MobileControlSystem
This change introduces a check for `_roomCombiner.CurrentScenario` being `null`. When it is `null`, a `MobileControlMessage` is created with the type `/system/roomKey`, `clientId`, and `roomKey`, which is then sent to the client. This improves the handling of scenarios without a current scenario by ensuring relevant room key information is communicated.
2025-09-30 12:11:25 -06:00
Andrew Welker
5120b2b574 Merge branch 'main' into mc-messenger-improvements 2025-09-29 11:02:39 -05:00
Andrew Welker
7c90027578 feat: set direct server debug level via console command
The `mobilewsdebug` console command can now be used to set the internal websocket logging level for both the API client and the direct server at the same time.
2025-09-26 21:47:06 -05:00
Andrew Welker
bb694b4200 feat: enable subscription logic for messengers
In order to help control traffic over the websocket, a subscription feature has been added:
* A config option, `enableMessengerSubscriptions` has been added
* When true, the MessengerBase class will assume that any message sent using the `PostStatusMessage` that has a valid client ID wants to send any subsequent unsolicited updates to that same client
* The client's ID will be added a list of subscribed client IDs
* Any subsequent messages sent using the `PostStatusMessage` methods that have a null clientId will ONLY be sent to subscribed clients
* When a client disconnects, it will be removed from the list of subscribed clients

This should cut down drastically on the traffic to the UI, especially when combined with requesting partial status updates from a device rather than the entire state.
2025-09-26 21:31:54 -05:00
Andrew Welker
087d0a1149 chore: move classes and interfaces to individual files 2025-09-26 21:21:01 -05:00
Andrew Welker
4747c16b02 build: delete all local build clzs on build 2025-09-26 21:17:28 -05:00
Andrew Welker
aa4d241dde Merge pull request #1337 from PepperDash/Ibasicvolumecontrols-messenger
fix: modify volume messenger to start with IBasicVolumeControls
2025-09-26 10:40:44 -04:00
Andrew Welker
9fc5586531 fix: use IBasicVolumeControls to build DeviceVolumeMessenger 2025-09-25 15:29:35 -05:00
Andrew Welker
d33fd56529 feat: add method to force panel reload
Other refactorings for factory method and log statements
2025-09-25 14:47:11 -05:00
Neil Dorin
8fc4d21f02 fix: Add DeviceInterfaceSupport property to JoinResponse
This commit introduces a new property, `DeviceInterfaceSupport`,
to the `JoinResponse` class in the `PepperDash.Essentials.WebSocketServer`
namespace. This property is a dictionary that maps strings to
`DeviceInterfaceInfo` objects, enhancing support for device interfaces.
A summary comment has also been added for clarity.
2025-09-25 11:50:42 -06:00
Neil Dorin
6d93662b31 Merge pull request #1338 from PepperDash/io-updates 2025-09-25 09:59:36 -06:00
Andrew Welker
11b190e76f docs: put XML comments on correct thing 2025-09-25 10:51:30 -05:00
Andrew Welker
df03a71cbc chore: fix errors in log statements
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-09-25 09:50:55 -05:00
Andrew Welker
72e67a1c4c fix: implement IHasFeedback for all IO devices and modify logging 2025-09-25 09:47:44 -05:00
Andrew Welker
f8728b6825 docs: update XML comments for EssentialsDeviceFactory 2025-09-25 09:46:55 -05:00
Andrew Welker
d9ef8a2289 docs: add XML comments for IAnalogInput 2025-09-25 09:46:03 -05:00
Andrew Welker
278408a3bc docs: reword XML comments for IHasFeedback Interface 2025-09-25 09:45:42 -05:00
Andrew Welker
fd70377c7f fix: log errors and disconnects for UI Clients 2025-09-25 08:51:11 -05:00
Neil Dorin
06341b14f3 feat: Adds device interface support info to joinroom response in MC websocket server.
Enhance MessengerBase and WebSocketServer functionality

Updated MessengerBase with new methods for action management and message posting, along with improved documentation. Introduced DeviceMessageBase for better message representation.

Enhanced MobileControlWebsocketServer to support device interfaces, adding DeviceInterfaceSupport to JoinResponse and a new DeviceInterfaceInfo class for detailed device information.
2025-09-24 14:49:41 -06:00
Andrew Welker
d7f9c74b2f fix: modify volume messenger to start with IBasicVolumeControls 2025-09-23 13:39:03 -05:00
Andrew Welker
5921b5dbb0 Merge pull request #1336 from PepperDash/hotfix/check-for-cs-lan-on-mctp
Hotfix/check for cs lan on mctp
2025-09-23 13:58:15 -04:00
Neil Dorin
ba0de5128f Update src/PepperDash.Essentials.MobileControl/Touchpanel/MobileControlTouchpanelController.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-09-23 11:56:57 -06:00
Andrew Welker
b0a090062f Merge branch 'hotfix/check-for-cs-lan-on-mctp' into mc-messenger-improvements 2025-09-23 11:25:41 -05:00
Andrew Welker
9c0cab8218 fix: use id parameter and fix some formatting 2025-09-23 11:09:44 -05:00
Andrew Welker
c0af637108 fix: use correct property names 2025-09-23 11:07:24 -05:00
Andrew Welker
9c9eaea928 feat: unique status requests for messengers
UI Applications can now request status for specific feature sets instead of full status for a device. This will hopefully cut down on the traffic and messages required to get the data for the UI.
2025-09-23 10:55:16 -05:00
Neil Dorin
9de94bd65f fix: Update v2 config detection criteria
Changed the logic for identifying v2 configuration files. The check now looks for the presence of a "versions" node instead of the absence of "system" or "template" nodes, reflecting an update in the configuration file structure.
2025-09-22 15:05:06 -06:00
Neil Dorin
ff46fb8f29 feat: Add versioning support to EssentialsConfig
Introduce `Versions` property in `EssentialsConfig` to hold version information.
Add `VersionData` class for Essentials and package versions, and `NugetVersion` class for individual package details.
Retain and document `SystemTemplateConfigs` class.
2025-09-22 14:55:58 -06:00
Neil Dorin
d9243def30 feat: Adds ability to read configs generated from v2 config tool that are pre-merged don't have system or template objects
Refactor config handling and improve documentation

- Updated `PortalConfigReader.cs` to use constants for configuration keys, enhancing maintainability and readability. Improved error logging with `Debug.LogError`.
- Modified `ConfigReader.cs` to handle v2 configuration format, streamlining the loading process and avoiding redundant parsing.
- Added XML documentation comments to properties in `EssentialsConfig.cs`, improving code documentation. Initialized `Rooms` property in the constructor.
- Enhanced `SystemTemplateConfigs` class with XML documentation for better clarity on its properties.
2025-09-22 14:22:57 -06:00
Neil Dorin
258699fbcd fix: Enhance AppUrl change logging
Updated logging to include the new AppUrl value when it changes. This provides better context in the logs, making it easier to track the specific URL that was set.
2025-09-18 18:23:12 -06:00
Neil Dorin
738504e9fc fix: Add error handling for network parameter retrieval
Introduced a try-catch block to handle exceptions when fetching the Crestron Ethernet adapter's ID, subnet mask, and IP address. Added logging for cases where the processor lacks a CS LAN. Also included a new using directive for Serilog.Events.
2025-09-18 14:48:21 -06:00
erikdred
cae1bbd6e6 Merge pull request #1332 from PepperDash/feature/add-hide-property-to-room-combine-scenario
feat: Add HideInUi property to room combiner classes
2025-09-17 18:38:21 -04:00
Neil Dorin
dea4407e3e Update src/PepperDash.Essentials.Core/Room/Combining/IEssentialsRoomCombiner.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-09-17 16:36:33 -06:00
Neil Dorin
ab08a546f7 feat: Add HideInUi property to room combiner classes
- Introduced `HideInUi` property in `EssentialsRoomCombinerPropertiesConfig` to control UI visibility.
- Added `HideInUi` interface property in `IEssentialsRoomCombiner`.
- Implemented `HideInUi` in `RoomCombinationScenario`, along with new `Key` and `Name` properties for improved data representation.
2025-09-17 15:25:23 -06:00
Neil Dorin
ef98d47792 Merge pull request #1331 from PepperDash/dev-list-fix 2025-09-17 08:36:04 -06:00
Andrew Welker
c05976ee60 fix: modify formatting and sorting for devfb response 2025-09-17 09:28:04 -05:00
Andrew Welker
4ca1031bef docs: fix CS1587 errors 2025-09-17 08:52:44 -05:00
Andrew Welker
6d61c4525e fix: use ConsoleCommandResponse for device feedbacks 2025-09-17 08:52:29 -05:00
Andrew Welker
3db274ace5 fix: add line endings for devlist console command 2025-09-17 08:51:45 -05:00
Neil Dorin
f0f708294c Merge pull request #1329 from PepperDash/devlist-fix
fix: print devlist output using ConsoleCommandResponse
2025-09-10 09:58:13 -06:00
Andrew Welker
a00d186c62 fix: print devlist output using ConsoleCommandResponse 2025-09-10 10:45:55 -05:00
Neil Dorin
0bfec16622 Merge pull request #1328 from PepperDash/temp-to-dev
Temp to dev
2025-09-08 11:29:40 -06:00
Neil Dorin
51da668dfd Merge pull request #1327 from PepperDash/cs-lan-mc-panel
fix: add config property for devices on CS LAN
2025-09-05 16:20:42 -06:00
Andrew Welker
d2b7400039 fix: INvxNetworkPortInformation inherits from IKeyed 2025-09-05 15:55:31 -05:00
Andrew Welker
2424838b7f chore: remove unused using
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-09-02 12:56:14 -05:00
Andrew Welker
9556edc064 fix: add config property for devices on CS LAN 2025-09-02 12:54:51 -05:00
Andrew Welker
94e7b8210f Merge pull request #1323 from PepperDash/temp-to-dev
Temp to dev
2025-08-26 10:19:59 -04:00
Sumanth Rayancha
f9088691fd Merge pull request #1322 from PepperDash/release
Release
2025-08-26 10:16:18 -04:00
Andrew Welker
e40b6a8b4c Merge pull request #1320 from PepperDash/feature/extract-html-assets
feat: add html assets extraction from zip files in ControlSystem
2025-08-26 09:58:02 -04:00
Erik Meyer
c3b39a87da fix: enhance zip extraction to prevent directory traversal attacks 2025-08-22 15:15:02 -04:00
erikdred
06dc0e947e fix: check for null when getting directory
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-22 09:12:20 -04:00
aknous
147997f460 Merge pull request #1321 from PepperDash/IBasicVideoMuteWithFeedbackMessenger
Adds IBasicVideoMuteWithFeedbackMessenger
2025-08-21 14:26:10 -04:00
aknous
49abec5eea Update src/PepperDash.Essentials.MobileControl.Messengers/Messengers/CameraBaseMessenger.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-21 13:23:43 -04:00
aknous
6830efe42a fix: fixes CameraBaseMessenger hold timer for PTZ controls, adds storePreset messenger 2025-08-19 18:34:16 -04:00
Neil Dorin
d013068a0c Update src/PepperDash.Essentials/ControlSystem.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-19 14:54:11 -06:00
Neil Dorin
52916d29f4 Update src/PepperDash.Essentials/ControlSystem.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-19 14:53:06 -06:00
Erik Meyer
19e8489166 feat: add html assets extraction from zip files in ControlSystem 2025-08-19 15:48:43 -04:00
Neil Dorin
fe33443b25 fix: Update IP handling in MobileControlTouchpanelController
Updates logic to handle setting the URL sent to the CH5 wrapper app to use the CS LAN IP based on the actual IpInformationChange event on the panel itself.

- Added `._PepperDash.Essentials.4Series.sln` to .gitignore.
- Introduced new using directives for Regex and Crestron libraries.
- Added `csIpAddress` and `csSubnetMask` fields to store device IP info.
- Modified constructor to retrieve and assign current IP and subnet mask.
- Updated `Panel.IpInformationChange` event handler for logging and URL setting.
- Created `GetUrlWithCorrectIp` method to determine the correct URL based on IP.
- Refactored `SetAppUrl` to utilize the new URL method.
- Commented out old IP determination logic in `MobileControlWebsocketServer.cs` as it was moved to the touchpanel controller.
2025-08-19 13:28:45 -06:00
aknous
8cf195b262 feat: adds IBasicVideoMuteWithFeedbackMessenger 2025-08-19 11:37:39 -04:00
Neil Dorin
40406b797d Merge pull request #1314 from PepperDash/streaming-device-properties 2025-08-15 11:52:20 -06:00
Andrew Welker
65bc408ebf fix: add StreamUrl to baseStreamingDeviceProperties 2025-08-15 12:41:21 -05:00
Neil Dorin
9d49fb8357 Merge pull request #1313 from PepperDash/temp-to-dev
Temp to dev
2025-08-15 09:34:41 -06:00
Neil Dorin
fb7797dac7 Merge pull request #1312 from PepperDash/comm-bridge-add 2025-08-15 09:02:23 -06:00
Andrew Welker
574f5f6dc9 chore: remove unused using directives in CommFactory.cs 2025-08-15 09:51:17 -05:00
Andrew Welker
e49c69a12f feat: add CommBridge class and enhance EssentialsBridgeableDevice with new constructors 2025-08-15 09:48:30 -05:00
Sumanth Rayancha
7889cba196 Merge pull request #1307 from PepperDash/feature/assets-folder
feat: add LoadAssets method to manage asset loading and configuration…
2025-08-13 12:08:14 -04:00
Nick Genovese
033aa1f3dd feat: enhance asset extraction and configuration file handling in ControlSystem 2025-08-12 12:17:58 -04:00
Neil Dorin
9b6c2d80ea Merge pull request #1308 from PepperDash/current-sources 2025-08-11 22:53:11 -06:00
Andrew Welker
a0fc731701 chore: apply copilot suggestions
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-11 17:55:02 -05:00
Andrew Welker
f2d0dca7b8 fix: make appdebug case-insensitive
when commands like `appdebug verbose` are used rather than `appdebug 2`, the command would fail because Verbose != verbose. The enum conversion is now case-insensitive.
2025-08-11 17:50:50 -05:00
Andrew Welker
ab4e85d081 fix: use different methods for extensions
The logging extension methods now use the appropriate methods from
the debug class. Previously some messages were not getting handled correctly, and it was causing issues with log statements.
2025-08-11 17:48:46 -05:00
Andrew Welker
47017da527 fix: use correct property names 2025-08-11 17:47:14 -05:00
Andrew Welker
4f0d464ba4 Merge pull request #1306 from PepperDash/fix/remove-unsued-method-in-cs
Fix/remove unsued method in cs
2025-08-11 15:30:04 -05:00
Nick Genovese
0107422507 refactor: remove unused assembly resolution logic from ControlSystem 2025-08-11 16:24:44 -04:00
Nick Genovese
1a366790e7 feat: add LoadAssets method to manage asset loading and configuration file cleanup 2025-08-11 16:23:31 -04:00
Andrew Welker
97448f4f0f Merge branch 'main' into current-sources 2025-08-06 09:01:40 -05:00
Andrew Welker
cf3ece4237 fix: use cr-lf for line endings 2025-08-06 09:00:45 -05:00
Andrew Welker
bb4b2f88b6 Merge pull request #1304 from PepperDash/temp-to-dev
Temp to dev
2025-08-05 16:39:38 -05:00
aknous
808e8042a7 Merge pull request #1305 from PepperDash/default-debug-levels
fix: set default debug levels if not found
2025-08-04 14:47:31 -04:00
Andrew Welker
0bc4388bfd Merge branch 'default-debug-levels' into current-sources 2025-08-04 13:31:26 -05:00
Andrew Welker
dbc132c0da fix: set default debug levels if not found 2025-08-04 13:31:17 -05:00
Andrew Welker
5bb0ab2626 fix: base config properties for use with streaming devices 2025-08-01 21:17:35 -05:00
Andrew Welker
27bf36c58c fix: modify how current sources dictionary gets updated 2025-08-01 09:22:31 -05:00
Andrew Welker
ce886aea63 chore: update local build version 2025-08-01 09:22:31 -05:00
Andrew Welker
ef920bf54c Merge branch 'main' into current-sources 2025-07-31 13:27:43 -05:00
Andrew Welker
88466818ce Merge pull request #1303 from PepperDash/debug-fixes
fix: use correct overload for logging at levels
2025-07-31 13:03:14 -05:00
Andrew Welker
0871a902e1 fix: use correct overload for logging at levels
Some overloads that had the first argument as an Exception were calling the _logger.Write method with a null where the message template should have been, causing messages logged with that overload to be swallowed and not logged.
2025-07-31 12:50:41 -05:00
Andrew Welker
a031424752 fix: add destination & source keys to routelist 2025-07-30 11:20:54 -05:00
Andrew Welker
fd1ba345aa fix: remove StringEnumConverter 2025-07-29 23:01:13 -05:00
Andrew Welker
e03874a7a9 fix: add messenger and event to ICurrentSources 2025-07-29 22:26:07 -05:00
aknous
2efab4f196 Merge pull request #1301 from PepperDash/mc-tp-ip-fix
fix: only adjust IP if processor is a CS processor
2025-07-28 12:57:24 -04:00
Andrew Welker
a41aba1904 Merge pull request #1300 from PepperDash/temp-to-dev
Temp to dev
2025-07-28 11:54:11 -05:00
Andrew Welker
d0ca6721f5 fix: only adjust IP if processor is a CS processor 2025-07-28 11:48:00 -05:00
Andrew Welker
c732eb48f2 Merge pull request #1299 from PepperDash/factory-updates
Multiple Updates
2025-07-25 10:38:15 -05:00
Andrew Welker
efe70208d3 fix: check for null assembly name 2025-07-25 10:32:43 -05:00
Andrew Welker
615f640ebb fix: use continue instead of return
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-25 10:27:50 -05:00
Andrew Welker
ee6f9416a3 chore: remove unused configSnippet 2025-07-25 10:24:32 -05:00
Andrew Welker
4fc6ecbd0b style: switch to auto property for attributes 2025-07-25 09:56:39 -05:00
Andrew Welker
58bcc3315d fix: add changes from code review 2025-07-25 09:51:04 -05:00
Andrew Welker
08cc84a8e8 fix: apply suggestions from code review
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-25 09:28:53 -05:00
Andrew Welker
226014fee0 fix: apply suggestions from code review
- remove commented out debug statements
- null check for description attribute

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-25 09:27:33 -05:00
Andrew Welker
dc7f99e176 fix: mark FeedbackBase default constructor as obsolete
There are situations now where feedbacks in the feedback collection
can be used to update things on UIs. If the feedback doesn't have a key, it can't
be used for this purpose.
2025-07-25 09:18:22 -05:00
Andrew Welker
f0af9f8d19 fix: mark IHasMultipleDisplays and associated enum as obsolete
Fixes #1219
2025-07-25 09:14:45 -05:00
Andrew Welker
31143f56df fix: modify how exceptions are printed to reduce noise
When an exception occurs during the connect method,
only the exception message will be printed at the Error log level.
The entire stack trace will be printed when at the Verbose level.

fixes #1273
2025-07-25 09:13:42 -05:00
Andrew Welker
43989b9588 chore: move interfaces to their own files 2025-07-25 09:12:09 -05:00
Andrew Welker
8db559f197 feat: factory updates & refactoring
This commit introduces significant updates to the device factory system, enhancing the way devices are created and managed within the PepperDash Essentials framework.
The changes include:
- New attributes for device configuration and description.
- Refactoring of the device manager and essentials device classes to support new factory methods.
- modified factory classes for essentials devices, plugin development devices, and processor extension devices.
- The device factory interface has been updated to include a factory method for creating devices.
- Added a wrapper for the device factory to streamline device creation.
- Updated plugin loader to accommodate the new device factory structure.

Fixes #1065
Fixed #1277
2025-07-25 09:05:40 -05:00
Andrew Welker
86f20da116 Merge pull request #1291 from PepperDash/copilot/fix-1290
docs: Add comprehensive XML documentation to all public members
2025-07-25 08:02:37 -05:00
Andrew Welker
0674dbda37 Merge branch 'main' into copilot/fix-1290 2025-07-25 07:48:49 -05:00
Andrew Welker
592607f3c8 Merge pull request #1296 from PepperDash/feature/add-IHasCamerasMessenger 2025-07-24 18:53:05 -05:00
Neil Dorin
ea0a779f8b Merge branch 'feature/add-IHasCamerasMessenger' of https://github.com/PepperDash/Essentials into feature/add-IHasCamerasMessenger 2025-07-24 16:40:06 -06:00
Neil Dorin
86e4d2f7fb feat: Update SendFullStatus to target specific clients
Modified the `SendFullStatus` method to accept a `string clientId` parameter, allowing it to send status messages to specific clients. Updated the action for `"/fullStatus"` to pass the client ID and adjusted the `PostStatusMessage` call accordingly.
2025-07-24 16:39:28 -06:00
Neil Dorin
0069233e13 Update src/PepperDash.Essentials.Devices.Common/Cameras/CameraControl.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-24 16:16:05 -06:00
Neil Dorin
4048efb07e Merge branch 'main' into feature/add-IHasCamerasMessenger 2025-07-24 16:03:41 -06:00
Andrew Welker
b12cdbc75c docs: apply suggestions from copilot
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-23 09:08:10 -05:00
Neil Dorin
b47f1d6b77 Merge pull request #1293 from PepperDash/to-dev 2025-07-23 07:53:44 -06:00
Andrew Welker
1dbac7d1c8 Merge pull request #1292 from PepperDash/portkey-add
feat: add destination and source port key properties for advanced routing
2025-07-22 15:26:44 -05:00
Neil Dorin
799d4c127c Update src/PepperDash.Essentials.Core/Devices/DestinationListItem.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-22 14:02:01 -06:00
Andrew Welker
a6cd9a0571 feat: add destination and source port key properties for advanced routing 2025-07-22 14:56:28 -05:00
copilot-swe-agent[bot]
80da4ad98f docs: fix duplicate and malformed XML documentation tags
Co-authored-by: andrew-welker <1765622+andrew-welker@users.noreply.github.com>
2025-07-22 17:40:17 +00:00
Andrew Welker
b283ed34b4 docs: remove duplicate
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-22 12:08:28 -05:00
Andrew Welker
899f13eadb docs: remove duplicate
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-22 12:08:10 -05:00
Andrew Welker
fc1e29565e docs: remove duplicate
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-22 12:03:33 -05:00
Andrew Welker
f9a74567d2 docs: remove duplicates
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-22 12:03:18 -05:00
Andrew Welker
53b1e5d142 docs: remove duplicates
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-22 12:03:03 -05:00
Andrew Welker
78e9ea8070 docs: duplicate tags
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-22 12:02:38 -05:00
Andrew Welker
df201558a5 docs: update
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-22 12:02:24 -05:00
Andrew Welker
130c874684 docs: fix documentation
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-22 11:37:08 -05:00
Andrew Welker
aee40ffe14 docs: fix duplicate XML tags
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-22 11:36:43 -05:00
Andrew Welker
3ffad13abf docs: remove duplicate
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-22 11:30:04 -05:00
Andrew Welker
5ee7aaa991 docs: revert to old comment
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-22 11:29:25 -05:00
Andrew Welker
4fa8433e73 docs: change wording
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-22 11:19:10 -05:00
Andrew Welker
5fe99518a0 docs: update formatting
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-22 11:18:50 -05:00
Andrew Welker
5a2a2129e6 docs: remove duplicate
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-22 11:13:33 -05:00
Andrew Welker
4fbfda62d6 docs: remove duplicate
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-22 11:13:21 -05:00
Andrew Welker
b8ab54cbe0 docs: remove duplicate
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-22 11:12:55 -05:00
Andrew Welker
f6f1619bc2 docs: remove duplicate
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-22 11:12:38 -05:00
Andrew Welker
41fd4d6adc docs: revert to original documentation
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-22 11:12:25 -05:00
Andrew Welker
5b73f8fbd2 docs: remove duplicate docs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-22 11:12:03 -05:00
Andrew Welker
c70a8edc24 docs: remove duplicate documentation
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-22 11:11:33 -05:00
copilot-swe-agent[bot]
7987eb8f9b docs: complete XML documentation for all projects with inheritdoc tags
Co-authored-by: andrew-welker <1765622+andrew-welker@users.noreply.github.com>
2025-07-22 15:53:01 +00:00
copilot-swe-agent[bot]
260677a37f docs: add XML documentation to PepperDash.Core project
Co-authored-by: andrew-welker <1765622+andrew-welker@users.noreply.github.com>
2025-07-22 15:48:23 +00:00
copilot-swe-agent[bot]
eeb0e84dc7 docs: enable XML documentation generation and add initial documentation
Co-authored-by: andrew-welker <1765622+andrew-welker@users.noreply.github.com>
2025-07-22 15:44:52 +00:00
copilot-swe-agent[bot]
d282487da6 Initial plan 2025-07-22 15:35:41 +00:00
Andrew Welker
da30424657 Merge pull request #1289 from PepperDash/meter-feedback-interface
meter feedback interface
2025-07-21 15:20:29 -05:00
Andrew Welker
311452beac fix: use correct namespaces
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-21 13:30:11 -05:00
Andrew Welker
789113008e docs: update comments
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-21 13:29:11 -05:00
Andrew Welker
660836bd5a docs: remove spaces
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-21 13:28:59 -05:00
Andrew Welker
97b2ffed9c docs: fix comment
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-21 13:28:37 -05:00
Andrew Welker
2bbefa062d docs: fix comments
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-21 13:28:10 -05:00
Andrew Welker
3421b2f28c Merge branch 'main' into meter-feedback-interface 2025-07-17 12:34:16 -05:00
Andrew Welker
82889e9794 Merge pull request #1288 from PepperDash/mc-touchpanel-cs
Backwards Compatibility issues
2025-07-17 12:33:01 -05:00
Andrew Welker
1dcd4e328c fix: Destination support for USB 2025-07-17 12:32:26 -05:00
Andrew Welker
e76369726d docs: XML comments for DestinationListItem 2025-07-17 12:25:52 -05:00
Andrew Welker
2bf0f2092b fix: use new interface in direct server 2025-07-17 12:16:32 -05:00
Neil Dorin
f8455d4110 feat: Refactor IHasCamerasMessenger constructor parameters
Updated the constructor of `IHasCamerasMessenger` to reorder parameters, placing `IHasCameras cameraController` last. Added logic in `MobileControlSystemController.cs` to instantiate and add `IHasCamerasMessenger` for devices implementing the `IHasCameras` interface.
2025-07-17 11:14:32 -06:00
Andrew Welker
c1eccfd790 fix: refactor interfaces for backwards compatibility 2025-07-17 12:13:08 -05:00
aknous
f1a89161bc Merge pull request #1287 from PepperDash/mc-touchpanel-cs
fix: use Control Subnet IP if MC TP devices are on the CS Lan
2025-07-17 11:17:03 -04:00
Andrew Welker
e59c50d0aa refactor: use tryParse for IP Address parsing
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-17 10:15:19 -05:00
Andrew Welker
9d313d8c7c fix: use Control Subnet IP if MC TP devices are on the CS Lan 2025-07-17 09:54:08 -05:00
Andrew Welker
9813673b66 feat: ICurrentSources interface to allow for tracking breakaway routing 2025-07-17 09:15:25 -05:00
Neil Dorin
f006ed0076 feat: Add camera control interfaces and messenger classes
This commit introduces new interfaces in `CameraControl.cs` for various camera functionalities, including muting, panning, tilting, zooming, and auto modes. The `IHasCameras` interface is expanded to manage camera lists and selections, with the addition of `CameraSelectedEventArgs` for event handling.

In `CameraBaseMessenger.cs`, a new `IHasCamerasMessenger` class is created to facilitate communication for devices implementing the `IHasCameras` interface, along with the `IHasCamerasStateMessage` class to represent the state of camera devices. These changes enhance the overall camera control capabilities and improve interaction management within the application.
2025-07-16 16:54:57 -06:00
Andrew Welker
ddbcc13c50 fix: add property for sync device association 2025-07-16 10:41:46 -05:00
Andrew Welker
2a70fc678e fix: add IStateFeedback interface 2025-07-11 13:13:52 -05:00
Andrew Welker
056614cba1 fix: add IMeterFeedback interface 2025-07-09 14:32:01 -05:00
Neil Dorin
5ff587a8c9 Merge pull request #1285 from PepperDash/feature/add-isMic-support
feat: Update .gitignore and enhance routing-related classes
2025-07-08 10:55:05 -06:00
Neil Dorin
26c1baa1f8 Merge pull request #1284 from PepperDash/network-port 2025-07-07 09:19:42 -06:00
Andrew Welker
2b15c2a56f docs: remove extra space
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-07 10:17:37 -05:00
Andrew Welker
a076d531bc chore: remove BOM
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-07 10:17:08 -05:00
Andrew Welker
5e880f0111 chore: add missing brace 2025-07-07 10:06:23 -05:00
Andrew Welker
8f1fb86d37 fix: add NVX network port info interface 2025-07-07 09:49:01 -05:00
Neil Dorin
2fa297a204 feat: Update .gitignore and enhance routing-related classes
- Updated `.gitignore` to include additional files and directories.
- Added summary comments and new properties in `LevelControlListItem.cs` for better clarity and functionality.
- Enhanced documentation in `SourceListItem.cs` and introduced new properties, including `Destinations` and a `ToString` method.
- Introduced `SourceRouteListItem` class with routing properties and expanded `eSourceListItemDestinationTypes` enum.
- Added `IRoutingSinkWithInputPort` interface in `IRoutingSink.cs` to support input port functionality.
2025-06-26 10:10:09 -06:00
614 changed files with 40457 additions and 26314 deletions

13
.config/dotnet-tools.json Normal file
View File

@@ -0,0 +1,13 @@
{
"version": 1,
"isRoot": true,
"tools": {
"csharpier": {
"version": "1.2.4",
"commands": [
"csharpier"
],
"rollForward": false
}
}
}

5
.gitignore vendored
View File

@@ -393,4 +393,7 @@ essentials-framework/Essentials Interfaces/PepperDash_Essentials_Interfaces/Pepp
/._PepperDash.Essentials.sln
.vscode/settings.json
_site/
api/
api/
*.DS_Store
/._PepperDash.Essentials.4Series.sln
dotnet

9
.vscode/extensions.json vendored Normal file
View File

@@ -0,0 +1,9 @@
{
"recommendations": [
"ms-dotnettools.vscode-dotnet-runtime",
"ms-dotnettools.csharp",
"ms-dotnettools.csdevkit",
"vivaxy.vscode-conventional-commits",
"mhutchie.git-graph"
]
}

View File

@@ -0,0 +1,282 @@
# Crestron Library Usage Analysis - PepperDash Essentials
This document provides a comprehensive analysis of Crestron classes and interfaces used throughout the PepperDash Essentials framework, organized by namespace and library component.
## Executive Summary
The PepperDash Essentials framework extensively leverages Crestron SDK components across 100+ files, providing abstractions for:
- Control system hardware (processors, touchpanels, IO devices)
- Communication interfaces (Serial, TCP/IP, SSH, CEC, IR)
- Device management and routing
- User interface components and smart objects
- System monitoring and diagnostics
## 1. Core Crestron Libraries
### 1.1 Crestron.SimplSharp
**Primary Usage**: Foundational framework components, collections, and basic types.
**Key Files**:
- Multiple files across all projects use `Crestron.SimplSharp` namespaces
- Provides basic C# runtime support for Crestron processors
### 1.2 Crestron.SimplSharpPro
**Primary Usage**: Main hardware abstraction layer for Crestron devices.
**Key Classes Used**:
#### CrestronControlSystem
- **File**: `/src/PepperDash.Essentials/ControlSystem.cs`
- **Usage**: Base class for the main control system implementation
- **Implementation**: `public class ControlSystem : CrestronControlSystem, ILoadConfig`
#### Device (Base Class)
- **Files**: 50+ files inherit from or use this class
- **Key Implementations**:
- `/src/PepperDash.Core/Device.cs` - Core device abstraction
- `/src/PepperDash.Essentials.Core/Devices/EssentialsDevice.cs` - Extended device base
- `/src/PepperDash.Essentials.Core/Room/Room.cs` - Room device implementation
- `/src/PepperDash.Essentials.Core/Devices/CrestronProcessor.cs` - Processor device wrapper
#### BasicTriList
- **Files**: 30+ files use this class extensively
- **Primary Usage**: Touchpanel communication and SIMPL bridging
- **Key Files**:
- `/src/PepperDash.Essentials.Core/Touchpanels/TriListExtensions.cs` - Extension methods for signal handling
- `/src/PepperDash.Essentials.Core/Devices/EssentialsBridgeableDevice.cs` - Bridge interface
- `/src/PepperDash.Essentials.Core/Touchpanels/ModalDialog.cs` - UI dialog implementation
#### BasicTriListWithSmartObject
- **Files**: Multiple touchpanel and UI files
- **Usage**: Enhanced touchpanel support with smart object integration
- **Key Files**:
- `/src/PepperDash.Essentials.Core/Touchpanels/Interfaces.cs` - Interface definitions
- `/src/PepperDash.Essentials.Core/SmartObjects/SubpageReferenceList/SubpageReferenceList.cs`
## 2. Communication Hardware
### 2.1 Serial Communication (ComPort)
**Primary Class**: `ComPort`
**Key Files**:
- `/src/PepperDash.Essentials.Core/Comm and IR/ComPortController.cs`
- `/src/PepperDash.Essentials.Core/Comm and IR/CommFactory.cs`
**Usage Pattern**:
```csharp
public class ComPortController : Device, IBasicCommunicationWithStreamDebugging
public static ComPort GetComPort(EssentialsControlPropertiesConfig config)
```
**Interface Support**: `IComPorts` - Used for devices that provide multiple COM ports
### 2.2 IR Communication (IROutputPort)
**Primary Class**: `IROutputPort`
**Key Files**:
- `/src/PepperDash.Essentials.Core/Devices/IrOutputPortController.cs`
- `/src/PepperDash.Essentials.Core/Devices/GenericIRController.cs`
- `/src/PepperDash.Essentials.Core/Comm and IR/IRPortHelper.cs`
**Usage Pattern**:
```csharp
public class IrOutputPortController : Device
IROutputPort IrPort;
public IrOutputPortController(string key, IROutputPort port, string irDriverFilepath)
```
### 2.3 CEC Communication (ICec)
**Primary Interface**: `ICec`
**Key Files**:
- `/src/PepperDash.Essentials.Core/Comm and IR/CecPortController.cs`
- `/src/PepperDash.Essentials.Core/Comm and IR/CommFactory.cs`
**Usage Pattern**:
```csharp
public class CecPortController : Device, IBasicCommunicationWithStreamDebugging
public static ICec GetCecPort(ControlPropertiesConfig config)
```
## 3. Input/Output Hardware
### 3.1 Digital Input
**Primary Interface**: `IDigitalInput`
**Key Files**:
- `/src/PepperDash.Essentials.Core/CrestronIO/GenericDigitalInputDevice.cs`
- `/src/PepperDash.Essentials.Core/Microphone Privacy/MicrophonePrivacyController.cs`
**Usage Pattern**:
```csharp
public List<IDigitalInput> Inputs { get; private set; }
void AddInput(IDigitalInput input)
```
### 3.2 Versiport Support
**Key Files**:
- `/src/PepperDash.Essentials.Core/CrestronIO/GenericVersiportInputDevice.cs`
- `/src/PepperDash.Essentials.Core/CrestronIO/GenericVersiportAnalogInputDevice.cs`
- `/src/PepperDash.Essentials.Core/CrestronIO/GenericVersiportOutputDevice.cs`
**Usage**: Provides flexible I/O port configuration for various signal types
## 4. Touchpanel Hardware
### 4.1 MPC3 Touchpanel
**Primary Class**: `MPC3Basic`
**Key File**: `/src/PepperDash.Essentials.Core/Touchpanels/Mpc3Touchpanel.cs`
**Usage Pattern**:
```csharp
public class Mpc3TouchpanelController : Device
readonly MPC3Basic _touchpanel;
_touchpanel = processor.ControllerTouchScreenSlotDevice as MPC3Basic;
```
### 4.2 TSW Series Support
**Evidence**: References found in messenger files and mobile control components
**Usage**: Integrated through mobile control messaging system for TSW touchpanel features
## 5. Timer and Threading
### 5.1 CTimer
**Primary Class**: `CTimer`
**Key File**: `/src/PepperDash.Core/PasswordManagement/PasswordManager.cs`
**Usage Pattern**:
```csharp
Debug.Console(1, string.Format("PasswordManager.UpdatePassword: CTimer Started"));
Debug.Console(1, string.Format("PasswordManager.UpdatePassword: CTimer Reset"));
```
## 6. Networking and Communication
### 6.1 Ethernet Communication
**Libraries Used**:
- `Crestron.SimplSharpPro.EthernetCommunication`
- `Crestron.SimplSharp.Net.Utilities.EthernetHelper`
**Key Files**:
- `/src/PepperDash.Core/Comm/GenericTcpIpClient.cs`
- `/src/PepperDash.Core/Comm/GenericTcpIpServer.cs`
- `/src/PepperDash.Core/Comm/GenericSecureTcpIpClient.cs`
- `/src/PepperDash.Core/Comm/GenericSshClient.cs`
- `/src/PepperDash.Core/Comm/GenericUdpServer.cs`
**Usage Pattern**:
```csharp
public class GenericTcpIpClient : Device, ISocketStatusWithStreamDebugging, IAutoReconnect
public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging, IAutoReconnect
```
## 7. Device Management Libraries
### 7.1 DeviceSupport
**Library**: `Crestron.SimplSharpPro.DeviceSupport`
**Usage**: Core device support infrastructure used throughout the framework
### 7.2 DM (DigitalMedia)
**Library**: `Crestron.SimplSharpPro.DM`
**Usage**: Digital media routing and switching support
**Evidence**: Found in routing configuration and DM output card references
## 8. User Interface Libraries
### 8.1 UI Components
**Library**: `Crestron.SimplSharpPro.UI`
**Usage**: User interface elements and touchpanel controls
### 8.2 Smart Objects
**Key Files**:
- `/src/PepperDash.Essentials.Core/SmartObjects/SmartObjectDynamicList.cs`
- `/src/PepperDash.Essentials.Core/SmartObjects/SubpageReferenceList/SubpageReferenceList.cs`
**Usage**: Advanced UI components with dynamic content
## 9. System Monitoring and Diagnostics
### 9.1 Diagnostics
**Library**: `Crestron.SimplSharpPro.Diagnostics`
**Usage**: System health monitoring and performance tracking
### 9.2 System Information
**Key Files**:
- `/src/PepperDash.Essentials.Core/Monitoring/SystemMonitorController.cs`
**Usage**: Provides system status, Ethernet information, and program details
## 10. Integration Patterns
### 10.1 SIMPL Bridging
**Pattern**: Extensive use of `BasicTriList` for SIMPL integration
**Files**: Bridge classes throughout the framework implement `LinkToApi` methods:
```csharp
public abstract void LinkToApi(BasicTriList trilist, uint joinStart, string joinMapKey, EiscApiAdvanced bridge);
```
### 10.2 Device Factory Pattern
**Implementation**: Factory classes create hardware-specific implementations
**Example**: `CommFactory.cs` provides communication device creation
### 10.3 Extension Methods
**Pattern**: Extensive use of extension methods for Crestron classes
**Example**: `TriListExtensions.cs` adds 30+ extension methods to `BasicTriList`
## 11. Signal Processing
### 11.1 Signal Types
**Bool Signals**: Digital control and feedback
**UShort Signals**: Analog values and numeric data
**String Signals**: Text and configuration data
**Implementation**: Comprehensive signal handling in `TriListExtensions.cs`
## 12. Error Handling and Logging
**Pattern**: Consistent use of Crestron's Debug logging throughout
**Examples**:
```csharp
Debug.LogMessage(LogEventLevel.Information, "Device {0} is not a valid device", dc.PortDeviceKey);
Debug.LogMessage(LogEventLevel.Debug, "Error Waking Panel. Maybe testing with Xpanel?");
```
## 13. Threading and Synchronization
**Components**:
- CTimer for time-based operations
- Thread-safe collections and patterns
- Event-driven programming models
## Conclusion
The PepperDash Essentials framework demonstrates sophisticated integration with the Crestron ecosystem, leveraging:
- **Core Infrastructure**: CrestronControlSystem, Device base classes
- **Communication**: COM, IR, CEC, TCP/IP, SSH protocols
- **Hardware Abstraction**: Touchpanels, I/O devices, processors
- **User Interface**: Smart objects, signal processing, SIMPL bridging
- **System Services**: Monitoring, diagnostics, device management
This analysis shows that Essentials serves as a comprehensive middleware layer, abstracting Crestron hardware complexities while providing modern software development patterns and practices.
---
*Generated: [Current Date]*
*Framework Version: PepperDash Essentials (Based on codebase analysis)*

View File

@@ -52,6 +52,7 @@
"_appLogoPath": "docs/images/favicon-32x32.png",
"_appFaviconPath": "docs/images/favicon.ico",
"_disableToc": false,
"_enableNewTab": true,
"pdf": false
}
}

View File

@@ -4,23 +4,44 @@
[YouTube Video - Getting Started with PepperDash Essentials](https://youtu.be/FxEZtbpCwiQ)
***
## Download or clone
## Get a CPZ
You may clone Essentials at <https://github.com/PepperDash/Essentials.git>
### Prerequisites
* [VS Code](https://code.visualstudio.com/)
* [.NET 9 SDK](https://dotnet.microsoft.com/en-us/download)
* [Git](https://git-scm.com/)
> Note: Essentials 2.x.x uses .NET Framework 4.7.2 currently. The .NET 9 SDK will build the project with the appropriate references
### Build From Source
1. Clone the repo: `git clone https://github.com/PepperDash/Essentials.git`
2. Open the folder in VS Code
3. Build using the dotnet CLI: `dotnet build`
### Download the latest release
The latest release can be found on [Github](https://github.com/PepperDash/Essentials/releases/latest)
## How to Get Started
This section assumes knowledge of loading programs to and working with the file system on a Crestron processor.
2. Using an SFTP client or Crestron Toolbox, load the downloaded (or built) cpz to the processor in program slot 1
1. If using SFTP, connect via SSH and start the program by sending console command `progload -p:1`
3. On first boot, the Essentials Application will build the necessary configuration folder structure in the user/program1/ path.
4. The application has some example configuration files included. Copy `/Program01/Example Configuration/EssentialsSpaceHuddleRoom/configurationFile-HuddleSpace-2-Source.json` to the `/User/Program1/` folder.
6. Reset the program via console `progreset -p:1`. The program will load the example configuration file.
1. Using an SFTP client, load `PepperDashEssentials1.4.32.cpz` to the processor in program slot 1 and start the program by sending console command `progload -p:1`
1. On first boot, the Essentials Application will build the necessary configuration folder structure in the User/Program1/ path.
1. The application has some example configuration files included. Copy `/Program01/Example Configuration/EssentialsSpaceHuddleRoom/configurationFile-HuddleSpace-2-Source.json` to the `/User/Program1/` folder.
1. Copy the SGD files from `/Program01/SGD` to `/User/Program1/sgd`
1. Reset the program via console `progreset -p:1`. The program will load the example configuration file.
1. Via console, you can run the `devlist:1` command to get some insight into what has been loaded from the configuration file into the system . This will print the basic device information in the form of ["key"] "Name". The "key" value is what we can use to interact with each device uniquely.
1. Run the command `devprops:1 display-1`. This will print the real-time property values of the device with key "display-1".
1. Run the command `devmethods:1 display-1`. This will print the public methods available for the device with key "display-1".
1. Run the command `devjson:1 {"deviceKey":"display-1","methodName":"PowerOn", "params": []}`. This will call the method PowerOn() on the device with key "display-1".
1. Run the provided example XPanel SmartGraphics project and connect to your processor at the appropriate IPID.
Once Essentials is running with a valid configuration, the following console commands can be used to see what's going on:
Next: [Standalone use](~/docs/Standalone-Use.md)
* ```devlist:1```
* Print the list of devices in [{key}] {name} format
* The key of a device can be used with the rest of the commands to get more information
* `devprops:1 {deviceKey}`
* Print the real-time property values of the device with key "display-1".
* `devmethods:1 display-1`
* Print the public methods available for the device with key "display-1".
* `devjson:1 {"deviceKey":"display-1","methodName":"PowerOn", "params": []}`
* Call the method `PowerOn()` on the device with key "display-1".
Next: [Standalone use](~/docs/usage/Standalone-Use.md)

View File

@@ -39,7 +39,7 @@ Thanks!
## Collaboration
Essentials is an open-source project and we encourage collaboration on this community project. For features that may not be useful to the greater community, or for just-plain learning, we want to remind developers to try writing plugins for Essentials. More information can be found here: [Plugins](~/docs/Plugins.md)
Essentials is an open-source project and we encourage collaboration on this community project. For features that may not be useful to the greater community, or for just-plain learning, we want to remind developers to try writing plugins for Essentials. More information can be found here: [Plugins](~/docs/technical-docs/Plugins.md)
### Open-source-collaborative workflow

View File

@@ -1,6 +1,6 @@
# Deprecated
**Note : this entry is out of date - please see [Plugins](~/docs/Plugins.md)**
**Note : this entry is out of date - please see [Plugins](~/docs/technical-docs/Plugins.md)**
## What are Essentials Plugins?

View File

@@ -358,7 +358,7 @@ Example device config: <https://github.com/PepperDash/Essentials/blob/main/Peppe
1. A bridge need not only bridge between applications on the same processor. A bridge may bridge to an application on a completely separate processor; simply define the ip address in the Bridge control properties accordingly.
1. For devices included in Essentials, you will be able to find defined join maps below. If you are building your own plugins, you will need to build the join map yourself. It would be beneficial to review the wiki entry on the [Feedback Class](~/docs/Feedback-Classes.md) for this.
1. For devices included in Essentials, you will be able to find defined join maps below. If you are building your own plugins, you will need to build the join map yourself. It would be beneficial to review the wiki entry on the [Feedback Class](~/docs/technical-docs/Feedback-Classes.md) for this.
1. When building plugins, we highly recommend reusing JoinMaps, as this will make code more easily interchangeable. For example; if you were to build a display plugin, we'd recommend you use/extend the existing DisplayControllerJoinMap. This way, you can swap plugins without needing any change on the Simpl Windows side. This is extremely powerful when maintaining Simpl Windows code bases for large deployments that may utilize differing equipment per room. If you can build a Simpl Windows program that interacts with established join maps, you can swap out the device via config without any change needed to Simpl Windows.
@@ -474,4 +474,4 @@ Example device config: <https://github.com/PepperDash/Essentials/blob/main/Peppe
We've provided an [example program](https://github.com/PepperDash/EssentialsSIMPLWindowsBridgeExample) for SIMPL Windows that works with the provided example Essentials configuration file [SIMPLBridgeExample_configurationFile.json](https://github.com/PepperDash/Essentials/blob/main/PepperDashEssentials/Example%20Configuration/SIMPLBridging/SIMPLBridgeExample_configurationFile.json). Load Essentials and the example SIMPL program to two slots on the same processor and you can get a better idea of how to take advantage of SIMPL Windows bridging.
Next: [Essentials architecture](~/docs/Arch-summary.md)
Next: [Essentials architecture](~/docs/technical-docs/Arch-summary.md)

View File

@@ -1,6 +1,6 @@
# SIMPL Windows Bridging
**Note : this entry is out of date - please see [SIMPL Windows Bridging - Updated](~/docs/SIMPL-Bridging-Updated.md)**
**Note : this entry is out of date - please see [SIMPL Windows Bridging - Updated](~/docs/usage/SIMPL-Bridging-Updated.md)**
Essentials allows for devices defined within the SIMPL# Pro application to be bridged to a SIMPL Windows application over Ethernet Intersystem Communication (EISC). This allows a SIMPL Windows program to take advantage of some of the features of the SIMPL# Pro environment, without requiring the entire application to be written in C#.
@@ -356,7 +356,7 @@ Example device config: <https://github.com/PepperDash/Essentials/blob/main/Peppe
1. A bridge need not only bridge between applications on the same processor. A bridge may bridge to an application on a completely separate processor; simply define the ip address in the Bridge control properties accordingly.
1. For devices included in Essentials, you will be able to find defined join maps below. If you are building your own plugins, you will need to build the join map yourself. It would be beneficial to review the wiki entry on the [Feedback Class](~/docs/Feedback-Classes.md) for this.
1. For devices included in Essentials, you will be able to find defined join maps below. If you are building your own plugins, you will need to build the join map yourself. It would be beneficial to review the wiki entry on the [Feedback Class](~/docs/technical-docs/Feedback-Classes.md) for this.
1. When building plugins, we highly recommend reusing JoinMaps, as this will make code more easily interchangeable. For example; if you were to build a display plugin, we'd recommend you use/extend the existing DisplayControllerJoinMap. This way, you can swap plugins without needing any change on the Simpl Windows side. This is extremely powerful when maintaining Simpl Windows code bases for large deployments that may utilize differing equipment per room. If you can build a Simpl Windows program that interacts with established join maps, you can swap out the device via config without any change needed to Simpl Windows.
@@ -472,4 +472,4 @@ Example device config: <https://github.com/PepperDash/Essentials/blob/main/Peppe
We've provided an [example program](https://github.com/PepperDash/EssentialsSIMPLWindowsBridgeExample) for SIMPL Windows that works with the provided example Essentials configuration file [SIMPLBridgeExample_configurationFile.json](https://github.com/PepperDash/Essentials/blob/main/PepperDashEssentials/Example%20Configuration/SIMPLBridging/SIMPLBridgeExample_configurationFile.json). Load Essentials and the example SIMPL program to two slots on the same processor and you can get a better idea of how to take advantage of SIMPL Windows bridging.
Next: [Essentials architecture](~/docs/Arch-summary.md)
Next: [Essentials architecture](~/docs/technical-docs/Arch-summary.md)

View File

@@ -0,0 +1,148 @@
# How to Add Documentation to Essentials
This guide explains how to add new documentation articles to the Essentials docFx site.
## Overview
The Essentials documentation uses [docFx](https://dotnet.github.io/docfx/) to generate a static documentation website. Documentation files are organized in a hierarchical structure with a table of contents (TOC) file that defines the site navigation. Documentation should be organized and written to fit into the [Diátaxis](https://diataxis.fr/start-here/) conceptual framework.
## Documentation Structure
Documentation files are located in `/docs/docs/` and organized into the following subdirectories:
- **how-to/** - Step-by-step guides and tutorials
- **usage/** - Usage documentation for SIMPL bridging, standalone use, and hardware integration
- **technical-docs/** - Technical documentation including architecture, plugins, and API references
- **images/** - Image assets used in documentation
## Adding a New Document
### Step 1: Create Your Markdown File
1. Determine which category your document belongs to (how-to, usage, or technical-docs)
2. Create a new `.md` file in the appropriate subdirectory
3. Use a descriptive filename with hyphens (e.g., `my-new-feature.md`)
**Example:**
```bash
# For a how-to guide
touch /docs/docs/how-to/configure-audio-settings.md
# For usage documentation
touch /docs/docs/usage/video-switcher-control.md
# For technical documentation
touch /docs/docs/technical-docs/custom-device-plugin.md
```
### Step 2: Write Your Content
Start your markdown file with a level 1 heading (`#`) that serves as the page title:
```markdown
# Your Document Title
Brief introduction to the topic.
## Section Heading
Content goes here...
### Subsection
More detailed content...
```
**Markdown Features:**
- Use standard markdown syntax
- Include code blocks with language specifiers (```csharp, ```json, etc.)
- Add images: `![Alt text](../images/your-image.png)`
- Link to other docs: `[Link text](../usage/related-doc.md)`
### Step 3: Add to Table of Contents
Edit `/docs/docs/toc.yml` to add your new document to the navigation:
```yaml
- name: How-to's
items:
- href: how-to/how-to-add-docs.md
- href: how-to/your-new-doc.md # Add your document here
```
**TOC Structure:**
- `name:` - Display name in the navigation menu
- `href:` - Relative path to the markdown file
- `items:` - Nested items for hierarchical navigation
**Example with nested items:**
```yaml
- name: Usage
items:
- name: SIMPL Bridging
href: usage/SIMPL-Bridging-Updated.md
items:
- name: Your Sub-Topic
href: usage/your-sub-topic.md
```
### Step 4: Test Locally
Build and preview the docFx site locally to verify your changes:
```bash
# Navigate to the docs directory
cd docs
# Build the documentation
docfx build
# Serve the site locally
docfx serve _site
```
Then open your browser to `http://localhost:8080` to view the site.
## Best Practices
### File Naming
- Use lowercase with hyphens: `my-document-name.md`
- Be descriptive but concise
- Avoid special characters
### Content Guidelines
- Start with a clear introduction
- Use hierarchical headings (H1 → H2 → H3)
- Include code examples where appropriate
- Add images to illustrate complex concepts
- Link to related documentation
### TOC Organization
- Group related documents under the same parent
- Order items logically (basic → advanced)
- Keep the TOC hierarchy shallow (2-3 levels max)
- Use clear, descriptive names for navigation items
## Common Issues
### Document Not Appearing
- Verify the file path in `toc.yml` is correct and uses forward slashes
- Ensure the markdown file exists in the specified location
- Check for YAML syntax errors in `toc.yml`
### Images Not Loading
- Verify image path is relative to the markdown file location
- Use `../images/` for files in the images directory
- Ensure image files are committed to the repository
### Broken Links
- Use relative paths for internal links
- Test all links after building the site
- Use `.md` extension when linking to other documentation files
## Additional Resources
- [docFx Documentation](https://dotnet.github.io/docfx/)
- [Markdown Guide](https://www.markdownguide.org/)
- [YAML Syntax](https://yaml.org/spec/1.2/spec.html)
- [Diátaxis](https://diataxis.fr/start-here/)

View File

@@ -26,7 +26,7 @@ Types of things in `DeviceManager`:
A Device doesn't always represent a physical piece of hardware, but rather a logical construct that "does something" and is used by one or more other devices in the running program. For example, we create a room device, and its corresponding Fusion device, and that room has a Cisco codec device, with an attached SSh client device. All of these lie in a flat collection in the `DeviceManager`.
> The `DeviceManager` is nothing more than a modified collection of things, and technically those things don't have to be Devices, but must at least implement the `IKeyed` (`PepperDash.Core.IKeyed`) interface (simply so items can be looked up by their key.) Items in the `DeviceManager` that are Devices are run through additional steps of [activation](~/docs/Arch-activate.md#2-pre-activation) at startup. This collection of devices is all interrelated by their string keys.
> The `DeviceManager` is nothing more than a modified collection of things, and technically those things don't have to be Devices, but must at least implement the `IKeyed` (`PepperDash.Core.IKeyed`) interface (simply so items can be looked up by their key.) Items in the `DeviceManager` that are Devices are run through additional steps of [activation](~/docs/technical-docs/Arch-activate.md#2-pre-activation) at startup. This collection of devices is all interrelated by their string keys.
In this flat design, we spin up devices, and then introduce them to their "coworkers and bosses" - the other devices and logical units that they will interact with - and get them all operating together to form a running unit. For example: A room configuration will contain a "VideoCodecKey" property and a "DefaultDisplayKey" property. The `DeviceManager` provides the room with the codec or displays having the appropriate keys. What the room does with those is dependent on its coding.
@@ -38,4 +38,4 @@ This flat structure ensures that every device in a system exists in one place an
![Architecture overview](~/docs/images/arch-overview.png)
Next: [Configurable lifecycle](~/docs/Arch-lifecycle.md)
Next: [Configurable lifecycle](~/docs/technical-docs/Arch-lifecycle.md)

View File

@@ -105,7 +105,7 @@ Each of the three activation phases operates in a try/catch block for each devic
In any real-world system, devices and business logic need to talk to each other, otherwise, what's the point of all this coding? When creating your classes and configuration, it is best practice to _try_ not to "plug" one device into another during construction or activation. For example your touchpanel controller class has a `Display1` property that holds the display-1 object. Rather, it may be better to refer to the device as it is stored in the `DeviceManager` when it's needed using the static `DeviceManager.GetDeviceForKey(key)` method to get a reference to the device, which can be cast using various interfaces/class types, and then interacted with. This prevents objects from being referenced in places where the developer may later forget to dereference them, causing memory leak. This will become more important as Essentials becomes more able to be reconfigured at runtime.
As an example, [connection-based routing](~/docs/Connection-based-routing.md#essentials-connection-based-routing) uses these methods. When a route is requested, the collection of tielines and devices is searched for the devices and paths necessary to complete a route, but there are no devices or tie lines that are object-referenced in running code. It can all be torn down and reconfigured without any memory-management dereferencing, setting things to null.
As an example, [connection-based routing](~/docs/technical-docs/Connection-based-routing.md#essentials-connection-based-routing) uses these methods. When a route is requested, the collection of tielines and devices is searched for the devices and paths necessary to complete a route, but there are no devices or tie lines that are object-referenced in running code. It can all be torn down and reconfigured without any memory-management dereferencing, setting things to null.
## Device Initialization
@@ -155,4 +155,4 @@ Robust C#-based system code should not depend on "order" or "time" to get runnin
When designing new Device-based classes, be it rooms, devices, port controllers, bridges, make them as independent as possible. They could exist alone in a program with no required partner objects, and just quietly exist without failing. We want the system to be fast and flexible, and keeping the interdependence between objects at a minimum improves this flexibility into the future.
Next: [More architecture](~/docs/Arch-topics.md)
Next: [More architecture](~/docs/technical-docs/Arch-topics.md)

View File

@@ -2,8 +2,8 @@
The diagram below describes how Essentials gets a program up and running.
(The various activation phases are covered in more detail on the [next page](~/docs/Arch-activate.md))
(The various activation phases are covered in more detail on the [next page](~/docs/technical-docs/Arch-activate.md))
![Lifecycle](~/docs/images/lifecycle.png)
Next: [Activation phases](~/docs/Arch-activate.md)
Next: [Activation phases](~/docs/technical-docs/Arch-activate.md)

View File

@@ -16,4 +16,4 @@ The diagram below shows the reference dependencies that exist between the differ
![Architecture drawing](~/docs/images/arch-high-level.png)
Next: [Architecture](~/docs/Arch-1.md)
Next: [Architecture](~/docs/technical-docs/Arch-1.md)

View File

@@ -1,48 +1,52 @@
- name: Get Started With Essentials
- href: ../index.md
- href: Get-started.md
- href: Get-started.md
- name: How-to's
items:
- name: How to add an article or doc page
href: how-to/how-to-add-docs.md
- name: Usage
items:
- href: Standalone-Use.md
- href: SIMPL-Bridging-Updated.md
- href: usage/Standalone-Use.md
- href: usage/SIMPL-Bridging-Updated.md
items:
- name: Join Maps
href: JoinMaps.md
href: usage/JoinMaps.md
- name: Bridging to Hardware Resources
href: Bridging-To-Hardware-Resources.md
href: usage/Bridging-To-Hardware-Resources.md
items:
- name: GenericComm Bridging
href: GenericComm.md
href: usage/GenericComm.md
- name: RelayOutput Bridging
href: RelayOutput.md
href: usage/RelayOutput.md
- name: Digital Input Bridging
href: DigitalInput.md
href: usage/DigitalInput.md
- name: IR Driver Bridging
href: IR-Driver-Bridging.md
href: usage/IR-Driver-Bridging.md
- name: Technical documentation
items:
- href: Arch-summary.md
- href: technical-docs/Arch-summary.md
- name: Devices and DeviceManager
href: Arch-1.md
href: technical-docs/Arch-1.md
- name: Configurable lifecycle
href: Arch-lifecycle.md
href: technical-docs/Arch-lifecycle.md
- name: Activation phases
href: Arch-activate.md
href: technical-docs/Arch-activate.md
- name: More
href: Arch-topics.md
href: technical-docs/Arch-topics.md
- name: Plugins
href: Plugins.md
href: technical-docs/Plugins.md
- name: Communication Basics
href: Communication-Basics.md
href: technical-docs/Communication-Basics.md
- name: Debugging
href: Debugging.md
href: technical-docs/Debugging.md
- name: Feedback Classes
href: Feedback-Classes.md
href: technical-docs/Feedback-Classes.md
- name: Connection Based Routing
href: Connection-Based-Routing.md
href: technical-docs/Connection-Based-Routing.md
- name: Configuration Structure
href: ConfigurationStructure.md
href: technical-docs/ConfigurationStructure.md
- name: Supported Devices
href: Supported-Devices.md
href: technical-docs/Supported-Devices.md
- name: Glossary of Terms
href: Glossary-of-Terms.md
href: technical-docs/Glossary-of-Terms.md

View File

@@ -6,10 +6,10 @@ One of the most powerful features of Essentials is the ability to bridge SIMPL t
Follow the links below for examples of bridging to hardware and network resources.
**[GenericComm Bridging](~/docs/GenericComm.md)**
**[GenericComm Bridging](~/docs/usage/GenericComm.md)**
**[RelayOutput Bridging](~/docs/RelayOutput.md)**
**[RelayOutput Bridging](~/docs/usage/RelayOutput.md)**
**[Digital Input Bridging](~/docs/DigitalInput.md)**
**[Digital Input Bridging](~/docs/usage/DigitalInput.md)**
**[Card Frame Bridging](~/docs/CardFrame.md)**

View File

@@ -286,7 +286,7 @@ Example device config: <https://github.com/PepperDash/Essentials/blob/main/Peppe
3. A bridge need not only bridge between applications on the same processor. A bridge may bridge to an application on a completely separate processor; simply define the ip address in the Bridge control properties accordingly.
4. For devices included in Essentials, you will be able to find defined join maps below. If you are building your own plugins, you will need to build the join map yourself. It would be beneficial to review the wiki entry on the [Feedback Class](~/docs/Feedback-Classes.md) for this.
4. For devices included in Essentials, you will be able to find defined join maps below. If you are building your own plugins, you will need to build the join map yourself. It would be beneficial to review the wiki entry on the [Feedback Class](~/docs/technical-docs/Feedback-Classes.md) for this.
5. When building plugins, we highly recommend reusing JoinMaps, as this will make code more easily interchangeable. For example; if you were to build a display plugin, we'd recommend you use/extend the existing `DisplayControllerJoinMap`. This way, you can swap plugins without needing any change on the SIMPL Windows side. This is extremely powerful when maintaining SIMPL Windows code bases for large deployments that may utilize differing equipment per room. If you can build a SIMPL Windows program that interacts with established join maps, you can swap out the device via config without any change needed to SIMPL Windows.
@@ -302,7 +302,7 @@ Example device config: <https://github.com/PepperDash/Essentials/blob/main/Peppe
## Join Map Documentation
[Join Map Documentation](~/docs/JoinMaps.md)
[Join Map Documentation](~/docs/usage/JoinMaps.md)
## Device Type Join Maps
@@ -408,4 +408,4 @@ Please note that these joinmaps _may_ be using a deprecated implementation. The
We've provided an [example program](https://github.com/PepperDash/EssentialsSIMPLWindowsBridgeExample) for SIMPL Windows that works with the provided example Essentials configuration file [SIMPLBridgeExample_configurationFile.json](https://github.com/PepperDash/Essentials/blob/main/PepperDashEssentials/Example%20Configuration/SIMPLBridging/SIMPLBridgeExample_configurationFile.json). Load Essentials and the example SIMPL program to two slots on the same processor and you can get a better idea of how to take advantage of SIMPL Windows bridging.
Next: [Essentials architecture](~/docs/Arch-summary.md)
Next: [Essentials architecture](~/docs/technical-docs/Arch-summary.md)

View File

@@ -8,7 +8,7 @@ By defining devices and a room in a JSON configuration file, Essentials can cont
### Devices
Essentials supports device plugins for communicating with various devices using both standard Crestron CIP communications, Cresnet, SSH, or other TCP/IP-based communication methods. See [the Plugins section](~/docs/Plugins.md) for more details
Essentials supports device plugins for communicating with various devices using both standard Crestron CIP communications, Cresnet, SSH, or other TCP/IP-based communication methods. See [the Plugins section](~/docs/technical-docs/Plugins.md) for more details
### Rooms
@@ -16,4 +16,4 @@ In order to tie together equipment into a unit that comprises what devices are u
See Also: [[Supported Devices|Supported-Devices]]
Next: [Simpl Windows bridging](~/docs/SIMPL-Bridging-Updated.md)
Next: [Simpl Windows bridging](~/docs/usage/SIMPL-Bridging-Updated.md)

View File

@@ -8,12 +8,12 @@ Essentials is a collection of C# libraries that can be used in many ways. It is
## Get started
- [Download essentials build or clone repo](~/docs/Get-started.md)
- [How to get started](~/docs/Get-started.md)
- [Download an Essentials build or clone the repo](~/docs/Get-started.md)
- [Get started](~/docs/Get-started.md)
- [YouTube Video Series Playlist](https://youtube.com/playlist?list=PLKOoNNwgPFZdV5wDEBDZxTHu1KROspaBu)
- [Discord Server](https://discord.gg/6Vh3ssDdPs)
Or use the links to the right to navigate our documentation.
Or use the links to the left to navigate our documentation.
---
@@ -25,21 +25,12 @@ Or use the links to the right to navigate our documentation.
- Shared resources made easily available
- More flexibility with less code
- Configurable using simple JSON files
- Is awesome
---
## Comment
The Essentials wiki is clearly in-progress right now. Take a look at the links to the right. We are actively working on this documentation, so please be patient with us. If you have any comments on or suggestions for the documentation, please file an issue here, with as much detail as you can provide: <https://github.com/PepperDash/Essentials/issues>
Thanks!
---
## Collaboration
Essentials is an open-source project and we encourage collaboration on this community project. For features that may not be useful to the greater community, or for just-plain learning, we want to remind developers to try writing plugins for Essentials. More information can be found here: [Plugins](~/docs/Plugins.md)
Essentials is an open-source project and we encourage collaboration on this community project. For features that may not be useful to the greater community, or for just-plain learning, we want to remind developers to try writing plugins for Essentials. More information can be found here: [Plugins](~/docs/technical-docs/Plugins.md)
### Open-source-collaborative workflow
@@ -52,7 +43,7 @@ The `main` branch always contain the latest stable version. The `development` br
- Example: `feature/add-awesomeness` or `hotfix/really-big-oops`
- When working on a new feature or bugfix, branch from the `development` branch. When working on a hotfix, branch from `main`.
3. Make commits as necessary (often is better). And use concise, descriptive language, leveraging issue notation and/or [Closing Keywords](https://help.github.com/articles/closing-issues-using-keywords) to ensure any issues addressed by your work are referenced accordingly.
4. When the scope of the work for your branch is complete, make sure to rebase your branch in case further progress has been made since the repo was forked
4. When the scope of the work for your branch is complete, make sure to update your branch in case further progress has been made since the repo was forked
5. Create a Pull Request to pull your branch into the appropriate branch in the main repository.
6. Your Pull Request will be reviewed by our team and evaluated for inclusion into the main repository.

View File

@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<Version>2.4.0-local</Version>
<Version>3.0.0-local</Version>
<InformationalVersion>$(Version)</InformationalVersion>
<Authors>PepperDash Technology</Authors>
<Company>PepperDash Technology</Company>

View File

@@ -1,14 +1,14 @@
<Project>
<ItemGroup>
<None Include="$(TargetDir)$(TargetName).$(Version).$(TargetFramework).clz" Condition="$(ProjectType) == 'Library'">
<None Include="$(TargetDir)$(TargetName).$(Version).$(TargetFramework).clz" Condition="$(ProjectType) == 'Library' And '$(TargetFramework)' != '' And '$(TargetName)' != '' And '$(TargetDir)' != ''">
<Pack>true</Pack>
<PackagePath>build;</PackagePath>
</None>
<None Include="$(TargetDir)$(TargetName).$(Version).$(TargetFramework).cpz" Condition="$(ProjectType) == 'Program'">
<None Include="$(TargetDir)$(TargetName).$(Version).$(TargetFramework).cpz" Condition="$(ProjectType) == 'Program' And '$(TargetFramework)' != '' And '$(TargetName)' != '' And '$(TargetDir)' != '' And ( '$(TargetFramework)' != 'net6.0' ) And ( '$(TargetFramework)' != 'net8.0' )">
<Pack>true</Pack>
<PackagePath>build;</PackagePath>
</None>
<None Include="$(TargetDir)$(TargetName).$(Version).$(TargetFramework).cplz" Condition="$(ProjectType) == 'ProgramLibrary'">
<None Include="$(TargetDir)$(TargetName).$(Version).$(TargetFramework).cplz" Condition="$(ProjectType) == 'ProgramLibrary' And '$(TargetFramework)' != '' And '$(TargetName)' != '' And '$(TargetDir)' != ''">
<Pack>true</Pack>
<PackagePath>build;</PackagePath>
</None>
@@ -23,23 +23,32 @@
<FileName>$(TargetDir)$(TargetName).$(Version).$(TargetFramework).cpz</FileName>
</PropertyGroup>
<Target Name="DeleteCLZ" BeforeTargets="PreBuildEvent" Condition="$(ProjectType) == 'Library' And $(TargetDir) != '' And Exists($(FileName))">
<Delete Files="$(TargetDir)$(TargetName).$(Version).$(TargetFramework).clz">
<Target Name="DeleteCLZ" BeforeTargets="CoreBuild" Condition="$(ProjectType) == 'Library' And $(TargetDir) != ''">
<ItemGroup>
<OldCLZFiles Include="$(TargetDir)$(TargetName).*.$(TargetFramework).clz" />
</ItemGroup>
<Delete Files="@(OldCLZFiles)" Condition="@(OldCLZFiles) != ''">
<Output TaskParameter="DeletedFiles" ItemName="DeletedList"/>
</Delete>
<Message Text="Deleted files: '@(DeletedList)'" />
<Message Text="Deleted old CLZ files: '@(DeletedList)'" Condition="@(DeletedList) != ''" />
</Target>
<Target Name="DeleteCPZ" BeforeTargets="PreBuildEvent" Condition="$(ProjectType) == 'Program' And $(TargetDir) != '' And Exists($(FileName))">
<Delete Files="$(TargetDir)$(TargetName).$(Version).$(TargetFramework).cpz">
<Target Name="DeleteCPZ" BeforeTargets="CoreBuild" Condition="$(ProjectType) == 'Program' And $(TargetDir) != ''">
<ItemGroup>
<OldCPZFiles Include="$(TargetDir)$(TargetName).*.$(TargetFramework).cpz" />
</ItemGroup>
<Delete Files="@(OldCPZFiles)" Condition="@(OldCPZFiles) != ''">
<Output TaskParameter="DeletedFiles" ItemName="DeletedList"/>
</Delete>
<Message Text="Deleted files: '@(DeletedList)'" />
<Message Text="Deleted old CPZ files: '@(DeletedList)'" Condition="@(DeletedList) != ''" />
</Target>
<Target Name="DeleteCPLZ" BeforeTargets="PreBuildEvent" Condition="$(ProjectType) == 'ProgramLibrary' And $(TargetDir) != '' And Exists($(FileName))">
<Delete Files="$(TargetDir)$(TargetName).$(Version).$(TargetFramework).cplz">
<Target Name="DeleteCPLZ" BeforeTargets="CoreBuild" Condition="$(ProjectType) == 'ProgramLibrary' And $(TargetDir) != ''">
<ItemGroup>
<OldCPLZFiles Include="$(TargetDir)$(TargetName).*.$(TargetFramework).cplz" />
</ItemGroup>
<Delete Files="@(OldCPLZFiles)" Condition="@(OldCPLZFiles) != ''">
<Output TaskParameter="DeletedFiles" ItemName="DeletedList"/>
</Delete>
<Message Text="Deleted files: '@(DeletedList)'" />
<Message Text="Deleted old CPLZ files: '@(DeletedList)'" Condition="@(DeletedList) != ''" />
</Target>
<Target Name="CreateCPLZ" AfterTargets="Build" Condition="$(ProjectType) == 'ProgramLibrary' And $(TargetDir) != ''" DependsOnTargets="DeleteCPLZ">

View File

@@ -84,10 +84,9 @@ namespace PepperDash.Core;
port.TextReceived += Port_TextReceivedStringDelimiter;
}
/// <summary>
/// Disconnects this gather from the Port's TextReceived event. This will not fire LineReceived
/// after the this call.
/// </summary>
/// <summary>
/// Stop method
/// </summary>
public void Stop()
{
Port.TextReceived -= Port_TextReceived;

View File

@@ -2,7 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
using System.Timers;
using PepperDash.Core;
namespace PepperDash.Core;
@@ -20,7 +20,7 @@ public class CommunicationStreamDebugging
/// <summary>
/// Timer to disable automatically if not manually disabled
/// </summary>
private CTimer DebugExpiryPeriod;
private Timer DebugExpiryPeriod;
/// <summary>
/// The current debug setting
@@ -93,7 +93,9 @@ public class CommunicationStreamDebugging
StopDebugTimer();
DebugExpiryPeriod = new CTimer((o) => DisableDebugging(), _DebugTimeoutInMs);
DebugExpiryPeriod = new Timer(_DebugTimeoutInMs) { AutoReset = false };
DebugExpiryPeriod.Elapsed += (s, e) => DisableDebugging();
DebugExpiryPeriod.Start();
if ((setting & eStreamDebuggingSetting.Rx) == eStreamDebuggingSetting.Rx)
RxStreamDebuggingIsEnabled = true;
@@ -155,22 +157,3 @@ public enum eStreamDebuggingSetting
Both = Rx | Tx
}
/// <summary>
/// The available settings for stream debugging response types
/// </summary>
[Flags]
public enum eStreamDebuggingDataTypeSettings
{
/// <summary>
/// Debug data in byte format
/// </summary>
Bytes = 0,
/// <summary>
/// Debug data in text format
/// </summary>
Text = 1,
/// <summary>
/// Debug data in both byte and text formats
/// </summary>
Both = Bytes | Text,
}

View File

@@ -1,7 +1,12 @@
using System;
extern alias NewtonsoftJson;
using System;
using Crestron.SimplSharp;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using JsonConverter = NewtonsoftJson::Newtonsoft.Json.JsonConverterAttribute;
using JsonIgnore = NewtonsoftJson::Newtonsoft.Json.JsonIgnoreAttribute;
using JsonProperty = NewtonsoftJson::Newtonsoft.Json.JsonPropertyAttribute;
using NullValueHandling = NewtonsoftJson::Newtonsoft.Json.NullValueHandling;
using StringEnumConverter = NewtonsoftJson::Newtonsoft.Json.Converters.StringEnumConverter;
namespace PepperDash.Core;

View File

@@ -27,25 +27,25 @@ public delegate void GenericSocketStatusChangeEventDelegate(ISocketStatus client
/// <summary>
/// EventArgs class for socket status changes
/// </summary>
public class GenericSocketStatusChageEventArgs : EventArgs
{
public class GenericSocketStatusChageEventArgs : EventArgs
{
/// <summary>
///
/// Gets or sets the Client
/// </summary>
public ISocketStatus Client { get; private set; }
public ISocketStatus Client { get; private set; }
/// <summary>
///
/// Constructor
/// </summary>
/// <param name="client"></param>
public GenericSocketStatusChageEventArgs(ISocketStatus client)
{
Client = client;
}
/// <summary>
/// S+ Constructor
/// </summary>
public GenericSocketStatusChageEventArgs() { }
public GenericSocketStatusChageEventArgs(ISocketStatus client)
{
Client = client;
}
/// <summary>
/// S+ Constructor
/// </summary>
public GenericSocketStatusChageEventArgs() { }
}
/// <summary>
@@ -60,22 +60,22 @@ public delegate void GenericTcpServerStateChangedEventDelegate(ServerState state
public class GenericTcpServerStateChangedEventArgs : EventArgs
{
/// <summary>
///
/// Gets or sets the State
/// </summary>
public ServerState State { get; private set; }
/// <summary>
///
/// Constructor
/// </summary>
/// <param name="state"></param>
public GenericTcpServerStateChangedEventArgs(ServerState state)
{
State = state;
}
/// <summary>
/// S+ Constructor
/// </summary>
public GenericTcpServerStateChangedEventArgs() { }
/// <summary>
/// S+ Constructor
/// </summary>
public GenericTcpServerStateChangedEventArgs() { }
}
/// <summary>
@@ -91,20 +91,22 @@ public delegate void GenericTcpServerSocketStatusChangeEventDelegate(object sock
public class GenericTcpServerSocketStatusChangeEventArgs : EventArgs
{
/// <summary>
///
/// Gets or sets the Socket
/// </summary>
public object Socket { get; private set; }
/// <summary>
///
/// Gets or sets the index of the client from which the status change was received
/// </summary>
public uint ReceivedFromClientIndex { get; private set; }
/// <summary>
///
/// Gets or sets the ClientStatus
/// </summary>
public SocketStatus ClientStatus { get; set; }
/// <summary>
///
/// Constructor
/// </summary>
/// <param name="socket"></param>
/// <param name="clientStatus"></param>
@@ -115,7 +117,7 @@ public class GenericTcpServerSocketStatusChangeEventArgs : EventArgs
}
/// <summary>
///
/// Constructor
/// </summary>
/// <param name="socket"></param>
/// <param name="clientIndex"></param>
@@ -126,10 +128,10 @@ public class GenericTcpServerSocketStatusChangeEventArgs : EventArgs
ReceivedFromClientIndex = clientIndex;
ClientStatus = clientStatus;
}
/// <summary>
/// S+ Constructor
/// </summary>
public GenericTcpServerSocketStatusChangeEventArgs() { }
/// <summary>
/// S+ Constructor
/// </summary>
public GenericTcpServerSocketStatusChangeEventArgs() { }
}
/// <summary>
@@ -138,28 +140,28 @@ public class GenericTcpServerSocketStatusChangeEventArgs : EventArgs
public class GenericTcpServerCommMethodReceiveTextArgs : EventArgs
{
/// <summary>
///
/// Gets or sets the index of the client from which the text was received
/// </summary>
public uint ReceivedFromClientIndex { get; private set; }
/// <summary>
///
/// Gets the index of the client from which the text was received as a ushort
/// </summary>
public ushort ReceivedFromClientIndexShort
{
get
{
return (ushort)ReceivedFromClientIndex;
}
}
public ushort ReceivedFromClientIndexShort
{
get
{
return (ushort)ReceivedFromClientIndex;
}
}
/// <summary>
///
/// Gets or sets the Text
/// </summary>
public string Text { get; private set; }
/// <summary>
///
/// Constructor
/// </summary>
/// <param name="text"></param>
public GenericTcpServerCommMethodReceiveTextArgs(string text)
@@ -168,7 +170,7 @@ public class GenericTcpServerCommMethodReceiveTextArgs : EventArgs
}
/// <summary>
///
/// Constructor
/// </summary>
/// <param name="text"></param>
/// <param name="clientIndex"></param>
@@ -177,10 +179,10 @@ public class GenericTcpServerCommMethodReceiveTextArgs : EventArgs
Text = text;
ReceivedFromClientIndex = clientIndex;
}
/// <summary>
/// S+ Constructor
/// </summary>
public GenericTcpServerCommMethodReceiveTextArgs() { }
/// <summary>
/// S+ Constructor
/// </summary>
public GenericTcpServerCommMethodReceiveTextArgs() { }
}
/// <summary>
@@ -189,22 +191,23 @@ public class GenericTcpServerCommMethodReceiveTextArgs : EventArgs
public class GenericTcpServerClientReadyForcommunicationsEventArgs : EventArgs
{
/// <summary>
///
/// Gets or sets IsReady
/// </summary>
public bool IsReady;
/// <summary>
///
/// Constructor
/// </summary>
/// <param name="isReady"></param>
public GenericTcpServerClientReadyForcommunicationsEventArgs(bool isReady)
{
IsReady = isReady;
}
/// <summary>
/// S+ Constructor
/// </summary>
public GenericTcpServerClientReadyForcommunicationsEventArgs() { }
/// <summary>
/// S+ Constructor
/// </summary>
public GenericTcpServerClientReadyForcommunicationsEventArgs() { }
}
/// <summary>
@@ -213,11 +216,12 @@ public class GenericTcpServerClientReadyForcommunicationsEventArgs : EventArgs
public class GenericUdpConnectedEventArgs : EventArgs
{
/// <summary>
///
/// Gets or sets the UConnected
/// </summary>
public ushort UConnected;
/// <summary>
///
/// Gets or sets the Connected status
/// </summary>
public bool Connected;
@@ -227,7 +231,7 @@ public class GenericUdpConnectedEventArgs : EventArgs
public GenericUdpConnectedEventArgs() { }
/// <summary>
///
/// Constructor
/// </summary>
/// <param name="uconnected"></param>
public GenericUdpConnectedEventArgs(ushort uconnected)
@@ -236,7 +240,7 @@ public class GenericUdpConnectedEventArgs : EventArgs
}
/// <summary>
///
/// Constructor
/// </summary>
/// <param name="connected"></param>
public GenericUdpConnectedEventArgs(bool connected)

View File

@@ -3,6 +3,8 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Crestron.SimplSharp;
using Crestron.SimplSharp.CrestronSockets;
using PepperDash.Core.Logging;
@@ -277,7 +279,7 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
/// <summary>
/// Queue lock
/// </summary>
CCriticalSection DequeueLock = new CCriticalSection();
private readonly object _dequeueLock = new();
/// <summary>
/// Receive Queue size. Defaults to 20. Will set to 20 if QueueSize property is less than 20. Use constructor or set queue size property before
@@ -357,7 +359,7 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
{
if (config == null)
{
Debug.Console(0, this, "Could not initialize client with key: {0}", Key);
this.LogWarning( "Could not initialize client with key: {0}", Key);
return;
}
try
@@ -397,7 +399,7 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
}
catch (Exception ex)
{
Debug.Console(0, this, "Exception initializing client with key: {0}\rException: {1}", Key, ex);
this.LogError("Exception initializing client with key: {0}\rException: {1}", Key, ex);
}
}
@@ -410,7 +412,7 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
{
if (programEventType == eProgramStatusEventType.Stopping || programEventType == eProgramStatusEventType.Paused)
{
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Program stopping. Closing _client connection");
this.LogInformation("Program stopping. Closing _client connection");
ProgramIsStopping = true;
Disconnect();
}
@@ -437,17 +439,17 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
public void Connect()
{
ConnectionCount++;
Debug.Console(2, this, "Attempting connect Count:{0}", ConnectionCount);
this.LogVerbose("Attempting connect Count:{0}", ConnectionCount);
if (IsConnected)
{
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Already connected. Ignoring.");
this.LogInformation("Already connected. Ignoring.");
return;
}
if (IsTryingToConnect)
{
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Already trying to connect. Ignoring.");
this.LogInformation("Already trying to connect. Ignoring.");
return;
}
try
@@ -460,17 +462,17 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
}
if (string.IsNullOrEmpty(Hostname))
{
Debug.Console(0, this, Debug.ErrorLogLevel.Warning, "DynamicTcpClient: No address set");
this.LogWarning("DynamicTcpClient: No address set");
return;
}
if (Port < 1 || Port > 65535)
{
Debug.Console(0, this, Debug.ErrorLogLevel.Warning, "DynamicTcpClient: Invalid port");
this.LogWarning("DynamicTcpClient: Invalid port");
return;
}
if (string.IsNullOrEmpty(SharedKey) && SharedKeyRequired)
{
Debug.Console(0, this, Debug.ErrorLogLevel.Warning, "DynamicTcpClient: No Shared Key set");
this.LogWarning("DynamicTcpClient: No Shared Key set");
return;
}
@@ -493,7 +495,7 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
ConnectFailTimer = new CTimer(o =>
{
Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Connect attempt has not finished after 30sec Count:{0}", ConnectionCount);
this.LogError("Connect attempt has not finished after 30sec Count:{0}", ConnectionCount);
if (IsTryingToConnect)
{
IsTryingToConnect = false;
@@ -507,10 +509,10 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
}
}, 30000);
Debug.Console(2, this, "Making Connection Count:{0}", ConnectionCount);
this.LogVerbose("Making Connection Count:{0}", ConnectionCount);
_client.ConnectToServerAsync(o =>
{
Debug.Console(2, this, "ConnectToServerAsync Count:{0} Ran!", ConnectionCount);
this.LogVerbose("ConnectToServerAsync Count:{0} Ran!", ConnectionCount);
if (ConnectFailTimer != null)
{
@@ -520,7 +522,7 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
if (o.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED)
{
Debug.Console(2, this, "_client connected to {0} on port {1}", o.AddressClientConnectedTo, o.LocalPortNumberOfClient);
this.LogVerbose("_client connected to {0} on port {1}", o.AddressClientConnectedTo, o.LocalPortNumberOfClient);
o.ReceiveDataAsync(Receive);
if (SharedKeyRequired)
@@ -529,7 +531,7 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
WaitForSharedKey = new CTimer(timer =>
{
Debug.Console(1, this, Debug.ErrorLogLevel.Warning, "Shared key exchange timer expired. IsReadyForCommunication={0}", IsReadyForCommunication);
this.LogWarning("Shared key exchange timer expired. IsReadyForCommunication={0}", IsReadyForCommunication);
// Debug.Console(1, this, "Connect attempt failed {0}", c.ClientStatus);
// This is the only case where we should call DisconectFromServer...Event handeler will trigger the cleanup
o.DisconnectFromServer();
@@ -549,14 +551,14 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
}
else
{
Debug.Console(1, this, "Connect attempt failed {0}", o.ClientStatus);
this.LogWarning("Connect attempt failed {0}", o.ClientStatus);
CheckClosedAndTryReconnect();
}
});
}
catch (Exception ex)
{
Debug.Console(0, this, Debug.ErrorLogLevel.Error, "_client connection exception: {0}", ex.Message);
this.LogError("_client connection exception: {0}", ex.Message);
IsTryingToConnect = false;
CheckClosedAndTryReconnect();
}
@@ -592,14 +594,14 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
{
if (_client == null) return;
Debug.Console(1, this, "Disconnecting client");
this.LogInformation("Disconnecting client");
if (IsConnected)
_client.DisconnectFromServer();
// close up client. ALWAYS use this when disconnecting.
IsTryingToConnect = false;
Debug.Console(2, this, "Disconnecting _client {0}", DisconnectCalledByUser ? ", Called by user" : "");
this.LogVerbose("Disconnecting _client {0}", DisconnectCalledByUser ? ", Called by user" : "");
_client.SocketStatusChange -= Client_SocketStatusChange;
_client.Dispose();
_client = null;
@@ -620,14 +622,14 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
{
if (_client != null)
{
Debug.Console(2, this, "Cleaning up remotely closed/failed connection.");
this.LogVerbose("Cleaning up remotely closed/failed connection.");
Disconnect();
}
if (!DisconnectCalledByUser && AutoReconnect)
{
var halfInterval = AutoReconnectIntervalMs / 2;
var rndTime = new Random().Next(-halfInterval, halfInterval) + AutoReconnectIntervalMs;
Debug.Console(2, this, "Attempting reconnect in {0} ms, randomized", rndTime);
this.LogVerbose("Attempting reconnect in {0} ms, randomized", rndTime);
if (RetryTimer != null)
{
RetryTimer.Stop();
@@ -654,13 +656,13 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
{
var bytes = client.IncomingDataBuffer.Take(numBytes).ToArray();
str = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length);
Debug.Console(2, this, "_client Received:\r--------\r{0}\r--------", str);
this.LogVerbose("_client Received:\r--------\r{0}\r--------", str);
if (!string.IsNullOrEmpty(checkHeartbeat(str)))
{
if (SharedKeyRequired && str == "SharedKey:")
{
Debug.Console(2, this, "Server asking for shared key, sending");
this.LogVerbose("Server asking for shared key, sending");
SendText(SharedKey + "\n");
}
else if (SharedKeyRequired && str == "Shared Key Match")
@@ -668,7 +670,7 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
StopWaitForSharedKeyTimer();
Debug.Console(2, this, "Shared key confirmed. Ready for communication");
this.LogVerbose("Shared key confirmed. Ready for communication");
OnClientReadyForcommunications(true); // Successful key exchange
}
else
@@ -688,7 +690,7 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
}
catch (Exception ex)
{
Debug.Console(1, this, "Error receiving data: {1}. Error: {0}", ex.Message, str);
this.LogError("Error receiving data: {1}. Error: {0}", str, ex.Message);
}
if (client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED)
client.ReceiveDataAsync(Receive);
@@ -696,9 +698,8 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
//Check to see if there is a subscription to the TextReceivedQueueInvoke event. If there is start the dequeue thread.
if (handler != null)
{
var gotLock = DequeueLock.TryEnter();
if (gotLock)
CrestronInvoke.BeginInvoke((o) => DequeueEvent());
if (Monitor.TryEnter(_dequeueLock))
Task.Run(() => DequeueEvent());
}
}
else //JAG added this as I believe the error return is 0 bytes like the server. See help when hover on ReceiveAsync
@@ -708,7 +709,7 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
}
/// <summary>
/// This method gets spooled up in its own thread an protected by a CCriticalSection to prevent multiple threads from running concurrently.
/// This method gets spooled up in its own thread an protected by a lock to prevent multiple threads from running concurrently.
/// It will dequeue items as they are enqueued automatically.
/// </summary>
void DequeueEvent()
@@ -728,13 +729,10 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
}
catch (Exception e)
{
this.LogException(e, "DequeueEvent error");
}
// Make sure to leave the CCritical section in case an exception above stops this thread, or we won't be able to restart it.
if (DequeueLock != null)
{
DequeueLock.Leave();
this.LogError(e, "DequeueEvent error: {0}", e.Message);
}
// Make sure to release the lock in case an exception above stops this thread, or we won't be able to restart it.
Monitor.Exit(_dequeueLock);
}
void HeartbeatStart()
@@ -759,13 +757,13 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
if (HeartbeatSendTimer != null)
{
Debug.Console(2, this, "Stoping Heartbeat Send");
this.LogVerbose("Stoping Heartbeat Send");
HeartbeatSendTimer.Stop();
HeartbeatSendTimer = null;
}
if (HeartbeatAckTimer != null)
{
Debug.Console(2, this, "Stoping Heartbeat Ack");
this.LogVerbose("Stoping Heartbeat Ack");
HeartbeatAckTimer.Stop();
HeartbeatAckTimer = null;
}
@@ -774,7 +772,7 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
void SendHeartbeat(object notused)
{
this.SendText(HeartbeatString);
Debug.Console(2, this, "Sending Heartbeat");
this.LogVerbose("Sending Heartbeat");
}
@@ -799,7 +797,7 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
{
HeartbeatAckTimer = new CTimer(HeartbeatAckTimerFail, null, (HeartbeatInterval * 2), (HeartbeatInterval * 2));
}
Debug.Console(2, this, "Heartbeat Received: {0}, from Server", HeartbeatString);
this.LogVerbose("Heartbeat Received: {0}, from Server", HeartbeatString);
return remainingText;
}
}
@@ -807,7 +805,7 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
}
catch (Exception ex)
{
Debug.Console(1, this, "Error checking heartbeat: {0}", ex.Message);
this.LogError(ex, "Error checking heartbeat: {0}", ex.Message);
}
return received;
}
@@ -821,7 +819,7 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
if (IsConnected)
{
Debug.Console(1, Debug.ErrorLogLevel.Warning, "Heartbeat not received from Server...DISCONNECTING BECAUSE HEARTBEAT REQUIRED IS TRUE");
this.LogWarning("Heartbeat not received from Server...DISCONNECTING BECAUSE HEARTBEAT REQUIRED IS TRUE");
SendText("Heartbeat not received by server, closing connection");
CheckClosedAndTryReconnect();
}
@@ -829,7 +827,7 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
}
catch (Exception ex)
{
ErrorLog.Error("Heartbeat timeout Error on _client: {0}, {1}", Key, ex);
this.LogError(ex, "Heartbeat timeout Error on _client: {0}, {1}", Key, ex.Message);
}
}
@@ -862,14 +860,14 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
// HOW IN THE HELL DO WE CATCH AN EXCEPTION IN SENDING?????
if (n <= 0)
{
Debug.Console(1, Debug.ErrorLogLevel.Warning, "[{0}] Sent zero bytes. Was there an error?", this.Key);
this.LogWarning("[{0}] Sent zero bytes. Was there an error?", this.Key);
}
});
}
}
catch (Exception ex)
{
Debug.Console(0, this, "Error sending text: {1}. Error: {0}", ex.Message, text);
this.LogError(ex, "Error sending text: {1}. Error: {0}", ex.Message, text);
}
}
}
@@ -888,7 +886,7 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
}
catch (Exception ex)
{
Debug.Console(0, this, "Error sending bytes. Error: {0}", ex.Message);
this.LogError(ex, "Error sending bytes. Error: {0}", ex.Message);
}
}
}
@@ -907,7 +905,7 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
}
try
{
Debug.Console(2, this, "Socket status change: {0} ({1})", client.ClientStatus, (ushort)(client.ClientStatus));
this.LogVerbose("Socket status change: {0} ({1})", client.ClientStatus, (ushort)(client.ClientStatus));
OnConnectionChange();
// The client could be null or disposed by this time...
@@ -920,7 +918,7 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
}
catch (Exception ex)
{
Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Error in socket status change callback. Error: {0}\r\r{1}", ex, ex.InnerException);
this.LogError(ex, "Error in socket status change callback. Error: {0}\r\r{1}", ex, ex.InnerException);
}
}

View File

@@ -15,6 +15,8 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Crestron.SimplSharp;
using Crestron.SimplSharp.CrestronSockets;
using PepperDash.Core.Logging;
@@ -80,7 +82,7 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
public string Hostname { get; set; }
/// <summary>
/// Port on server
/// The port number on which the server is listening.
/// </summary>
public int Port { get; set; }
@@ -113,7 +115,7 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
}
/// <summary>
/// SharedKey is sent for varification to the server. Shared key can be any text (255 char limit in SIMPL+ Module), but must match the Shared Key on the Server module
/// SharedKey is sent for verification to the server. Shared key can be any text (255 char limit in SIMPL+ Module), but must match the Shared Key on the Server module
/// </summary>
public string SharedKey { get; set; }
@@ -271,7 +273,7 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
/// <summary>
/// Queue lock
/// </summary>
CCriticalSection DequeueLock = new CCriticalSection();
private readonly object _dequeueLock = new();
/// <summary>
/// Receive Queue size. Defaults to 20. Will set to 20 if QueueSize property is less than 20. Use constructor or set queue size property before
@@ -336,8 +338,9 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
#region Methods
/// <summary>
/// Just to help S+ set the key
/// Initializes the client's key property, which is used to identify this client instance.
/// </summary>
/// <param name="key">The unique key that identifies this client instance.</param>
public void Initialize(string key)
{
Key = key;
@@ -346,7 +349,7 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
/// <summary>
/// Initialize called by the constructor that accepts a client config object. Can be called later to reset properties of client.
/// </summary>
/// <param name="clientConfigObject"></param>
/// <param name="clientConfigObject">The configuration object containing the client's settings.</param>
public void Initialize(TcpClientConfigObject clientConfigObject)
{
try
@@ -361,7 +364,7 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
SharedKey = clientConfigObject.SharedKey;
SharedKeyRequired = clientConfigObject.SharedKeyRequired;
HeartbeatEnabled = clientConfigObject.HeartbeatRequired;
HeartbeatRequiredIntervalInSeconds = clientConfigObject.HeartbeatRequiredIntervalInSeconds > 0 ?
HeartbeatRequiredIntervalInSeconds = clientConfigObject.HeartbeatRequiredIntervalInSeconds > 0 ?
clientConfigObject.HeartbeatRequiredIntervalInSeconds : (ushort)15;
HeartbeatString = string.IsNullOrEmpty(clientConfigObject.HeartbeatStringToMatch) ? "heartbeat" : clientConfigObject.HeartbeatStringToMatch;
Port = TcpSshProperties.Port;
@@ -387,7 +390,7 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
{
if (programEventType == eProgramStatusEventType.Stopping || programEventType == eProgramStatusEventType.Paused)
{
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Program stopping. Closing Client connection");
this.LogInformation("Program stopping. Closing Client connection");
ProgramIsStopping = true;
Disconnect();
}
@@ -400,17 +403,17 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
public void Connect()
{
ConnectionCount++;
Debug.Console(2, this, "Attempting connect Count:{0}", ConnectionCount);
this.LogVerbose("Attempting connect Count:{0}", ConnectionCount);
if (IsConnected)
{
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Already connected. Ignoring.");
this.LogInformation("Already connected. Ignoring.");
return;
}
if (IsTryingToConnect)
{
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Already trying to connect. Ignoring.");
this.LogInformation("Already trying to connect. Ignoring.");
return;
}
try
@@ -423,17 +426,17 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
}
if (string.IsNullOrEmpty(Hostname))
{
Debug.Console(0, this, Debug.ErrorLogLevel.Warning, "DynamicTcpClient: No address set");
this.LogWarning("DynamicTcpClient: No address set");
return;
}
if (Port < 1 || Port > 65535)
{
Debug.Console(0, this, Debug.ErrorLogLevel.Warning, "DynamicTcpClient: Invalid port");
this.LogWarning("DynamicTcpClient: Invalid port");
return;
}
if (string.IsNullOrEmpty(SharedKey) && SharedKeyRequired)
{
Debug.Console(0, this, Debug.ErrorLogLevel.Warning, "DynamicTcpClient: No Shared Key set");
this.LogWarning("DynamicTcpClient: No Shared Key set");
return;
}
@@ -456,7 +459,7 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
ConnectFailTimer = new CTimer(o =>
{
Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Connect attempt has not finished after 30sec Count:{0}", ConnectionCount);
this.LogError("Connect attempt has not finished after 30sec Count:{0}", ConnectionCount);
if (IsTryingToConnect)
{
IsTryingToConnect = false;
@@ -470,10 +473,10 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
}
}, 30000);
Debug.Console(2, this, "Making Connection Count:{0}", ConnectionCount);
this.LogVerbose("Making Connection Count:{0}", ConnectionCount);
Client.ConnectToServerAsync(o =>
{
Debug.Console(2, this, "ConnectToServerAsync Count:{0} Ran!", ConnectionCount);
this.LogVerbose("ConnectToServerAsync Count:{0} Ran!", ConnectionCount);
if (ConnectFailTimer != null)
{
@@ -483,7 +486,7 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
if (o.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED)
{
Debug.Console(2, this, "Client connected to {0} on port {1}", o.AddressClientConnectedTo, o.LocalPortNumberOfClient);
this.LogVerbose("Client connected to {0} on port {1}", o.AddressClientConnectedTo, o.LocalPortNumberOfClient);
o.ReceiveDataAsync(Receive);
if (SharedKeyRequired)
@@ -492,7 +495,7 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
WaitForSharedKey = new CTimer(timer =>
{
Debug.Console(1, this, Debug.ErrorLogLevel.Warning, "Shared key exchange timer expired. IsReadyForCommunication={0}", IsReadyForCommunication);
this.LogWarning("Shared key exchange timer expired. IsReadyForCommunication={0}", IsReadyForCommunication);
// Debug.Console(1, this, "Connect attempt failed {0}", c.ClientStatus);
// This is the only case where we should call DisconectFromServer...Event handeler will trigger the cleanup
o.DisconnectFromServer();
@@ -512,14 +515,14 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
}
else
{
Debug.Console(1, this, "Connect attempt failed {0}", o.ClientStatus);
this.LogWarning("Connect attempt failed {0}", o.ClientStatus);
CheckClosedAndTryReconnect();
}
});
}
catch (Exception ex)
{
Debug.Console(0, this, Debug.ErrorLogLevel.Error, "Client connection exception: {0}", ex.Message);
this.LogError("Client connection exception: {0}", ex.Message);
IsTryingToConnect = false;
CheckClosedAndTryReconnect();
}
@@ -556,7 +559,7 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
if (Client != null)
{
//SecureClient.DisconnectFromServer();
Debug.Console(2, this, "Disconnecting Client {0}", DisconnectCalledByUser ? ", Called by user" : "");
this.LogVerbose("Disconnecting Client {0}", DisconnectCalledByUser ? ", Called by user" : "");
Client.SocketStatusChange -= Client_SocketStatusChange;
Client.Dispose();
Client = null;
@@ -578,20 +581,20 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
{
if (Client != null)
{
Debug.Console(2, this, "Cleaning up remotely closed/failed connection.");
this.LogVerbose("Cleaning up remotely closed/failed connection.");
Cleanup();
}
if (!DisconnectCalledByUser && AutoReconnect)
{
var halfInterval = AutoReconnectIntervalMs / 2;
var rndTime = new Random().Next(-halfInterval, halfInterval) + AutoReconnectIntervalMs;
Debug.Console(2, this, "Attempting reconnect in {0} ms, randomized", rndTime);
this.LogVerbose("Attempting reconnect in {0} ms, randomized", rndTime);
if (RetryTimer != null)
{
RetryTimer.Stop();
RetryTimer = null;
}
if(AutoReconnectTriggered != null)
if (AutoReconnectTriggered != null)
AutoReconnectTriggered(this, new EventArgs());
RetryTimer = new CTimer(o => Connect(), rndTime);
}
@@ -612,13 +615,13 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
{
var bytes = client.IncomingDataBuffer.Take(numBytes).ToArray();
str = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length);
Debug.Console(2, this, "Client Received:\r--------\r{0}\r--------", str);
this.LogVerbose("Client Received:\r--------\r{0}\r--------", str);
if (!string.IsNullOrEmpty(checkHeartbeat(str)))
{
if (SharedKeyRequired && str == "SharedKey:")
{
Debug.Console(2, this, "Server asking for shared key, sending");
this.LogVerbose("Server asking for shared key, sending");
SendText(SharedKey + "\n");
}
else if (SharedKeyRequired && str == "Shared Key Match")
@@ -626,7 +629,7 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
StopWaitForSharedKeyTimer();
Debug.Console(2, this, "Shared key confirmed. Ready for communication");
this.LogVerbose("Shared key confirmed. Ready for communication");
OnClientReadyForcommunications(true); // Successful key exchange
}
else
@@ -646,7 +649,7 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
}
catch (Exception ex)
{
Debug.Console(1, this, "Error receiving data: {1}. Error: {0}", ex.Message, str);
this.LogError("Error receiving data: {1}. Error: {0}", ex.Message, str);
}
if (client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED)
client.ReceiveDataAsync(Receive);
@@ -654,9 +657,8 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
//Check to see if there is a subscription to the TextReceivedQueueInvoke event. If there is start the dequeue thread.
if (handler != null)
{
var gotLock = DequeueLock.TryEnter();
if (gotLock)
CrestronInvoke.BeginInvoke((o) => DequeueEvent());
if (Monitor.TryEnter(_dequeueLock))
Task.Run(() => DequeueEvent());
}
}
else //JAG added this as I believe the error return is 0 bytes like the server. See help when hover on ReceiveAsync
@@ -666,7 +668,7 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
}
/// <summary>
/// This method gets spooled up in its own thread an protected by a CCriticalSection to prevent multiple threads from running concurrently.
/// This method gets spooled up in its own thread an protected by a lock to prevent multiple threads from running concurrently.
/// It will dequeue items as they are enqueued automatically.
/// </summary>
void DequeueEvent()
@@ -686,20 +688,17 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
}
catch (Exception e)
{
this.LogException(e, "DequeueEvent error");
}
// Make sure to leave the CCritical section in case an exception above stops this thread, or we won't be able to restart it.
if (DequeueLock != null)
{
DequeueLock.Leave();
this.LogError("DequeueEvent error: {0}", e.Message, e);
}
// Make sure to release the lock in case an exception above stops this thread, or we won't be able to restart it.
Monitor.Exit(_dequeueLock);
}
void HeartbeatStart()
{
if (HeartbeatEnabled)
{
Debug.Console(2, this, "Starting Heartbeat");
this.LogVerbose("Starting Heartbeat");
if (HeartbeatSendTimer == null)
{
@@ -717,13 +716,13 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
if (HeartbeatSendTimer != null)
{
Debug.Console(2, this, "Stoping Heartbeat Send");
this.LogVerbose("Stopping Heartbeat Send");
HeartbeatSendTimer.Stop();
HeartbeatSendTimer = null;
}
if (HeartbeatAckTimer != null)
{
Debug.Console(2, this, "Stoping Heartbeat Ack");
this.LogVerbose("Stopping Heartbeat Ack");
HeartbeatAckTimer.Stop();
HeartbeatAckTimer = null;
}
@@ -732,7 +731,7 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
void SendHeartbeat(object notused)
{
this.SendText(HeartbeatString);
Debug.Console(2, this, "Sending Heartbeat");
this.LogVerbose("Sending Heartbeat");
}
@@ -757,7 +756,7 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
{
HeartbeatAckTimer = new CTimer(HeartbeatAckTimerFail, null, (HeartbeatInterval * 2), (HeartbeatInterval * 2));
}
Debug.Console(2, this, "Heartbeat Received: {0}, from Server", HeartbeatString);
this.LogVerbose("Heartbeat Received: {0}, from Server", HeartbeatString);
return remainingText;
}
}
@@ -765,7 +764,7 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
}
catch (Exception ex)
{
Debug.Console(1, this, "Error checking heartbeat: {0}", ex.Message);
this.LogError("Error checking heartbeat: {0}", ex.Message, ex);
}
return received;
}
@@ -779,7 +778,7 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
if (IsConnected)
{
Debug.Console(1, Debug.ErrorLogLevel.Warning, "Heartbeat not received from Server...DISCONNECTING BECAUSE HEARTBEAT REQUIRED IS TRUE");
this.LogWarning("Heartbeat not received from Server...DISCONNECTING BECAUSE HEARTBEAT REQUIRED IS TRUE");
SendText("Heartbeat not received by server, closing connection");
CheckClosedAndTryReconnect();
}
@@ -787,7 +786,7 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
}
catch (Exception ex)
{
ErrorLog.Error("Heartbeat timeout Error on Client: {0}, {1}", Key, ex);
this.LogError("Heartbeat timeout Error on Client: {0}, {1}", Key, ex);
}
}
@@ -820,14 +819,14 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
// HOW IN THE HELL DO WE CATCH AN EXCEPTION IN SENDING?????
if (n <= 0)
{
Debug.Console(1, Debug.ErrorLogLevel.Warning, "[{0}] Sent zero bytes. Was there an error?", this.Key);
this.LogWarning("[{0}] Sent zero bytes. Was there an error?", this.Key);
}
});
}
}
catch (Exception ex)
{
Debug.Console(0, this, "Error sending text: {1}. Error: {0}", ex.Message, text);
this.LogError("Error sending text: {1}. Error: {0}", text, ex.Message);
}
}
}
@@ -846,7 +845,7 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
}
catch (Exception ex)
{
Debug.Console(0, this, "Error sending bytes. Error: {0}", ex.Message);
this.LogError("Error sending bytes. Error: {0}", ex.Message, ex);
}
}
}
@@ -865,7 +864,7 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
}
try
{
Debug.Console(2, this, "Socket status change: {0} ({1})", client.ClientStatus, (ushort)(client.ClientStatus));
this.LogDebug("Socket status change: {0} ({1})", client.ClientStatus, (ushort)(client.ClientStatus));
OnConnectionChange();
// The client could be null or disposed by this time...
@@ -878,7 +877,7 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
}
catch (Exception ex)
{
Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Error in socket status change callback. Error: {0}\r\r{1}", ex, ex.InnerException);
this.LogError("Error in socket status change callback. Error: {0}\r\r{1}", ex, ex.InnerException);
}
}

View File

@@ -1,18 +1,9 @@
/*PepperDash Technology Corp.
JAG
Copyright: 2017
------------------------------------
***Notice of Ownership and Copyright***
The material in which this notice appears is the property of PepperDash Technology Corporation,
which claims copyright under the laws of the United States of America in the entire body of material
and in all parts thereof, regardless of the use to which it is being put. Any use, in whole or in part,
of this material by another party without the express written permission of PepperDash Technology Corporation is prohibited.
PepperDash Technology Corporation reserves all rights under applicable laws.
------------------------------------ */
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Crestron.SimplSharp;
using Crestron.SimplSharp.CrestronSockets;
using PepperDash.Core.Logging;
@@ -69,12 +60,17 @@ public class GenericSecureTcpIpServer : Device
/// <summary>
/// Server listen lock
/// </summary>
CCriticalSection ServerCCSection = new CCriticalSection();
private readonly object _serverLock = new();
/// <summary>
/// Queue lock
/// </summary>
CCriticalSection DequeueLock = new CCriticalSection();
private readonly object _dequeueLock = new();
/// <summary>
/// Broadcast lock
/// </summary>
private readonly object _broadcastLock = new();
/// <summary>
/// Receive Queue size. Defaults to 20. Will set to 20 if QueueSize property is less than 20. Use constructor or set queue size property before
@@ -399,18 +395,19 @@ public class GenericSecureTcpIpServer : Device
/// </summary>
public void Listen()
{
ServerCCSection.Enter();
lock (_serverLock)
{
try
{
if (Port < 1 || Port > 65535)
{
Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Server '{0}': Invalid port", Key);
this.LogError("Server '{0}': Invalid port", Key);
ErrorLog.Warn(string.Format("Server '{0}': Invalid port", Key));
return;
}
if (string.IsNullOrEmpty(SharedKey) && SharedKeyRequired)
{
Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Server '{0}': No Shared Key set", Key);
this.LogError("Server '{0}': No Shared Key set", Key);
ErrorLog.Warn(string.Format("Server '{0}': No Shared Key set", Key));
return;
}
@@ -434,22 +431,21 @@ public class GenericSecureTcpIpServer : Device
SocketErrorCodes status = SecureServer.WaitForConnectionAsync(IPAddress.Any, SecureConnectCallback);
if (status != SocketErrorCodes.SOCKET_OPERATION_PENDING)
{
Debug.Console(0, this, Debug.ErrorLogLevel.Error, "Error starting WaitForConnectionAsync {0}", status);
this.LogError("Error starting WaitForConnectionAsync {0}", status);
}
else
{
ServerStopped = false;
}
OnServerStateChange(SecureServer.State);
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Secure Server Status: {0}, Socket Status: {1}", SecureServer.State, SecureServer.ServerSocketStatus);
ServerCCSection.Leave();
this.LogInformation("Secure Server Status: {0}, Socket Status: {1}", SecureServer.State, SecureServer.ServerSocketStatus);
}
catch (Exception ex)
{
ServerCCSection.Leave();
ErrorLog.Error("{1} Error with Dynamic Server: {0}", ex.ToString(), Key);
this.LogException(ex, "{1} Error with Dynamic Server: {0}", ex.ToString(), Key);
}
} // end lock
}
/// <summary>
@@ -459,18 +455,18 @@ public class GenericSecureTcpIpServer : Device
{
try
{
Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Stopping Listener");
this.LogVerbose("Stopping Listener");
if (SecureServer != null)
{
SecureServer.Stop();
Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Server State: {0}", SecureServer.State);
this.LogVerbose("Server State: {0}", SecureServer.State);
OnServerStateChange(SecureServer.State);
}
ServerStopped = true;
}
catch (Exception ex)
{
Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error stopping server. Error: {0}", ex);
this.LogException(ex, "Error stopping server. Error: {0}", ex.Message);
}
}
@@ -483,11 +479,11 @@ public class GenericSecureTcpIpServer : Device
try
{
SecureServer.Disconnect(client);
Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Disconnected client index: {0}", client);
this.LogVerbose("Disconnected client index: {0}", client);
}
catch (Exception ex)
{
Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error Disconnecting client index: {0}. Error: {1}", client, ex);
this.LogException(ex, "Error Disconnecting client index: {0}. Error: {1}", client, ex.Message);
}
}
/// <summary>
@@ -495,7 +491,7 @@ public class GenericSecureTcpIpServer : Device
/// </summary>
public void DisconnectAllClientsForShutdown()
{
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Disconnecting All Clients");
this.LogInformation("Disconnecting All Clients");
if (SecureServer != null)
{
SecureServer.SocketStatusChange -= SecureServer_SocketStatusChange;
@@ -507,17 +503,17 @@ public class GenericSecureTcpIpServer : Device
try
{
SecureServer.Disconnect(i);
Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Disconnected client index: {0}", i);
this.LogInformation("Disconnected client index: {0}", i);
}
catch (Exception ex)
{
Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error Disconnecting client index: {0}. Error: {1}", i, ex);
this.LogException(ex, "Error Disconnecting client index: {0}. Error: {1}", i, ex.Message);
}
}
Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Server Status: {0}", SecureServer.ServerSocketStatus);
this.LogInformation("Server Status: {0}", SecureServer.ServerSocketStatus);
}
Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Disconnected All Clients");
this.LogInformation("Disconnected All Clients");
ConnectedClientsIndexes.Clear();
if (!ProgramIsStopping)
@@ -535,8 +531,8 @@ public class GenericSecureTcpIpServer : Device
/// <param name="text"></param>
public void BroadcastText(string text)
{
CCriticalSection CCBroadcast = new CCriticalSection();
CCBroadcast.Enter();
lock (_broadcastLock)
{
try
{
if (ConnectedClientsIndexes.Count > 0)
@@ -552,13 +548,12 @@ public class GenericSecureTcpIpServer : Device
}
}
}
CCBroadcast.Leave();
}
catch (Exception ex)
{
CCBroadcast.Leave();
Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error Broadcasting messages from server. Error: {0}", ex.Message);
this.LogException(ex, "Error Broadcasting messages from server. Error: {0}", ex.Message);
}
} // end lock
}
/// <summary>
@@ -579,7 +574,7 @@ public class GenericSecureTcpIpServer : Device
}
catch (Exception ex)
{
Debug.Console(2, this, "Error sending text to client. Text: {1}. Error: {0}", ex.Message, text);
this.LogException(ex, "Error sending text to client. Text: {1}. Error: {0}", ex.Message, text);
}
}
@@ -603,7 +598,7 @@ public class GenericSecureTcpIpServer : Device
CTimer HeartbeatTimer = new CTimer(HeartbeatTimer_CallbackFunction, clientIndex, HeartbeatRequiredIntervalMs);
HeartbeatTimerDictionary.Add(clientIndex, HeartbeatTimer);
}
Debug.Console(1, this, "Heartbeat Received: {0}, from client index: {1}", HeartbeatStringToMatch, clientIndex);
this.LogDebug("Heartbeat Received: {0}, from client index: {1}", HeartbeatStringToMatch, clientIndex);
// Return Heartbeat
SendTextToClient(HeartbeatStringToMatch, clientIndex);
return remainingText;
@@ -618,13 +613,13 @@ public class GenericSecureTcpIpServer : Device
CTimer HeartbeatTimer = new CTimer(HeartbeatTimer_CallbackFunction, clientIndex, HeartbeatRequiredIntervalMs);
HeartbeatTimerDictionary.Add(clientIndex, HeartbeatTimer);
}
Debug.Console(1, this, "Heartbeat Received: {0}, from client index: {1}", received, clientIndex);
this.LogInformation("Heartbeat Received: {0}, from client index: {1}", received, clientIndex);
}
}
}
catch (Exception ex)
{
Debug.Console(1, this, "Error checking heartbeat: {0}", ex.Message);
this.LogException(ex, "Error checking heartbeat: {0}", ex.Message);
}
return received;
}
@@ -636,11 +631,11 @@ public class GenericSecureTcpIpServer : Device
/// <returns></returns>
public string GetClientIPAddress(uint clientIndex)
{
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "GetClientIPAddress Index: {0}", clientIndex);
this.LogInformation("GetClientIPAddress Index: {0}", clientIndex);
if (!SharedKeyRequired || (SharedKeyRequired && ClientReadyAfterKeyExchange.Contains(clientIndex)))
{
var ipa = this.SecureServer.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex);
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "GetClientIPAddress IPAddreess: {0}", ipa);
this.LogInformation("GetClientIPAddress IPAddreess: {0}", ipa);
return ipa;
}
@@ -663,7 +658,7 @@ public class GenericSecureTcpIpServer : Device
clientIndex = (uint)o;
address = SecureServer.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex);
Debug.Console(1, this, Debug.ErrorLogLevel.Warning, "Heartbeat not received for Client index {2} IP: {0}, DISCONNECTING BECAUSE HEARTBEAT REQUIRED IS TRUE {1}",
this.LogInformation("Heartbeat not received for Client index {2} IP: {0}, DISCONNECTING BECAUSE HEARTBEAT REQUIRED IS TRUE {1}",
address, string.IsNullOrEmpty(HeartbeatStringToMatch) ? "" : ("HeartbeatStringToMatch: " + HeartbeatStringToMatch), clientIndex);
if (SecureServer.GetServerSocketStatusForSpecificClient(clientIndex) == SocketStatus.SOCKET_STATUS_CONNECTED)
@@ -703,7 +698,7 @@ public class GenericSecureTcpIpServer : Device
// Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "SecureServerSocketStatusChange Index:{0} status:{1} Port:{2} IP:{3}", clientIndex, serverSocketStatus, this.SecureServer.GetPortNumberServerAcceptedConnectionFromForSpecificClient(clientIndex), this.SecureServer.GetLocalAddressServerAcceptedConnectionFromForSpecificClient(clientIndex));
if (serverSocketStatus != SocketStatus.SOCKET_STATUS_CONNECTED)
{
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "SecureServerSocketStatusChange ConnectedCLients: {0} ServerState: {1} Port: {2}", SecureServer.NumberOfClientsConnected, SecureServer.State, SecureServer.PortNumber);
this.LogInformation("SecureServerSocketStatusChange ConnectedCLients: {0} ServerState: {1} Port: {2}", SecureServer.NumberOfClientsConnected, SecureServer.State, SecureServer.PortNumber);
if (ConnectedClientsIndexes.Contains(clientIndex))
ConnectedClientsIndexes.Remove(clientIndex);
@@ -725,12 +720,12 @@ public class GenericSecureTcpIpServer : Device
}
catch (Exception ex)
{
Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error in Socket Status Change Callback. Error: {0}", ex);
this.LogException(ex, "Error in Socket Status Change Callback. Error: {0}", ex.Message);
}
//Use a thread for this event so that the server state updates to listening while this event is processed. Listening must be added to the server state
//after every client connection so that the server can check and see if it is at max clients. Due to this the event fires and server listening enum bit flag
//is not set. Putting in a thread allows the state to update before this event processes so that the subscribers to this event get accurate isListening in the event.
CrestronInvoke.BeginInvoke(o => onConnectionChange(clientIndex, server.GetServerSocketStatusForSpecificClient(clientIndex)), null);
Task.Run(() => onConnectionChange(clientIndex, server.GetServerSocketStatusForSpecificClient(clientIndex)));
}
#endregion
@@ -745,7 +740,7 @@ public class GenericSecureTcpIpServer : Device
{
try
{
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "ConnectCallback: IPAddress: {0}. Index: {1}. Status: {2}",
this.LogInformation("ConnectCallback: IPAddress: {0}. Index: {1}. Status: {2}",
server.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex),
clientIndex, server.GetServerSocketStatusForSpecificClient(clientIndex));
if (clientIndex != 0)
@@ -765,7 +760,7 @@ public class GenericSecureTcpIpServer : Device
}
byte[] b = Encoding.GetEncoding(28591).GetBytes("SharedKey:");
server.SendDataAsync(clientIndex, b, b.Length, (x, y, z) => { });
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Sent Shared Key Request to client at {0}", server.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex));
this.LogInformation("Sent Shared Key Request to client at {0}", server.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex));
}
else
{
@@ -784,19 +779,19 @@ public class GenericSecureTcpIpServer : Device
}
else
{
Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Client attempt faulty.");
this.LogError("Client attempt faulty.");
}
}
catch (Exception ex)
{
Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error in Socket Status Connect Callback. Error: {0}", ex);
this.LogException(ex, "Error in Socket Status Connect Callback. Error: {0}", ex.Message);
}
// Rearm the listner
SocketErrorCodes status = server.WaitForConnectionAsync(IPAddress.Any, SecureConnectCallback);
if (status != SocketErrorCodes.SOCKET_OPERATION_PENDING)
{
Debug.Console(0, this, Debug.ErrorLogLevel.Error, "Socket status connect callback status {0}", status);
this.LogError("Socket status connect callback status {0}", status);
if (status == SocketErrorCodes.SOCKET_CONNECTION_IN_PROGRESS)
{
// There is an issue where on a failed negotiation we need to stop and start the server. This should still leave connected clients intact.
@@ -833,7 +828,7 @@ public class GenericSecureTcpIpServer : Device
if (received != SharedKey)
{
byte[] b = Encoding.GetEncoding(28591).GetBytes("Shared key did not match server. Disconnecting");
Debug.Console(1, this, Debug.ErrorLogLevel.Warning, "Client at index {0} Shared key did not match the server, disconnecting client. Key: {1}", clientIndex, received);
this.LogWarning("Client at index {0} Shared key did not match the server, disconnecting client. Key: {1}", clientIndex, received);
mySecureTCPServer.SendData(clientIndex, b, b.Length);
mySecureTCPServer.Disconnect(clientIndex);
@@ -844,7 +839,7 @@ public class GenericSecureTcpIpServer : Device
byte[] success = Encoding.GetEncoding(28591).GetBytes("Shared Key Match");
mySecureTCPServer.SendDataAsync(clientIndex, success, success.Length, null);
OnServerClientReadyForCommunications(clientIndex);
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Client with index {0} provided the shared key and successfully connected to the server", clientIndex);
this.LogInformation("Client with index {0} provided the shared key and successfully connected to the server", clientIndex);
}
else if (!string.IsNullOrEmpty(checkHeartbeat(clientIndex, received)))
{
@@ -857,7 +852,7 @@ public class GenericSecureTcpIpServer : Device
}
catch (Exception ex)
{
Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error Receiving data: {0}. Error: {1}", received, ex);
this.LogException(ex, "Error Receiving data: {0}. Error: {1}", received, ex.Message);
}
if (mySecureTCPServer.GetServerSocketStatusForSpecificClient(clientIndex) == SocketStatus.SOCKET_STATUS_CONNECTED)
mySecureTCPServer.ReceiveDataAsync(clientIndex, SecureReceivedDataAsyncCallback);
@@ -865,9 +860,8 @@ public class GenericSecureTcpIpServer : Device
//Check to see if there is a subscription to the TextReceivedQueueInvoke event. If there is start the dequeue thread.
if (handler != null)
{
var gotLock = DequeueLock.TryEnter();
if (gotLock)
CrestronInvoke.BeginInvoke((o) => DequeueEvent());
if (Monitor.TryEnter(_dequeueLock))
Task.Run(() => DequeueEvent());
}
}
else
@@ -877,7 +871,7 @@ public class GenericSecureTcpIpServer : Device
}
/// <summary>
/// This method gets spooled up in its own thread an protected by a CCriticalSection to prevent multiple threads from running concurrently.
/// This method gets spooled up in its own thread an protected by a lock to prevent multiple threads from running concurrently.
/// It will dequeue items as they are enqueued automatically.
/// </summary>
void DequeueEvent()
@@ -897,13 +891,10 @@ public class GenericSecureTcpIpServer : Device
}
catch (Exception e)
{
this.LogException(e, "DequeueEvent error");
}
// Make sure to leave the CCritical section in case an exception above stops this thread, or we won't be able to restart it.
if (DequeueLock != null)
{
DequeueLock.Leave();
this.LogError(e, "DequeueEvent error");
}
// Make sure to release the lock in case an exception above stops this thread, or we won't be able to restart it.
Monitor.Exit(_dequeueLock);
}
#endregion
@@ -974,7 +965,7 @@ public class GenericSecureTcpIpServer : Device
if (MonitorClient != null)
MonitorClient.Disconnect();
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Program stopping. Closing server");
this.LogInformation("Program stopping. Closing server");
KillServer();
}
}
@@ -1015,7 +1006,7 @@ public class GenericSecureTcpIpServer : Device
//MonitorClient.ConnectionChange += MonitorClient_ConnectionChange;
MonitorClient.ClientReadyForCommunications += MonitorClient_IsReadyForComm;
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Starting monitor check");
this.LogInformation("Starting monitor check");
MonitorClient.Connect();
// From here MonitorCLient either connects or hangs, MonitorClient will call back
@@ -1042,7 +1033,7 @@ public class GenericSecureTcpIpServer : Device
{
if (args.IsReady)
{
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Monitor client connection success. Disconnecting in 2s");
this.LogInformation("Monitor client connection success. Disconnecting in 2s");
MonitorClientTimer.Stop();
MonitorClientTimer = null;
MonitorClientFailureCount = 0;
@@ -1063,13 +1054,13 @@ public class GenericSecureTcpIpServer : Device
StopMonitorClient();
if (MonitorClientFailureCount < MonitorClientMaxFailureCount)
{
Debug.Console(2, this, Debug.ErrorLogLevel.Warning, "Monitor client connection has hung {0} time{1}, maximum {2}",
this.LogWarning("Monitor client connection has hung {0} time{1}, maximum {2}",
MonitorClientFailureCount, MonitorClientFailureCount > 1 ? "s" : "", MonitorClientMaxFailureCount);
StartMonitorClient();
}
else
{
Debug.Console(2, this, Debug.ErrorLogLevel.Error,
this.LogError(
"\r***************************\rMonitor client connection has hung a maximum of {0} times. \r***************************",
MonitorClientMaxFailureCount);

View File

@@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Crestron.SimplSharp;
using Crestron.SimplSharp.CrestronSockets;
using Org.BouncyCastle.Utilities;
@@ -79,7 +81,7 @@ public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoR
}
/// <summary>
///
/// SSH Client
/// </summary>
public SocketStatus ClientStatus
{
@@ -134,8 +136,6 @@ public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoR
CTimer ReconnectTimer;
//Lock object to prevent simulatneous connect/disconnect operations
//private CCriticalSection connectLock = new CCriticalSection();
private SemaphoreSlim connectLock = new SemaphoreSlim(1);
private bool DisconnectLogged = false;
@@ -147,31 +147,31 @@ public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoR
base(key)
{
StreamDebugging = new CommunicationStreamDebugging(key);
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
Key = key;
Hostname = hostname;
Port = port;
Username = username;
Password = password;
AutoReconnectIntervalMs = 5000;
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
Key = key;
Hostname = hostname;
Port = port;
Username = username;
Password = password;
AutoReconnectIntervalMs = 5000;
ReconnectTimer = new CTimer(o =>
{
if (ConnectEnabled)
{
Connect();
}
}, System.Threading.Timeout.Infinite);
}
if (ConnectEnabled)
{
Connect();
}
}, System.Threading.Timeout.Infinite);
}
/// <summary>
/// S+ Constructor - Must set all properties before calling Connect
/// </summary>
public GenericSshClient()
: base(SPlusKey)
{
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
AutoReconnectIntervalMs = 5000;
/// <summary>
/// S+ Constructor - Must set all properties before calling Connect
/// </summary>
public GenericSshClient()
: base(SPlusKey)
{
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
AutoReconnectIntervalMs = 5000;
ReconnectTimer = new CTimer(o =>
{
@@ -180,18 +180,18 @@ public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoR
Connect();
}
}, System.Threading.Timeout.Infinite);
}
}
/// <summary>
/// Handles closing this up when the program shuts down
/// </summary>
void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType)
{
if (programEventType == eProgramStatusEventType.Stopping)
{
if (Client != null)
{
this.LogDebug("Program stopping. Closing connection");
/// <summary>
/// Handles closing this up when the program shuts down
/// </summary>
void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType)
{
if (programEventType == eProgramStatusEventType.Stopping)
{
if (Client != null)
{
this.LogDebug("Program stopping. Closing connection");
Disconnect();
}
}
@@ -224,10 +224,10 @@ public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoR
this.LogDebug("Attempting connect");
// Cancel reconnect if running.
if (ReconnectTimer != null)
{
ReconnectTimer.Stop();
}
if (ReconnectTimer != null)
{
ReconnectTimer.Stop();
}
// Cleanup the old client if it already exists
if (Client != null)
@@ -269,7 +269,7 @@ public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoR
if (ie is SocketException)
{
this.LogException(ie, "CONNECTION failure: Cannot reach host");
this.LogException(ie, "CONNECTION failure: Cannot reach host");
}
if (ie is System.Net.Sockets.SocketException socketException)
@@ -292,7 +292,7 @@ public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoR
ReconnectTimer.Reset(AutoReconnectIntervalMs);
}
}
catch(SshOperationTimeoutException ex)
catch (SshOperationTimeoutException ex)
{
this.LogWarning("Connection attempt timed out: {message}", ex.Message);
@@ -324,18 +324,14 @@ public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoR
}
}
/// <summary>
/// Disconnect the clients and put away it's resources.
/// </summary>
public void Disconnect()
{
ConnectEnabled = false;
// Stop trying reconnects, if we are
if (ReconnectTimer != null)
{
ReconnectTimer.Stop();
// ReconnectTimer = null;
}
/// <summary>
/// Disconnect method
/// </summary>
public void Disconnect()
{
ConnectEnabled = false;
// Stop trying reconnects, if we are
ReconnectTimer.Stop();
KillClient(SocketStatus.SOCKET_STATUS_BROKEN_LOCALLY);
}
@@ -361,15 +357,15 @@ public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoR
}
catch (Exception ex)
{
this.LogException(ex,"Exception in Kill Client");
this.LogException(ex, "Exception in Kill Client");
}
}
/// <summary>
/// Kills the stream
/// </summary>
void KillStream()
{
void KillStream()
{
try
{
if (TheStream != null)
@@ -385,60 +381,60 @@ public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoR
{
this.LogException(ex, "Exception in Kill Stream:{0}");
}
}
}
/// <summary>
/// Handles the keyboard interactive authentication, should it be required.
/// </summary>
void kauth_AuthenticationPrompt(object sender, AuthenticationPromptEventArgs e)
{
foreach (AuthenticationPrompt prompt in e.Prompts)
if (prompt.Request.IndexOf("Password:", StringComparison.InvariantCultureIgnoreCase) != -1)
prompt.Response = Password;
}
/// <summary>
/// Handler for data receive on ShellStream. Passes data across to queue for line parsing.
/// </summary>
void Stream_DataReceived(object sender, ShellDataEventArgs e)
{
/// <summary>
/// Handles the keyboard interactive authentication, should it be required.
/// </summary>
void kauth_AuthenticationPrompt(object sender, AuthenticationPromptEventArgs e)
{
foreach (AuthenticationPrompt prompt in e.Prompts)
if (prompt.Request.IndexOf("Password:", StringComparison.InvariantCultureIgnoreCase) != -1)
prompt.Response = Password;
}
/// <summary>
/// Handler for data receive on ShellStream. Passes data across to queue for line parsing.
/// </summary>
void Stream_DataReceived(object sender, ShellDataEventArgs e)
{
if (((ShellStream)sender).Length <= 0L)
{
return;
}
var response = ((ShellStream)sender).Read();
var response = ((ShellStream)sender).Read();
var bytesHandler = BytesReceived;
if (bytesHandler != null)
{
var bytesHandler = BytesReceived;
if (bytesHandler != null)
{
var bytes = Encoding.UTF8.GetBytes(response);
if (StreamDebugging.RxStreamDebuggingIsEnabled)
{
this.LogInformation("Received {1} bytes: '{0}'", ComTextHelper.GetEscapedText(bytes), bytes.Length);
}
if (StreamDebugging.RxStreamDebuggingIsEnabled)
{
this.LogInformation("Received {1} bytes: '{0}'", ComTextHelper.GetEscapedText(bytes), bytes.Length);
}
bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes));
}
var textHandler = TextReceived;
if (textHandler != null)
{
}
var textHandler = TextReceived;
if (textHandler != null)
{
if (StreamDebugging.RxStreamDebuggingIsEnabled)
this.LogInformation("Received: '{0}'", ComTextHelper.GetDebugText(response));
textHandler(this, new GenericCommMethodReceiveTextArgs(response));
}
}
}
/// <summary>
/// Error event handler for client events - disconnect, etc. Will forward those events via ConnectionChange
/// event
/// </summary>
void Client_ErrorOccurred(object sender, ExceptionEventArgs e)
{
CrestronInvoke.BeginInvoke(o =>
/// <summary>
/// Error event handler for client events - disconnect, etc. Will forward those events via ConnectionChange
/// event
/// </summary>
void Client_ErrorOccurred(object sender, ExceptionEventArgs e)
{
Task.Run(() =>
{
if (e.Exception is SshConnectionException || e.Exception is System.Net.Sockets.SocketException)
this.LogError("Disconnected by remote");
@@ -472,49 +468,49 @@ public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoR
#region IBasicCommunication Members
/// <summary>
/// Sends text to the server
/// </summary>
/// <param name="text"></param>
public void SendText(string text)
{
try
{
if (Client != null && TheStream != null && IsConnected)
{
if (StreamDebugging.TxStreamDebuggingIsEnabled)
this.LogInformation(
"Sending {length} characters of text: '{text}'",
text.Length,
ComTextHelper.GetDebugText(text));
TheStream.Write(text);
TheStream.Flush();
}
else
{
this.LogDebug("Client is null or disconnected. Cannot Send Text");
}
}
catch (ObjectDisposedException)
/// <summary>
/// Sends text to the server
/// </summary>
/// <param name="text"></param>
public void SendText(string text)
{
try
{
this.LogError("ObjectDisposedException sending '{message}'. Restarting connection...", text.Trim());
if (Client != null && TheStream != null && IsConnected)
{
if (StreamDebugging.TxStreamDebuggingIsEnabled)
this.LogInformation(
"Sending {length} characters of text: '{text}'",
text.Length,
ComTextHelper.GetDebugText(text));
TheStream.Write(text);
TheStream.Flush();
}
else
{
this.LogDebug("Client is null or disconnected. Cannot Send Text");
}
}
catch (ObjectDisposedException)
{
this.LogError("ObjectDisposedException sending '{message}'. Restarting connection...", text.Trim());
KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED);
ReconnectTimer.Reset();
}
catch (Exception ex)
{
}
catch (Exception ex)
{
this.LogException(ex, "Exception sending text: '{message}'", text);
}
}
}
}
/// <summary>
/// Sends Bytes to the server
/// </summary>
/// <param name="bytes"></param>
public void SendBytes(byte[] bytes)
{
public void SendBytes(byte[] bytes)
{
try
{
if (Client != null && TheStream != null && IsConnected)
@@ -540,52 +536,51 @@ public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoR
catch (Exception ex)
{
this.LogException(ex, "Exception sending {message}", ComTextHelper.GetEscapedText(bytes));
}
}
#endregion
}
}
#endregion
}
//*****************************************************************************************************
//*****************************************************************************************************
/// <summary>
/// Fired when connection changes
/// </summary>
public class SshConnectionChangeEventArgs : EventArgs
{
//*****************************************************************************************************
//*****************************************************************************************************
/// <summary>
/// Connection State
/// Fired when connection changes
/// </summary>
public bool IsConnected { get; private set; }
/// <summary>
/// Connection Status represented as a ushort
/// </summary>
public ushort UIsConnected { get { return (ushort)(Client.IsConnected ? 1 : 0); } }
/// <summary>
/// The client
/// </summary>
public GenericSshClient Client { get; private set; }
/// <summary>
/// Socket Status as represented by
/// </summary>
public ushort Status { get { return Client.UStatus; } }
/// <summary>
/// S+ Constructor
/// </summary>
public SshConnectionChangeEventArgs() { }
/// <summary>
/// EventArgs class
/// </summary>
/// <param name="isConnected">Connection State</param>
/// <param name="client">The Client</param>
public SshConnectionChangeEventArgs(bool isConnected, GenericSshClient client)
public class SshConnectionChangeEventArgs : EventArgs
{
IsConnected = isConnected;
Client = client;
/// <summary>
/// Connection State
/// </summary>
public bool IsConnected { get; private set; }
/// <summary>
/// Connection Status represented as a ushort
/// </summary>
public ushort UIsConnected { get { return (ushort)(Client.IsConnected ? 1 : 0); } }
/// <summary>
/// The client
/// </summary>
public GenericSshClient Client { get; private set; }
/// <summary>
/// Socket Status as represented by
/// </summary>
public ushort Status { get { return Client.UStatus; } }
/// <summary>
/// S+ Constructor
/// </summary>
public SshConnectionChangeEventArgs() { }
/// <summary>
/// EventArgs class
/// </summary>
/// <param name="isConnected">Connection State</param>
/// <param name="client">The Client</param>
public SshConnectionChangeEventArgs(bool isConnected, GenericSshClient client)
{
IsConnected = isConnected;
Client = client;
}
}
}

View File

@@ -1,17 +1,24 @@
using System;
extern alias NewtonsoftJson;
using System;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using Timer = System.Timers.Timer;
using Crestron.SimplSharp;
using Crestron.SimplSharp.CrestronSockets;
using Newtonsoft.Json;
using JsonProperty = NewtonsoftJson::Newtonsoft.Json.JsonPropertyAttribute;
using Required = NewtonsoftJson::Newtonsoft.Json.Required;
using PepperDash.Core.Logging;
using System.Threading.Tasks;
namespace PepperDash.Core;
/// <summary>
/// A class to handle basic TCP/IP communications with a server
/// </summary>
public class GenericTcpIpClient : Device, ISocketStatusWithStreamDebugging, IAutoReconnect
public class GenericTcpIpClient : Device, ISocketStatusWithStreamDebugging, IAutoReconnect
{
private const string SplusKey = "Uninitialized TcpIpClient";
/// <summary>
@@ -19,44 +26,44 @@ namespace PepperDash.Core;
/// </summary>
public CommunicationStreamDebugging StreamDebugging { get; private set; }
/// <summary>
/// Fires when data is received from the server and returns it as a Byte array
/// </summary>
public event EventHandler<GenericCommMethodReceiveBytesArgs> BytesReceived;
/// <summary>
/// Fires when data is received from the server and returns it as a Byte array
/// </summary>
public event EventHandler<GenericCommMethodReceiveBytesArgs> BytesReceived;
/// <summary>
/// Fires when data is received from the server and returns it as text
/// </summary>
public event EventHandler<GenericCommMethodReceiveTextArgs> TextReceived;
/// <summary>
/// Fires when data is received from the server and returns it as text
/// </summary>
public event EventHandler<GenericCommMethodReceiveTextArgs> TextReceived;
/// <summary>
///
/// </summary>
//public event GenericSocketStatusChangeEventDelegate SocketStatusChange;
public event EventHandler<GenericSocketStatusChageEventArgs> ConnectionChange;
/// <summary>
///
/// </summary>
//public event GenericSocketStatusChangeEventDelegate SocketStatusChange;
public event EventHandler<GenericSocketStatusChageEventArgs> ConnectionChange;
private string _hostname;
private string _hostname;
/// <summary>
/// Address of server
/// </summary>
public string Hostname
{
get
{
return _hostname;
}
get
{
return _hostname;
}
set
{
_hostname = value;
if (_client != null)
{
_client.AddressClientConnectedTo = _hostname;
}
}
}
set
{
_hostname = value;
if (_client != null)
{
_client.AddressClientConnectedTo = _hostname;
}
}
}
/// <summary>
/// Port on server
@@ -78,19 +85,19 @@ namespace PepperDash.Core;
/// </summary>
public int BufferSize { get; set; }
/// <summary>
/// The actual client class
/// </summary>
private TCPClient _client;
/// <summary>
/// The actual client class
/// </summary>
private TCPClient _client;
/// <summary>
/// Bool showing if socket is connected
/// </summary>
public bool IsConnected
{
get { return _client != null && _client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; }
/// <summary>
/// Bool showing if socket is connected
/// </summary>
public bool IsConnected
{
get { return _client != null && _client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; }
}
/// <summary>
/// S+ helper for IsConnected
/// </summary>
@@ -99,15 +106,15 @@ namespace PepperDash.Core;
get { return (ushort)(IsConnected ? 1 : 0); }
}
/// <summary>
/// _client socket status Read only
/// </summary>
public SocketStatus ClientStatus
{
get
/// <summary>
/// _client socket status Read only
/// </summary>
public SocketStatus ClientStatus
{
get
{
return _client == null ? SocketStatus.SOCKET_STATUS_NO_CONNECT : _client.ClientStatus;
}
return _client == null ? SocketStatus.SOCKET_STATUS_NO_CONNECT : _client.ClientStatus;
}
}
/// <summary>
@@ -119,26 +126,26 @@ namespace PepperDash.Core;
get { return (ushort)ClientStatus; }
}
/// <summary>
/// <summary>
/// Status text shows the message associated with socket status
/// </summary>
public string ClientStatusText { get { return ClientStatus.ToString(); } }
/// </summary>
public string ClientStatusText { get { return ClientStatus.ToString(); } }
/// <summary>
/// Ushort representation of client status
/// </summary>
/// <summary>
/// Ushort representation of client status
/// </summary>
[Obsolete]
public ushort UClientStatus { get { return (ushort)ClientStatus; } }
public ushort UClientStatus { get { return (ushort)ClientStatus; } }
/// <summary>
/// Connection failure reason
/// </summary>
public string ConnectionFailure { get { return ClientStatus.ToString(); } }
/// <summary>
/// Connection failure reason
/// </summary>
public string ConnectionFailure { get { return ClientStatus.ToString(); } }
/// <summary>
/// bool to track if auto reconnect should be set on the socket
/// </summary>
public bool AutoReconnect { get; set; }
/// <summary>
/// Gets or sets the AutoReconnect
/// </summary>
public bool AutoReconnect { get; set; }
/// <summary>
/// S+ helper for AutoReconnect
@@ -149,29 +156,29 @@ namespace PepperDash.Core;
set { AutoReconnect = value == 1; }
}
/// <summary>
/// Milliseconds to wait before attempting to reconnect. Defaults to 5000
/// </summary>
public int AutoReconnectIntervalMs { get; set; }
/// <summary>
/// Milliseconds to wait before attempting to reconnect. Defaults to 5000
/// </summary>
public int AutoReconnectIntervalMs { get; set; }
/// <summary>
/// Set only when the disconnect method is called
/// </summary>
bool DisconnectCalledByUser;
/// <summary>
/// Set only when the disconnect method is called
/// </summary>
bool DisconnectCalledByUser;
/// <summary>
///
/// </summary>
public bool Connected
{
get { return _client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; }
}
/// <summary>
///
/// </summary>
public bool Connected
{
get { return _client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; }
}
//Lock object to prevent simulatneous connect/disconnect operations
private CCriticalSection connectLock = new CCriticalSection();
private readonly object _connectLock = new();
// private Timer for auto reconnect
private CTimer RetryTimer;
private Timer RetryTimer;
/// <summary>
/// Constructor
@@ -180,9 +187,9 @@ namespace PepperDash.Core;
/// <param name="address"></param>
/// <param name="port"></param>
/// <param name="bufferSize"></param>
public GenericTcpIpClient(string key, string address, int port, int bufferSize)
: base(key)
{
public GenericTcpIpClient(string key, string address, int port, int bufferSize)
: base(key)
{
StreamDebugging = new CommunicationStreamDebugging(key);
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
AutoReconnectIntervalMs = 5000;
@@ -190,10 +197,7 @@ namespace PepperDash.Core;
Port = port;
BufferSize = bufferSize;
RetryTimer = new CTimer(o =>
{
Reconnect();
}, Timeout.Infinite);
SetupRetryTimer();
}
/// <summary>
@@ -208,28 +212,30 @@ namespace PepperDash.Core;
AutoReconnectIntervalMs = 5000;
BufferSize = 2000;
RetryTimer = new CTimer(o =>
{
Reconnect();
}, Timeout.Infinite);
SetupRetryTimer();
}
/// <summary>
/// Default constructor for S+
/// </summary>
public GenericTcpIpClient()
: base(SplusKey)
: base(SplusKey)
{
StreamDebugging = new CommunicationStreamDebugging(SplusKey);
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
AutoReconnectIntervalMs = 5000;
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
AutoReconnectIntervalMs = 5000;
BufferSize = 2000;
RetryTimer = new CTimer(o =>
{
Reconnect();
}, Timeout.Infinite);
}
SetupRetryTimer();
}
private void SetupRetryTimer()
{
RetryTimer = new Timer { AutoReset = false, Enabled = false };
RetryTimer.Elapsed += (s, e) => Reconnect();
}
/// <summary>
/// Just to help S+ set the key
@@ -246,7 +252,7 @@ namespace PepperDash.Core;
{
if (programEventType == eProgramStatusEventType.Stopping)
{
Debug.Console(1, this, "Program stopping. Closing connection");
this.LogInformation("Program stopping. Closing connection");
Deactivate();
}
}
@@ -255,42 +261,41 @@ namespace PepperDash.Core;
///
/// </summary>
/// <returns></returns>
public override bool Deactivate()
{
public override bool Deactivate()
{
RetryTimer.Stop();
RetryTimer.Dispose();
if (_client != null)
{
_client.SocketStatusChange -= this.Client_SocketStatusChange;
_client.SocketStatusChange -= this.Client_SocketStatusChange;
DisconnectClient();
}
return true;
}
return true;
}
/// <summary>
/// Attempts to connect to the server
/// </summary>
public void Connect()
{
public void Connect()
{
if (string.IsNullOrEmpty(Hostname))
{
Debug.Console(1, Debug.ErrorLogLevel.Warning, "GenericTcpIpClient '{0}': No address set", Key);
this.LogWarning("GenericTcpIpClient '{0}': No address set", Key);
return;
}
if (Port < 1 || Port > 65535)
{
{
Debug.Console(1, Debug.ErrorLogLevel.Warning, "GenericTcpIpClient '{0}': Invalid port", Key);
this.LogWarning("GenericTcpIpClient '{0}': Invalid port", Key);
return;
}
}
try
lock (_connectLock)
{
connectLock.Enter();
if (IsConnected)
{
Debug.Console(1, this, "Connection already connected. Exiting Connect()");
this.LogInformation("Connection already connected. Exiting Connect()");
}
else
{
@@ -303,11 +308,7 @@ namespace PepperDash.Core;
_client.ConnectToServerAsync(ConnectToServerCallback);
}
}
finally
{
connectLock.Leave();
}
}
}
private void Reconnect()
{
@@ -315,44 +316,34 @@ namespace PepperDash.Core;
{
return;
}
try
lock (_connectLock)
{
connectLock.Enter();
if (IsConnected || DisconnectCalledByUser == true)
{
Debug.Console(1, this, "Reconnect no longer needed. Exiting Reconnect()");
this.LogInformation("Reconnect no longer needed. Exiting Reconnect()");
}
else
{
Debug.Console(1, this, "Attempting reconnect now");
this.LogInformation("Attempting reconnect now");
_client.ConnectToServerAsync(ConnectToServerCallback);
}
}
finally
{
connectLock.Leave();
}
}
/// <summary>
/// Attempts to disconnect the client
/// </summary>
public void Disconnect()
{
try
public void Disconnect()
{
lock (_connectLock)
{
connectLock.Enter();
DisconnectCalledByUser = true;
// Stop trying reconnects, if we are
RetryTimer.Stop();
DisconnectClient();
}
finally
{
connectLock.Leave();
}
}
}
/// <summary>
/// Does the actual disconnect business
@@ -361,7 +352,7 @@ namespace PepperDash.Core;
{
if (_client != null)
{
Debug.Console(1, this, "Disconnecting client");
this.LogInformation("Disconnecting client");
if (IsConnected)
_client.DisconnectFromServer();
}
@@ -371,50 +362,47 @@ namespace PepperDash.Core;
/// Callback method for connection attempt
/// </summary>
/// <param name="c"></param>
void ConnectToServerCallback(TCPClient c)
{
void ConnectToServerCallback(TCPClient c)
{
if (c.ClientStatus != SocketStatus.SOCKET_STATUS_CONNECTED)
{
Debug.Console(0, this, "Server connection result: {0}", c.ClientStatus);
this.LogInformation("Server connection result: {0}", c.ClientStatus);
WaitAndTryReconnect();
}
else
{
Debug.Console(1, this, "Server connection result: {0}", c.ClientStatus);
this.LogInformation("Server connection result: {0}", c.ClientStatus);
}
}
}
/// <summary>
/// Disconnects, waits and attemtps to connect again
/// </summary>
void WaitAndTryReconnect()
{
CrestronInvoke.BeginInvoke(o =>
void WaitAndTryReconnect()
{
Task.Run(() =>
{
try
lock (_connectLock)
{
connectLock.Enter();
if (!IsConnected && AutoReconnect && !DisconnectCalledByUser && _client != null)
{
DisconnectClient();
Debug.Console(1, this, "Attempting reconnect, status={0}", _client.ClientStatus);
RetryTimer.Reset(AutoReconnectIntervalMs);
this.LogInformation("Attempting reconnect, status={0}", _client.ClientStatus);
RetryTimer.Stop();
RetryTimer.Interval = AutoReconnectIntervalMs;
RetryTimer.Start();
}
}
finally
{
connectLock.Leave();
}
});
}
}
/// <summary>
/// Recieves incoming data
/// </summary>
/// <param name="client"></param>
/// <param name="numBytes"></param>
void Receive(TCPClient client, int numBytes)
{
void Receive(TCPClient client, int numBytes)
{
if (client != null)
{
if (numBytes > 0)
@@ -425,7 +413,7 @@ namespace PepperDash.Core;
{
if (StreamDebugging.RxStreamDebuggingIsEnabled)
{
Debug.Console(0, this, "Received {1} bytes: '{0}'", ComTextHelper.GetEscapedText(bytes), bytes.Length);
this.LogInformation("Received {1} bytes: '{0}'", ComTextHelper.GetEscapedText(bytes), bytes.Length);
}
bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes));
}
@@ -436,129 +424,135 @@ namespace PepperDash.Core;
if (StreamDebugging.RxStreamDebuggingIsEnabled)
{
Debug.Console(0, this, "Received {1} characters of text: '{0}'", ComTextHelper.GetDebugText(str), str.Length);
this.LogInformation("Received {1} characters of text: '{0}'", ComTextHelper.GetDebugText(str), str.Length);
}
textHandler(this, new GenericCommMethodReceiveTextArgs(str));
}
}
}
client.ReceiveDataAsync(Receive);
}
}
}
/// <summary>
/// General send method
/// </summary>
public void SendText(string text)
{
var bytes = Encoding.GetEncoding(28591).GetBytes(text);
// Check debug level before processing byte array
/// <summary>
/// General send method
/// </summary>
public void SendText(string text)
{
var bytes = Encoding.GetEncoding(28591).GetBytes(text);
// Check debug level before processing byte array
if (StreamDebugging.TxStreamDebuggingIsEnabled)
Debug.Console(0, this, "Sending {0} characters of text: '{1}'", text.Length, ComTextHelper.GetDebugText(text));
this.LogInformation("Sending {0} characters of text: '{1}'", text.Length, ComTextHelper.GetDebugText(text));
if (_client != null)
_client.SendData(bytes, bytes.Length);
}
_client.SendData(bytes, bytes.Length);
}
/// <summary>
/// This is useful from console and...?
/// </summary>
public void SendEscapedText(string text)
{
var unescapedText = Regex.Replace(text, @"\\x([0-9a-fA-F][0-9a-fA-F])", s =>
{
var hex = s.Groups[1].Value;
return ((char)Convert.ToByte(hex, 16)).ToString();
});
SendText(unescapedText);
}
/// <summary>
/// SendEscapedText method
/// </summary>
public void SendEscapedText(string text)
{
var unescapedText = Regex.Replace(text, @"\\x([0-9a-fA-F][0-9a-fA-F])", s =>
{
var hex = s.Groups[1].Value;
return ((char)Convert.ToByte(hex, 16)).ToString();
});
SendText(unescapedText);
}
/// <summary>
/// Sends Bytes to the server
/// </summary>
/// <param name="bytes"></param>
public void SendBytes(byte[] bytes)
{
public void SendBytes(byte[] bytes)
{
if (StreamDebugging.TxStreamDebuggingIsEnabled)
Debug.Console(0, this, "Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes));
this.LogInformation("Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes));
if (_client != null)
_client.SendData(bytes, bytes.Length);
}
_client.SendData(bytes, bytes.Length);
}
/// <summary>
/// Socket Status Change Handler
/// </summary>
/// <param name="client"></param>
/// <param name="clientSocketStatus"></param>
void Client_SocketStatusChange(TCPClient client, SocketStatus clientSocketStatus)
{
void Client_SocketStatusChange(TCPClient client, SocketStatus clientSocketStatus)
{
if (clientSocketStatus != SocketStatus.SOCKET_STATUS_CONNECTED)
{
Debug.Console(0, this, "Socket status change {0} ({1})", clientSocketStatus, ClientStatusText);
this.LogDebug("Socket status change {0} ({1})", clientSocketStatus, ClientStatusText);
WaitAndTryReconnect();
}
else
{
Debug.Console(1, this, "Socket status change {0} ({1})", clientSocketStatus, ClientStatusText);
_client.ReceiveDataAsync(Receive);
this.LogDebug("Socket status change {0} ({1})", clientSocketStatus, ClientStatusText);
_client.ReceiveDataAsync(Receive);
}
var handler = ConnectionChange;
if (handler != null)
ConnectionChange(this, new GenericSocketStatusChageEventArgs(this));
}
}
var handler = ConnectionChange;
if (handler != null)
ConnectionChange(this, new GenericSocketStatusChageEventArgs(this));
}
}
/// <summary>
/// Configuration properties for TCP/SSH Connections
/// </summary>
public class TcpSshPropertiesConfig
{
public class TcpSshPropertiesConfig
{
/// <summary>
/// Address to connect to
/// </summary>
[JsonProperty(Required = Required.Always)]
public string Address { get; set; }
[JsonProperty(Required = Required.Always)]
public string Address { get; set; }
/// <summary>
/// Port to connect to
/// </summary>
[JsonProperty(Required = Required.Always)]
public int Port { get; set; }
[JsonProperty(Required = Required.Always)]
public int Port { get; set; }
/// <summary>
/// Username credential
/// </summary>
public string Username { get; set; }
public string Username { get; set; }
/// <summary>
/// Passord credential
/// </summary>
public string Password { get; set; }
public string Password { get; set; }
/// <summary>
/// Defaults to 32768
/// </summary>
public int BufferSize { get; set; }
/// <summary>
/// Defaults to 32768
/// </summary>
public int BufferSize { get; set; }
/// <summary>
/// Defaults to true
/// </summary>
public bool AutoReconnect { get; set; }
/// <summary>
/// Gets or sets the AutoReconnect
/// </summary>
public bool AutoReconnect { get; set; }
/// <summary>
/// Defaults to 5000ms
/// </summary>
public int AutoReconnectIntervalMs { get; set; }
/// <summary>
/// Gets or sets the AutoReconnectIntervalMs
/// </summary>
public int AutoReconnectIntervalMs { get; set; }
/// <summary>
/// When true, turns off echo for the SSH session
/// </summary>
[JsonProperty("disableSshEcho")]
public bool DisableSshEcho { get; set; }
/// <summary>
/// Default constructor
/// </summary>
public TcpSshPropertiesConfig()
{
BufferSize = 32768;
AutoReconnect = true;
AutoReconnectIntervalMs = 5000;
public TcpSshPropertiesConfig()
{
BufferSize = 32768;
AutoReconnect = true;
AutoReconnectIntervalMs = 5000;
Username = "";
Password = "";
}
}
}
}

View File

@@ -13,6 +13,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using Crestron.SimplSharp;
using Crestron.SimplSharp.CrestronSockets;
using PepperDash.Core.Logging;
@@ -61,9 +62,14 @@ public class GenericTcpIpServer : Device
#region Properties/Variables
/// <summary>
///
/// Server listen lock
/// </summary>
CCriticalSection ServerCCSection = new CCriticalSection();
object _serverLock = new();
/// <summary>
/// Broadcast lock
/// </summary>
private readonly object _broadcastLock = new();
/// <summary>
@@ -365,12 +371,12 @@ public class GenericTcpIpServer : Device
}
else
{
ErrorLog.Error("Could not initialize server with key: {0}", serverConfigObject.Key);
this.LogError("Could not initialize server with key: {0}", serverConfigObject.Key);
}
}
catch
{
ErrorLog.Error("Could not initialize server with key: {0}", serverConfigObject.Key);
this.LogError("Could not initialize server with key: {0}", serverConfigObject.Key);
}
}
@@ -379,19 +385,18 @@ public class GenericTcpIpServer : Device
/// </summary>
public void Listen()
{
ServerCCSection.Enter();
lock (_serverLock)
{
try
{
if (Port < 1 || Port > 65535)
{
Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Server '{0}': Invalid port", Key);
ErrorLog.Warn(string.Format("Server '{0}': Invalid port", Key));
this.LogError("Server '{0}': Invalid port", Key);
return;
}
if (string.IsNullOrEmpty(SharedKey) && SharedKeyRequired)
{
Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Server '{0}': No Shared Key set", Key);
ErrorLog.Warn(string.Format("Server '{0}': No Shared Key set", Key));
this.LogError("Server '{0}': No Shared Key set", Key);
return;
}
if (IsListening)
@@ -417,18 +422,15 @@ public class GenericTcpIpServer : Device
ServerStopped = false;
myTcpServer.WaitForConnectionAsync(IPAddress.Any, TcpConnectCallback);
OnServerStateChange(myTcpServer.State);
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "TCP Server Status: {0}, Socket Status: {1}", myTcpServer.State, myTcpServer.ServerSocketStatus);
this.LogInformation("TCP Server Status: {0}, Socket Status: {1}", myTcpServer.State, myTcpServer.ServerSocketStatus);
// StartMonitorClient();
ServerCCSection.Leave();
}
catch (Exception ex)
{
ServerCCSection.Leave();
ErrorLog.Error("{1} Error with Dynamic Server: {0}", ex.ToString(), Key);
this.LogException(ex, "Error with Dynamic Server: {0}", ex.Message);
}
} // end lock
}
/// <summary>
@@ -438,18 +440,18 @@ public class GenericTcpIpServer : Device
{
try
{
Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Stopping Listener");
this.LogDebug("Stopping Listener");
if (myTcpServer != null)
{
myTcpServer.Stop();
Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Server State: {0}", myTcpServer.State);
this.LogDebug("Server State: {0}", myTcpServer.State);
OnServerStateChange(myTcpServer.State);
}
ServerStopped = true;
}
catch (Exception ex)
{
Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error stopping server. Error: {0}", ex);
this.LogException(ex, "Error stopping server. Error: {0}", ex.Message);
}
}
@@ -462,11 +464,11 @@ public class GenericTcpIpServer : Device
try
{
myTcpServer.Disconnect(client);
Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Disconnected client index: {0}", client);
this.LogVerbose("Disconnected client index: {0}", client);
}
catch (Exception ex)
{
Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error Disconnecting client index: {0}. Error: {1}", client, ex);
this.LogException(ex, "Error Disconnecting client index: {0}. Error: {1}", client, ex.Message);
}
}
/// <summary>
@@ -474,7 +476,7 @@ public class GenericTcpIpServer : Device
/// </summary>
public void DisconnectAllClientsForShutdown()
{
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Disconnecting All Clients");
this.LogInformation("Disconnecting All Clients");
if (myTcpServer != null)
{
myTcpServer.SocketStatusChange -= TcpServer_SocketStatusChange;
@@ -486,17 +488,17 @@ public class GenericTcpIpServer : Device
try
{
myTcpServer.Disconnect(i);
Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Disconnected client index: {0}", i);
this.LogVerbose("Disconnected client index: {0}", i);
}
catch (Exception ex)
{
Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error Disconnecting client index: {0}. Error: {1}", i, ex);
this.LogException(ex, "Error Disconnecting client index: {0}. Error: {1}", i, ex.Message);
}
}
Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Server Status: {0}", myTcpServer.ServerSocketStatus);
this.LogVerbose("Server Status: {0}", myTcpServer.ServerSocketStatus);
}
Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Disconnected All Clients");
this.LogInformation("Disconnected All Clients");
ConnectedClientsIndexes.Clear();
if (!ProgramIsStopping)
@@ -514,8 +516,8 @@ public class GenericTcpIpServer : Device
/// <param name="text"></param>
public void BroadcastText(string text)
{
CCriticalSection CCBroadcast = new CCriticalSection();
CCBroadcast.Enter();
lock (_broadcastLock)
{
try
{
if (ConnectedClientsIndexes.Count > 0)
@@ -531,13 +533,12 @@ public class GenericTcpIpServer : Device
}
}
}
CCBroadcast.Leave();
}
catch (Exception ex)
{
CCBroadcast.Leave();
Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error Broadcasting messages from server. Error: {0}", ex.Message);
this.LogException(ex, "Error Broadcasting messages from server. Error: {0}", ex.Message);
}
} // end lock
}
/// <summary>
@@ -558,7 +559,7 @@ public class GenericTcpIpServer : Device
}
catch (Exception ex)
{
Debug.Console(2, this, "Error sending text to client. Text: {1}. Error: {0}", ex.Message, text);
this.LogException(ex, "Error sending text to client. Text: {1}. Error: {0}", ex.Message, text);
}
}
@@ -582,7 +583,7 @@ public class GenericTcpIpServer : Device
CTimer HeartbeatTimer = new CTimer(HeartbeatTimer_CallbackFunction, clientIndex, HeartbeatRequiredIntervalMs);
HeartbeatTimerDictionary.Add(clientIndex, HeartbeatTimer);
}
Debug.Console(1, this, "Heartbeat Received: {0}, from client index: {1}", HeartbeatStringToMatch, clientIndex);
this.LogVerbose("Heartbeat Received: {0}, from client index: {1}", HeartbeatStringToMatch, clientIndex);
// Return Heartbeat
SendTextToClient(HeartbeatStringToMatch, clientIndex);
return remainingText;
@@ -597,13 +598,13 @@ public class GenericTcpIpServer : Device
CTimer HeartbeatTimer = new CTimer(HeartbeatTimer_CallbackFunction, clientIndex, HeartbeatRequiredIntervalMs);
HeartbeatTimerDictionary.Add(clientIndex, HeartbeatTimer);
}
Debug.Console(1, this, "Heartbeat Received: {0}, from client index: {1}", received, clientIndex);
this.LogVerbose("Heartbeat Received: {0}, from client index: {1}", received, clientIndex);
}
}
}
catch (Exception ex)
{
Debug.Console(1, this, "Error checking heartbeat: {0}", ex.Message);
this.LogException(ex, "Error checking heartbeat: {0}", ex.Message);
}
return received;
}
@@ -615,11 +616,11 @@ public class GenericTcpIpServer : Device
/// <returns>IP address of the client</returns>
public string GetClientIPAddress(uint clientIndex)
{
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "GetClientIPAddress Index: {0}", clientIndex);
this.LogVerbose("GetClientIPAddress Index: {0}", clientIndex);
if (!SharedKeyRequired || (SharedKeyRequired && ClientReadyAfterKeyExchange.Contains(clientIndex)))
{
var ipa = this.myTcpServer.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex);
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "GetClientIPAddress IPAddreess: {0}", ipa);
this.LogVerbose("GetClientIPAddress IPAddreess: {0}", ipa);
return ipa;
}
@@ -642,7 +643,7 @@ public class GenericTcpIpServer : Device
clientIndex = (uint)o;
address = myTcpServer.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex);
Debug.Console(1, this, Debug.ErrorLogLevel.Warning, "Heartbeat not received for Client index {2} IP: {0}, DISCONNECTING BECAUSE HEARTBEAT REQUIRED IS TRUE {1}",
this.LogWarning("Heartbeat not received for Client index {2} IP: {0}, DISCONNECTING BECAUSE HEARTBEAT REQUIRED IS TRUE {1}",
address, string.IsNullOrEmpty(HeartbeatStringToMatch) ? "" : ("HeartbeatStringToMatch: " + HeartbeatStringToMatch), clientIndex);
if (myTcpServer.GetServerSocketStatusForSpecificClient(clientIndex) == SocketStatus.SOCKET_STATUS_CONNECTED)
@@ -678,7 +679,7 @@ public class GenericTcpIpServer : Device
try
{
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "SecureServerSocketStatusChange Index:{0} status:{1} Port:{2} IP:{3}", clientIndex, serverSocketStatus, this.myTcpServer.GetPortNumberServerAcceptedConnectionFromForSpecificClient(clientIndex), this.myTcpServer.GetLocalAddressServerAcceptedConnectionFromForSpecificClient(clientIndex));
this.LogInformation("SecureServerSocketStatusChange Index:{0} status:{1} Port:{2} IP:{3}", clientIndex, serverSocketStatus, this.myTcpServer.GetPortNumberServerAcceptedConnectionFromForSpecificClient(clientIndex), this.myTcpServer.GetLocalAddressServerAcceptedConnectionFromForSpecificClient(clientIndex));
if (serverSocketStatus != SocketStatus.SOCKET_STATUS_CONNECTED)
{
if (ConnectedClientsIndexes.Contains(clientIndex))
@@ -697,7 +698,7 @@ public class GenericTcpIpServer : Device
}
catch (Exception ex)
{
Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error in Socket Status Change Callback. Error: {0}", ex);
this.LogException(ex, "Error in Socket Status Change Callback. Error: {0}", ex);
}
onConnectionChange(clientIndex, server.GetServerSocketStatusForSpecificClient(clientIndex));
}
@@ -714,7 +715,7 @@ public class GenericTcpIpServer : Device
{
try
{
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "ConnectCallback: IPAddress: {0}. Index: {1}. Status: {2}",
this.LogDebug("ConnectCallback: IPAddress: {0}. Index: {1}. Status: {2}",
server.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex),
clientIndex, server.GetServerSocketStatusForSpecificClient(clientIndex));
if (clientIndex != 0)
@@ -734,10 +735,11 @@ public class GenericTcpIpServer : Device
}
byte[] b = Encoding.GetEncoding(28591).GetBytes("SharedKey:");
server.SendDataAsync(clientIndex, b, b.Length, (x, y, z) => { });
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Sent Shared Key Request to client at {0}", server.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex));
this.LogDebug("Sent Shared Key Request to client at {0}", server.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex));
}
else
{
this.LogDebug("Client at index {0} is ready for communications", clientIndex);
OnServerClientReadyForCommunications(clientIndex);
}
if (HeartbeatRequired)
@@ -753,7 +755,7 @@ public class GenericTcpIpServer : Device
}
else
{
Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Client attempt faulty.");
this.LogError("Client attempt faulty.");
if (!ServerStopped)
{
server.WaitForConnectionAsync(IPAddress.Any, TcpConnectCallback);
@@ -763,7 +765,7 @@ public class GenericTcpIpServer : Device
}
catch (Exception ex)
{
Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error in Socket Status Connect Callback. Error: {0}", ex);
this.LogException(ex, "Error in Socket Status Connect Callback. Error: {0}", ex);
}
//Debug.Console(1, this, Debug.ErrorLogLevel, "((((((Server State bitfield={0}; maxclient={1}; ServerStopped={2}))))))",
// server.State,
@@ -771,7 +773,7 @@ public class GenericTcpIpServer : Device
// ServerStopped);
if ((server.State & ServerState.SERVER_LISTENING) != ServerState.SERVER_LISTENING && MaxClients > 1 && !ServerStopped)
{
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Waiting for next connection");
this.LogDebug("Waiting for next connection");
server.WaitForConnectionAsync(IPAddress.Any, TcpConnectCallback);
}
@@ -802,7 +804,7 @@ public class GenericTcpIpServer : Device
if (received != SharedKey)
{
byte[] b = Encoding.GetEncoding(28591).GetBytes("Shared key did not match server. Disconnecting");
Debug.Console(1, this, Debug.ErrorLogLevel.Warning, "Client at index {0} Shared key did not match the server, disconnecting client. Key: {1}", clientIndex, received);
this.LogWarning("Client at index {0} Shared key did not match the server, disconnecting client. Key: {1}", clientIndex, received);
myTCPServer.SendData(clientIndex, b, b.Length);
myTCPServer.Disconnect(clientIndex);
return;
@@ -812,7 +814,7 @@ public class GenericTcpIpServer : Device
byte[] success = Encoding.GetEncoding(28591).GetBytes("Shared Key Match");
myTCPServer.SendDataAsync(clientIndex, success, success.Length, null);
OnServerClientReadyForCommunications(clientIndex);
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Client with index {0} provided the shared key and successfully connected to the server", clientIndex);
this.LogDebug("Client with index {0} provided the shared key and successfully connected to the server", clientIndex);
}
else if (!string.IsNullOrEmpty(checkHeartbeat(clientIndex, received)))
@@ -820,7 +822,7 @@ public class GenericTcpIpServer : Device
}
catch (Exception ex)
{
Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error Receiving data: {0}. Error: {1}", received, ex);
this.LogException(ex, "Error Receiving data: {0}. Error: {1}", received, ex);
}
if (myTCPServer.GetServerSocketStatusForSpecificClient(clientIndex) == SocketStatus.SOCKET_STATUS_CONNECTED)
myTCPServer.ReceiveDataAsync(clientIndex, TcpServerReceivedDataAsyncCallback);
@@ -901,7 +903,7 @@ public class GenericTcpIpServer : Device
if (MonitorClient != null)
MonitorClient.Disconnect();
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Program stopping. Closing server");
this.LogInformation("Program stopping. Closing server");
KillServer();
}
}
@@ -942,7 +944,7 @@ public class GenericTcpIpServer : Device
//MonitorClient.ConnectionChange += MonitorClient_ConnectionChange;
MonitorClient.ClientReadyForCommunications += MonitorClient_IsReadyForComm;
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Starting monitor check");
this.LogDebug("Starting monitor check");
MonitorClient.Connect();
// From here MonitorCLient either connects or hangs, MonitorClient will call back
@@ -969,7 +971,7 @@ public class GenericTcpIpServer : Device
{
if (args.IsReady)
{
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Monitor client connection success. Disconnecting in 2s");
this.LogInformation("Monitor client connection success. Disconnecting in 2s");
MonitorClientTimer.Stop();
MonitorClientTimer = null;
MonitorClientFailureCount = 0;
@@ -990,13 +992,13 @@ public class GenericTcpIpServer : Device
StopMonitorClient();
if (MonitorClientFailureCount < MonitorClientMaxFailureCount)
{
Debug.Console(2, this, Debug.ErrorLogLevel.Warning, "Monitor client connection has hung {0} time{1}, maximum {2}",
this.LogWarning("Monitor client connection has hung {0} time{1}, maximum {2}",
MonitorClientFailureCount, MonitorClientFailureCount > 1 ? "s" : "", MonitorClientMaxFailureCount);
StartMonitorClient();
}
else
{
Debug.Console(2, this, Debug.ErrorLogLevel.Error,
this.LogError(
"\r***************************\rMonitor client connection has hung a maximum of {0} times.\r***************************",
MonitorClientMaxFailureCount);

View File

@@ -1,12 +1,15 @@
extern alias NewtonsoftJson;
using System;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
using Crestron.SimplSharp.CrestronSockets;
using Newtonsoft.Json;
using JsonProperty = NewtonsoftJson::Newtonsoft.Json.JsonPropertyAttribute;
using PepperDash.Core.Logging;
using Required = NewtonsoftJson::Newtonsoft.Json.Required;
namespace PepperDash.Core;
@@ -57,7 +60,7 @@ public class GenericUdpServer : Device, ISocketStatusWithStreamDebugging
}
/// <summary>
///
/// Represents a GenericUdpReceiveTextExtraArgs
/// </summary>
public ushort UStatus
{
@@ -350,18 +353,18 @@ public class GenericUdpServer : Device, ISocketStatusWithStreamDebugging
/// <param name="port"></param>
/// <param name="bytes"></param>
public GenericUdpReceiveTextExtraArgs(string text, string ipAddress, int port, byte[] bytes)
{
Text = text;
IpAddress = ipAddress;
Port = port;
Bytes = bytes;
}
{
Text = text;
IpAddress = ipAddress;
Port = port;
Bytes = bytes;
}
/// <summary>
/// Stupid S+ Constructor
/// </summary>
public GenericUdpReceiveTextExtraArgs() { }
}
/// <summary>
/// Stupid S+ Constructor
/// </summary>
public GenericUdpReceiveTextExtraArgs() { }
}
/// <summary>
///

View File

@@ -0,0 +1,69 @@
using System;
using Crestron.SimplSharp;
namespace PepperDash.Core
{
/// <summary>
/// Extension methods for stream debugging
/// </summary>
public static class StreamDebuggingExtensions
{
private static readonly string app = CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance ? $"App {InitialParametersClass.ApplicationNumber}" : $"{InitialParametersClass.RoomId}";
/// <summary>
/// Print the sent bytes to the console
/// </summary>
/// <param name="comms">comms device</param>
/// <param name="bytes">bytes to print</param>
public static void PrintSentBytes(this IStreamDebugging comms, byte[] bytes)
{
if (!comms.StreamDebugging.TxStreamDebuggingIsEnabled) return;
var timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff");
CrestronConsole.PrintLine($"[{timestamp}][{app}][{comms.Key}] Sending {bytes.Length} bytes: '{ComTextHelper.GetEscapedText(bytes)}'");
}
/// <summary>
/// Print the received bytes to the console
/// </summary>
/// <param name="comms">comms device</param>
/// <param name="bytes">bytes to print</param>
public static void PrintReceivedBytes(this IStreamDebugging comms, byte[] bytes)
{
if (!comms.StreamDebugging.RxStreamDebuggingIsEnabled) return;
var timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff");
CrestronConsole.PrintLine($"[{timestamp}][{app}][{comms.Key}] Received {bytes.Length} bytes: '{ComTextHelper.GetEscapedText(bytes)}'");
}
/// <summary>
/// Print the sent text to the console
/// </summary>
/// <param name="comms">comms device</param>
/// <param name="text">text to print</param>
public static void PrintSentText(this IStreamDebugging comms, string text)
{
if (!comms.StreamDebugging.TxStreamDebuggingIsEnabled) return;
var timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff");
CrestronConsole.PrintLine($"[{timestamp}][{app}][{comms.Key}] Sending Text: '{ComTextHelper.GetDebugText(text)}'");
}
/// <summary>
/// Print the received text to the console
/// </summary>
/// <param name="comms">comms device</param>
/// <param name="text">text to print</param>
public static void PrintReceivedText(this IStreamDebugging comms, string text)
{
if (!comms.StreamDebugging.RxStreamDebuggingIsEnabled) return;
var timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff");
CrestronConsole.PrintLine($"[{timestamp}][{app}][{comms.Key}] Received Text: '{ComTextHelper.GetDebugText(text)}'");
}
}
}

View File

@@ -1,4 +1,6 @@
using Newtonsoft.Json;
extern alias NewtonsoftJson;
using JsonProperty = NewtonsoftJson::Newtonsoft.Json.JsonPropertyAttribute;
namespace PepperDash.Core;

View File

@@ -74,5 +74,13 @@ public enum eControlMethod
/// <summary>
/// Secure TCP/IP
/// </summary>
SecureTcpIp
SecureTcpIp,
/// <summary>
/// Crestron COM bridge
/// </summary>
ComBridge,
/// <summary>
/// Crestron Infinet EX device
/// </summary>
InfinetEx
}

View File

@@ -0,0 +1,24 @@
using System;
namespace PepperDash.Core
{
/// <summary>
/// The available settings for stream debugging data format types
/// </summary>
[Flags]
public enum eStreamDebuggingDataTypeSettings
{
/// <summary>
/// Debug data in byte format
/// </summary>
Bytes = 0,
/// <summary>
/// Debug data in text format
/// </summary>
Text = 1,
/// <summary>
/// Debug data in both byte and text formats
/// </summary>
Both = Bytes | Text
}
}

View File

@@ -1,11 +1,15 @@
using System;
extern alias NewtonsoftJson;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using Crestron.SimplSharp;
using Crestron.SimplSharp.CrestronSockets;
using System.Text.RegularExpressions;
using Newtonsoft.Json;
using JsonConverter = NewtonsoftJson::Newtonsoft.Json.JsonConverterAttribute;
using JsonProperty = NewtonsoftJson::Newtonsoft.Json.JsonPropertyAttribute;
using StringEnumConverter = NewtonsoftJson::Newtonsoft.Json.Converters.StringEnumConverter;
namespace PepperDash.Core;
@@ -39,7 +43,7 @@ public interface ICommunicationReceiver : IKeyed
}
/// <summary>
/// Represents a device that uses basic connection
/// Extends <see cref="ICommunicationReceiver"/> with methods for sending text and bytes to a device.
/// </summary>
public interface IBasicCommunication : ICommunicationReceiver
{
@@ -54,7 +58,7 @@ public interface IBasicCommunication : ICommunicationReceiver
/// </summary>
/// <param name="bytes"></param>
void SendBytes(byte[] bytes);
}
}
/// <summary>
/// Represents a device that implements IBasicCommunication and IStreamDebugging
@@ -67,7 +71,7 @@ public interface IBasicCommunicationWithStreamDebugging : IBasicCommunication, I
/// <summary>
/// Represents a device with stream debugging capablities
/// </summary>
public interface IStreamDebugging
public interface IStreamDebugging : IKeyed
{
/// <summary>
/// Object to enable stream debugging
@@ -91,7 +95,7 @@ public interface IStreamDebugging
/// The current socket status of the client
/// </summary>
[JsonProperty("clientStatus")]
[JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
[JsonConverter(typeof(StringEnumConverter))]
SocketStatus ClientStatus { get; }
}
@@ -135,59 +139,60 @@ public interface ISocketStatusWithStreamDebugging : ISocketStatus, IStreamDebugg
Disconnected
}
/// <summary>
/// This delegate defines handler for IBasicCommunication status changes
/// </summary>
/// <param name="comm">Device firing the status change</param>
/// <param name="status"></param>
public delegate void GenericCommMethodStatusHandler(IBasicCommunication comm, eGenericCommMethodStatusChangeType status);
/// <summary>
/// This delegate defines handler for IBasicCommunication status changes
/// </summary>
/// <param name="comm">Device firing the status change</param>
/// <param name="status"></param>
public delegate void GenericCommMethodStatusHandler(IBasicCommunication comm, eGenericCommMethodStatusChangeType status);
/// <summary>
///
/// Event args for bytes received from a communication method
/// </summary>
public class GenericCommMethodReceiveBytesArgs : EventArgs
{
/// <summary>
///
/// The bytes received
/// </summary>
public byte[] Bytes { get; private set; }
/// <summary>
///
/// Constructor
/// </summary>
/// <param name="bytes"></param>
public GenericCommMethodReceiveBytesArgs(byte[] bytes)
{
Bytes = bytes;
}
{
Bytes = bytes;
}
/// <summary>
/// S+ Constructor
/// </summary>
public GenericCommMethodReceiveBytesArgs() { }
}
/// <summary>
/// S+ Constructor
/// </summary>
public GenericCommMethodReceiveBytesArgs() { }
}
/// <summary>
///
/// Event args for text received
/// </summary>
public class GenericCommMethodReceiveTextArgs : EventArgs
{
/// <summary>
///
/// The text received
/// </summary>
public string Text { get; private set; }
/// <summary>
///
/// The delimiter used to determine the end of a message, if applicable
/// </summary>
public string Delimiter { get; private set; }
/// <summary>
///
/// Constructor
/// </summary>
/// <param name="text"></param>
public GenericCommMethodReceiveTextArgs(string text)
{
Text = text;
}
{
Text = text;
}
/// <summary>
///
@@ -209,7 +214,7 @@ public interface ISocketStatusWithStreamDebugging : ISocketStatus, IStreamDebugg
/// <summary>
///
/// Helper class to get escaped text for debugging communication streams
/// </summary>
public class ComTextHelper
{
@@ -243,4 +248,4 @@ public interface ISocketStatusWithStreamDebugging : ISocketStatus, IStreamDebugg
{
return Regex.Replace(text, @"[^\u0020-\u007E]", a => GetEscapedText(a.Value));
}
}
}

View File

@@ -1,9 +1,13 @@
using System;
extern alias NewtonsoftJson;
using System;
using System.Linq;
using Crestron.SimplSharp;
using Crestron.SimplSharp.CrestronIO;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Formatting = NewtonsoftJson::Newtonsoft.Json.Formatting;
using JArray = NewtonsoftJson::Newtonsoft.Json.Linq.JArray;
using JObject = NewtonsoftJson::Newtonsoft.Json.Linq.JObject;
using JToken = NewtonsoftJson::Newtonsoft.Json.Linq.JToken;
using PepperDash.Core;
using Serilog.Events;
@@ -14,35 +18,52 @@ namespace PepperDash.Core.Config;
/// </summary>
public class PortalConfigReader
{
/// <summary>
/// Reads the config file, checks if it needs a merge, merges and saves, then returns the merged Object.
/// </summary>
/// <returns>JObject of config file</returns>
public static void ReadAndMergeFileIfNecessary(string filePath, string savePath)
const string template = "template";
const string system = "system";
const string systemUrl = "system_url";
const string templateUrl = "template_url";
const string info = "info";
const string devices = "devices";
const string rooms = "rooms";
const string sourceLists = "sourceLists";
const string destinationLists = "destinationLists";
const string cameraLists = "cameraLists";
const string audioControlPointLists = "audioControlPointLists";
const string tieLines = "tieLines";
const string joinMaps = "joinMaps";
const string global = "global";
/// <summary>
/// Reads the config file, checks if it needs a merge, merges and saves, then returns the merged Object.
/// </summary>
/// <returns>JObject of config file</returns>
public static void ReadAndMergeFileIfNecessary(string filePath, string savePath)
{
try
{
if (!File.Exists(filePath))
{
Debug.Console(1, Debug.ErrorLogLevel.Error,
Debug.LogError(
"ERROR: Configuration file not present. Please load file to {0} and reset program", filePath);
}
using (StreamReader fs = new StreamReader(filePath))
{
var jsonObj = JObject.Parse(fs.ReadToEnd());
if(jsonObj["template"] != null && jsonObj["system"] != null)
if(jsonObj[template] != null && jsonObj[system] != null)
{
// it's a double-config, merge it.
var merged = MergeConfigs(jsonObj);
if (jsonObj["system_url"] != null)
if (jsonObj[systemUrl] != null)
{
merged["systemUrl"] = jsonObj["system_url"].Value<string>();
merged[systemUrl] = (string)jsonObj[systemUrl];
}
if (jsonObj["template_url"] != null)
if (jsonObj[templateUrl] != null)
{
merged["templateUrl"] = jsonObj["template_url"].Value<string>();
merged[templateUrl] = (string)jsonObj[templateUrl];
}
jsonObj = merged;
@@ -67,6 +88,9 @@ namespace PepperDash.Core.Config;
/// </summary>
/// <param name="doubleConfig"></param>
/// <returns></returns>
/// <summary>
/// MergeConfigs method
/// </summary>
public static JObject MergeConfigs(JObject doubleConfig)
{
var system = JObject.FromObject(doubleConfig["system"]);
@@ -74,30 +98,30 @@ namespace PepperDash.Core.Config;
var merged = new JObject();
// Put together top-level objects
if (system["info"] != null)
merged.Add("info", Merge(template["info"], system["info"], "infO"));
if (system[info] != null)
merged.Add(info, Merge(template[info], system[info], info));
else
merged.Add("info", template["info"]);
merged.Add(info, template[info]);
merged.Add("devices", MergeArraysOnTopLevelProperty(template["devices"] as JArray,
system["devices"] as JArray, "key", "devices"));
merged.Add(devices, MergeArraysOnTopLevelProperty(template[devices] as JArray,
system[devices] as JArray, "key", devices));
if (system["rooms"] == null)
merged.Add("rooms", template["rooms"]);
if (system[rooms] == null)
merged.Add(rooms, template[rooms]);
else
merged.Add("rooms", MergeArraysOnTopLevelProperty(template["rooms"] as JArray,
system["rooms"] as JArray, "key", "rooms"));
merged.Add(rooms, MergeArraysOnTopLevelProperty(template[rooms] as JArray,
system[rooms] as JArray, "key", rooms));
if (system["sourceLists"] == null)
merged.Add("sourceLists", template["sourceLists"]);
if (system[sourceLists] == null)
merged.Add(sourceLists, template[sourceLists]);
else
merged.Add("sourceLists", Merge(template["sourceLists"], system["sourceLists"], "sourceLists"));
merged.Add(sourceLists, Merge(template[sourceLists], system[sourceLists], sourceLists));
if (system["destinationLists"] == null)
merged.Add("destinationLists", template["destinationLists"]);
if (system[destinationLists] == null)
merged.Add(destinationLists, template[destinationLists]);
else
merged.Add("destinationLists",
Merge(template["destinationLists"], system["destinationLists"], "destinationLists"));
merged.Add(destinationLists,
Merge(template[destinationLists], system[destinationLists], destinationLists));
if (system["cameraLists"] == null)
@@ -119,17 +143,17 @@ namespace PepperDash.Core.Config;
else if (system["tieLines"] != null)
merged.Add("tieLines", system["tieLines"]);
else
merged.Add("tieLines", new JArray());
merged.Add(tieLines, new JArray());
if (template["joinMaps"] != null)
merged.Add("joinMaps", template["joinMaps"]);
else
merged.Add("joinMaps", new JObject());
if (system["global"] != null)
merged.Add("global", Merge(template["global"], system["global"], "global"));
if (system[global] != null)
merged.Add(global, Merge(template[global], system[global], global));
else
merged.Add("global", template["global"]);
merged.Add(global, template[global]);
//Debug.Console(2, "MERGED CONFIG RESULT: \x0d\x0a{0}", merged);
return merged;
@@ -225,7 +249,7 @@ namespace PepperDash.Core.Config;
}
catch (Exception e)
{
Debug.Console(1, Debug.ErrorLogLevel.Warning, "Cannot merge items at path {0}: \r{1}", propPath, e);
Debug.LogError($"Cannot merge items at path {propPath}: \r{e}");
}
}
}

View File

@@ -1,9 +1,11 @@
using System;
extern alias NewtonsoftJson;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
using Newtonsoft.Json;
using JsonProperty = NewtonsoftJson::Newtonsoft.Json.JsonPropertyAttribute;
using Serilog;
namespace PepperDash.Core;

View File

@@ -4,188 +4,192 @@ using Serilog.Events;
namespace PepperDash.Core;
//*********************************************************************************************************
//*********************************************************************************************************
/// <summary>
/// Represents a Device
/// </summary>
public class Device : IKeyName
{
/// <summary>
/// The core event and status-bearing class that most if not all device and connectors can derive from.
/// Unique Key
/// </summary>
public class Device : IKeyName
public string Key { get; protected set; }
/// <summary>
/// Gets or sets the Name
/// </summary>
public string Name { get; protected set; }
/// <summary>
///
/// </summary>
public bool Enabled { get; protected set; }
/// <summary>
/// A place to store reference to the original config object, if any. These values should
/// NOT be used as properties on the device as they are all publicly-settable values.
/// </summary>
//public DeviceConfig Config { get; private set; }
/// <summary>
/// Helper method to check if Config exists
/// </summary>
//public bool HasConfig { get { return Config != null; } }
List<Action> _PreActivationActions;
List<Action> _PostActivationActions;
/// <summary>
///
/// </summary>
public static Device DefaultDevice { get { return _DefaultDevice; } }
static Device _DefaultDevice = new Device("Default", "Default");
/// <summary>
/// Base constructor for all Devices.
/// </summary>
/// <param name="key"></param>
public Device(string key)
{
Key = key;
if (key.Contains(".")) Debug.LogMessage(LogEventLevel.Information, "WARNING: Device key should not include '.'", this);
Name = "";
}
/// <summary>
/// Unique Key
/// </summary>
public string Key { get; protected set; }
/// <summary>
/// Name of the devie
/// </summary>
public string Name { get; protected set; }
/// <summary>
///
/// </summary>
public bool Enabled { get; protected set; }
/// <summary>
/// Constructor with key and name
/// </summary>
/// <param name="key"></param>
/// <param name="name"></param>
public Device(string key, string name) : this(key)
{
Name = name;
///// <summary>
///// A place to store reference to the original config object, if any. These values should
///// NOT be used as properties on the device as they are all publicly-settable values.
///// </summary>
//public DeviceConfig Config { get; private set; }
///// <summary>
///// Helper method to check if Config exists
///// </summary>
//public bool HasConfig { get { return Config != null; } }
}
List<Action> _PreActivationActions;
List<Action> _PostActivationActions;
//public Device(DeviceConfig config)
// : this(config.Key, config.Name)
//{
// Config = config;
//}
/// <summary>
///
/// </summary>
public static Device DefaultDevice { get { return _DefaultDevice; } }
static Device _DefaultDevice = new Device("Default", "Default");
/// <summary>
/// Adds a pre activation action
/// </summary>
/// <param name="act"></param>
public void AddPreActivationAction(Action act)
{
if (_PreActivationActions == null)
_PreActivationActions = new List<Action>();
_PreActivationActions.Add(act);
}
/// <summary>
/// Base constructor for all Devices.
/// </summary>
/// <param name="key"></param>
public Device(string key)
{
Key = key;
if (key.Contains(".")) Debug.LogMessage(LogEventLevel.Information, "WARNING: Device key should not include '.'", this);
Name = "";
}
/// <summary>
/// Adds a post activation action
/// </summary>
/// <param name="act"></param>
/// <summary>
/// AddPostActivationAction method
/// </summary>
public void AddPostActivationAction(Action act)
{
if (_PostActivationActions == null)
_PostActivationActions = new List<Action>();
_PostActivationActions.Add(act);
}
/// <summary>
/// Constructor with key and name
/// </summary>
/// <param name="key"></param>
/// <param name="name"></param>
public Device(string key, string name) : this(key)
{
Name = name;
}
//public Device(DeviceConfig config)
// : this(config.Key, config.Name)
//{
// Config = config;
//}
/// <summary>
/// Adds a pre activation action
/// </summary>
/// <param name="act"></param>
public void AddPreActivationAction(Action act)
{
if (_PreActivationActions == null)
_PreActivationActions = new List<Action>();
_PreActivationActions.Add(act);
}
/// <summary>
/// Adds a post activation action
/// </summary>
/// <param name="act"></param>
public void AddPostActivationAction(Action act)
{
if (_PostActivationActions == null)
_PostActivationActions = new List<Action>();
_PostActivationActions.Add(act);
}
/// <summary>
/// Executes the preactivation actions
/// </summary>
public void PreActivate()
{
if (_PreActivationActions != null)
_PreActivationActions.ForEach(a =>
/// <summary>
/// PreActivate method
/// </summary>
public void PreActivate()
{
if (_PreActivationActions != null)
_PreActivationActions.ForEach(a =>
{
try
{
try
{
a.Invoke();
}
catch (Exception e)
{
Debug.LogMessage(e, "Error in PreActivationAction: " + e.Message, this);
}
});
}
/// <summary>
/// Gets this device ready to be used in the system. Runs any added pre-activation items, and
/// all post-activation at end. Classes needing additional logic to
/// run should override CustomActivate()
/// </summary>
public bool Activate()
{
//if (_PreActivationActions != null)
// _PreActivationActions.ForEach(a => a.Invoke());
var result = CustomActivate();
//if(result && _PostActivationActions != null)
// _PostActivationActions.ForEach(a => a.Invoke());
return result;
}
/// <summary>
/// Executes the postactivation actions
/// </summary>
public void PostActivate()
{
if (_PostActivationActions != null)
_PostActivationActions.ForEach(a =>
a.Invoke();
}
catch (Exception e)
{
try
{
a.Invoke();
}
catch (Exception e)
{
Debug.LogMessage(e, "Error in PostActivationAction: " + e.Message, this);
}
});
}
Debug.LogMessage(e, "Error in PreActivationAction: " + e.Message, this);
}
});
}
/// <summary>
/// Called in between Pre and PostActivationActions when Activate() is called.
/// Override to provide addtitional setup when calling activation. Overriding classes
/// do not need to call base.CustomActivate()
/// </summary>
/// <returns>true if device activated successfully.</returns>
public virtual bool CustomActivate() { return true; }
/// <summary>
/// Activate method
/// </summary>
public bool Activate()
{
//if (_PreActivationActions != null)
// _PreActivationActions.ForEach(a => a.Invoke());
var result = CustomActivate();
//if(result && _PostActivationActions != null)
// _PostActivationActions.ForEach(a => a.Invoke());
return result;
}
/// <summary>
/// Call to deactivate device - unlink events, etc. Overriding classes do not
/// need to call base.Deactivate()
/// </summary>
/// <returns></returns>
public virtual bool Deactivate() { return true; }
/// <summary>
/// PostActivate method
/// </summary>
public void PostActivate()
{
if (_PostActivationActions != null)
_PostActivationActions.ForEach(a =>
{
try
{
a.Invoke();
}
catch (Exception e)
{
Debug.LogMessage(e, "Error in PostActivationAction: " + e.Message, this);
}
});
}
/// <summary>
/// Call this method to start communications with a device. Overriding classes do not need to call base.Initialize()
/// </summary>
public virtual void Initialize()
{
}
/// <summary>
/// Called in between Pre and PostActivationActions when Activate() is called.
/// Override to provide addtitional setup when calling activation. Overriding classes
/// do not need to call base.CustomActivate()
/// </summary>
/// <returns>true if device activated successfully.</returns>
/// <summary>
/// CustomActivate method
/// </summary>
public virtual bool CustomActivate() { return true; }
/// <summary>
/// Helper method to check object for bool value false and fire an Action method
/// </summary>
/// <param name="o">Should be of type bool, others will be ignored</param>
/// <param name="a">Action to be run when o is false</param>
public void OnFalse(object o, Action a)
{
if (o is bool && !(bool)o) a();
}
/// <summary>
/// Call to deactivate device - unlink events, etc. Overriding classes do not
/// need to call base.Deactivate()
/// </summary>
/// <returns></returns>
public virtual bool Deactivate() { return true; }
/// <summary>
/// Returns a string representation of the object, including its key and name.
/// </summary>
/// <remarks>The returned string is formatted as "{Key} - {Name}". If the <c>Name</c> property is
/// null or empty, "---" is used in place of the name.</remarks>
/// <returns>A string that represents the object, containing the key and name in the format "{Key} - {Name}".</returns>
public override string ToString()
{
return string.Format("{0} - {1}", Key, string.IsNullOrEmpty(Name) ? "---" : Name);
}
}
/// <summary>
/// Call this method to start communications with a device. Overriding classes do not need to call base.Initialize()
/// </summary>
public virtual void Initialize()
{
}
/// <summary>
/// Helper method to check object for bool value false and fire an Action method
/// </summary>
/// <param name="o">Should be of type bool, others will be ignored</param>
/// <param name="a">Action to be run when o is false</param>
public void OnFalse(object o, Action a)
{
if (o is bool && !(bool)o) a();
}
/// <summary>
/// Returns a string representation of the object, including its key and name.
/// </summary>
/// <remarks>The returned string is formatted as "{Key} - {Name}". If the <c>Name</c> property is
/// null or empty, "---" is used in place of the name.</remarks>
/// <returns>A string that represents the object, containing the key and name in the format "{Key} - {Name}".</returns>
public override string ToString()
{
return string.Format("{0} - {1}", Key, string.IsNullOrEmpty(Name) ? "---" : Name);
}
}

View File

@@ -1,11 +1,13 @@
using Crestron.SimplSharp;
using Newtonsoft.Json;
extern alias NewtonsoftJson;
using Crestron.SimplSharp;
using JsonProperty = NewtonsoftJson::Newtonsoft.Json.JsonPropertyAttribute;
using Serilog.Events;
namespace PepperDash.Core;
/// <summary>
/// Class to help with accessing values from the CrestronEthernetHelper class
/// Represents an EthernetHelper.
/// </summary>
public class EthernetHelper
{
@@ -24,9 +26,9 @@ namespace PepperDash.Core;
// ADD OTHER HELPERS HERE
/// <summary>
///
/// </summary>
/// <summary>
/// Gets or sets the PortNumber
/// </summary>
public int PortNumber { get; private set; }
private EthernetHelper(int portNumber)

View File

@@ -16,19 +16,19 @@ namespace PepperDash.Core;
/// </summary>
public bool State { get; set; }
/// <summary>
/// Boolean ushort value property
/// </summary>
/// <summary>
/// Gets or sets the IntValue
/// </summary>
public ushort IntValue { get { return (ushort)(State ? 1 : 0); } }
/// <summary>
/// Boolean change event args type
/// </summary>
/// <summary>
/// Gets or sets the Type
/// </summary>
public ushort Type { get; set; }
/// <summary>
/// Boolean change event args index
/// </summary>
/// <summary>
/// Gets or sets the Index
/// </summary>
public ushort Index { get; set; }
/// <summary>
@@ -64,9 +64,9 @@ namespace PepperDash.Core;
}
}
/// <summary>
/// Ushort change event args
/// </summary>
/// <summary>
/// Represents a UshrtChangeEventArgs
/// </summary>
public class UshrtChangeEventArgs : EventArgs
{
/// <summary>
@@ -74,14 +74,14 @@ namespace PepperDash.Core;
/// </summary>
public ushort IntValue { get; set; }
/// <summary>
/// Ushort change event args type
/// </summary>
/// <summary>
/// Gets or sets the Type
/// </summary>
public ushort Type { get; set; }
/// <summary>
/// Ushort change event args index
/// </summary>
/// <summary>
/// Gets or sets the Index
/// </summary>
public ushort Index { get; set; }
/// <summary>
@@ -117,9 +117,9 @@ namespace PepperDash.Core;
}
}
/// <summary>
/// String change event args
/// </summary>
/// <summary>
/// Represents a StringChangeEventArgs
/// </summary>
public class StringChangeEventArgs : EventArgs
{
/// <summary>
@@ -127,14 +127,14 @@ namespace PepperDash.Core;
/// </summary>
public string StringValue { get; set; }
/// <summary>
/// String change event args type
/// </summary>
/// <summary>
/// Gets or sets the Type
/// </summary>
public ushort Type { get; set; }
/// <summary>
/// string change event args index
/// </summary>
/// <summary>
/// Gets or sets the Index
/// </summary>
public ushort Index { get; set; }
/// <summary>

View File

@@ -32,14 +32,14 @@ namespace PepperDash.Core.JsonStandardObjects;
/// </summary>
public DeviceConfig Device { get; set; }
/// <summary>
/// Device change event args type
/// </summary>
/// <summary>
/// Gets or sets the Type
/// </summary>
public ushort Type { get; set; }
/// <summary>
/// Device change event args index
/// </summary>
/// <summary>
/// Gets or sets the Index
/// </summary>
public ushort Index { get; set; }
/// <summary>

View File

@@ -58,6 +58,9 @@ namespace PepperDash.Core.JsonStandardObjects;
/// </summary>
/// <param name="uniqueID"></param>
/// <param name="deviceKey"></param>
/// <summary>
/// Initialize method
/// </summary>
public void Initialize(string uniqueID, string deviceKey)
{
// S+ set EvaluateFb low

View File

@@ -47,9 +47,9 @@ namespace PepperDash.Core.JsonStandardObjects;
]
}
*/
/// <summary>
/// Device communication parameter class
/// </summary>
/// <summary>
/// Represents a ComParamsConfig
/// </summary>
public class ComParamsConfig
{
/// <summary>
@@ -207,9 +207,9 @@ namespace PepperDash.Core.JsonStandardObjects;
}
}
/// <summary>
/// Device properties class
/// </summary>
/// <summary>
/// Represents a PropertiesConfig
/// </summary>
public class PropertiesConfig
{
/// <summary>

View File

@@ -122,9 +122,9 @@ namespace PepperDash.Core.JsonToSimpl;
}
}
/// <summary>
/// S+ types enum
/// </summary>
/// <summary>
/// Enumeration of SPlusType values
/// </summary>
public enum SPlusType
{
/// <summary>

View File

@@ -49,9 +49,9 @@ namespace PepperDash.Core.JsonToSimpl;
}
}
/// <summary>
/// Gets a master by its key. Case-insensitive
/// </summary>
/// <summary>
/// GetMasterByFile method
/// </summary>
public static JsonToSimplMaster GetMasterByFile(string file)
{
return Masters.FirstOrDefault(m => m.UniqueID.Equals(file, StringComparison.OrdinalIgnoreCase));

View File

@@ -1,6 +1,8 @@
using System;
extern alias NewtonsoftJson;
using System;
using System.Linq;
using Newtonsoft.Json.Linq;
using JArray = NewtonsoftJson::Newtonsoft.Json.Linq.JArray;
using Serilog.Events;
namespace PepperDash.Core.JsonToSimpl;
@@ -128,7 +130,7 @@ namespace PepperDash.Core.JsonToSimpl;
var item = array.FirstOrDefault(o =>
{
var prop = o[SearchPropertyName];
return prop != null && prop.Value<string>()
return prop != null && ((string)prop)
.Equals(SearchPropertyValue, StringComparison.OrdinalIgnoreCase);
});
if (item == null)

View File

@@ -1,7 +1,9 @@
using System;
extern alias NewtonsoftJson;
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json.Linq;
using JValue = NewtonsoftJson::Newtonsoft.Json.Linq.JValue;
namespace PepperDash.Core.JsonToSimpl;
@@ -28,9 +30,9 @@ namespace PepperDash.Core.JsonToSimpl;
/// </summary>
public SPlusValuesDelegate GetAllValuesDelegate { get; set; }
/// <summary>
/// Use a callback to reduce task switch/threading
/// </summary>
/// <summary>
/// Gets or sets the SetAllPathsDelegate
/// </summary>
public SPlusValuesDelegate SetAllPathsDelegate { get; set; }
/// <summary>
@@ -110,9 +112,9 @@ namespace PepperDash.Core.JsonToSimpl;
BoolPaths[index] = path;
}
/// <summary>
/// Set the JPath for a ushort out index.
/// </summary>
/// <summary>
/// SetUshortPath method
/// </summary>
public void SetUshortPath(ushort index, string path)
{
Debug.Console(1, "JSON Child[{0}] SetUshortPath {1}={2}", Key, index, path);
@@ -120,9 +122,9 @@ namespace PepperDash.Core.JsonToSimpl;
UshortPaths[index] = path;
}
/// <summary>
/// Set the JPath for a string output index.
/// </summary>
/// <summary>
/// SetStringPath method
/// </summary>
public void SetStringPath(ushort index, string path)
{
Debug.Console(1, "JSON Child[{0}] SetStringPath {1}={2}", Key, index, path);
@@ -130,10 +132,10 @@ namespace PepperDash.Core.JsonToSimpl;
StringPaths[index] = path;
}
/// <summary>
/// Evalutates all outputs with defined paths. called by S+ when paths are ready to process
/// and by Master when file is read.
/// </summary>
/// <summary>
/// ProcessAll method
/// </summary>
/// <inheritdoc />
public virtual void ProcessAll()
{
if (!LinkedToObject)
@@ -230,7 +232,7 @@ namespace PepperDash.Core.JsonToSimpl;
if (isCount)
response = (t.HasValues ? t.Children().Count() : 0).ToString();
else
response = t.Value<string>();
response = (string)t;
Debug.Console(1, " ='{0}'", response);
return true;
}

View File

@@ -1,11 +1,15 @@
using System;
extern alias NewtonsoftJson;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using Crestron.SimplSharp;
using Crestron.SimplSharp.CrestronIO;
using Newtonsoft.Json.Linq;
using Formatting = NewtonsoftJson::Newtonsoft.Json.Formatting;
using JObject = NewtonsoftJson::Newtonsoft.Json.Linq.JObject;
using JValue = NewtonsoftJson::Newtonsoft.Json.Linq.JValue;
namespace PepperDash.Core.JsonToSimpl;

View File

@@ -1,7 +1,10 @@
using System;
extern alias NewtonsoftJson;
using System;
using System.Collections.Generic;
using Crestron.SimplSharp;
using Newtonsoft.Json.Linq;
using JObject = NewtonsoftJson::Newtonsoft.Json.Linq.JObject;
using JValue = NewtonsoftJson::Newtonsoft.Json.Linq.JValue;
namespace PepperDash.Core.JsonToSimpl;
@@ -60,6 +63,9 @@ namespace PepperDash.Core.JsonToSimpl;
/// Loads JSON into JsonObject, but does not trigger evaluation by children
/// </summary>
/// <param name="json"></param>
/// <summary>
/// SetJsonWithoutEvaluating method
/// </summary>
public void SetJsonWithoutEvaluating(string json)
{
try
@@ -72,9 +78,10 @@ namespace PepperDash.Core.JsonToSimpl;
}
}
/// <summary>
///
/// </summary>
/// <summary>
/// Save method
/// </summary>
/// <inheritdoc />
public override void Save()
{
// this code is duplicated in the other masters!!!!!!!!!!!!!

View File

@@ -1,9 +1,15 @@
extern alias NewtonsoftJson;
using System;
using System.Collections.Generic;
using System.IO;
using Crestron.SimplSharp;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Crestron.SimplSharp.CrestronIO;
using JArray = NewtonsoftJson::Newtonsoft.Json.Linq.JArray;
using JObject = NewtonsoftJson::Newtonsoft.Json.Linq.JObject;
using JValue = NewtonsoftJson::Newtonsoft.Json.Linq.JValue;
using JsonSerializationException = NewtonsoftJson::Newtonsoft.Json.JsonSerializationException;
using JsonTextReader = NewtonsoftJson::Newtonsoft.Json.JsonTextReader;
namespace PepperDash.Core.JsonToSimpl;
@@ -52,10 +58,9 @@ namespace PepperDash.Core.JsonToSimpl;
}
string _DebugName = "";
/// <summary>
/// This will be prepended to all paths to allow path swapping or for more organized
/// sub-paths
/// </summary>
/// <summary>
/// Gets or sets the PathPrefix
/// </summary>
public string PathPrefix { get; set; }
/// <summary>
@@ -119,6 +124,9 @@ namespace PepperDash.Core.JsonToSimpl;
/// Adds a child "module" to this master
/// </summary>
/// <param name="child"></param>
/// <summary>
/// AddChild method
/// </summary>
public void AddChild(JsonToSimplChildObjectBase child)
{
if (!Children.Contains(child))
@@ -127,9 +135,9 @@ namespace PepperDash.Core.JsonToSimpl;
}
}
/// <summary>
/// Called from the child to add changed or new values for saving
/// </summary>
/// <summary>
/// AddUnsavedValue method
/// </summary>
public void AddUnsavedValue(string path, JValue value)
{
if (UnsavedValues.ContainsKey(path))
@@ -159,7 +167,7 @@ namespace PepperDash.Core.JsonToSimpl;
/// <returns></returns>
public static JObject ParseObject(string json)
{
using (var reader = new JsonTextReader(new StringReader(json)))
using (var reader = new JsonTextReader(new System.IO.StringReader(json)))
{
var startDepth = reader.Depth;
var obj = JObject.Load(reader);
@@ -177,7 +185,7 @@ namespace PepperDash.Core.JsonToSimpl;
public static JArray ParseArray(string json)
{
using (var reader = new JsonTextReader(new StringReader(json)))
using (var reader = new JsonTextReader(new System.IO.StringReader(json)))
{
var startDepth = reader.Depth;
var obj = JArray.Load(reader);

View File

@@ -1,9 +1,12 @@
using System;
extern alias NewtonsoftJson;
using System;
using System.Collections.Generic;
using System.Linq;
using Crestron.SimplSharp;
using Crestron.SimplSharp.CrestronIO;
using Newtonsoft.Json.Linq;
using JObject = NewtonsoftJson::Newtonsoft.Json.Linq.JObject;
using JValue = NewtonsoftJson::Newtonsoft.Json.Linq.JValue;
using PepperDash.Core.Config;
namespace PepperDash.Core.JsonToSimpl;
@@ -128,6 +131,9 @@ public class JsonToSimplPortalFileMaster : JsonToSimplMaster
///
/// </summary>
/// <param name="level"></param>
/// <summary>
/// setDebugLevel method
/// </summary>
public void setDebugLevel(uint level)
{
Debug.SetDebugLevel(level);

View File

@@ -1,8 +1,15 @@
using Crestron.SimplSharp;
extern alias NewtonsoftJson;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text.RegularExpressions;
using Crestron.SimplSharp;
using Crestron.SimplSharp.CrestronDataStore;
using Crestron.SimplSharp.CrestronIO;
using Crestron.SimplSharp.CrestronLogger;
using Newtonsoft.Json;
using Formatting = NewtonsoftJson::Newtonsoft.Json.Formatting;
using JsonConvert = NewtonsoftJson::Newtonsoft.Json.JsonConvert;
using PepperDash.Core.Logging;
using Serilog;
using Serilog.Context;
@@ -11,10 +18,6 @@ using Serilog.Events;
using Serilog.Formatting.Compact;
using Serilog.Formatting.Json;
using Serilog.Templates;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text.RegularExpressions;
namespace PepperDash.Core;
@@ -97,19 +100,19 @@ public static class Debug
/// <summary>
/// Version for the currently loaded PepperDashCore dll
/// </summary>
public static string PepperDashCoreVersion { get; private set; }
public static string PepperDashCoreVersion { get; private set; }
private static CTimer _saveTimer;
/// <summary>
/// When true, the IncludedExcludedKeys dict will contain keys to include.
/// When false (default), IncludedExcludedKeys will contain keys to exclude.
/// </summary>
private static bool _excludeAllMode;
/// <summary>
/// When true, the IncludedExcludedKeys dict will contain keys to include.
/// When false (default), IncludedExcludedKeys will contain keys to exclude.
/// </summary>
private static bool _excludeAllMode;
//static bool ExcludeNoKeyMessages;
//static bool ExcludeNoKeyMessages;
private static readonly Dictionary<string, object> IncludedExcludedKeys;
private static readonly Dictionary<string, object> IncludedExcludedKeys;
private static readonly LoggerConfiguration _defaultLoggerConfiguration;
@@ -119,120 +122,120 @@ public static class Debug
static Debug()
{
try
{
CrestronDataStoreStatic.InitCrestronDataStore();
var defaultConsoleLevel = GetStoredLogEventLevel(LevelStoreKey);
var defaultWebsocketLevel = GetStoredLogEventLevel(WebSocketLevelStoreKey);
var defaultErrorLogLevel = GetStoredLogEventLevel(ErrorLogLevelStoreKey);
var defaultFileLogLevel = GetStoredLogEventLevel(FileLevelStoreKey);
_consoleLoggingLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultConsoleLevel);
_websocketLoggingLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultWebsocketLevel);
_errorLogLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultErrorLogLevel);
_fileLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultFileLogLevel);
_websocketSink = new DebugWebsocketSink(new JsonFormatter(renderMessage: true));
var logFilePath = CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance ?
$@"{Directory.GetApplicationRootDirectory()}{Path.DirectorySeparatorChar}user{Path.DirectorySeparatorChar}debug{Path.DirectorySeparatorChar}app{InitialParametersClass.ApplicationNumber}{Path.DirectorySeparatorChar}global-log.log" :
$@"{Directory.GetApplicationRootDirectory()}{Path.DirectorySeparatorChar}user{Path.DirectorySeparatorChar}debug{Path.DirectorySeparatorChar}room{InitialParametersClass.RoomId}{Path.DirectorySeparatorChar}global-log.log";
CrestronConsole.PrintLine($"Saving log files to {logFilePath}");
var errorLogTemplate = CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance
? "{@t:fff}ms [{@l:u4}]{#if Key is not null}[{Key}]{#end} {@m}{#if @x is not null}\r\n{@x}{#end}"
: "[{@t:yyyy-MM-dd HH:mm:ss.fff}][{@l:u4}][{App}]{#if Key is not null}[{Key}]{#end} {@m}{#if @x is not null}\r\n{@x}{#end}";
_defaultLoggerConfiguration = new LoggerConfiguration()
.MinimumLevel.Verbose()
.Enrich.FromLogContext()
.Enrich.With(new CrestronEnricher())
.WriteTo.Sink(new DebugConsoleSink(new ExpressionTemplate("[{@t:yyyy-MM-dd HH:mm:ss.fff}][{@l:u4}][{App}]{#if Key is not null}[{Key}]{#end} {@m}{#if @x is not null}\r\n{@x}{#end}")), levelSwitch: _consoleLoggingLevelSwitch)
.WriteTo.Sink(_websocketSink, levelSwitch: _websocketLoggingLevelSwitch)
.WriteTo.Sink(new DebugErrorLogSink(new ExpressionTemplate(errorLogTemplate)), levelSwitch: _errorLogLevelSwitch)
.WriteTo.File(new RenderedCompactJsonFormatter(), logFilePath,
rollingInterval: RollingInterval.Day,
restrictedToMinimumLevel: LogEventLevel.Debug,
retainedFileCountLimit: CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance ? 30 : 60,
levelSwitch: _fileLevelSwitch
);
// Instantiate the root logger
_loggerConfiguration = _defaultLoggerConfiguration;
_logger = _loggerConfiguration.CreateLogger();
// Get the assembly version and print it to console and the log
GetVersion();
string msg = $"[App {InitialParametersClass.ApplicationNumber}] Using PepperDash_Core v{PepperDashCoreVersion}";
if (CrestronEnvironment.DevicePlatform == eDevicePlatform.Server)
{
msg = $"[Room {InitialParametersClass.RoomId}] Using PepperDash_Core v{PepperDashCoreVersion}";
}
CrestronConsole.PrintLine(msg);
LogMessage(LogEventLevel.Information, msg);
IncludedExcludedKeys = new Dictionary<string, object>();
if (CrestronEnvironment.RuntimeEnvironment == eRuntimeEnvironment.SimplSharpPro)
{
// Add command to console
CrestronConsole.AddNewConsoleCommand(SetDoNotLoadOnNextBootFromConsole, "donotloadonnextboot",
"donotloadonnextboot:P [true/false]: Should the application load on next boot", ConsoleAccessLevelEnum.AccessOperator);
CrestronConsole.AddNewConsoleCommand(SetDebugFromConsole, "appdebug",
"appdebug:P [0-5]: Sets the application's console debug message level",
ConsoleAccessLevelEnum.AccessOperator);
CrestronConsole.AddNewConsoleCommand(ShowDebugLog, "appdebuglog",
"appdebuglog:P [all] Use \"all\" for full log.",
ConsoleAccessLevelEnum.AccessOperator);
CrestronConsole.AddNewConsoleCommand(s => CrestronLogger.Clear(false), "appdebugclear",
"appdebugclear:P Clears the current custom log",
ConsoleAccessLevelEnum.AccessOperator);
CrestronConsole.AddNewConsoleCommand(SetDebugFilterFromConsole, "appdebugfilter",
"appdebugfilter [params]", ConsoleAccessLevelEnum.AccessOperator);
}
CrestronEnvironment.ProgramStatusEventHandler += CrestronEnvironment_ProgramStatusEventHandler;
DoNotLoadConfigOnNextBoot = GetDoNotLoadOnNextBoot();
if (DoNotLoadConfigOnNextBoot)
CrestronConsole.PrintLine(string.Format("Program {0} will not load config after next boot. Use console command go:{0} to load the config manually", InitialParametersClass.ApplicationNumber));
_consoleLoggingLevelSwitch.MinimumLevelChanged += (sender, args) =>
{
LogMessage(LogEventLevel.Information, "Console debug level set to {minimumLevel}", _consoleLoggingLevelSwitch.MinimumLevel);
};
}
try
{
CrestronDataStoreStatic.InitCrestronDataStore();
var defaultConsoleLevel = GetStoredLogEventLevel(LevelStoreKey);
var defaultWebsocketLevel = GetStoredLogEventLevel(WebSocketLevelStoreKey);
var defaultErrorLogLevel = GetStoredLogEventLevel(ErrorLogLevelStoreKey);
var defaultFileLogLevel = GetStoredLogEventLevel(FileLevelStoreKey);
_consoleLoggingLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultConsoleLevel);
_websocketLoggingLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultWebsocketLevel);
_errorLogLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultErrorLogLevel);
_fileLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultFileLogLevel);
_websocketSink = new DebugWebsocketSink(new JsonFormatter(renderMessage: true));
var logFilePath = CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance ?
$@"{Directory.GetApplicationRootDirectory()}{Path.DirectorySeparatorChar}user{Path.DirectorySeparatorChar}debug{Path.DirectorySeparatorChar}app{InitialParametersClass.ApplicationNumber}{Path.DirectorySeparatorChar}global-log.log" :
$@"{Directory.GetApplicationRootDirectory()}{Path.DirectorySeparatorChar}user{Path.DirectorySeparatorChar}debug{Path.DirectorySeparatorChar}room{InitialParametersClass.RoomId}{Path.DirectorySeparatorChar}global-log.log";
CrestronConsole.PrintLine($"Saving log files to {logFilePath}");
var errorLogTemplate = CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance
? "{@t:fff}ms [{@l:u4}]{#if Key is not null}[{Key}]{#end} {@m}{#if @x is not null}\r\n{@x}{#end}"
: "[{@t:yyyy-MM-dd HH:mm:ss.fff}][{@l:u4}][{App}]{#if Key is not null}[{Key}]{#end} {@m}{#if @x is not null}\r\n{@x}{#end}";
_defaultLoggerConfiguration = new LoggerConfiguration()
.MinimumLevel.Verbose()
.Enrich.FromLogContext()
.Enrich.With(new CrestronEnricher())
.WriteTo.Sink(new DebugConsoleSink(new ExpressionTemplate("[{@t:yyyy-MM-dd HH:mm:ss.fff}][{@l:u4}][{App}]{#if Key is not null}[{Key}]{#end} {@m}{#if @x is not null}\r\n{@x}{#end}")), levelSwitch: _consoleLoggingLevelSwitch)
.WriteTo.Sink(_websocketSink, levelSwitch: _websocketLoggingLevelSwitch)
.WriteTo.Sink(new DebugErrorLogSink(new ExpressionTemplate(errorLogTemplate)), levelSwitch: _errorLogLevelSwitch)
.WriteTo.File(new RenderedCompactJsonFormatter(), logFilePath,
rollingInterval: RollingInterval.Day,
restrictedToMinimumLevel: LogEventLevel.Debug,
retainedFileCountLimit: CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance ? 30 : 60,
levelSwitch: _fileLevelSwitch
);
// Instantiate the root logger
_loggerConfiguration = _defaultLoggerConfiguration;
_logger = _loggerConfiguration.CreateLogger();
// Get the assembly version and print it to console and the log
GetVersion();
string msg = $"[App {InitialParametersClass.ApplicationNumber}] Using PepperDash_Core v{PepperDashCoreVersion}";
if (CrestronEnvironment.DevicePlatform == eDevicePlatform.Server)
{
msg = $"[Room {InitialParametersClass.RoomId}] Using PepperDash_Core v{PepperDashCoreVersion}";
}
CrestronConsole.PrintLine(msg);
LogMessage(LogEventLevel.Information, msg);
IncludedExcludedKeys = new Dictionary<string, object>();
if (CrestronEnvironment.RuntimeEnvironment == eRuntimeEnvironment.SimplSharpPro)
{
// Add command to console
CrestronConsole.AddNewConsoleCommand(SetDoNotLoadOnNextBootFromConsole, "donotloadonnextboot",
"donotloadonnextboot:P [true/false]: Should the application load on next boot", ConsoleAccessLevelEnum.AccessOperator);
CrestronConsole.AddNewConsoleCommand(SetDebugFromConsole, "appdebug",
"appdebug:P [0-5]: Sets the application's console debug message level",
ConsoleAccessLevelEnum.AccessOperator);
CrestronConsole.AddNewConsoleCommand(ShowDebugLog, "appdebuglog",
"appdebuglog:P [all] Use \"all\" for full log.",
ConsoleAccessLevelEnum.AccessOperator);
CrestronConsole.AddNewConsoleCommand(s => CrestronLogger.Clear(false), "appdebugclear",
"appdebugclear:P Clears the current custom log",
ConsoleAccessLevelEnum.AccessOperator);
CrestronConsole.AddNewConsoleCommand(SetDebugFilterFromConsole, "appdebugfilter",
"appdebugfilter [params]", ConsoleAccessLevelEnum.AccessOperator);
}
CrestronEnvironment.ProgramStatusEventHandler += CrestronEnvironment_ProgramStatusEventHandler;
DoNotLoadConfigOnNextBoot = GetDoNotLoadOnNextBoot();
if (DoNotLoadConfigOnNextBoot)
CrestronConsole.PrintLine(string.Format("Program {0} will not load config after next boot. Use console command go:{0} to load the config manually", InitialParametersClass.ApplicationNumber));
_consoleLoggingLevelSwitch.MinimumLevelChanged += (sender, args) =>
{
LogMessage(LogEventLevel.Information, "Console debug level set to {minimumLevel}", _consoleLoggingLevelSwitch.MinimumLevel);
};
}
catch (Exception ex)
{
LogError(ex, "Exception in Debug static constructor: {message}", ex.Message);
}
}
private static bool GetDoNotLoadOnNextBoot()
{
var err = CrestronDataStoreStatic.GetLocalBoolValue(DoNotLoadOnNextBootKey, out var doNotLoad);
if (err != CrestronDataStore.CDS_ERROR.CDS_SUCCESS)
{
LogError("Error retrieving DoNotLoadOnNextBoot value: {err}", err);
doNotLoad = false;
}
return doNotLoad;
private static bool GetDoNotLoadOnNextBoot()
{
var err = CrestronDataStoreStatic.GetLocalBoolValue(DoNotLoadOnNextBootKey, out var doNotLoad);
if (err != CrestronDataStore.CDS_ERROR.CDS_SUCCESS)
{
LogError("Error retrieving DoNotLoadOnNextBoot value: {err}", err);
doNotLoad = false;
}
return doNotLoad;
}
public static void UpdateLoggerConfiguration(LoggerConfiguration config)
@@ -253,7 +256,7 @@ public static class Debug
{
try
{
var result = CrestronDataStoreStatic.GetLocalIntValue(levelStoreKey, out int logLevel);
var result = CrestronDataStoreStatic.GetLocalIntValue(levelStoreKey, out int logLevel);
if (result != CrestronDataStore.CDS_ERROR.CDS_SUCCESS)
{
@@ -261,14 +264,15 @@ public static class Debug
return LogEventLevel.Information;
}
if(logLevel < 0 || logLevel > 5)
if (logLevel < 0 || logLevel > 5)
{
CrestronConsole.PrintLine($"Stored Log level not valid for {levelStoreKey}: {logLevel}. Setting level to {LogEventLevel.Information}");
return LogEventLevel.Information;
}
return (LogEventLevel)logLevel;
} catch (Exception ex)
}
catch (Exception ex)
{
CrestronConsole.PrintLine($"Exception retrieving log level for {levelStoreKey}: {ex.Message}");
return LogEventLevel.Information;
@@ -280,7 +284,7 @@ public static class Debug
var assembly = Assembly.GetExecutingAssembly();
var ver =
assembly
.GetCustomAttributes(typeof (AssemblyInformationalVersionAttribute), false);
.GetCustomAttributes(typeof(AssemblyInformationalVersionAttribute), false);
if (ver != null && ver.Length > 0)
{
@@ -328,13 +332,13 @@ public static class Debug
if (levelString.Trim() == "?")
{
CrestronConsole.ConsoleCommandResponse(
$@"Used to set the minimum level of debug messages to be printed to the console:
{_logLevels[0]} = 0
{_logLevels[1]} = 1
{_logLevels[2]} = 2
{_logLevels[3]} = 3
{_logLevels[4]} = 4
{_logLevels[5]} = 5");
"Used to set the minimum level of debug messages to be printed to the console:\r\n" +
$"{_logLevels[0]} = 0\r\n" +
$"{_logLevels[1]} = 1\r\n" +
$"{_logLevels[2]} = 2\r\n" +
$"{_logLevels[3]} = 3\r\n" +
$"{_logLevels[4]} = 4\r\n" +
$"{_logLevels[5]} = 5");
return;
}
@@ -344,25 +348,25 @@ public static class Debug
return;
}
if(int.TryParse(levelString, out var levelInt))
if (int.TryParse(levelString, out var levelInt))
{
if(levelInt < 0 || levelInt > 5)
if (levelInt < 0 || levelInt > 5)
{
CrestronConsole.ConsoleCommandResponse($"Error: Unable to parse {levelString} to valid log level. If using a number, value must be between 0-5");
return;
}
SetDebugLevel((uint) levelInt);
SetDebugLevel((uint)levelInt);
return;
}
if(Enum.TryParse<LogEventLevel>(levelString, out var levelEnum))
if (Enum.TryParse<LogEventLevel>(levelString, out var levelEnum))
{
SetDebugLevel(levelEnum);
return;
}
CrestronConsole.ConsoleCommandResponse($"Error: Unable to parse {levelString} to valid log level");
}
}
catch
{
CrestronConsole.ConsoleCommandResponse("Usage: appdebug:P [0-5]");
@@ -375,7 +379,7 @@ public static class Debug
/// <param name="level"> Valid values 0-5</param>
public static void SetDebugLevel(uint level)
{
if(!_logLevels.TryGetValue(level, out var logLevel))
if (!_logLevels.TryGetValue(level, out var logLevel))
{
logLevel = LogEventLevel.Information;
@@ -394,9 +398,9 @@ public static class Debug
CrestronConsole.ConsoleCommandResponse("[Application {0}], Debug level set to {1}\r\n",
InitialParametersClass.ApplicationNumber, _consoleLoggingLevelSwitch.MinimumLevel);
CrestronConsole.ConsoleCommandResponse($"Storing level {level}:{(int) level}");
CrestronConsole.ConsoleCommandResponse($"Storing level {level}:{(int)level}");
var err = CrestronDataStoreStatic.SetLocalIntValue(LevelStoreKey, (int) level);
var err = CrestronDataStoreStatic.SetLocalIntValue(LevelStoreKey, (int)level);
CrestronConsole.ConsoleCommandResponse($"Store result: {err}:{(int)level}");
@@ -406,9 +410,9 @@ public static class Debug
public static void SetWebSocketMinimumDebugLevel(LogEventLevel level)
{
_websocketLoggingLevelSwitch.MinimumLevel = level;
_websocketLoggingLevelSwitch.MinimumLevel = level;
var err = CrestronDataStoreStatic.SetLocalUintValue(WebSocketLevelStoreKey, (uint) level);
var err = CrestronDataStoreStatic.SetLocalUintValue(WebSocketLevelStoreKey, (uint)level);
if (err != CrestronDataStore.CDS_ERROR.CDS_SUCCESS)
LogMessage(LogEventLevel.Information, "Error saving websocket debug level setting: {erro}", err);
@@ -466,80 +470,80 @@ public static class Debug
/// Callback for console command
/// </summary>
/// <param name="items"></param>
public static void SetDebugFilterFromConsole(string items)
{
var str = items.Trim();
if (str == "?")
{
CrestronConsole.ConsoleCommandResponse("Usage:\r APPDEBUGFILTER key1 key2 key3....\r " +
"+all: at beginning puts filter into 'default include' mode\r" +
" All keys that follow will be excluded from output.\r" +
"-all: at beginning puts filter into 'default exclude all' mode.\r" +
" All keys that follow will be the only keys that are shown\r" +
"+nokey: Enables messages with no key (default)\r" +
"-nokey: Disables messages with no key.\r" +
"(nokey settings are independent of all other settings)");
return;
}
var keys = Regex.Split(str, @"\s*");
foreach (var keyToken in keys)
{
var lkey = keyToken.ToLower();
if (lkey == "+all")
{
IncludedExcludedKeys.Clear();
_excludeAllMode = false;
}
else if (lkey == "-all")
{
IncludedExcludedKeys.Clear();
_excludeAllMode = true;
}
//else if (lkey == "+nokey")
//{
// ExcludeNoKeyMessages = false;
//}
//else if (lkey == "-nokey")
//{
// ExcludeNoKeyMessages = true;
//}
else
{
string key;
if (lkey.StartsWith("-"))
{
key = lkey.Substring(1);
// if in exclude all mode, we need to remove this from the inclusions
if (_excludeAllMode)
{
if (IncludedExcludedKeys.ContainsKey(key))
IncludedExcludedKeys.Remove(key);
}
// otherwise include all mode, add to the exclusions
else
{
IncludedExcludedKeys[key] = new object();
}
}
else if (lkey.StartsWith("+"))
{
key = lkey.Substring(1);
// if in exclude all mode, we need to add this as inclusion
if (_excludeAllMode)
{
public static void SetDebugFilterFromConsole(string items)
{
var str = items.Trim();
if (str == "?")
{
CrestronConsole.ConsoleCommandResponse("Usage:\r APPDEBUGFILTER key1 key2 key3....\r " +
"+all: at beginning puts filter into 'default include' mode\r" +
" All keys that follow will be excluded from output.\r" +
"-all: at beginning puts filter into 'default exclude all' mode.\r" +
" All keys that follow will be the only keys that are shown\r" +
"+nokey: Enables messages with no key (default)\r" +
"-nokey: Disables messages with no key.\r" +
"(nokey settings are independent of all other settings)");
return;
}
var keys = Regex.Split(str, @"\s*");
foreach (var keyToken in keys)
{
var lkey = keyToken.ToLower();
if (lkey == "+all")
{
IncludedExcludedKeys.Clear();
_excludeAllMode = false;
}
else if (lkey == "-all")
{
IncludedExcludedKeys.Clear();
_excludeAllMode = true;
}
//else if (lkey == "+nokey")
//{
// ExcludeNoKeyMessages = false;
//}
//else if (lkey == "-nokey")
//{
// ExcludeNoKeyMessages = true;
//}
else
{
string key;
if (lkey.StartsWith("-"))
{
key = lkey.Substring(1);
// if in exclude all mode, we need to remove this from the inclusions
if (_excludeAllMode)
{
if (IncludedExcludedKeys.ContainsKey(key))
IncludedExcludedKeys.Remove(key);
}
// otherwise include all mode, add to the exclusions
else
{
IncludedExcludedKeys[key] = new object();
}
}
else if (lkey.StartsWith("+"))
{
key = lkey.Substring(1);
// if in exclude all mode, we need to add this as inclusion
if (_excludeAllMode)
{
IncludedExcludedKeys[key] = new object();
}
// otherwise include all mode, remove this from exclusions
else
{
if (IncludedExcludedKeys.ContainsKey(key))
IncludedExcludedKeys.Remove(key);
}
}
}
}
}
IncludedExcludedKeys[key] = new object();
}
// otherwise include all mode, remove this from exclusions
else
{
if (IncludedExcludedKeys.ContainsKey(key))
IncludedExcludedKeys.Remove(key);
}
}
}
}
}
@@ -564,7 +568,7 @@ public static class Debug
public static object GetDeviceDebugSettingsForKey(string deviceKey)
{
return _contexts.GetDebugSettingsForKey(deviceKey);
}
}
/// <summary>
/// Sets the flag to prevent application starting on next boot
@@ -572,8 +576,8 @@ public static class Debug
/// <param name="state"></param>
public static void SetDoNotLoadConfigOnNextBoot(bool state)
{
DoNotLoadConfigOnNextBoot = state;
DoNotLoadConfigOnNextBoot = state;
var err = CrestronDataStoreStatic.SetLocalBoolValue(DoNotLoadOnNextBootKey, state);
if (err != CrestronDataStore.CDS_ERROR.CDS_SUCCESS)
@@ -624,7 +628,7 @@ public static class Debug
public static void LogMessage(LogEventLevel level, string message, params object[] args)
{
_logger.Write(level, message, args);
_logger.Write(level, message, args);
}
public static void LogMessage(LogEventLevel level, Exception ex, string message, params object[] args)
@@ -648,7 +652,7 @@ public static class Debug
#region Explicit methods for logging levels
public static void LogVerbose(IKeyed keyed, string message, params object[] args)
{
using(LogContext.PushProperty("Key", keyed?.Key))
using (LogContext.PushProperty("Key", keyed?.Key))
{
_logger.Write(LogEventLevel.Verbose, message, args);
}
@@ -656,7 +660,7 @@ public static class Debug
public static void LogVerbose(Exception ex, IKeyed keyed, string message, params object[] args)
{
using(LogContext.PushProperty("Key", keyed?.Key))
using (LogContext.PushProperty("Key", keyed?.Key))
{
_logger.Write(LogEventLevel.Verbose, ex, message, args);
}
@@ -810,7 +814,7 @@ public static class Debug
if (!_logLevels.ContainsKey(level)) return;
var logLevel = _logLevels[level];
LogMessage(logLevel, format, items);
}
@@ -847,7 +851,7 @@ public static class Debug
}
/// <summary>
/// Logs to Console when at-level, and all messages to error log, including device key
/// Logs to Console when at-level, and all messages to error log, including device key
/// </summary>
[Obsolete("Use LogMessage methods, Will be removed in 2.2.0 and later versions")]
public static void Console(uint level, IKeyed dev, string format, params object[] items)
@@ -865,7 +869,7 @@ public static class Debug
[Obsolete("Use LogMessage methods, Will be removed in 2.2.0 and later versions")]
public static void Console(uint level, IKeyed dev, ErrorLogLevel errorLogLevel,
string format, params object[] items)
{
{
LogMessage(level, dev, format, items);
}
@@ -956,15 +960,24 @@ public static class Debug
//if (!Directory.Exists(dir))
// Directory.Create(dir);
var fileName = GetMemoryFileName();
LogMessage(LogEventLevel.Information, "Loading debug settings file from {fileName}", fileName);
using (var sw = new StreamWriter(fileName))
try
{
var json = JsonConvert.SerializeObject(_contexts);
sw.Write(json);
sw.Flush();
var fileName = GetMemoryFileName();
LogMessage(LogEventLevel.Information, "Loading debug settings file from {fileName}", fileName);
using (var sw = new StreamWriter(fileName))
{
var json = JsonConvert.SerializeObject(_contexts);
sw.Write(json);
sw.Flush();
}
}
catch (Exception ex)
{
ErrorLog.Error("Exception saving debug settings: {message}", ex);
CrestronConsole.PrintLine("Exception saving debug settings: {message}", ex.Message);
return;
}
}
@@ -1003,7 +1016,7 @@ public static class Debug
return string.Format(@"\user\debugSettings\program{0}", InitialParametersClass.ApplicationNumber);
}
return string.Format("{0}{1}user{1}debugSettings{1}{2}.json",Directory.GetApplicationRootDirectory(), Path.DirectorySeparatorChar, InitialParametersClass.RoomId);
return string.Format("{0}{1}user{1}debugSettings{1}{2}.json", Directory.GetApplicationRootDirectory(), Path.DirectorySeparatorChar, InitialParametersClass.RoomId);
}
/// <summary>
@@ -1014,15 +1027,15 @@ public static class Debug
/// <summary>
/// Error
/// </summary>
Error,
Error,
/// <summary>
/// Warning
/// </summary>
Warning,
Warning,
/// <summary>
/// Notice
/// </summary>
Notice,
Notice,
/// <summary>
/// None
/// </summary>

View File

@@ -1,9 +1,12 @@
using System;
extern alias NewtonsoftJson;
using System;
using System.Collections.Generic;
using System.Linq;
using Crestron.SimplSharp;
using Crestron.SimplSharp.CrestronIO;
using Newtonsoft.Json;
using Formatting = NewtonsoftJson::Newtonsoft.Json.Formatting;
using JsonConvert = NewtonsoftJson::Newtonsoft.Json.JsonConvert;
namespace PepperDash.Core;

View File

@@ -1,5 +1,5 @@
using Serilog.Events;
using System;
using System;
using Serilog.Events;
using Log = PepperDash.Core.Debug;
namespace PepperDash.Core.Logging;

View File

@@ -1,62 +1,70 @@
using System.Collections.Generic;
extern alias NewtonsoftJson;
using System.Collections.Generic;
using System.Threading;
using Crestron.SimplSharp;
using Newtonsoft.Json;
using JsonProperty = NewtonsoftJson::Newtonsoft.Json.JsonPropertyAttribute;
namespace PepperDash.Core.Logging;
/// <summary>
/// Class to persist current Debug settings across program restarts
/// </summary>
public class DebugContextCollection
{
public class DebugContextCollection
{
/// <summary>
/// To prevent threading issues with the DeviceDebugSettings collection
/// </summary>
private readonly CCriticalSection _deviceDebugSettingsLock;
private readonly object _deviceDebugSettingsLock = new();
[JsonProperty("items")] private readonly Dictionary<string, DebugContextItem> _items;
[JsonProperty("items")]
private readonly Dictionary<string, DebugContextItem> _items = new Dictionary<string, DebugContextItem>();
/// <summary>
/// Collection of the debug settings for each device where the dictionary key is the device key
/// </summary>
[JsonProperty("deviceDebugSettings")]
private Dictionary<string, object> DeviceDebugSettings { get; set; }
private Dictionary<string, object> DeviceDebugSettings { get; set; } = new Dictionary<string, object>();
/// <summary>
/// Default constructor
/// </summary>
public DebugContextCollection()
{
_deviceDebugSettingsLock = new CCriticalSection();
DeviceDebugSettings = new Dictionary<string, object>();
_items = new Dictionary<string, DebugContextItem>();
}
/// <summary>
/// Default constructor
/// </summary>
public DebugContextCollection()
{
/// <summary>
/// Sets the level of a given context item, and adds that item if it does not
/// exist
/// </summary>
/// <param name="contextKey"></param>
/// <param name="level"></param>
public void SetLevel(string contextKey, int level)
{
if (level < 0 || level > 2)
return;
GetOrCreateItem(contextKey).Level = level;
}
}
/// <summary>
/// Gets a level or creates it if not existing
/// </summary>
/// <param name="contextKey"></param>
/// <returns></returns>
public DebugContextItem GetOrCreateItem(string contextKey)
{
if (!_items.ContainsKey(contextKey))
_items[contextKey] = new DebugContextItem { Level = 0 };
return _items[contextKey];
}
/// <summary>
/// Sets the level of a given context item, and adds that item if it does not
/// exist
/// </summary>
/// <param name="contextKey"></param>
/// <param name="level"></param>
/// <summary>
/// SetLevel method
/// </summary>
public void SetLevel(string contextKey, int level)
{
if (level < 0 || level > 2)
return;
GetOrCreateItem(contextKey).Level = level;
}
/// <summary>
/// Gets a level or creates it if not existing
/// </summary>
/// <param name="contextKey"></param>
/// <returns></returns>
/// <summary>
/// GetOrCreateItem method
/// </summary>
public DebugContextItem GetOrCreateItem(string contextKey)
{
if (!_items.ContainsKey(contextKey))
_items[contextKey] = new DebugContextItem { Level = 0 };
return _items[contextKey];
}
/// <summary>
@@ -67,10 +75,8 @@ namespace PepperDash.Core.Logging;
/// <returns></returns>
public void SetDebugSettingsForKey(string deviceKey, object settings)
{
try
lock (_deviceDebugSettingsLock)
{
_deviceDebugSettingsLock.Enter();
if (DeviceDebugSettings.ContainsKey(deviceKey))
{
DeviceDebugSettings[deviceKey] = settings;
@@ -78,10 +84,6 @@ namespace PepperDash.Core.Logging;
else
DeviceDebugSettings.Add(deviceKey, settings);
}
finally
{
_deviceDebugSettingsLock.Leave();
}
}
/// <summary>
@@ -93,22 +95,22 @@ namespace PepperDash.Core.Logging;
{
return DeviceDebugSettings[deviceKey];
}
}
}
/// <summary>
/// Contains information about
/// </summary>
public class DebugContextItem
{
/// <summary>
/// Contains information about
/// </summary>
public class DebugContextItem
{
/// <summary>
/// The level of debug messages to print
/// </summary>
[JsonProperty("level")]
public int Level { get; set; }
[JsonProperty("level")]
public int Level { get; set; }
/// <summary>
/// Property to tell the program not to intitialize when it boots, if desired
/// </summary>
[JsonProperty("doNotLoadOnNextBoot")]
public bool DoNotLoadOnNextBoot { get; set; }
}
}

View File

@@ -1,3 +1,6 @@
extern alias NewtonsoftJson;
using System;
using Crestron.SimplSharp;
using Org.BouncyCastle.Asn1.X509;
using Serilog;
@@ -5,19 +8,20 @@ using Serilog.Configuration;
using Serilog.Core;
using Serilog.Events;
using Serilog.Formatting;
using JObject = NewtonsoftJson::Newtonsoft.Json.Linq.JObject;
using Serilog.Formatting.Json;
using System;
using System.IO;
using System.Security.Authentication;
using WebSocketSharp;
using WebSocketSharp.Server;
using X509Certificate2 = System.Security.Cryptography.X509Certificates.X509Certificate2;
using WebSocketSharp.Net;
namespace PepperDash.Core;
/// <summary>
/// Provides a WebSocket-based logging sink for debugging purposes, allowing log events to be broadcast to connected
/// WebSocket clients.
/// <summary>
/// Provides a WebSocket-based logging sink for debugging purposes, allowing log events to be broadcast to connected
/// WebSocket clients.
/// </summary>
/// <remarks>This class implements the <see cref="ILogEventSink"/> interface and is designed to send
/// formatted log events to WebSocket clients connected to a secure WebSocket server. The server is hosted locally
@@ -30,8 +34,8 @@ public class DebugWebsocketSink : ILogEventSink, IKeyed
private const string _certificateName = "selfCres";
private const string _certificatePassword = "cres12345";
/// <summary>
/// Gets the port number on which the HTTPS server is currently running.
/// <summary>
/// Gets the port number on which the HTTPS server is currently running.
/// </summary>
public int Port
{ get
@@ -42,8 +46,8 @@ public class DebugWebsocketSink : ILogEventSink, IKeyed
}
}
/// <summary>
/// Gets the WebSocket URL for the current server instance.
/// <summary>
/// Gets the WebSocket URL for the current server instance.
/// </summary>
/// <remarks>The URL is dynamically constructed based on the server's current IP address, port,
/// and WebSocket path.</remarks>
@@ -56,8 +60,8 @@ public class DebugWebsocketSink : ILogEventSink, IKeyed
}
}
/// <summary>
/// Gets a value indicating whether the HTTPS server is currently listening for incoming connections.
/// <summary>
/// Gets a value indicating whether the HTTPS server is currently listening for incoming connections.
/// </summary>
public bool IsRunning { get => _httpsServer?.IsListening ?? false; }
@@ -66,13 +70,13 @@ public class DebugWebsocketSink : ILogEventSink, IKeyed
private readonly ITextFormatter _textFormatter;
/// <summary>
/// Initializes a new instance of the <see cref="DebugWebsocketSink"/> class with the specified text formatter.
/// </summary>
/// <remarks>This constructor initializes the WebSocket sink and ensures that a certificate is
/// available for secure communication. If the required certificate does not exist, it will be created
/// automatically. Additionally, the sink is configured to stop the server when the program is
/// stopping.</remarks>
/// <summary>
/// Initializes a new instance of the <see cref="DebugWebsocketSink"/> class with the specified text formatter.
/// </summary>
/// <remarks>This constructor initializes the WebSocket sink and ensures that a certificate is
/// available for secure communication. If the required certificate does not exist, it will be created
/// automatically. Additionally, the sink is configured to stop the server when the program is
/// stopping.</remarks>
/// <param name="formatProvider">The text formatter used to format log messages. If null, a default JSON formatter is used.</param>
public DebugWebsocketSink(ITextFormatter formatProvider)
{
@@ -119,12 +123,12 @@ public class DebugWebsocketSink : ILogEventSink, IKeyed
}
}
/// <summary>
/// Sends a log event to all connected WebSocket clients.
/// </summary>
/// <remarks>The log event is formatted using the configured text formatter and then broadcasted
/// to all clients connected to the WebSocket server. If the WebSocket server is not initialized or not
/// listening, the method exits without performing any action.</remarks>
/// <summary>
/// Sends a log event to all connected WebSocket clients.
/// </summary>
/// <remarks>The log event is formatted using the configured text formatter and then broadcasted
/// to all clients connected to the WebSocket server. If the WebSocket server is not initialized or not
/// listening, the method exits without performing any action.</remarks>
/// <param name="logEvent">The log event to be formatted and broadcasted. Cannot be null.</param>
public void Emit(LogEvent logEvent)
{
@@ -136,12 +140,12 @@ public class DebugWebsocketSink : ILogEventSink, IKeyed
_httpsServer.WebSocketServices[_path].Sessions.Broadcast(sw.ToString());
}
/// <summary>
/// Starts the WebSocket server on the specified port and configures it with the appropriate certificate.
/// </summary>
/// <remarks>This method initializes the WebSocket server and binds it to the specified port. It
/// also applies the server's certificate for secure communication. Ensure that the port is not already in use
/// and that the certificate file is accessible.</remarks>
/// <summary>
/// Starts the WebSocket server on the specified port and configures it with the appropriate certificate.
/// </summary>
/// <remarks>This method initializes the WebSocket server and binds it to the specified port. It
/// also applies the server's certificate for secure communication. Ensure that the port is not already in use
/// and that the certificate file is accessible.</remarks>
/// <param name="port">The port number on which the WebSocket server will listen. Must be a valid, non-negative port number.</param>
public void StartServerAndSetPort(int port)
{
@@ -162,15 +166,15 @@ public class DebugWebsocketSink : ILogEventSink, IKeyed
Debug.Console(0, "Assigning SSL Configuration");
_httpsServer.SslConfiguration.ServerCertificate = new X509Certificate2(certPath, certPassword);
_httpsServer.SslConfiguration.ClientCertificateRequired = false;
_httpsServer.SslConfiguration.CheckCertificateRevocation = false;
_httpsServer.SslConfiguration.EnabledSslProtocols = SslProtocols.Tls12;
//this is just to test, you might want to actually validate
_httpsServer.SslConfiguration.ClientCertificateRequired = false;
_httpsServer.SslConfiguration.CheckCertificateRevocation = false;
_httpsServer.SslConfiguration.EnabledSslProtocols = SslProtocols.Tls12;
//this is just to test, you might want to actually validate
_httpsServer.SslConfiguration.ClientCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) =>
{
Debug.Console(0, "HTTPS ClientCerticateValidation Callback triggered");
return true;
};
};
}
Debug.Console(0, "Adding Debug Client Service");
_httpsServer.AddWebSocketService<DebugClient>(_path);
@@ -218,8 +222,8 @@ public class DebugWebsocketSink : ILogEventSink, IKeyed
}
}
/// <summary>
/// Stops the WebSocket server if it is currently running.
/// <summary>
/// Stops the WebSocket server if it is currently running.
/// </summary>
/// <remarks>This method halts the WebSocket server and releases any associated resources. After
/// calling this method, the server will no longer accept or process incoming connections.</remarks>
@@ -232,20 +236,20 @@ public class DebugWebsocketSink : ILogEventSink, IKeyed
}
}
/// <summary>
/// Configures the logger to write log events to a debug WebSocket sink.
/// <summary>
/// Configures the logger to write log events to a debug WebSocket sink.
/// </summary>
/// <remarks>This extension method allows you to direct log events to a WebSocket sink for debugging
/// purposes.</remarks>
public static class DebugWebsocketSinkExtensions
{
/// <summary>
/// Configures a logger to write log events to a debug WebSocket sink.
/// </summary>
/// <remarks>This method adds a sink that writes log events to a WebSocket for debugging purposes.
/// It is typically used during development to stream log events in real-time.</remarks>
/// <param name="loggerConfiguration">The logger sink configuration to apply the WebSocket sink to.</param>
/// <param name="formatProvider">An optional text formatter to format the log events. If not provided, a default formatter will be used.</param>
/// <summary>
/// Configures a logger to write log events to a debug WebSocket sink.
/// </summary>
/// <remarks>This method adds a sink that writes log events to a WebSocket for debugging purposes.
/// It is typically used during development to stream log events in real-time.</remarks>
/// <param name="loggerConfiguration">The logger sink configuration to apply the WebSocket sink to.</param>
/// <param name="formatProvider">An optional text formatter to format the log events. If not provided, a default formatter will be used.</param>
/// <returns>A <see cref="LoggerConfiguration"/> object that can be used to further configure the logger.</returns>
public static LoggerConfiguration DebugWebsocketSink(
this LoggerSinkConfiguration loggerConfiguration,
@@ -255,9 +259,9 @@ public static class DebugWebsocketSinkExtensions
}
}
/// <summary>
/// Represents a WebSocket client for debugging purposes, providing connection lifecycle management and message
/// handling functionality.
/// <summary>
/// Represents a WebSocket client for debugging purposes, providing connection lifecycle management and message
/// handling functionality.
/// </summary>
/// <remarks>The <see cref="DebugClient"/> class extends <see cref="WebSocketBehavior"/> to handle
/// WebSocket connections, including events for opening, closing, receiving messages, and errors. It tracks the
@@ -266,8 +270,8 @@ public class DebugClient : WebSocketBehavior
{
private DateTime _connectionTime;
/// <summary>
/// Gets the duration of time the WebSocket connection has been active.
/// <summary>
/// Gets the duration of time the WebSocket connection has been active.
/// </summary>
public TimeSpan ConnectedDuration
{
@@ -284,8 +288,8 @@ public class DebugClient : WebSocketBehavior
}
}
/// <summary>
/// Initializes a new instance of the <see cref="DebugClient"/> class.
/// <summary>
/// Initializes a new instance of the <see cref="DebugClient"/> class.
/// </summary>
/// <remarks>This constructor creates a new <see cref="DebugClient"/> instance and logs its
/// creation using the <see cref="Debug.Console(int, string)"/> method with a debug level of 0.</remarks>

View File

@@ -59,6 +59,9 @@ namespace PepperDash.Core.PasswordManagement;
/// Retrieve password by index
/// </summary>
/// <param name="key"></param>
/// <summary>
/// GetPasswordByIndex method
/// </summary>
public void GetPasswordByIndex(ushort key)
{
OnUshrtChange((ushort)PasswordManager.Passwords.Count, 0, PasswordManagementConstants.PasswordManagerCountChange);
@@ -81,6 +84,9 @@ namespace PepperDash.Core.PasswordManagement;
/// Password validation method
/// </summary>
/// <param name="password"></param>
/// <summary>
/// ValidatePassword method
/// </summary>
public void ValidatePassword(string password)
{
if (string.IsNullOrEmpty(password))
@@ -99,6 +105,9 @@ namespace PepperDash.Core.PasswordManagement;
/// password against the selected password when the length of the 2 are equal
/// </summary>
/// <param name="data"></param>
/// <summary>
/// BuildPassword method
/// </summary>
public void BuildPassword(string data)
{
PasswordToValidate = String.Concat(PasswordToValidate, data);
@@ -108,9 +117,9 @@ namespace PepperDash.Core.PasswordManagement;
ValidatePassword(PasswordToValidate);
}
/// <summary>
/// Clears the user entered password and resets the LEDs
/// </summary>
/// <summary>
/// ClearPassword method
/// </summary>
public void ClearPassword()
{
PasswordToValidate = "";

View File

@@ -71,6 +71,9 @@ namespace PepperDash.Core.PasswordManagement;
/// </summary>
/// <param name="key"></param>
/// <param name="password"></param>
/// <summary>
/// UpdatePassword method
/// </summary>
public void UpdatePassword(ushort key, string password)
{
// validate the parameters
@@ -152,6 +155,9 @@ namespace PepperDash.Core.PasswordManagement;
/// Method to change the default timer value, (default 5000ms/5s)
/// </summary>
/// <param name="time"></param>
/// <summary>
/// PasswordTimerMs method
/// </summary>
public void PasswordTimerMs(ushort time)
{
PasswordTimerElapsedMs = Convert.ToInt64(time);

View File

@@ -35,16 +35,13 @@
<EmbeddedResource Remove="Properties\**" />
<None Remove="lib\**" />
<None Remove="Properties\**" />
</ItemGroup>
<ItemGroup>
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Net.Http" />
</ItemGroup>
</ItemGroup>
<ItemGroup>
<PackageReference Include="BouncyCastle.Cryptography" Version="2.6.1" />
<PackageReference Include="Crestron.SimplSharp.SDK.Library" Version="2.21.128" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.4">
<Aliases>global,NewtonsoftJson</Aliases>
</PackageReference>
<PackageReference Include="Serilog" Version="4.3.0" />
<PackageReference Include="Serilog.Expressions" Version="5.0.0" />
<PackageReference Include="Serilog.Formatting.Compact" Version="3.0.0" />
@@ -53,9 +50,6 @@
<PackageReference Include="SSH.NET" Version="2025.0.0" />
<PackageReference Include="WebSocketSharp-netstandard" Version="1.0.1" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net6'">
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>
<ItemGroup>
<Compile Remove="Comm\._GenericSshClient.cs" />
<Compile Remove="Comm\._GenericTcpIpClient.cs" />

View File

@@ -68,9 +68,9 @@ namespace PepperDash.Core.SystemInfo;
public const ushort ProgramConfigChange = 305;
}
/// <summary>
/// Processor Change Event Args Class
/// </summary>
/// <summary>
/// Represents a ProcessorChangeEventArgs
/// </summary>
public class ProcessorChangeEventArgs : EventArgs
{
/// <summary>
@@ -114,9 +114,9 @@ namespace PepperDash.Core.SystemInfo;
}
}
/// <summary>
/// Ethernet Change Event Args Class
/// </summary>
/// <summary>
/// Represents a EthernetChangeEventArgs
/// </summary>
public class EthernetChangeEventArgs : EventArgs
{
/// <summary>
@@ -165,9 +165,9 @@ namespace PepperDash.Core.SystemInfo;
}
}
/// <summary>
/// Control Subnet Chage Event Args Class
/// </summary>
/// <summary>
/// Represents a ControlSubnetChangeEventArgs
/// </summary>
public class ControlSubnetChangeEventArgs : EventArgs
{
/// <summary>
@@ -211,9 +211,9 @@ namespace PepperDash.Core.SystemInfo;
}
}
/// <summary>
/// Program Change Event Args Class
/// </summary>
/// <summary>
/// Represents a ProgramChangeEventArgs
/// </summary>
public class ProgramChangeEventArgs : EventArgs
{
/// <summary>

View File

@@ -100,9 +100,9 @@ namespace PepperDash.Core.SystemInfo;
OnBoolChange(false, 0, SystemInfoConstants.BusyBoolChange);
}
/// <summary>
/// Gets the current ethernet info
/// </summary>
/// <summary>
/// GetEthernetInfo method
/// </summary>
public void GetEthernetInfo()
{
OnBoolChange(true, 0, SystemInfoConstants.BusyBoolChange);
@@ -161,9 +161,9 @@ namespace PepperDash.Core.SystemInfo;
OnBoolChange(false, 0, SystemInfoConstants.BusyBoolChange);
}
/// <summary>
/// Gets the current control subnet info
/// </summary>
/// <summary>
/// GetControlSubnetInfo method
/// </summary>
public void GetControlSubnetInfo()
{
OnBoolChange(true, 0, SystemInfoConstants.BusyBoolChange);
@@ -206,6 +206,9 @@ namespace PepperDash.Core.SystemInfo;
/// Gets the program info by index
/// </summary>
/// <param name="index"></param>
/// <summary>
/// GetProgramInfoByIndex method
/// </summary>
public void GetProgramInfoByIndex(ushort index)
{
if (index < 1 || index > 10)
@@ -263,9 +266,9 @@ namespace PepperDash.Core.SystemInfo;
OnBoolChange(false, 0, SystemInfoConstants.BusyBoolChange);
}
/// <summary>
/// Gets the processor uptime and passes it to S+
/// </summary>
/// <summary>
/// RefreshProcessorUptime method
/// </summary>
public void RefreshProcessorUptime()
{
try
@@ -287,6 +290,9 @@ namespace PepperDash.Core.SystemInfo;
/// Gets the program uptime, by index, and passes it to S+
/// </summary>
/// <param name="index"></param>
/// <summary>
/// RefreshProgramUptimeByIndex method
/// </summary>
public void RefreshProgramUptimeByIndex(int index)
{
try
@@ -308,6 +314,9 @@ namespace PepperDash.Core.SystemInfo;
/// Sends command to console, passes response back using string change event
/// </summary>
/// <param name="cmd"></param>
/// <summary>
/// SendConsoleCommand method
/// </summary>
public void SendConsoleCommand(string cmd)
{
if (string.IsNullOrEmpty(cmd))

View File

@@ -144,6 +144,9 @@ namespace PepperDash.Core.Web.RequestHandlers;
/// Process request
/// </summary>
/// <param name="context"></param>
/// <summary>
/// ProcessRequest method
/// </summary>
public void ProcessRequest(HttpCwsContext context)
{
Action<HttpCwsContext> handler;

View File

@@ -1,283 +1,252 @@
using System;
extern alias NewtonsoftJson;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Crestron.SimplSharp;
using Crestron.SimplSharp.WebScripting;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Formatting = NewtonsoftJson::Newtonsoft.Json.Formatting;
using JsonConvert = NewtonsoftJson::Newtonsoft.Json.JsonConvert;
using JObject = NewtonsoftJson::Newtonsoft.Json.Linq.JObject;
using PepperDash.Core.Web.RequestHandlers;
using PepperDash.Core.Logging;
namespace PepperDash.Core.Web;
/// <summary>
/// Web API server
/// </summary>
public class WebApiServer : IKeyName
{
private const string SplusKey = "Uninitialized Web API Server";
private const string DefaultName = "Web API Server";
private const string DefaultBasePath = "/api";
private readonly object _serverLock = new();
private HttpCwsServer _server;
/// <summary>
/// Web API server
/// Gets or sets the Key
/// </summary>
public class WebApiServer : IKeyName
public string Key { get; private set; }
/// <summary>
/// Gets or sets the Name
/// </summary>
public string Name { get; private set; }
/// <summary>
/// Gets or sets the BasePath
/// </summary>
public string BasePath { get; private set; }
/// <summary>
/// Gets or sets the IsRegistered
/// </summary>
public bool IsRegistered { get; private set; }
/// <summary>
/// Constructor for S+. Make sure to set necessary properties using init method
/// </summary>
public WebApiServer()
: this(SplusKey, DefaultName, null)
{
private const string SplusKey = "Uninitialized Web API Server";
private const string DefaultName = "Web API Server";
private const string DefaultBasePath = "/api";
}
private const uint DebugTrace = 0;
private const uint DebugInfo = 1;
private const uint DebugVerbose = 2;
/// <summary>
/// Constructor
/// </summary>
/// <param name="key"></param>
/// <param name="basePath"></param>
public WebApiServer(string key, string basePath)
: this(key, DefaultName, basePath)
{
}
private readonly CCriticalSection _serverLock = new CCriticalSection();
private HttpCwsServer _server;
/// <summary>
/// Constructor
/// </summary>
/// <param name="key"></param>
/// <param name="name"></param>
/// <param name="basePath"></param>
public WebApiServer(string key, string name, string basePath)
{
Key = key;
Name = string.IsNullOrEmpty(name) ? DefaultName : name;
BasePath = string.IsNullOrEmpty(basePath) ? DefaultBasePath : basePath;
/// <summary>
/// Web API server key
/// </summary>
public string Key { get; private set; }
if (_server == null) _server = new HttpCwsServer(BasePath);
/// <summary>
/// Web API server name
/// </summary>
public string Name { get; private set; }
_server.setProcessName(Key);
_server.HttpRequestHandler = new DefaultRequestHandler();
/// <summary>
/// CWS base path, will default to "/api" if not set via initialize method
/// </summary>
public string BasePath { get; private set; }
CrestronEnvironment.ProgramStatusEventHandler += CrestronEnvironment_ProgramStatusEventHandler;
CrestronEnvironment.EthernetEventHandler += CrestronEnvironment_EthernetEventHandler;
}
/// <summary>
/// Indicates CWS is registered with base path
/// </summary>
public bool IsRegistered { get; private set; }
/// <summary>
/// Program status event handler
/// </summary>
/// <param name="programEventType"></param>
void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType)
{
if (programEventType != eProgramStatusEventType.Stopping) return;
/// <summary>
/// Http request handler
/// </summary>
//public IHttpCwsHandler HttpRequestHandler
//{
// get { return _server.HttpRequestHandler; }
// set
// {
// if (_server == null) return;
// _server.HttpRequestHandler = value;
// }
//}
this.LogInformation("Program stopping. stopping server");
/// <summary>
/// Received request event handler
/// </summary>
//public event EventHandler<HttpCwsRequestEventArgs> ReceivedRequestEvent
//{
// add { _server.ReceivedRequestEvent += new HttpCwsRequestEventHandler(value); }
// remove { _server.ReceivedRequestEvent -= new HttpCwsRequestEventHandler(value); }
//}
Stop();
}
/// <summary>
/// Constructor for S+. Make sure to set necessary properties using init method
/// </summary>
public WebApiServer()
: this(SplusKey, DefaultName, null)
/// <summary>
/// Ethernet event handler
/// </summary>
/// <param name="ethernetEventArgs"></param>
void CrestronEnvironment_EthernetEventHandler(EthernetEventArgs ethernetEventArgs)
{
// Re-enable the server if the link comes back up and the status should be connected
if (ethernetEventArgs.EthernetEventType == eEthernetEventType.LinkUp && IsRegistered)
{
this.LogInformation("Ethernet link up. Server is already registered.");
return;
}
/// <summary>
/// Constructor
/// </summary>
/// <param name="key"></param>
/// <param name="basePath"></param>
public WebApiServer(string key, string basePath)
: this(key, DefaultName, basePath)
this.LogInformation("Ethernet link up. Starting server");
Start();
}
/// <summary>
/// Initialize method
/// </summary>
public void Initialize(string key, string basePath)
{
Key = key;
BasePath = string.IsNullOrEmpty(basePath) ? DefaultBasePath : basePath;
}
/// <summary>
/// Adds a route to CWS
/// </summary>
public void AddRoute(HttpCwsRoute route)
{
if (route == null)
{
this.LogWarning("Failed to add route, route parameter is null");
return;
}
/// <summary>
/// Constructor
/// </summary>
/// <param name="key"></param>
/// <param name="name"></param>
/// <param name="basePath"></param>
public WebApiServer(string key, string name, string basePath)
_server.Routes.Add(route);
}
/// <summary>
/// Removes a route from CWS
/// </summary>
/// <param name="route"></param>
/// <summary>
/// RemoveRoute method
/// </summary>
public void RemoveRoute(HttpCwsRoute route)
{
if (route == null)
{
Key = key;
Name = string.IsNullOrEmpty(name) ? DefaultName : name;
BasePath = string.IsNullOrEmpty(basePath) ? DefaultBasePath : basePath;
if (_server == null) _server = new HttpCwsServer(BasePath);
_server.setProcessName(Key);
_server.HttpRequestHandler = new DefaultRequestHandler();
CrestronEnvironment.ProgramStatusEventHandler += CrestronEnvironment_ProgramStatusEventHandler;
CrestronEnvironment.EthernetEventHandler += CrestronEnvironment_EthernetEventHandler;
this.LogWarning("Failed to remove route, route parameter is null");
return;
}
/// <summary>
/// Program status event handler
/// </summary>
/// <param name="programEventType"></param>
void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType)
{
if (programEventType != eProgramStatusEventType.Stopping) return;
_server.Routes.Remove(route);
}
Debug.Console(DebugInfo, this, "Program stopping. stopping server");
/// <summary>
/// GetRouteCollection method
/// </summary>
public HttpCwsRouteCollection GetRouteCollection()
{
return _server.Routes;
}
Stop();
}
/// <summary>
/// Ethernet event handler
/// </summary>
/// <param name="ethernetEventArgs"></param>
void CrestronEnvironment_EthernetEventHandler(EthernetEventArgs ethernetEventArgs)
{
// Re-enable the server if the link comes back up and the status should be connected
if (ethernetEventArgs.EthernetEventType == eEthernetEventType.LinkUp && IsRegistered)
{
Debug.Console(DebugInfo, this, "Ethernet link up. Server is alreedy registered.");
return;
}
Debug.Console(DebugInfo, this, "Ethernet link up. Starting server");
Start();
}
/// <summary>
/// Initializes CWS class
/// </summary>
public void Initialize(string key, string basePath)
{
Key = key;
BasePath = string.IsNullOrEmpty(basePath) ? DefaultBasePath : basePath;
}
/// <summary>
/// Adds a route to CWS
/// </summary>
public void AddRoute(HttpCwsRoute route)
{
if (route == null)
{
Debug.Console(DebugInfo, this, "Failed to add route, route parameter is null");
return;
}
_server.Routes.Add(route);
}
/// <summary>
/// Removes a route from CWS
/// </summary>
/// <param name="route"></param>
public void RemoveRoute(HttpCwsRoute route)
{
if (route == null)
{
Debug.Console(DebugInfo, this, "Failed to remote route, orute parameter is null");
return;
}
_server.Routes.Remove(route);
}
/// <summary>
/// Returns a list of the current routes
/// </summary>
public HttpCwsRouteCollection GetRouteCollection()
{
return _server.Routes;
}
/// <summary>
/// Starts CWS instance
/// </summary>
public void Start()
/// <summary>
/// Starts CWS instance
/// </summary>
public void Start()
{
lock (_serverLock)
{
try
{
_serverLock.Enter();
if (_server == null)
{
Debug.Console(DebugInfo, this, "Server is null, unable to start");
this.LogDebug("Server is null, unable to start");
return;
}
if (IsRegistered)
{
Debug.Console(DebugInfo, this, "Server has already been started");
this.LogDebug("Server has already been started");
return;
}
IsRegistered = _server.Register();
Debug.Console(DebugInfo, this, "Starting server, registration {0}", IsRegistered ? "was successful" : "failed");
this.LogDebug("Starting server, registration {0}", IsRegistered ? "was successful" : "failed");
}
catch (Exception ex)
{
Debug.Console(DebugInfo, this, "Start Exception Message: {0}", ex.Message);
Debug.Console(DebugVerbose, this, "Start Exception StackTrace: {0}", ex.StackTrace);
if (ex.InnerException != null)
Debug.Console(DebugVerbose, this, "Start Exception InnerException: {0}", ex.InnerException);
this.LogException(ex, "Start Exception Message: {0}", ex.Message);
this.LogVerbose("Start Exception StackTrace: {0}", ex.StackTrace);
}
finally
{
_serverLock.Leave();
}
}
} // end lock
}
/// <summary>
/// Stop CWS instance
/// </summary>
public void Stop()
/// <summary>
/// Stop method
/// </summary>
public void Stop()
{
lock (_serverLock)
{
try
{
_serverLock.Enter();
if (_server == null)
{
Debug.Console(DebugInfo, this, "Server is null or has already been stopped");
this.LogDebug("Server is null or has already been stopped");
return;
}
IsRegistered = _server.Unregister() == false;
Debug.Console(DebugInfo, this, "Stopping server, unregistration {0}", IsRegistered ? "failed" : "was successful");
this.LogDebug("Stopping server, unregistration {0}", IsRegistered ? "failed" : "was successful");
_server.Dispose();
_server = null;
}
catch (Exception ex)
{
Debug.Console(DebugInfo, this, "Server Stop Exception Message: {0}", ex.Message);
Debug.Console(DebugVerbose, this, "Server Stop Exception StackTrace: {0}", ex.StackTrace);
if (ex.InnerException != null)
Debug.Console(DebugVerbose, this, "Server Stop Exception InnerException: {0}", ex.InnerException);
this.LogException(ex, "Server Stop Exception Message: {0}", ex.Message);
}
finally
{
_serverLock.Leave();
}
}
} // end lock
}
/// <summary>
/// Received request handler
/// </summary>
/// <remarks>
/// This is here for development and testing
/// </remarks>
/// <param name="sender"></param>
/// <param name="args"></param>
public void ReceivedRequestEventHandler(object sender, HttpCwsRequestEventArgs args)
/// <summary>
/// Received request handler
/// </summary>
/// <remarks>
/// This is here for development and testing
/// </remarks>
/// <param name="sender"></param>
/// <param name="args"></param>
public void ReceivedRequestEventHandler(object sender, HttpCwsRequestEventArgs args)
{
try
{
try
{
var j = JsonConvert.SerializeObject(args.Context, Formatting.Indented);
Debug.Console(DebugVerbose, this, "RecieveRequestEventHandler Context:\x0d\x0a{0}", j);
}
catch (Exception ex)
{
Debug.Console(DebugInfo, this, "ReceivedRequestEventHandler Exception Message: {0}", ex.Message);
Debug.Console(DebugVerbose, this, "ReceivedRequestEventHandler Exception StackTrace: {0}", ex.StackTrace);
if (ex.InnerException != null)
Debug.Console(DebugVerbose, this, "ReceivedRequestEventHandler Exception InnerException: {0}", ex.InnerException);
}
var j = JsonConvert.SerializeObject(args.Context, Formatting.Indented);
this.LogVerbose("RecieveRequestEventHandler Context:\x0d\x0a{0}", j);
}
}
catch (Exception ex)
{
this.LogException(ex, "ReceivedRequestEventHandler Exception Message: {0}", ex.Message);
this.LogVerbose("ReceivedRequestEventHandler Exception StackTrace: {0}", ex.StackTrace);
}
}
}

View File

@@ -48,9 +48,9 @@ namespace PepperDash.Core.WebApi.Presets;
}
}
/// <summary>
///
/// </summary>
/// <summary>
/// Represents a PresetReceivedEventArgs
/// </summary>
public class PresetReceivedEventArgs : EventArgs
{
/// <summary>

View File

@@ -70,9 +70,9 @@ namespace PepperDash.Core.WebApi.Presets;
}
}
/// <summary>
///
/// </summary>
/// <summary>
/// Represents a UserAndRoomMessage
/// </summary>
public class UserAndRoomMessage
{
/// <summary>

View File

@@ -1,10 +1,12 @@
using System;
extern alias NewtonsoftJson;
using System;
using Crestron.SimplSharp; // For Basic SIMPL# Classes
using Crestron.SimplSharp.CrestronIO;
using Crestron.SimplSharp.Net.Http;
using Crestron.SimplSharp.Net.Https;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using JsonConvert = NewtonsoftJson::Newtonsoft.Json.JsonConvert;
using JObject = NewtonsoftJson::Newtonsoft.Json.Linq.JObject;
using PepperDash.Core.JsonToSimpl;
@@ -115,6 +117,9 @@ namespace PepperDash.Core.WebApi.Presets;
/// </summary>
/// <param name="roomTypeId"></param>
/// <param name="presetNumber"></param>
/// <summary>
/// GetPresetForThisUser method
/// </summary>
public void GetPresetForThisUser(int roomTypeId, int presetNumber)
{
if (CurrentUser == null)
@@ -212,6 +217,9 @@ namespace PepperDash.Core.WebApi.Presets;
/// </summary>
/// <param name="roomTypeId"></param>
/// <param name="presetNumber"></param>
/// <summary>
/// SavePresetForThisUser method
/// </summary>
public void SavePresetForThisUser(int roomTypeId, int presetNumber)
{
if (CurrentPreset == null)

View File

@@ -3,18 +3,15 @@
using System;
using System.Collections.Generic;
using Crestron.SimplSharp;
using System.Reflection;
using Crestron.SimplSharpPro;
using Crestron.SimplSharpPro.DeviceSupport;
using Crestron.SimplSharpPro.EthernetCommunication;
using Newtonsoft.Json;
using PepperDash.Core;
using PepperDash.Core.Logging;
using PepperDash.Essentials.Core.Config;
using Serilog.Events;
//using PepperDash.Essentials.Devices.Common.Cameras;
namespace PepperDash.Essentials.Core.Bridges;

View File

@@ -4,6 +4,9 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
public sealed class GenericIrControllerJoinMap : JoinMapBaseAdvanced
{
/// <summary>
/// PLAY
/// </summary>
[JoinName("PLAY")]
public JoinDataComplete Play = new JoinDataComplete(
new JoinData
@@ -17,7 +20,10 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinCapabilities = eJoinCapabilities.FromSIMPL,
JoinType = eJoinType.Digital
});
/// <summary>
/// STOP
/// </summary>
[JoinName("STOP")]
public JoinDataComplete Stop = new JoinDataComplete(
new JoinData
@@ -31,7 +37,10 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinCapabilities = eJoinCapabilities.FromSIMPL,
JoinType = eJoinType.Digital
});
/// <summary>
/// PAUSE
/// </summary>
[JoinName("PAUSE")]
public JoinDataComplete Pause = new JoinDataComplete(
new JoinData
@@ -46,6 +55,9 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinType = eJoinType.Digital
});
/// <summary>
/// FSCAN
/// </summary>
[JoinName("FSCAN")]
public JoinDataComplete ForwardScan = new JoinDataComplete(
new JoinData
@@ -60,6 +72,9 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinType = eJoinType.Digital
});
/// <summary>
/// RSCAN
/// </summary>
[JoinName("RSCAN")]
public JoinDataComplete ReverseScan = new JoinDataComplete(
new JoinData
@@ -73,7 +88,10 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinCapabilities = eJoinCapabilities.FromSIMPL,
JoinType = eJoinType.Digital
});
/// <summary>
/// F_SKIP
/// </summary>
[JoinName("F_SKIP")]
public JoinDataComplete ForwardSkip = new JoinDataComplete(
new JoinData
@@ -88,6 +106,9 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinType = eJoinType.Digital
});
/// <summary>
/// R_SKIP
/// </summary>
[JoinName("R_SKIP")]
public JoinDataComplete ReverseSkip = new JoinDataComplete(
new JoinData
@@ -101,7 +122,10 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinCapabilities = eJoinCapabilities.FromSIMPL,
JoinType = eJoinType.Digital
});
/// <summary>
/// RECORD
/// </summary>
[JoinName("RECORD")]
public JoinDataComplete Record = new JoinDataComplete(
new JoinData
@@ -116,6 +140,9 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinType = eJoinType.Digital
});
/// <summary>
/// POWER
/// </summary>
[JoinName("POWER")]
public JoinDataComplete Power = new JoinDataComplete(
new JoinData
@@ -130,6 +157,9 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinType = eJoinType.Digital
});
/// <summary>
/// 0
/// </summary>
[JoinName("0")]
public JoinDataComplete Kp0 = new JoinDataComplete(
new JoinData
@@ -143,7 +173,10 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinCapabilities = eJoinCapabilities.FromSIMPL,
JoinType = eJoinType.Digital
});
/// <summary>
/// 1
/// </summary>
[JoinName("1")]
public JoinDataComplete Kp1 = new JoinDataComplete(
new JoinData
@@ -157,7 +190,10 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinCapabilities = eJoinCapabilities.FromSIMPL,
JoinType = eJoinType.Digital
});
/// <summary>
/// 2
/// </summary>
[JoinName("2")]
public JoinDataComplete Kp2 = new JoinDataComplete(
new JoinData
@@ -171,7 +207,10 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinCapabilities = eJoinCapabilities.FromSIMPL,
JoinType = eJoinType.Digital
});
/// <summary>
/// 3
/// </summary>
[JoinName("3")]
public JoinDataComplete Kp3 = new JoinDataComplete(
new JoinData
@@ -185,7 +224,10 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinCapabilities = eJoinCapabilities.FromSIMPL,
JoinType = eJoinType.Digital
});
/// <summary>
/// 4
/// </summary>
[JoinName("4")]
public JoinDataComplete Kp4 = new JoinDataComplete(
new JoinData
@@ -199,7 +241,10 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinCapabilities = eJoinCapabilities.FromSIMPL,
JoinType = eJoinType.Digital
});
/// <summary>
/// 5
/// </summary>
[JoinName("5")]
public JoinDataComplete Kp5 = new JoinDataComplete(
new JoinData
@@ -213,7 +258,10 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinCapabilities = eJoinCapabilities.FromSIMPL,
JoinType = eJoinType.Digital
});
/// <summary>
/// 6
/// </summary>
[JoinName("6")]
public JoinDataComplete Kp6 = new JoinDataComplete(
new JoinData
@@ -227,7 +275,10 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinCapabilities = eJoinCapabilities.FromSIMPL,
JoinType = eJoinType.Digital
});
/// <summary>
/// 7
/// </summary>
[JoinName("7")]
public JoinDataComplete Kp7 = new JoinDataComplete(
new JoinData
@@ -241,7 +292,10 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinCapabilities = eJoinCapabilities.FromSIMPL,
JoinType = eJoinType.Digital
});
/// <summary>
/// 8
/// </summary>
[JoinName("8")]
public JoinDataComplete Kp8 = new JoinDataComplete(
new JoinData
@@ -255,7 +309,10 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinCapabilities = eJoinCapabilities.FromSIMPL,
JoinType = eJoinType.Digital
});
/// <summary>
/// 9
/// </summary>
[JoinName("9")]
public JoinDataComplete Kp9 = new JoinDataComplete(
new JoinData
@@ -283,7 +340,10 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
// JoinCapabilities = eJoinCapabilities.FromSIMPL,
// JoinType = eJoinType.Digital
// });
/// <summary>
/// ENTER
/// </summary>
[JoinName("ENTER")]
public JoinDataComplete Enter = new JoinDataComplete(
new JoinData
@@ -297,7 +357,10 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinCapabilities = eJoinCapabilities.FromSIMPL,
JoinType = eJoinType.Digital
});
/// <summary>
/// CH+
/// </summary>
[JoinName("CH+")]
public JoinDataComplete ChannelUp = new JoinDataComplete(
new JoinData
@@ -311,7 +374,10 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinCapabilities = eJoinCapabilities.FromSIMPL,
JoinType = eJoinType.Digital
});
/// <summary>
/// CH-
/// </summary>
[JoinName("CH-")]
public JoinDataComplete ChannelDown = new JoinDataComplete(
new JoinData
@@ -325,7 +391,10 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinCapabilities = eJoinCapabilities.FromSIMPL,
JoinType = eJoinType.Digital
});
/// <summary>
/// *
/// </summary>
[JoinName("*")]
public JoinDataComplete KpStar = new JoinDataComplete(
new JoinData
@@ -339,7 +408,10 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinCapabilities = eJoinCapabilities.FromSIMPL,
JoinType = eJoinType.Digital
});
/// <summary>
/// #
/// </summary>
[JoinName("#")]
public JoinDataComplete KpPound = new JoinDataComplete(
new JoinData
@@ -368,6 +440,9 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
// JoinType = eJoinType.Digital
// });
/// <summary>
/// POWER_ON
/// </summary>
[JoinName("POWER_ON")]
public JoinDataComplete PowerOn = new JoinDataComplete(
new JoinData
@@ -381,7 +456,10 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinCapabilities = eJoinCapabilities.FromSIMPL,
JoinType = eJoinType.Digital
});
/// <summary>
/// POWER_OFF
/// </summary>
[JoinName("POWER_OFF")]
public JoinDataComplete PowerOff = new JoinDataComplete(
new JoinData
@@ -395,7 +473,10 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinCapabilities = eJoinCapabilities.FromSIMPL,
JoinType = eJoinType.Digital
});
/// <summary>
/// PLAY_PAUSE
/// </summary>
[JoinName("PLAY_PAUSE")]
public JoinDataComplete PlayPause = new JoinDataComplete(
new JoinData
@@ -409,7 +490,10 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinCapabilities = eJoinCapabilities.FromSIMPL,
JoinType = eJoinType.Digital
});
/// <summary>
/// LAST
/// </summary>
[JoinName("LAST")]
public JoinDataComplete Last = new JoinDataComplete(
new JoinData
@@ -424,6 +508,9 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinType = eJoinType.Digital
});
/// <summary>
/// HOME
/// </summary>
[JoinName("HOME")]
public JoinDataComplete Home = new JoinDataComplete(
new JoinData
@@ -438,6 +525,9 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinType = eJoinType.Digital
});
/// <summary>
/// BACK
/// </summary>
[JoinName("BACK")]
public JoinDataComplete Back = new JoinDataComplete(
new JoinData
@@ -452,7 +542,9 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinType = eJoinType.Digital
});
/// <summary>
/// GUIDE
/// </summary>
[JoinName("GUIDE")]
public JoinDataComplete Guide = new JoinDataComplete(
new JoinData
@@ -466,7 +558,10 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinCapabilities = eJoinCapabilities.FromSIMPL,
JoinType = eJoinType.Digital
});
/// <summary>
/// INFO
/// </summary>
[JoinName("INFO")]
public JoinDataComplete Info = new JoinDataComplete(
new JoinData
@@ -480,7 +575,10 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinCapabilities = eJoinCapabilities.FromSIMPL,
JoinType = eJoinType.Digital
});
/// <summary>
/// MENU
/// </summary>
[JoinName("MENU")]
public JoinDataComplete Menu = new JoinDataComplete(
new JoinData
@@ -494,7 +592,10 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinCapabilities = eJoinCapabilities.FromSIMPL,
JoinType = eJoinType.Digital
});
/// <summary>
/// UP_ARROW
/// </summary>
[JoinName("UP_ARROW")]
public JoinDataComplete DpadUp = new JoinDataComplete(
new JoinData
@@ -508,7 +609,10 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinCapabilities = eJoinCapabilities.FromSIMPL,
JoinType = eJoinType.Digital
});
/// <summary>
/// DN_ARROW
/// </summary>
[JoinName("DN_ARROW")]
public JoinDataComplete DpadDown = new JoinDataComplete(
new JoinData
@@ -522,7 +626,10 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinCapabilities = eJoinCapabilities.FromSIMPL,
JoinType = eJoinType.Digital
});
/// <summary>
/// LEFT_ARROW
/// </summary>
[JoinName("LEFT_ARROW")]
public JoinDataComplete DpadLeft = new JoinDataComplete(
new JoinData
@@ -536,7 +643,10 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinCapabilities = eJoinCapabilities.FromSIMPL,
JoinType = eJoinType.Digital
});
/// <summary>
/// RIGHT_ARROW
/// </summary>
[JoinName("RIGHT_ARROW")]
public JoinDataComplete DpadRight = new JoinDataComplete(
new JoinData
@@ -550,7 +660,10 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinCapabilities = eJoinCapabilities.FromSIMPL,
JoinType = eJoinType.Digital
});
/// <summary>
/// SELECT
/// </summary>
[JoinName("SELECT")]
public JoinDataComplete DpadSelect = new JoinDataComplete(
new JoinData
@@ -564,7 +677,10 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinCapabilities = eJoinCapabilities.FromSIMPL,
JoinType = eJoinType.Digital
});
/// <summary>
/// OPTIONS
/// </summary>
[JoinName("OPTIONS")]
public JoinDataComplete Options = new JoinDataComplete(
new JoinData
@@ -578,7 +694,10 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinCapabilities = eJoinCapabilities.FromSIMPL,
JoinType = eJoinType.Digital
});
/// <summary>
/// RETURN
/// </summary>
[JoinName("RETURN")]
public JoinDataComplete Return = new JoinDataComplete(
new JoinData
@@ -593,6 +712,9 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinType = eJoinType.Digital
});
/// <summary>
/// DVR
/// </summary>
[JoinName("DVR")]
public JoinDataComplete Dvr = new JoinDataComplete(
new JoinData
@@ -607,7 +729,9 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinType = eJoinType.Digital
});
/// <summary>
/// ON_DEMAND
/// </summary>
[JoinName("ON_DEMAND")]
public JoinDataComplete OnDemand = new JoinDataComplete(
new JoinData
@@ -622,7 +746,9 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinType = eJoinType.Digital
});
/// <summary>
/// PAGE_UP
/// </summary>
[JoinName("PAGE_UP")]
public JoinDataComplete PageUp = new JoinDataComplete(
new JoinData
@@ -636,7 +762,10 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinCapabilities = eJoinCapabilities.FromSIMPL,
JoinType = eJoinType.Digital
});
/// <summary>
/// PAGE_DOWN
/// </summary>
[JoinName("PAGE_DOWN")]
public JoinDataComplete PageDown = new JoinDataComplete(
new JoinData
@@ -650,7 +779,10 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinCapabilities = eJoinCapabilities.FromSIMPL,
JoinType = eJoinType.Digital
});
/// <summary>
/// F_SRCH
/// </summary>
[JoinName("F_SRCH")]
public JoinDataComplete ForwardSearch = new JoinDataComplete(
new JoinData
@@ -664,7 +796,10 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinCapabilities = eJoinCapabilities.FromSIMPL,
JoinType = eJoinType.Digital
});
/// <summary>
/// R_SRCH
/// </summary>
[JoinName("R_SRCH")]
public JoinDataComplete ReverseSearch = new JoinDataComplete(
new JoinData
@@ -678,7 +813,10 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinCapabilities = eJoinCapabilities.FromSIMPL,
JoinType = eJoinType.Digital
});
/// <summary>
/// TRACK+
/// </summary>
[JoinName("TRACK+")]
public JoinDataComplete TrackPlus = new JoinDataComplete(
new JoinData
@@ -692,7 +830,10 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinCapabilities = eJoinCapabilities.FromSIMPL,
JoinType = eJoinType.Digital
});
/// <summary>
/// TRACK-
/// </summary>
[JoinName("TRACK-")]
public JoinDataComplete TrackMinus = new JoinDataComplete(
new JoinData
@@ -707,6 +848,9 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinType = eJoinType.Digital
});
/// <summary>
/// A
/// </summary>
[JoinName("A")]
public JoinDataComplete KpA = new JoinDataComplete(
new JoinData
@@ -720,7 +864,10 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinCapabilities = eJoinCapabilities.FromSIMPL,
JoinType = eJoinType.Digital
});
/// <summary>
/// B
/// </summary>
[JoinName("B")]
public JoinDataComplete KpB = new JoinDataComplete(
new JoinData
@@ -734,7 +881,10 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinCapabilities = eJoinCapabilities.FromSIMPL,
JoinType = eJoinType.Digital
});
/// <summary>
/// C
/// </summary>
[JoinName("C")]
public JoinDataComplete KpC = new JoinDataComplete(
new JoinData
@@ -748,7 +898,10 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinCapabilities = eJoinCapabilities.FromSIMPL,
JoinType = eJoinType.Digital
});
/// <summary>
/// D
/// </summary>
[JoinName("D")]
public JoinDataComplete KpD = new JoinDataComplete(
new JoinData
@@ -762,7 +915,10 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinCapabilities = eJoinCapabilities.FromSIMPL,
JoinType = eJoinType.Digital
});
/// <summary>
/// RED
/// </summary>
[JoinName("RED")]
public JoinDataComplete KpRed = new JoinDataComplete(
new JoinData
@@ -776,7 +932,10 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinCapabilities = eJoinCapabilities.FromSIMPL,
JoinType = eJoinType.Digital
});
/// <summary>
/// GREEN
/// </summary>
[JoinName("GREEN")]
public JoinDataComplete KpGreen = new JoinDataComplete(
new JoinData
@@ -790,7 +949,10 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinCapabilities = eJoinCapabilities.FromSIMPL,
JoinType = eJoinType.Digital
});
/// <summary>
/// YELLOW
/// </summary>
[JoinName("YELLOW")]
public JoinDataComplete KpYellow = new JoinDataComplete(
new JoinData
@@ -804,7 +966,10 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinCapabilities = eJoinCapabilities.FromSIMPL,
JoinType = eJoinType.Digital
});
/// <summary>
/// BLUE
/// </summary>
[JoinName("BLUE")]
public JoinDataComplete KpBlue = new JoinDataComplete(
new JoinData
@@ -819,6 +984,10 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinType = eJoinType.Digital
});
/// <summary>
/// Constructor
/// </summary>
/// <param name="joinStart">Join this join map will start at</param>
public GenericIrControllerJoinMap(uint joinStart)
: base(joinStart, typeof(GenericIrControllerJoinMap))
{

View File

@@ -8,6 +8,9 @@ namespace PepperDash.Essentials.Core.Bridges;
#region Digital
/// <summary>
/// Enable Automatic Routing on Xx1 Switchers
/// </summary>
[JoinName("EnableAutoRoute")]
public JoinDataComplete EnableAutoRoute = new JoinDataComplete(
new JoinData
@@ -22,7 +25,9 @@ namespace PepperDash.Essentials.Core.Bridges;
JoinType = eJoinType.Digital
});
/// <summary>
/// Device Input Sync
/// </summary>
[JoinName("InputSync")]
public JoinDataComplete InputSync = new JoinDataComplete(
new JoinData
@@ -37,7 +42,9 @@ namespace PepperDash.Essentials.Core.Bridges;
JoinType = eJoinType.Digital
});
/// <summary>
/// Device Enable Input Hdcp
/// </summary>
[JoinName("EnableInputHdcp")]
public JoinDataComplete EnableInputHdcp = new JoinDataComplete(
new JoinData
@@ -52,7 +59,9 @@ namespace PepperDash.Essentials.Core.Bridges;
JoinType = eJoinType.Digital
});
/// <summary>
/// Device Disnable Input Hdcp
/// </summary>
[JoinName("DisableInputHdcp")]
public JoinDataComplete DisableInputHdcp = new JoinDataComplete(
new JoinData
@@ -67,7 +76,9 @@ namespace PepperDash.Essentials.Core.Bridges;
JoinType = eJoinType.Digital
});
/// <summary>
/// Device Onlne
/// </summary>
[JoinName("IsOnline")]
public JoinDataComplete IsOnline = new JoinDataComplete(
new JoinData
@@ -87,6 +98,9 @@ namespace PepperDash.Essentials.Core.Bridges;
#region Analog
/// <summary>
/// Device Input Route Set/Get
/// </summary>
[JoinName("OutputRoute")]
public JoinDataComplete OutputRoute = new JoinDataComplete(
new JoinData
@@ -106,6 +120,9 @@ namespace PepperDash.Essentials.Core.Bridges;
#region Serial
/// <summary>
/// Device Name
/// </summary>
[JoinName("Name")]
public JoinDataComplete Name = new JoinDataComplete(
new JoinData
@@ -120,7 +137,9 @@ namespace PepperDash.Essentials.Core.Bridges;
JoinType = eJoinType.Serial
});
/// <summary>
/// Device Input Name
/// </summary>
[JoinName("InputName")]
public JoinDataComplete InputName = new JoinDataComplete(
new JoinData
@@ -135,7 +154,9 @@ namespace PepperDash.Essentials.Core.Bridges;
JoinType = eJoinType.Serial
});
/// <summary>
/// Device Output Name
/// </summary>
[JoinName("OutputName")]
public JoinDataComplete OutputName = new JoinDataComplete(
new JoinData
@@ -150,7 +171,9 @@ namespace PepperDash.Essentials.Core.Bridges;
JoinType = eJoinType.Serial
});
/// <summary>
/// Device Output Route Name
/// </summary>
[JoinName("OutputRoutedName")]
public JoinDataComplete OutputRoutedName = new JoinDataComplete(
new JoinData

View File

@@ -132,18 +132,30 @@ public class SystemMonitorJoinMap : JoinMapBaseAdvanced
public JoinDataComplete DhcpStatus = new JoinDataComplete(new JoinData { JoinNumber = 86, JoinSpan = 1 },
new JoinMetadata { Description = "Processor Ethernet Dhcp Status", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial });
/// <summary>
/// Processor Reboot
/// </summary>
[JoinName("ProcessorRebot")]
public JoinDataComplete ProcessorReboot = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 },
new JoinMetadata { Description = "Reboot processor", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital });
/// <summary>
/// Is Appliance Fb
/// </summary>
[JoinName("IsAppliance")]
public JoinDataComplete IsAppliance = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 },
new JoinMetadata { Description = "Is appliance Fb", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital });
/// <summary>
/// Is Server Fb
/// </summary>
[JoinName("IsServer")]
public JoinDataComplete IsServer = new JoinDataComplete(new JoinData { JoinNumber = 2, JoinSpan = 1 },
new JoinMetadata { Description = "Is server Fb", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital });
/// <summary>
/// Program Reset
/// </summary>
[JoinName("ProgramReset")]
public JoinDataComplete ProgramReset = new JoinDataComplete(new JoinData { JoinNumber = 15, JoinSpan = 1 },
new JoinMetadata { Description = "Resets the program", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital });

View File

@@ -6,6 +6,9 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
{
#region Digital
/// <summary>
/// Device is Online
/// </summary>
[JoinName("IsOnline")]
public JoinDataComplete IsOnline = new JoinDataComplete(
new JoinData
@@ -216,6 +219,9 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinType = eJoinType.Digital
});
/// <summary>
/// Current Hook State
/// </summary>
[JoinName("HookState")]
public JoinDataComplete HookState = new JoinDataComplete(
new JoinData
@@ -230,6 +236,9 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinType = eJoinType.Digital
});
/// <summary>
/// Speed Dial
/// </summary>
[JoinName("SpeedDialStart")]
public JoinDataComplete SpeedDialStart = new JoinDataComplete(
new JoinData
@@ -244,6 +253,9 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinType = eJoinType.Digital
});
/// <summary>
/// Incoming Call
/// </summary>
[JoinName("IncomingCall")]
public JoinDataComplete IncomingCall = new JoinDataComplete(
new JoinData
@@ -258,6 +270,9 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinType = eJoinType.Digital
});
/// <summary>
/// Answer Incoming Call
/// </summary>
[JoinName("IncomingAnswer")]
public JoinDataComplete IncomingAnswer = new JoinDataComplete(
new JoinData
@@ -272,6 +287,9 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinType = eJoinType.Digital
});
/// <summary>
/// Reject Incoming Call
/// </summary>
[JoinName("IncomingReject")]
public JoinDataComplete IncomingReject = new JoinDataComplete(
new JoinData
@@ -286,6 +304,9 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinType = eJoinType.Digital
});
/// <summary>
/// Manual Dial
/// </summary>
[JoinName("ManualDial")]
public JoinDataComplete ManualDial = new JoinDataComplete(
new JoinData
@@ -314,6 +335,9 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinType = eJoinType.Digital
});
/// <summary>
/// Phone Hook State
/// </summary>
[JoinName("PhoneHookState")]
public JoinDataComplete PhoneHookState = new JoinDataComplete(
new JoinData
@@ -384,6 +408,9 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinType = eJoinType.Digital
});
/// <summary>
/// Directory Search Busy
/// </summary>
[JoinName("DirectorySearchBusy")]
public JoinDataComplete DirectorySearchBusy = new JoinDataComplete(
new JoinData
@@ -398,7 +425,9 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinType = eJoinType.Digital
});
/// <summary>
/// Directory Selected Entry Is Contact
/// </summary>
[JoinName("DirectoryEntryIsContact")]
public JoinDataComplete DirectoryEntryIsContact = new JoinDataComplete(
new JoinData
@@ -413,6 +442,9 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinType = eJoinType.Digital
});
/// <summary>
/// Directory Line Selected
/// </summary>
[JoinName("DirectoryLineSelected")]
public JoinDataComplete DirectoryLineSelected = new JoinDataComplete(
new JoinData
@@ -427,6 +459,9 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinType = eJoinType.Digital
});
/// <summary>
/// Directory Is Root
/// </summary>
[JoinName("DirectoryIsRoot")]
public JoinDataComplete DirectoryIsRoot = new JoinDataComplete(
new JoinData
@@ -441,6 +476,9 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinType = eJoinType.Digital
});
/// <summary>
/// Directory Has Changed
/// </summary>
[JoinName("DirectoryHasChanged")]
public JoinDataComplete DirectoryHasChanged = new JoinDataComplete(
new JoinData
@@ -455,6 +493,9 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinType = eJoinType.Digital
});
/// <summary>
/// Directory Go to Root
/// </summary>
[JoinName("DirectoryRoot")]
public JoinDataComplete DirectoryRoot = new JoinDataComplete(
new JoinData
@@ -469,6 +510,9 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinType = eJoinType.Digital
});
/// <summary>
/// Directory Go Back One Level
/// </summary>
[JoinName("DirectoryFolderBack")]
public JoinDataComplete DirectoryFolderBack = new JoinDataComplete(
new JoinData
@@ -483,6 +527,9 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinType = eJoinType.Digital
});
/// <summary>
/// Directory Dial Selected Line
/// </summary>
[JoinName("DirectoryDialSelectedLine")]
public JoinDataComplete DirectoryDialSelectedLine = new JoinDataComplete(
new JoinData
@@ -539,7 +586,9 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinType = eJoinType.Digital
});
/// <summary>
/// Camera Tilt Up
/// </summary>
[JoinName("CameraTiltUp")]
public JoinDataComplete CameraTiltUp = new JoinDataComplete(
new JoinData
@@ -554,6 +603,9 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinType = eJoinType.Digital
});
/// <summary>
/// Camera Tilt Down
/// </summary>
[JoinName("CameraTiltDown")]
public JoinDataComplete CameraTiltDown = new JoinDataComplete(
new JoinData
@@ -568,6 +620,9 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinType = eJoinType.Digital
});
/// <summary>
/// Camera Pan Left
/// </summary>
[JoinName("CameraPanLeft")]
public JoinDataComplete CameraPanLeft = new JoinDataComplete(
new JoinData
@@ -582,6 +637,9 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinType = eJoinType.Digital
});
/// <summary>
/// Camera Pan Right
/// </summary>
[JoinName("CameraPanRight")]
public JoinDataComplete CameraPanRight = new JoinDataComplete(
new JoinData
@@ -596,6 +654,9 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinType = eJoinType.Digital
});
/// <summary>
/// Camera Zoom In
/// </summary>
[JoinName("CameraZoomIn")]
public JoinDataComplete CameraZoomIn = new JoinDataComplete(
new JoinData
@@ -610,6 +671,9 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinType = eJoinType.Digital
});
/// <summary>
/// Camera Zoom Out
/// </summary>
[JoinName("CameraZoomOut")]
public JoinDataComplete CameraZoomOut = new JoinDataComplete(
new JoinData
@@ -666,6 +730,9 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinType = eJoinType.Digital
});
/// <summary>
/// Camera Preset Save
/// </summary>
[JoinName("CameraPresetSave")]
public JoinDataComplete CameraPresetSave = new JoinDataComplete(
new JoinData
@@ -680,6 +747,9 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinType = eJoinType.Digital
});
/// <summary>
/// Camera Preset Recall
/// </summary>
[JoinName("CameraModeAuto")]
public JoinDataComplete CameraModeAuto = new JoinDataComplete(
new JoinData
@@ -694,6 +764,9 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinType = eJoinType.Digital
});
/// <summary>
/// Camera Mode Manual
/// </summary>
[JoinName("CameraModeManual")]
public JoinDataComplete CameraModeManual = new JoinDataComplete(
new JoinData
@@ -708,6 +781,9 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinType = eJoinType.Digital
});
/// <summary>
/// Camera Mode Off
/// </summary>
[JoinName("CameraModeOff")]
public JoinDataComplete CameraModeOff = new JoinDataComplete(
new JoinData
@@ -722,6 +798,9 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinType = eJoinType.Digital
});
/// <summary>
/// Camera Self View
/// </summary>
[JoinName("CameraSelfView")]
public JoinDataComplete CameraSelfView = new JoinDataComplete(
new JoinData
@@ -736,6 +815,9 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinType = eJoinType.Digital
});
/// <summary>
/// Camera Layout
/// </summary>
[JoinName("CameraLayout")]
public JoinDataComplete CameraLayout = new JoinDataComplete(
new JoinData
@@ -750,6 +832,9 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinType = eJoinType.Digital
});
/// <summary>
/// Camera Supports Auto Mode
/// </summary>
[JoinName("CameraSupportsAutoMode")]
public JoinDataComplete CameraSupportsAutoMode = new JoinDataComplete(
new JoinData
@@ -764,6 +849,9 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinType = eJoinType.Digital
});
/// <summary>
/// Camera Supports Off Mode
/// </summary>
[JoinName("CameraSupportsOffMode")]
public JoinDataComplete CameraSupportsOffMode = new JoinDataComplete(
new JoinData
@@ -778,6 +866,9 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinType = eJoinType.Digital
});
/// <summary>
/// Update Meetings
/// </summary>
[JoinName("UpdateMeetings")]
public JoinDataComplete UpdateMeetings = new JoinDataComplete(
new JoinData
@@ -806,6 +897,9 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinType = eJoinType.Digital
});
/// <summary>
/// Mic Mute On
/// </summary>
[JoinName("MicMuteOn")]
public JoinDataComplete MicMuteOn = new JoinDataComplete(
new JoinData
@@ -820,6 +914,9 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinType = eJoinType.Digital
});
/// <summary>
/// Mic Mute Off
/// </summary>
[JoinName("MicMuteOff")]
public JoinDataComplete MicMuteOff = new JoinDataComplete(
new JoinData
@@ -834,6 +931,9 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinType = eJoinType.Digital
});
/// <summary>
/// Mic Mute Toggle
/// </summary>
[JoinName("MicMuteToggle")]
public JoinDataComplete MicMuteToggle = new JoinDataComplete(
new JoinData
@@ -848,6 +948,9 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinType = eJoinType.Digital
});
/// <summary>
/// Volume Up
/// </summary>
[JoinName("VolumeUp")]
public JoinDataComplete VolumeUp = new JoinDataComplete(
new JoinData
@@ -862,6 +965,9 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinType = eJoinType.Digital
});
/// <summary>
/// Volume Down
/// </summary>
[JoinName("VolumeDown")]
public JoinDataComplete VolumeDown = new JoinDataComplete(
new JoinData
@@ -876,6 +982,9 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinType = eJoinType.Digital
});
/// <summary>
/// Volume Mute On
/// </summary>
[JoinName("VolumeMuteOn")]
public JoinDataComplete VolumeMuteOn = new JoinDataComplete(
new JoinData
@@ -890,6 +999,9 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinType = eJoinType.Digital
});
/// <summary>
/// Volume Mute Off
/// </summary>
[JoinName("VolumeMuteOff")]
public JoinDataComplete VolumeMuteOff = new JoinDataComplete(
new JoinData
@@ -904,6 +1016,9 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinType = eJoinType.Digital
});
/// <summary>
/// Volume Mute Toggle
/// </summary>
[JoinName("VolumeMuteToggle")]
public JoinDataComplete VolumeMuteToggle = new JoinDataComplete(
new JoinData
@@ -946,6 +1061,9 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinType = eJoinType.Digital
});
/// <summary>
/// Source Share Start
/// </summary>
[JoinName("SourceShareStart")]
public JoinDataComplete SourceShareStart = new JoinDataComplete(
new JoinData
@@ -960,6 +1078,9 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinType = eJoinType.Digital
});
/// <summary>
/// Source Share End
/// </summary>
[JoinName("SourceShareEnd")]
public JoinDataComplete SourceShareEnd = new JoinDataComplete(
new JoinData
@@ -974,6 +1095,9 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinType = eJoinType.Digital
});
/// <summary>
/// Source Share Auto Start
/// </summary>
[JoinName("AutoShareWhileInCall")]
public JoinDataComplete SourceShareAutoStart = new JoinDataComplete(
new JoinData
@@ -988,6 +1112,9 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinType = eJoinType.Digital
});
/// <summary>
/// Recieving Content
/// </summary>
[JoinName("RecievingContent")]
public JoinDataComplete RecievingContent = new JoinDataComplete(
new JoinData
@@ -1002,6 +1129,9 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinCapabilities = eJoinCapabilities.ToSIMPL
});
/// <summary>
/// Selfview Position
/// </summary>
[JoinName("SelfviewPosition")]
public JoinDataComplete SelfviewPosition = new JoinDataComplete(
new JoinData
@@ -1179,6 +1309,9 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinType = eJoinType.Analog
});
/// <summary>
/// Minutes Before Meeting Start
/// </summary>
[JoinName("MinutesBeforeMeetingStart")]
public JoinDataComplete MinutesBeforeMeetingStart = new JoinDataComplete(
new JoinData
@@ -1193,6 +1326,9 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinType = eJoinType.Analog
});
/// <summary>
/// Camera Number Select
/// </summary>
[JoinName("CameraNumberSelect")]
public JoinDataComplete CameraNumberSelect = new JoinDataComplete(
new JoinData
@@ -1221,6 +1357,9 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinType = eJoinType.Analog
});
/// <summary>
/// Directory Row Count
/// </summary>
[JoinName("DirectoryRowCount")]
public JoinDataComplete DirectoryRowCount = new JoinDataComplete(
new JoinData
@@ -1235,6 +1374,9 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinType = eJoinType.Analog
});
/// <summary>
/// Directory Select Row
/// </summary>
[JoinName("DirectorySelectRow")]
public JoinDataComplete DirectorySelectRow = new JoinDataComplete(
new JoinData
@@ -1293,7 +1435,9 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
});
/// <summary>
/// Camera Preset Select
/// </summary>
[JoinName("CameraPresetSelect")]
public JoinDataComplete CameraPresetSelect = new JoinDataComplete(
new JoinData
@@ -1322,6 +1466,9 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinType = eJoinType.Analog
});
/// <summary>
/// Participant Count
/// </summary>
[JoinName("ParticipantCount")]
public JoinDataComplete ParticipantCount = new JoinDataComplete(
new JoinData
@@ -1336,6 +1483,9 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinType = eJoinType.Analog
});
/// <summary>
/// Meeting Count
/// </summary>
[JoinName("Meeting Count Fb")]
public JoinDataComplete MeetingCount = new JoinDataComplete(
new JoinData
@@ -1350,6 +1500,9 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinType = eJoinType.Analog
});
/// <summary>
/// Volume Level
/// </summary>
[JoinName("VolumeLevel")]
public JoinDataComplete VolumeLevel = new JoinDataComplete(
new JoinData
@@ -1412,6 +1565,9 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
#region Serials
/// <summary>
/// Current Dial String
/// </summary>
[JoinName("CurrentDialString")]
public JoinDataComplete CurrentDialString = new JoinDataComplete(
new JoinData
@@ -1454,6 +1610,9 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinType = eJoinType.Serial
});
/// <summary>
/// Call Direction
/// </summary>
[JoinName("CallDirection")]
public JoinDataComplete CallDirection = new JoinDataComplete(
new JoinData
@@ -1468,6 +1627,9 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinType = eJoinType.Serial
});
/// <summary>
/// Incoming Call Name
/// </summary>
[JoinName("IncomingCallName")]
public JoinDataComplete IncomingCallName = new JoinDataComplete(
new JoinData
@@ -1482,6 +1644,9 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinType = eJoinType.Serial
});
/// <summary>
/// Incoming Call Number
/// </summary>
[JoinName("IncomingCallNumber")]
public JoinDataComplete IncomingCallNumber = new JoinDataComplete(
new JoinData
@@ -1496,7 +1661,9 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinType = eJoinType.Serial
});
/// <summary>
/// Directory Search String
/// </summary>
[JoinName("DirectorySearchString")]
public JoinDataComplete DirectorySearchString = new JoinDataComplete(
new JoinData
@@ -1511,6 +1678,9 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinType = eJoinType.Serial
});
/// <summary>
/// Directory Entries
/// </summary>
[JoinName("DirectoryEntries")]
public JoinDataComplete DirectoryEntries = new JoinDataComplete(
new JoinData
@@ -1525,6 +1695,9 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinType = eJoinType.Serial
});
/// <summary>
/// Schedule Data
/// </summary>
[JoinName("Schedule")]
public JoinDataComplete Schedule = new JoinDataComplete(
new JoinData
@@ -1553,6 +1726,9 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinType = eJoinType.Serial
});
/// <summary>
/// Camera Preset Names
/// </summary>
[JoinName("CameraPresetNames")]
public JoinDataComplete CameraPresetNames = new JoinDataComplete(
new JoinData
@@ -1610,7 +1786,9 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
});
/// <summary>
/// Current Participants XSig
/// </summary>
[JoinName("CurrentParticipants")]
public JoinDataComplete CurrentParticipants = new JoinDataComplete(
new JoinData
@@ -1695,6 +1873,9 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinType = eJoinType.Serial
});
/// <summary>
/// Current Source
/// </summary>
[JoinName("CurrentSource")]
public JoinDataComplete CurrentSource = new JoinDataComplete(
new JoinData
@@ -1709,6 +1890,9 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinType = eJoinType.Serial
});
/// <summary>
/// Selfview Position Feedback
/// </summary>
[JoinName("SelfviewPositionFb")]
public JoinDataComplete SelfviewPositionFb = new JoinDataComplete(
new JoinData
@@ -1793,6 +1977,9 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinType = eJoinType.Serial
});
/// <summary>
/// Selected Directory Entry Name
/// </summary>
[JoinName("DirectoryEntrySelectedName")]
public JoinDataComplete DirectoryEntrySelectedName = new JoinDataComplete(
new JoinData
@@ -1807,6 +1994,9 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinType = eJoinType.Serial
});
/// <summary>
/// Selected Directory Entry Number
/// </summary>
[JoinName("DirectoryEntrySelectedNumber")]
public JoinDataComplete DirectoryEntrySelectedNumber = new JoinDataComplete(
new JoinData
@@ -1821,6 +2011,9 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
JoinType = eJoinType.Serial
});
/// <summary>
/// Selected Directory Folder Name
/// </summary>
[JoinName("DirectorySelectedFolderName")]
public JoinDataComplete DirectorySelectedFolderName = new JoinDataComplete(
new JoinData
@@ -1838,11 +2031,20 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
#endregion
/// <summary>
/// Constructor for the VideoCodecControllerJoinMap class.
/// </summary>
/// <param name="joinStart">Join this join map will start at</param>
public VideoCodecControllerJoinMap(uint joinStart)
: base(joinStart, typeof(VideoCodecControllerJoinMap))
{
}
/// <summary>
///
/// </summary>
/// <param name="joinStart">Join this join map will start at</param>
/// <param name="type">Type of the child join map</param>
public VideoCodecControllerJoinMap(uint joinStart, Type type)
: base(joinStart, type)
{

View File

@@ -1,12 +1,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using Crestron.SimplSharp;
using Crestron.SimplSharpPro;
using Crestron.SimplSharpPro.GeneralIO;
using PepperDash.Core;
using PepperDash.Core.Logging;
using Serilog.Events;
@@ -16,9 +15,19 @@ namespace PepperDash.Essentials.Core;
{
public CommunicationStreamDebugging StreamDebugging { get; private set; }
/// <summary>
/// Event fired when bytes are received
/// </summary>
public event EventHandler<GenericCommMethodReceiveBytesArgs> BytesReceived;
/// <summary>
/// Event fired when text is received
/// </summary>
public event EventHandler<GenericCommMethodReceiveTextArgs> TextReceived;
/// <summary>
/// Gets or sets the IsConnected
/// </summary>
public bool IsConnected { get { return true; } }
ComPort Port;
@@ -29,7 +38,7 @@ namespace PepperDash.Essentials.Core;
{
StreamDebugging = new CommunicationStreamDebugging(key);
Spec = spec;
Spec = spec;
AddPostActivationAction(() =>
{
@@ -39,12 +48,18 @@ namespace PepperDash.Essentials.Core;
});
}
/// <summary>
/// Constructor
/// </summary>
/// <param name="key">Device key</param>
/// <param name="port">COM port instance</param>
/// <param name="spec">COM port specification</param>
public ComPortController(string key, ComPort port, ComPort.ComPortSpec spec)
: base(key)
{
if (port == null)
{
Debug.LogMessage(LogEventLevel.Information, this, "ERROR: Invalid com port, continuing but comms will not function");
Debug.LogMessage(LogEventLevel.Information, this, "ERROR: Invalid com port, continuing but comms will not function");
return;
}
@@ -72,16 +87,33 @@ namespace PepperDash.Essentials.Core;
}
}
var specResult = Port.SetComPortSpec(Spec);
if (specResult != 0)
{
Debug.LogMessage(LogEventLevel.Information, this, "WARNING: Cannot set comspec");
return;
}
Port.SerialDataReceived += Port_SerialDataReceived;
}
~ComPortController()
if (Port.Parent is CrestronControlSystem || Port.Parent is CenIoCom102)
{
var result = Port.Register();
if (result != eDeviceRegistrationUnRegistrationResponse.Success)
{
this.LogError($"Cannot register {Key} using {Port.Parent.GetType().Name}-comport-{Port.ID} (result == {result})");
return;
}
this.LogInformation($"Successfully registered {Key} using {Port.Parent.GetType().Name}-comport-{Port.ID} (result == {result})");
}
var specResult = Port.SetComPortSpec(Spec);
if (specResult != 0)
{
this.LogError($"Cannot set comspec for {Key} using {Port.Parent.GetType().Name}-comport-{Port.ID} (result == {specResult})");
return;
}
this.LogInformation($"Successfully set comspec for {Key} using {Port.Parent.GetType().Name}-comport-{Port.ID} (result == {specResult})");
Port.SerialDataReceived += Port_SerialDataReceived;
}
/// <summary>
/// Destructor
/// </summary>
~ComPortController()
{
Port.SerialDataReceived -= Port_SerialDataReceived;
}
@@ -116,6 +148,10 @@ namespace PepperDash.Essentials.Core;
if(!eventSubscribed) Debug.LogMessage(LogEventLevel.Warning, this, "Received data but no handler is registered");
}
/// <summary>
/// Deactivate method
/// </summary>
/// <inheritdoc />
public override bool Deactivate()
{
return Port.UnRegister() == eDeviceRegistrationUnRegistrationResponse.Success;
@@ -123,6 +159,9 @@ namespace PepperDash.Essentials.Core;
#region IBasicCommunication Members
/// <summary>
/// SendText method
/// </summary>
public void SendText(string text)
{
if (Port == null)
@@ -133,6 +172,9 @@ namespace PepperDash.Essentials.Core;
Port.Send(text);
}
/// <summary>
/// SendBytes method
/// </summary>
public void SendBytes(byte[] bytes)
{
if (Port == null)
@@ -144,10 +186,16 @@ namespace PepperDash.Essentials.Core;
Port.Send(text);
}
/// <summary>
/// Connect method
/// </summary>
public void Connect()
{
{
}
/// <summary>
/// Disconnect method
/// </summary>
public void Disconnect()
{
}
@@ -173,4 +221,4 @@ namespace PepperDash.Essentials.Core;
OnDataReceived(b.ToString());
}
}
}

View File

@@ -0,0 +1,138 @@
using System;
using System.Collections.Generic;
using System.Text;
using Crestron.SimplSharp.CrestronSockets;
using Crestron.SimplSharpPro.DeviceSupport;
using Newtonsoft.Json;
using PepperDash.Core;
using PepperDash.Core.Logging;
using PepperDash.Essentials.Core.Bridges;
using PepperDash.Essentials.Core.Config;
using PepperDash.Essentials.Core.Devices;
namespace PepperDash.Essentials.Core
{
/// <summary>
/// Implements IBasicCommunication and sends all communication through an EISC
/// </summary>
[Description("Generic communication wrapper class for any IBasicCommunication type")]
public class CommBridge : EssentialsBridgeableDevice, IBasicCommunication
{
private EiscApiAdvanced eisc;
private IBasicCommunicationJoinMap joinMap;
/// <summary>
/// Event triggered when text is received through the communication bridge.
/// </summary>
public event EventHandler<GenericCommMethodReceiveTextArgs> TextReceived;
/// <summary>
/// Event triggered when bytes are received through the communication bridge.
/// </summary>
public event EventHandler<GenericCommMethodReceiveBytesArgs> BytesReceived;
/// <summary>
/// Indicates whether the communication bridge is currently connected.
/// </summary>
public bool IsConnected { get; private set; }
/// <summary>
/// Initializes a new instance of the <see cref="CommBridge"/> class.
/// </summary>
/// <param name="key">The unique key for the communication bridge.</param>
/// <param name="name">The display name for the communication bridge.</param>
public CommBridge(string key, string name)
: base(key, name)
{
}
/// <summary>
/// Sends a byte array through the communication bridge.
/// </summary>
/// <param name="bytes">The byte array to send.</param>
public void SendBytes(byte[] bytes)
{
if (eisc == null)
{
this.LogWarning("EISC is null, cannot send bytes.");
return;
}
eisc.Eisc.SetString(joinMap.SendText.JoinNumber, Encoding.ASCII.GetString(bytes, 0, bytes.Length));
}
/// <summary>
/// Sends a text string through the communication bridge.
/// </summary>
/// <param name="text">The text string to send.</param>
public void SendText(string text)
{
if (eisc == null)
{
this.LogWarning("EISC is null, cannot send text.");
return;
}
eisc.Eisc.SetString(joinMap.SendText.JoinNumber, text);
}
/// <summary>
/// Initiates a connection through the communication bridge.
/// </summary>
public void Connect()
{
if (eisc == null)
{
this.LogWarning("EISC is null, cannot connect.");
return;
}
eisc.Eisc.SetBool(joinMap.Connect.JoinNumber, true);
}
/// <summary>
/// Terminates the connection through the communication bridge.
/// </summary>
public void Disconnect()
{
if (eisc == null)
{
this.LogWarning("EISC is null, cannot disconnect.");
return;
}
eisc.Eisc.SetBool(joinMap.Connect.JoinNumber, false);
}
/// <inheritdoc />
public override void LinkToApi(BasicTriList trilist, uint joinStart, string joinMapKey, EiscApiAdvanced bridge)
{
joinMap = new IBasicCommunicationJoinMap(joinStart);
var joinMapSerialized = JoinMapHelper.GetSerializedJoinMapForDevice(joinMapKey);
if (!string.IsNullOrEmpty(joinMapSerialized))
joinMap = JsonConvert.DeserializeObject<IBasicCommunicationJoinMap>(joinMapSerialized);
if (bridge != null)
{
bridge.AddJoinMap(Key, joinMap);
}
else
{
this.LogWarning("Please update config to use 'eiscapiadvanced' to get all join map features for this device.");
}
this.LogDebug("Linking to Trilist '{0}'", trilist.ID.ToString("X"));
eisc = bridge;
trilist.SetBoolSigAction(joinMap.Connected.JoinNumber, (b) => IsConnected = b);
trilist.SetStringSigAction(joinMap.TextReceived.JoinNumber, (s) =>
{
TextReceived?.Invoke(this, new GenericCommMethodReceiveTextArgs(s));
BytesReceived?.Invoke(this, new GenericCommMethodReceiveBytesArgs(Encoding.ASCII.GetBytes(s)));
});
}
}
}

View File

@@ -1,11 +1,9 @@
using System;
using Crestron.SimplSharp;
using Crestron.SimplSharpPro;
using Crestron.SimplSharpPro.DM;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using PepperDash.Core;
using PepperDash.Essentials.Core.Config;
using Serilog.Events;
@@ -17,6 +15,11 @@ namespace PepperDash.Essentials.Core;
/// </summary>
public class CommFactory
{
/// <summary>
/// GetControlPropertiesConfig method
/// </summary>
/// <param name="deviceConfig">The Device config object</param>
/// <returns>EssentialsControlPropertiesConfig object</returns>
public static EssentialsControlPropertiesConfig GetControlPropertiesConfig(DeviceConfig deviceConfig)
{
try
@@ -38,6 +41,9 @@ namespace PepperDash.Essentials.Core;
/// Returns a comm method of either com port, TCP, SSH, and puts this into the DeviceManager
/// </summary>
/// <param name="deviceConfig">The Device config object</param>
/// <summary>
/// CreateCommForDevice method
/// </summary>
public static IBasicCommunication CreateCommForDevice(DeviceConfig deviceConfig)
{
EssentialsControlPropertiesConfig controlConfig = GetControlPropertiesConfig(deviceConfig);
@@ -86,8 +92,10 @@ namespace PepperDash.Essentials.Core;
break;
case eControlMethod.SecureTcpIp:
{
var secureTcp = new GenericSecureTcpIpClient(deviceConfig.Key + "-secureTcp", c.Address, c.Port, c.BufferSize);
secureTcp.AutoReconnect = c.AutoReconnect;
var secureTcp = new GenericSecureTcpIpClient(deviceConfig.Key + "-secureTcp", c.Address, c.Port, c.BufferSize)
{
AutoReconnect = c.AutoReconnect
};
if (secureTcp.AutoReconnect)
secureTcp.AutoReconnectIntervalMs = c.AutoReconnectIntervalMs;
comm = secureTcp;
@@ -95,7 +103,7 @@ namespace PepperDash.Essentials.Core;
}
default:
break;
}
}
}
catch (Exception e)
{
@@ -104,12 +112,14 @@ namespace PepperDash.Essentials.Core;
}
// put it in the device manager if it's the right flavor
var comDev = comm as Device;
if (comDev != null)
if (comm is Device comDev)
DeviceManager.AddDevice(comDev);
return comm;
}
/// <summary>
/// GetComPort method
/// </summary>
public static ComPort GetComPort(EssentialsControlPropertiesConfig config)
{
var comPar = config.ComParams;
@@ -131,45 +141,45 @@ namespace PepperDash.Essentials.Core;
{
var dev = DeviceManager.GetDeviceForKey(config.ControlPortDevKey);
Debug.LogMessage(LogEventLevel.Information, "GetCecPort: device '{0}' {1}", config.ControlPortDevKey, dev == null
? "is not valid, failed to get cec port"
: "found in device manager, attempting to get cec port");
Debug.LogMessage(LogEventLevel.Information, "GetCecPort: device '{0}' {1}", config.ControlPortDevKey, dev == null
? "is not valid, failed to get cec port"
: "found in device manager, attempting to get cec port");
if (dev == null)
return null;
if (dev == null)
return null;
if (String.IsNullOrEmpty(config.ControlPortName))
{
Debug.LogMessage(LogEventLevel.Information, "GetCecPort: '{0}' - Configuration missing 'ControlPortName'", config.ControlPortDevKey);
return null;
}
if (String.IsNullOrEmpty(config.ControlPortName))
{
Debug.LogMessage(LogEventLevel.Information, "GetCecPort: '{0}' - Configuration missing 'ControlPortName'", config.ControlPortDevKey);
return null;
}
var inputsOutputs = dev as IRoutingInputsOutputs;
if (inputsOutputs == null)
{
var inputsOutputs = dev as IRoutingInputsOutputs;
if (inputsOutputs == null)
{
Debug.LogMessage(LogEventLevel.Information, "GetCecPort: Device '{0}' does not support IRoutingInputsOutputs, failed to get CEC port called '{1}'",
config.ControlPortDevKey, config.ControlPortName);
return null;
}
return null;
}
var inputPort = inputsOutputs.InputPorts[config.ControlPortName];
if (inputPort != null && inputPort.Port is ICec)
if (inputPort != null && inputPort.Port is ICec)
return inputPort.Port as ICec;
var outputPort = inputsOutputs.OutputPorts[config.ControlPortName];
if (outputPort != null && outputPort.Port is ICec)
return outputPort.Port as ICec;
}
catch (Exception ex)
{
Debug.LogMessage(LogEventLevel.Debug, "GetCecPort Exception Message: {0}", ex.Message);
Debug.LogMessage(LogEventLevel.Verbose, "GetCecPort Exception StackTrace: {0}", ex.StackTrace);
if (ex.InnerException != null)
Debug.LogMessage(LogEventLevel.Information, "GetCecPort Exception InnerException: {0}", ex.InnerException);
}
var outputPort = inputsOutputs.OutputPorts[config.ControlPortName];
if (outputPort != null && outputPort.Port is ICec)
return outputPort.Port as ICec;
}
catch (Exception ex)
{
Debug.LogMessage(LogEventLevel.Debug, "GetCecPort Exception Message: {0}", ex.Message);
Debug.LogMessage(LogEventLevel.Verbose, "GetCecPort Exception StackTrace: {0}", ex.StackTrace);
if (ex.InnerException != null)
Debug.LogMessage(LogEventLevel.Information, "GetCecPort Exception InnerException: {0}", ex.InnerException);
}
Debug.LogMessage(LogEventLevel.Information, "GetCecPort: Device '{0}' does not have a CEC port called '{1}'",
config.ControlPortDevKey, config.ControlPortName);
@@ -182,6 +192,9 @@ namespace PepperDash.Essentials.Core;
/// return the ControlSystem object from the Global class.
/// </summary>
/// <returns>IComPorts device or null if the device is not found or does not implement IComPorts</returns>
/// <summary>
/// GetIComPortsDeviceFromManagedDevice method
/// </summary>
public static IComPorts GetIComPortsDeviceFromManagedDevice(string ComPortDevKey)
{
if ((ComPortDevKey.Equals("controlSystem", System.StringComparison.OrdinalIgnoreCase)

View File

@@ -20,5 +20,8 @@ namespace PepperDash.Essentials.Core;
/// </summary>
public interface IComPortsDevice
{
/// <summary>
/// Gets the Device
/// </summary>
IComPorts Device { get; }
}

View File

@@ -13,20 +13,36 @@ namespace PepperDash.Essentials.Core;
public class ConsoleCommMockDevice : EssentialsDevice, ICommunicationMonitor
{
/// <summary>
/// Gets or sets the Communication
/// </summary>
public IBasicCommunication Communication { get; private set; }
/// <summary>
/// Gets or sets the PortGather
/// </summary>
public CommunicationGather PortGather { get; private set; }
/// <summary>
/// Gets or sets the CommunicationMonitor
/// </summary>
public StatusMonitorBase CommunicationMonitor { get; private set; }
/// <summary>
/// Defaults to \x0a
/// </summary>
/// <summary>
/// Gets or sets the LineEnding
/// </summary>
public string LineEnding { get; set; }
/// <summary>
/// Set to true to show responses in full hex
/// </summary>
/// <summary>
/// Gets or sets the ShowHexResponse
/// </summary>
public bool ShowHexResponse { get; set; }
/// <summary>
/// Initializes a new instance of the ConsoleCommMockDevice class.
/// </summary>
/// <param name="key">The key of the device.</param>
/// <param name="name">The name of the device.</param>
/// <param name="props">The properties configuration for the device.</param>
/// <param name="comm">The communication method for the device.</param>
public ConsoleCommMockDevice(string key, string name, ConsoleCommMockDevicePropertiesConfig props, IBasicCommunication comm)
:base(key, name)
{
@@ -37,6 +53,10 @@ namespace PepperDash.Essentials.Core;
LineEnding = props.LineEnding;
}
/// <summary>
/// CustomActivate method
/// </summary>
/// <inheritdoc />
public override bool CustomActivate()
{
Communication.Connect();
@@ -56,11 +76,24 @@ namespace PepperDash.Essentials.Core;
}
}
/// <summary>
/// Represents a ConsoleCommMockDevicePropertiesConfig
/// </summary>
public class ConsoleCommMockDevicePropertiesConfig
{
/// <summary>
/// Gets or sets the LineEnding
/// </summary>
public string LineEnding { get; set; }
/// <summary>
/// Gets or sets the CommunicationMonitorProperties
/// </summary>
public CommunicationMonitorConfig CommunicationMonitorProperties { get; set; }
/// <summary>
/// Initializes a new instance of the ConsoleCommMockDevicePropertiesConfig class.
/// </summary>
public ConsoleCommMockDevicePropertiesConfig()
{
LineEnding = "\x0a";

View File

@@ -1,111 +0,0 @@
using Crestron.SimplSharp.Net.Http;
using PepperDash.Core;
using System;
namespace PepperDash.Essentials.Core;
[Obsolete("Please use the builtin HttpClient class instead: https://learn.microsoft.com/en-us/dotnet/fundamentals/networking/http/httpclient-guidelines")]
public class GenericHttpClient : Device, IBasicCommunication
{
private readonly HttpClient Client;
public event EventHandler<GenericHttpClientEventArgs> ResponseRecived;
public GenericHttpClient(string key, string name, string hostname)
: base(key, name)
{
Client = new HttpClient
{
HostName = hostname
};
}
/// <summary>
///
/// </summary>
/// <param name="path"></param>
public void SendText(string path)
{
HttpClientRequest request = new HttpClientRequest();
string url = string.Format("http://{0}/{1}", Client.HostName, path);
request.Url = new UrlParser(url);
HttpClient.DISPATCHASYNC_ERROR error = Client.DispatchAsyncEx(request, Response, request);
}
public void SendText(string format, params object[] items)
{
HttpClientRequest request = new HttpClientRequest();
string url = string.Format("http://{0}/{1}", Client.HostName, string.Format(format, items));
request.Url = new UrlParser(url);
HttpClient.DISPATCHASYNC_ERROR error = Client.DispatchAsyncEx(request, Response, request);
}
public void SendTextNoResponse(string format, params object[] items)
{
HttpClientRequest request = new HttpClientRequest();
string url = string.Format("http://{0}/{1}", Client.HostName, string.Format(format, items));
request.Url = new UrlParser(url);
Client.Dispatch(request);
}
private void Response(HttpClientResponse response, HTTP_CALLBACK_ERROR error, object request)
{
if (error == HTTP_CALLBACK_ERROR.COMPLETED)
{
var responseReceived = response;
if (responseReceived.ContentString.Length > 0)
{
ResponseRecived?.Invoke(this, new GenericHttpClientEventArgs(responseReceived.ContentString, (request as HttpClientRequest).Url.ToString(), error));
}
}
}
#region IBasicCommunication Members
public void SendBytes(byte[] bytes)
{
throw new NotImplementedException();
}
#endregion
#region ICommunicationReceiver Members
public event EventHandler<GenericCommMethodReceiveBytesArgs> BytesReceived;
public void Connect()
{
throw new NotImplementedException();
}
public void Disconnect()
{
throw new NotImplementedException();
}
public bool IsConnected
{
get { return true; }
}
public event EventHandler<GenericCommMethodReceiveTextArgs> TextReceived;
#endregion
}
public class GenericHttpClientEventArgs : EventArgs
{
public string ResponseText { get; private set; }
public string RequestPath { get; private set; }
public HTTP_CALLBACK_ERROR Error { get; set; }
public GenericHttpClientEventArgs(string response, string request, HTTP_CALLBACK_ERROR error)
{
ResponseText = response;
RequestPath = request;
Error = error;
}
}

View File

@@ -15,10 +15,13 @@ using Serilog.Events;
namespace PepperDash.Essentials.Core;
/// <summary>
///
/// Helper class for IR port operations
/// </summary>
public static class IRPortHelper
{
/// <summary>
/// Gets the IrDriverPathPrefix
/// </summary>
public static string IrDriverPathPrefix
{
get
@@ -31,7 +34,7 @@ namespace PepperDash.Essentials.Core;
/// Finds either the ControlSystem or a device controller that contains IR ports and
/// returns a port from the hardware device
/// </summary>
/// <param name="propsToken"></param>
/// <param name="propsToken">JSON token containing properties</param>
/// <returns>IrPortConfig object. The port and or filename will be empty/null
/// if valid values don't exist on config</returns>
public static IrOutPortConfig GetIrPort(JToken propsToken)
@@ -83,6 +86,11 @@ namespace PepperDash.Essentials.Core;
}
}
/// <summary>
/// GetIrOutputPort method
/// </summary>
/// <param name="dc">DeviceConfig to get the IR port for</param>
/// <returns>IROutputPort or null if not found</returns>
public static IROutputPort GetIrOutputPort(DeviceConfig dc)
{
var irControllerKey = dc.Key + "-ir";
@@ -142,6 +150,11 @@ namespace PepperDash.Essentials.Core;
return port;
}
/// <summary>
/// GetIrOutputPortController method
/// </summary>
/// <param name="config">DeviceConfig to create the IrOutputPortController for</param>
/// <returns>IrOutputPortController object</returns>
public static IrOutputPortController GetIrOutputPortController(DeviceConfig config)
{
Debug.LogMessage(LogEventLevel.Debug, "Attempting to create new Ir Port Controller");
@@ -223,19 +236,31 @@ namespace PepperDash.Essentials.Core;
}
/// <summary>
/// Wrapper to help in IR port creation
/// Represents a IrOutPortConfig
/// </summary>
public class IrOutPortConfig
{
/// <summary>
/// Gets or sets the Port
/// </summary>
[JsonProperty("port")]
public IROutputPort Port { get; set; }
public IROutputPort Port { get; set; }
/// <summary>
/// Gets or sets the FileName
/// </summary>
[JsonProperty("fileName")]
public string FileName { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to use bridge join map
/// </summary>
[JsonProperty("useBridgeJoinMap")]
public bool UseBridgeJoinMap { get; set; }
/// <summary>
/// Constructor
/// </summary>
public IrOutPortConfig()
{
FileName = "";

View File

@@ -0,0 +1,29 @@
using System;
using Newtonsoft.Json;
namespace PepperDash.Essentials.Core.Config
{
/// <summary>
/// Represents the base properties for a streaming device.
/// </summary>
public class BaseStreamingDeviceProperties
{
/// <summary>
/// The multicast video address for the streaming device.
/// </summary>
[JsonProperty("multicastVideoAddress", NullValueHandling = NullValueHandling.Ignore)]
public string MulticastVideoAddress { get; set; }
/// <summary>
/// The multicast audio address for the streaming device.
/// </summary>
[JsonProperty("multicastAudioAddress", NullValueHandling = NullValueHandling.Ignore)]
public string MulticastAudioAddress { get; set; }
/// <summary>
/// The URL for the streaming device's media stream.
/// </summary>
[JsonProperty("streamUrl", NullValueHandling = NullValueHandling.Ignore)]
public string StreamUrl { get; set; }
}
}

View File

@@ -14,12 +14,21 @@ namespace PepperDash.Essentials.Core.Config;
/// </summary>
public class BasicConfig
{
/// <summary>
/// Gets or sets the Info
/// </summary>
[JsonProperty("info")]
public InfoConfig Info { get; set; }
/// <summary>
/// Gets or sets the Devices
/// </summary>
[JsonProperty("devices")]
public List<DeviceConfig> Devices { get; set; }
/// <summary>
/// Gets or sets the SourceLists
/// </summary>
[JsonProperty("sourceLists")]
public Dictionary<string, Dictionary<string, SourceListItem>> SourceLists { get; set; }
@@ -119,4 +128,4 @@ namespace PepperDash.Essentials.Core.Config;
return null;
}
}
}
}

View File

@@ -18,14 +18,23 @@ namespace PepperDash.Essentials.Core.Config;
/// </summary>
public class ConfigReader
{
/// <summary>
/// Local Config Present Message
/// </summary>
public const string LocalConfigPresent =
@"
***************************************************
************* Using Local config file *************
***************************************************";
/// <summary>
/// The loaded config object
/// </summary>
public static EssentialsConfig ConfigObject { get; private set; }
/// <summary>
/// LoadConfig2 method
/// </summary>
public static bool LoadConfig2()
{
Debug.LogMessage(LogEventLevel.Information, "Loading unmerged system/template portal configuration file.");
@@ -118,7 +127,46 @@ namespace PepperDash.Essentials.Core.Config;
{
ConfigObject = JObject.Parse(fs.ReadToEnd()).ToObject<EssentialsConfig>();
Debug.LogMessage(LogEventLevel.Information, "Successfully Loaded Local Config");
if (localConfigFound)
{
ConfigObject = JObject.Parse(fs.ReadToEnd()).ToObject<EssentialsConfig>();
Debug.LogMessage(LogEventLevel.Information, "Successfully Loaded Local Config");
return true;
}
else
{
var parsedConfig = JObject.Parse(fs.ReadToEnd());
// Check if it's a v2 config (check for "version" node)
// this means it's already merged by the Portal API
// from the v2 config tool
var isV2Config = parsedConfig["versions"] != null;
if (isV2Config)
{
Debug.LogMessage(LogEventLevel.Information, "Config file is a v2 format, no merge necessary.");
ConfigObject = parsedConfig.ToObject<EssentialsConfig>();
Debug.LogMessage(LogEventLevel.Information, "Successfully Loaded v2 Config");
return true;
}
// Extract SystemUrl and TemplateUrl into final config output
ConfigObject = PortalConfigReader.MergeConfigs(parsedConfig).ToObject<EssentialsConfig>();
if (parsedConfig["system_url"] != null)
{
ConfigObject.SystemUrl = parsedConfig["system_url"].Value<string>();
}
if (parsedConfig["template_url"] != null)
{
ConfigObject.TemplateUrl = parsedConfig["template_url"].Value<string>();
}
}
Debug.LogMessage(LogEventLevel.Information, "Successfully Loaded Merged Config");
return true;
}

View File

@@ -202,17 +202,51 @@ public static class ConfigUpdater
}
public enum eUpdateStatus
{
UpdateStarted,
ConfigFileReceived,
ArchivingConfigs,
DeletingLocalConfig,
WritingConfigFile,
RestartingProgram,
UpdateSucceeded,
UpdateFailed
}
/// <summary>
/// Enumeration of eUpdateStatus values
/// </summary>
public enum eUpdateStatus
{
/// <summary>
/// UpdateStarted status
/// </summary>
UpdateStarted,
/// <summary>
/// ConfigFileReceived status
/// </summary>
ConfigFileReceived,
/// <summary>
/// ArchivingConfigs status
/// </summary>
ArchivingConfigs,
/// <summary>
/// DeletingLocalConfig status
/// </summary>
DeletingLocalConfig,
/// <summary>
/// WritingConfigFile status
/// </summary>
WritingConfigFile,
/// <summary>
/// RestartingProgram status
/// </summary>
RestartingProgram,
/// <summary>
/// UpdateSucceeded status
/// </summary>
UpdateSucceeded,
/// <summary>
/// UpdateFailed status
/// </summary>
UpdateFailed
}
public class ConfigStatusEventArgs : EventArgs
{

View File

@@ -1,10 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
using System.Threading;
using Timer = System.Timers.Timer;
using Crestron.SimplSharp.CrestronIO;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
@@ -18,12 +17,27 @@ namespace PepperDash.Essentials.Core.Config;
/// </summary>
public class ConfigWriter
{
/// <summary>
/// The name of the subfolder where the config file will be written
/// </summary>
public const string LocalConfigFolder = "LocalConfig";
public const long WriteTimeout = 30000;
/// <summary>
/// The amount of time in milliseconds to wait after the last config update before writing the config file. This is to prevent multiple rapid updates from causing multiple file writes.
/// Default is 30 seconds.
/// </summary>
public const long WriteTimeoutInMs = 30000;
public static CTimer WriteTimer;
static CCriticalSection fileLock = new CCriticalSection();
private static Timer WriteTimer;
static readonly object _fileLock = new();
static ConfigWriter()
{
WriteTimer = new Timer(WriteTimeoutInMs);
WriteTimer.Elapsed += (s, e) => WriteConfigFile(null);
}
/// <summary>
/// Updates the config properties of a device
@@ -53,6 +67,9 @@ public class ConfigWriter
return success;
}
/// <summary>
/// Updates the config properties of a device
/// </summary>
public static bool UpdateDeviceConfig(DeviceConfig config)
{
bool success = false;
@@ -73,17 +90,20 @@ public class ConfigWriter
return success;
}
/// <summary>
/// Updates the config properties of a room
/// </summary>
public static bool UpdateRoomConfig(DeviceConfig config)
{
bool success = false;
var roomConfigIndex = ConfigReader.ConfigObject.Rooms.FindIndex(d => d.Key.Equals(config.Key));
var roomConfigIndex = ConfigReader.ConfigObject.Rooms.FindIndex(d => d.Key.Equals(config.Key));
if (roomConfigIndex >= 0)
if (roomConfigIndex >= 0)
{
ConfigReader.ConfigObject.Rooms[roomConfigIndex] = config;
Debug.LogMessage(LogEventLevel.Debug, "Updated room of device: '{0}'", config.Key);
Debug.LogMessage(LogEventLevel.Debug, "Updated config of room: '{0}'", config.Key);
success = true;
}
@@ -98,10 +118,9 @@ public class ConfigWriter
/// </summary>
static void ResetTimer()
{
if (WriteTimer == null)
WriteTimer = new CTimer(WriteConfigFile, WriteTimeout);
WriteTimer.Reset(WriteTimeout);
WriteTimer.Stop();
WriteTimer.Interval = WriteTimeoutInMs;
WriteTimer.Start();
Debug.LogMessage(LogEventLevel.Debug, "Config File write timer has been reset.");
}
@@ -120,10 +139,10 @@ public class ConfigWriter
}
/// <summary>
/// Writes
/// Writes the specified configuration data to a file.
/// </summary>
/// <param name="filepath"></param>
/// <param name="o"></param>
/// <param name="filePath">The path of the file to write to.</param>
/// <param name="configData">The configuration data to write.</param>
public static void WriteFile(string filePath, string configData)
{
if (WriteTimer != null)
@@ -133,9 +152,11 @@ public class ConfigWriter
Debug.LogMessage(LogEventLevel.Information, "Attempting to write config file: '{0}'", filePath);
var lockAcquired = false;
try
{
if (fileLock.TryEnter())
lockAcquired = Monitor.TryEnter(_fileLock);
if (lockAcquired)
{
using (StreamWriter sw = new StreamWriter(filePath))
{
@@ -154,11 +175,8 @@ public class ConfigWriter
}
finally
{
if (fileLock != null && !fileLock.Disposed)
fileLock.Leave();
if (lockAcquired)
Monitor.Exit(_fileLock);
}
}
}

Some files were not shown because too many files have changed in this diff Show More