1
0
Fork 0

Compare commits

...

993 Commits

Author SHA1 Message Date
Kevin Matz b6300cf80b handle ArtIpProg packets 2023-05-22 10:16:20 -04:00
Kevin Matz 47ac0220b4 certain high bits are used to encode enable/disable flags 2023-05-22 10:15:53 -04:00
Kevin Matz db2d793805 devices need to be aware of their subnet mask 2023-05-22 10:14:38 -04:00
Kevin Matz 822a3130b5 devices must be aware of their DHCP status 2023-05-22 09:13:03 -04:00
Kevin Matz f49250a610 add a field to Packet to track unicast reply destination 2023-05-22 09:12:36 -04:00
Kevin Matz f92bed2a8a set firmware version in ArtPollReply 2023-05-21 14:18:47 -04:00
Kevin Matz 97802bbebf only send device IP until binding is better implimented 2023-05-21 14:18:24 -04:00
Kevin Matz 2b8be50db8 differentiate between firmware version and protocol version 2023-05-21 14:17:35 -04:00
Kevin Matz 4b71f666e2 anticipate that inheritors may want to supply a pre-filled reply before calling the base class 2023-05-21 14:05:19 -04:00
Kevin Matz 877700625f maintain a general status register 2023-05-21 13:58:16 -04:00
Kevin Matz b25cdb102b disable Art-Net 1 and 2 by default 2023-05-21 13:57:57 -04:00
Kevin Matz 68ced05c57 node reporting 2023-05-21 13:46:35 -04:00
Kevin Matz 840f42d8dc begin filling out the ArtPollReply data 2023-05-21 12:48:42 -04:00
Kevin Matz 7b6946a1cc mechanism for reporting a MAC address 2023-05-21 12:48:16 -04:00
Kevin Matz 95a0d85b04 reference implementation configures the correct IP metadata 2023-05-21 12:47:47 -04:00
Kevin Matz 506deb7e5e devices have style codes 2023-05-21 12:43:27 -04:00
Kevin Matz c6e8c2528d set generic names 2023-05-21 11:21:41 -04:00
Kevin Matz 98ca641adc devices have long and short names 2023-05-21 11:12:41 -04:00
Kevin Matz d8a768848e default to disabling visual light control 2023-05-21 10:36:59 -04:00
Kevin Matz 75c5e107ea consistent naming for raw values 2023-05-21 10:36:39 -04:00
Kevin Matz a15b0d90f7 all ArtPolls count towards the minimum interval, not just from the periodic polling thread. 2023-05-21 10:22:19 -04:00
Kevin Matz 652e9e9017 periodic polling 2023-05-21 10:05:06 -04:00
Kevin Matz 188a403726 use an atomic to control thread-enabled 2023-05-21 10:04:38 -04:00
Kevin Matz 50f3145f9e omit MX extension Ops 2023-05-20 18:43:46 -04:00
Kevin Matz 6d257449f0 note where specification is disobeyed 2023-05-20 14:55:31 -04:00
Kevin Matz 9114e227f7 construct specialized Packets around preexisting data 2023-05-20 14:42:49 -04:00
Kevin Matz 33c6e35228 packet routing 2023-05-20 14:15:12 -04:00
Kevin Matz a255b915b5 stubs for the remaining Packet specializations 2023-05-20 14:14:59 -04:00
Kevin Matz 4a857f720a add missing Packet types with note that they still need data 2023-05-20 14:14:09 -04:00
Kevin Matz e565437988 directly create data types 2023-05-20 13:43:56 -04:00
Kevin Matz 26645b5c4d mark stream as bad if the identifier was not read completely 2023-05-20 13:43:28 -04:00
Kevin Matz 6b82f0eda4 use std::optional to control access to Packet data 2023-05-20 13:42:34 -04:00
Kevin Matz a26f6f0843 inheritance of Device doesn't need to be virtual 2023-05-20 13:41:49 -04:00
Kevin Matz 4368320e9f sending of ArtPoll Packets 2023-05-20 13:41:26 -04:00
Kevin Matz 30621d7bb1 Controllers emulate the operation of Input Gateways 2023-05-20 13:40:54 -04:00
Kevin Matz 2a777946f5 establish path to receive Packets 2023-05-20 13:40:10 -04:00
Kevin Matz eead2311ea use a callback to send stream data 2023-05-20 13:39:35 -04:00
Kevin Matz e76d466bf6 store diagnostice reporting configuration 2023-05-20 13:37:40 -04:00
Kevin Matz 325f6d4ffa determine if an ArtNzs is really an ArtVlc 2023-05-20 13:36:23 -04:00
Kevin Matz 29e7d788e6 not enough information is known at construction to send the required ArtPollReply 2023-05-20 13:35:37 -04:00
Kevin Matz 828e4f11a7 stubs for all rx opcodes 2023-05-20 13:34:46 -04:00
Kevin Matz c0dded8b94 use the IP Address type from SDT 2023-05-20 13:29:25 -04:00
Kevin Matz cf06c0fa9a the RDM section of the spec describes two gateway types 2023-05-20 13:27:20 -04:00
Kevin Matz 9e36ab422c additional device type described in the spec 2023-05-20 13:26:32 -04:00
Kevin Matz 796ef5f8ca provide OpCode in the Packet constructor 2023-05-20 13:24:14 -04:00
Kevin Matz 9a0fd158d6 never read into the net number in ways that could cause an invalid value 2023-05-20 13:20:33 -04:00
Kevin Matz 88c9cfa7c9 POD structure for PortAddress 2023-05-20 13:19:17 -04:00
Kevin Matz 9c1b14ac3e decoder for some acronyms 2023-05-20 07:20:50 -04:00
Kevin Matz a41d8bfc87 enforce base classes on header and data segments 2023-05-18 15:51:13 -04:00
Kevin Matz bf5f9812aa remove duplicate check for preexisting data segment 2023-05-18 15:50:39 -04:00
Kevin Matz 455ce23681 whitespace cleanup 2023-05-18 15:50:13 -04:00
Kevin Matz de346b15b4 shared pointer aliases require correct base class types 2023-05-18 15:39:26 -04:00
Kevin Matz 74815726bf only PDU derived classes are elegible to be Messages 2023-05-18 15:38:43 -04:00
Kevin Matz f01b3c8a56 be explicit when using shared pointers 2023-05-18 15:37:27 -04:00
Kevin Matz c4ab516efe set badbit if stream is corrupt 2023-05-18 14:45:38 -04:00
Kevin Matz 8dc6c36e36 use auto to prevent length truncation/rollover 2023-05-18 14:45:10 -04:00
Kevin Matz 41c89e06e0 initialize at allocation 2023-05-18 14:44:31 -04:00
Kevin Matz f7a79c5a40 omit length bytes from flag data 2023-05-18 14:44:06 -04:00
Kevin Matz 7315070462 pdu block members are owned by the block and do not require separate memory management. 2023-05-18 14:43:35 -04:00
Kevin Matz 0c442b74a2 whitespace cleanup 2023-05-18 14:42:06 -04:00
Kevin Matz 4a067ad66b evaluate unsigned integer as a bool 2023-05-18 14:39:05 -04:00
Kevin Matz 13981ff727 consider native byte order when interpreting data 2023-05-18 14:38:32 -04:00
Kevin Matz 14e4bdc5b5 immitate the C++20 byteorder enum 2023-05-18 14:37:50 -04:00
Kevin Matz 17f9ee21d6 use size type for size attributes 2023-05-18 14:36:14 -04:00
Kevin Matz 45c2e5c116 convenient streaming of null-terminated strings 2023-05-18 14:33:40 -04:00
Kevin Matz c0e7464919 remove unused alias, which just masked what was actually happening 2023-05-18 14:33:04 -04:00
Kevin Matz 999026abb7 whitespace cleanup 2023-05-18 14:32:33 -04:00
Kevin Matz bd15cbd5aa reorder header 2023-05-16 10:28:14 -04:00
Kevin Matz 7029286883 tolerate addresses that don't begin with a '/' 2023-05-16 10:27:56 -04:00
Kevin Matz d5bd61c35b introspection is public 2023-05-16 10:15:08 -04:00
Kevin Matz 79e9e2f360 rearrange header 2023-05-16 10:09:14 -04:00
Kevin Matz 7e679f3016 decouple address matching from message dispatching 2023-05-16 09:52:18 -04:00
Kevin Matz 0398b63907 capture less when scheduling bundles 2023-05-16 09:50:14 -04:00
Kevin Matz 39bba1f5b0 local include with quotes 2023-05-16 09:49:26 -04:00
Kevin Matz 1d2a3b7941 add loopback test for OSC #bundle 2023-05-16 08:44:10 -04:00
Kevin Matz 4fa70b4ff2 tests cleanup 2023-05-16 08:43:43 -04:00
Kevin Matz 16f3c66562 whitespace cleanup 2023-05-15 11:42:17 -04:00
Kevin Matz ba2927e6ee sending methods are constant 2023-05-15 11:40:43 -04:00
Kevin Matz 654dfed224 always offer parameter descriptions 2023-05-15 11:39:24 -04:00
Kevin Matz 7e4734f56a checksums are simply additive 2023-05-15 11:38:05 -04:00
Kevin Matz 258910efe1 loopback test for Tx->Rx 2023-05-15 11:29:22 -04:00
Kevin Matz 216a08f100 sender for bundles 2023-05-15 11:28:53 -04:00
Kevin Matz fdf801c962 use callbacks to send the byte stream 2023-05-15 11:28:39 -04:00
Kevin Matz 0bd30d1151 the received packet may be a message or a bundle 2023-05-15 11:24:19 -04:00
Kevin Matz 31ec3ade78 add documentation for the spec 1.0 stream format 2023-05-15 11:23:49 -04:00
Kevin Matz bf61bd9c3e recieve methods are public 2023-05-15 11:22:33 -04:00
Kevin Matz ac1c5d64d7 remove duplicate allocation 2023-05-15 11:21:49 -04:00
Kevin Matz 4fd1c4de29 introspection is protected 2023-05-15 11:21:08 -04:00
Kevin Matz 64901a27cb adding children doen't need to be virtual 2023-05-15 11:20:47 -04:00
Kevin Matz f0d5556230 name match a const string 2023-05-15 11:20:16 -04:00
Kevin Matz f72ca7e9f6 root dispatching doesn't pop the pattern list 2023-05-15 11:19:29 -04:00
Kevin Matz 6853a2af5c name matching is protected, and not virtual 2023-05-15 11:18:28 -04:00
Kevin Matz 0c22c70b38 check if method is root 2023-05-15 11:12:45 -04:00
Kevin Matz 767c0a9e34 remove unnecessary headers 2023-05-15 10:55:39 -04:00
Kevin Matz fe4d94628c use message string i/o 2023-05-15 10:55:24 -04:00
Kevin Matz 5b65fd8187 corrected initializer for blob data 2023-05-15 10:54:19 -04:00
Kevin Matz 6022d4bbc6 consistant patterns for byte alignment 2023-05-15 10:52:54 -04:00
Kevin Matz 745222cbf9 be wrong instead of throwing exemptions 2023-05-15 10:50:59 -04:00
Kevin Matz e638b09be6 fast integer types 2023-05-15 10:48:59 -04:00
Kevin Matz b64b185d73 independent checksum calculation 2023-05-13 11:38:41 -04:00
Kevin Matz 2d2ef07de7 test without the fixture 2023-05-13 11:38:18 -04:00
Kevin Matz 34d74375cc unit testing for OSC 2023-05-13 11:37:10 -04:00
Kevin Matz 706fe5a3f9 corrected sub-string indexing for nested arrays 2023-05-13 11:36:45 -04:00
Kevin Matz 81746d635e convenience functions for adding arguments by value 2023-05-13 11:34:50 -04:00
Kevin Matz 63df16831e use raw character data for regex definition 2023-05-13 11:33:45 -04:00
Kevin Matz 983ae8a412 destructor, for vtable 2023-05-13 11:33:15 -04:00
Kevin Matz 49a446c5aa optional method naming in constructor 2023-05-13 11:32:58 -04:00
Kevin Matz e1faba699e optional argument value setting in constructor 2023-05-13 11:32:35 -04:00
Kevin Matz 28c7bba442 optimize out unnecessary allocation 2023-05-13 11:31:01 -04:00
Kevin Matz 4cc4d16fba unify string padding 2023-05-13 11:29:38 -04:00
Kevin Matz 310eae05ec don't hard code constants 2023-05-13 11:25:32 -04:00
Kevin Matz 798fe44ca4 writing variable length strings gets a null terminator 2023-05-13 11:24:31 -04:00
Kevin Matz 5f98768a8a allow reading of null terminated (variable-length) strings 2023-05-13 11:23:27 -04:00
Kevin Matz 4f76a2f96c add code path for (eventually) receiving discovery messages 2023-05-03 16:10:52 -04:00
Kevin Matz 946a2dd9f4 add RDM testing 2023-05-03 16:04:16 -04:00
Kevin Matz 3745f44a5f remove hard-coded constants 2023-05-03 16:02:21 -04:00
Kevin Matz f4c91891e0 more nuanced failure mode tracking 2023-05-03 16:02:06 -04:00
Kevin Matz 3882af3659 corrected checksum element ordering 2023-05-03 15:59:14 -04:00
Kevin Matz 6c7d62ca6b arbitrary data checksumming 2023-05-03 15:57:37 -04:00
Kevin Matz c0074e62b7 documentation updates 2023-05-03 15:56:48 -04:00
Kevin Matz 41b51f7aa9 unit lookup helpers 2023-05-02 13:01:33 -04:00
Kevin Matz e1f0836a55 additional documentation 2023-05-02 13:00:57 -04:00
Kevin Matz 9985f21cb2 split device types 2023-05-02 13:00:41 -04:00
Kevin Matz 887ce0f5a4 comprehensive parameter descriptions 2023-05-02 12:59:26 -04:00
Kevin Matz 91fa0c71e4 structure for parameter metadata 2023-05-02 12:50:06 -04:00
Kevin Matz ec98049b39 bibs for RDM additional message sets 2023-05-02 12:43:47 -04:00
Kevin Matz a1b08fd22d publicly accessible UID 2023-04-28 21:02:35 -04:00
Kevin Matz 9b27820873 RDM API cleanup 2023-04-28 20:46:13 -04:00
Kevin Matz 29173eacf2 use the BCD serial number for the RDM device id 2023-04-28 20:19:15 -04:00
Kevin Matz ab17dc05ba template the BCD converters 2023-04-28 20:16:41 -04:00
Kevin Matz f06a5f97e6 convenience name for the bufferstream shared pointer 2023-04-28 20:12:51 -04:00
Kevin Matz 35dc0f30d2 configure RDM device info 2023-04-27 09:26:05 -04:00
Kevin Matz 73615db6c4 additional device info details 2023-04-27 09:25:07 -04:00
Kevin Matz 7fffc6d0dd formatting cleanup 2023-04-27 09:23:56 -04:00
Kevin Matz 08ca66fcb3 only claim the manufacturer ID if there isn't one 2023-04-27 09:20:41 -04:00
Kevin Matz 176ddf1df6 refactor check-summing to be pointers and referances 2023-04-27 09:10:15 -04:00
Kevin Matz d80e4c6559 group template functions for readability 2023-04-27 09:09:01 -04:00
Kevin Matz 5455d805d5 remove ineffective optimization 2023-04-27 09:05:09 -04:00
Kevin Matz bc36227387 cast to a pointer of the referance, not the value 2023-04-26 14:34:45 -04:00
Kevin Matz 34bcd7b174 remove debugging statements 2023-04-26 14:33:55 -04:00
Kevin Matz fc48300964 connect RDM responder I/O 2023-04-26 10:05:16 -04:00
Kevin Matz e951c25461 let the universe decide what to do with start codes 2023-04-26 10:04:06 -04:00
Kevin Matz f1612dc60e use a callback to send RDM data 2023-04-26 10:02:20 -04:00
Kevin Matz db3da280da pre allocate memory for the message data 2023-04-26 10:01:33 -04:00
Kevin Matz dafa95ec76 use callbacks to process alternate start codes 2023-04-26 09:59:14 -04:00
Kevin Matz 7802f201e0 documentation 2023-04-26 09:58:03 -04:00
Kevin Matz 2fdef29242 use the widget mode to determine the appropriate RDM configuration 2023-04-25 16:48:24 -04:00
Kevin Matz 54b2335c98 disambiguation for the include search path 2023-04-25 16:47:27 -04:00
Kevin Matz 098d936897 documentation and namespace cleanup 2023-04-25 12:14:03 -04:00
Kevin Matz 9442b6a7ff use a flat light-mode doxygen theme 2023-04-25 12:13:28 -04:00
Kevin Matz c40d1fcdf5 documentation cleanup 2023-04-25 10:25:54 -04:00
Kevin Matz 76d3cd00f8 include OSC as a non-standard protocol (message format) 2023-04-25 10:11:06 -04:00
Kevin Matz d91838dc5b receive packets 2023-04-25 10:10:16 -04:00
Kevin Matz 960e228e59 non-REST'style namespaces may need to reimpliment name matching 2023-04-25 10:09:51 -04:00
Kevin Matz 929ca62016 use guidance from opensoundcontrol website regarding client-server model 2023-04-25 09:03:46 -04:00
Kevin Matz 402bcf9188 add testing notes to the correct task list 2023-04-25 09:02:00 -04:00
Kevin Matz cba2cf9fd3 add todo item for delegate 2023-04-25 08:20:14 -04:00
Kevin Matz 38ff40e646 sending is a requirement of inheritors 2023-04-25 08:17:06 -04:00
Kevin Matz e29b41c812 connect unfinished actions to todo notes 2023-04-24 17:04:08 -04:00
Kevin Matz 773375757e bundle dispatching 2023-04-24 16:44:04 -04:00
Kevin Matz 0bfab08041 mark dispatching as const 2023-04-24 16:43:35 -04:00
Kevin Matz d7722f24f3 init the timetag to the epoch 2023-04-24 16:40:37 -04:00
Kevin Matz fa8b0482da omit the packet class 2023-04-24 16:39:40 -04:00
Kevin Matz e94db990ac explicit virtual destructor 2023-04-24 13:06:26 -04:00
Kevin Matz 031d00bed4 drop the packet class 2023-04-24 13:05:54 -04:00
Kevin Matz 23572904d2 timetag values to/from system time 2023-04-24 13:04:49 -04:00
Kevin Matz 4023cdaa81 drop the non-standard 128-bit timetag 2023-04-24 13:04:13 -04:00
Kevin Matz 22141d9a03 don't build NTP until there is a user 2023-04-24 13:03:30 -04:00
Kevin Matz 8fd3cc83cf a clock with the NTP epoch 2023-04-24 13:03:13 -04:00
Kevin Matz 7a25eeada7 callbacks for the method to trigger 2023-04-23 19:30:51 -04:00
Kevin Matz 913a1f789b use a regex to match the method name 2023-04-23 19:29:21 -04:00
Kevin Matz 76b8f974e7 patterns are a list 2023-04-23 11:56:57 -04:00
Kevin Matz 45a8c673f0 operators for floating point types 2023-04-23 11:36:17 -04:00
Kevin Matz 42ca307c4d receiver for OSC messages 2023-04-23 11:35:31 -04:00
Kevin Matz 1cce721179 endpoints for messages 2023-04-23 11:34:35 -04:00
Kevin Matz 877ab3ed4c message and bundle I/O 2023-04-23 11:33:58 -04:00
Kevin Matz d51d3163f9 the various OSC argument types 2023-04-23 11:32:52 -04:00
Kevin Matz abe41404cc remove extraneous brackets 2023-04-23 11:30:26 -04:00
Kevin Matz c52bf250b6 use column 0 to retrieve the widget pointer 2023-04-17 09:30:13 -04:00
Kevin Matz 9e390cff8b initialize as a bridge device 2023-04-17 09:08:46 -04:00
Kevin Matz 46a860fca9 display the name of the operating modes 2023-04-17 09:08:26 -04:00
Kevin Matz 4d797f2c1c also set the DMX device class when changing operating mode 2023-04-17 08:17:31 -04:00
Kevin Matz e0fb4ccbb3 add a watchdog to keep the status updated 2023-04-16 22:43:53 -04:00
Kevin Matz a34ada6da5 an asynchronous open procedure 2023-04-16 21:13:11 -04:00
Kevin Matz eb25959c08 retreive an index instead of creating 2023-04-16 15:02:51 -04:00
Kevin Matz 373e6dbda5 inspect DMX data 2023-04-16 14:57:28 -04:00
Kevin Matz cbd362b341 return the shared pointer for editing 2023-04-16 14:56:26 -04:00
Kevin Matz 3f366f2aa0 status feedback for RX_TIMEOUT 2023-04-16 14:54:56 -04:00
Kevin Matz c3bc6dc44e read the full length of remaining data 2023-04-16 14:54:01 -04:00
Kevin Matz 67e84624ee move the universe data viewer to the platform library 2023-04-16 12:18:00 -04:00
Kevin Matz b18823a009 rename the status indicator class 2023-04-16 11:34:07 -04:00
Kevin Matz 1563e620b9 model directly from the DMX::Universe 2023-04-16 11:10:04 -04:00
Kevin Matz 91128ca9f3 indicate directly from the DMX::Universe 2023-04-16 11:09:31 -04:00
Kevin Matz 6df0a06a04 add method to retrieve shared pointer of wrapped universe 2023-04-16 11:06:09 -04:00
Kevin Matz 63aef39f28 bring awareness of device operating classes to the base universe class 2023-04-16 11:05:04 -04:00
Kevin Matz f1f153f09e bring active slot awareness down to the base universe class 2023-04-16 11:03:53 -04:00
Kevin Matz 417d8cd18b alphabetize filenames 2023-04-16 10:54:48 -04:00
Kevin Matz 5223d1bb33 center align numeric columns 2023-04-16 10:53:46 -04:00
Kevin Matz b79ea8d976 set window title 2023-04-16 10:53:18 -04:00
Kevin Matz 3bc8c200f6 default column widths 2023-04-16 10:53:02 -04:00
Kevin Matz 4417a8888f add format options to the menubar 2023-04-16 10:51:30 -04:00
Kevin Matz f484c2c8bd drop sACN properties from the DMX view 2023-04-16 10:03:53 -04:00
Kevin Matz 5c1f343688 re select the current item when the model resets 2023-04-14 13:18:33 -04:00
Kevin Matz 95a5be2e65 use "save" as the accept button 2023-04-14 13:17:55 -04:00
Kevin Matz 8b5da1d1ca tell views to update when a device changes state 2023-04-14 13:17:37 -04:00
Kevin Matz 75598b4359 clear the current selection when the list refreshes 2023-04-14 13:16:50 -04:00
Kevin Matz 9b4e9eda03 connect device management actions 2023-04-14 10:29:22 -04:00
Kevin Matz f19f572daf keep ui actions consistent with selected widget 2023-04-14 10:28:17 -04:00
Kevin Matz 2c394a1bc7 new dialog for altering tx parameters 2023-04-14 10:25:37 -04:00
Kevin Matz 5f331f262e add a few debug messages 2023-04-14 10:23:07 -04:00
Kevin Matz 0ab8dc2052 prefer accessing from the hardware list 2023-04-14 10:22:20 -04:00
Kevin Matz 00b8309107 explicitly virtual also in the super class 2023-04-14 10:20:48 -04:00
Kevin Matz 4242854696 work around the stream operator sometimes not writing the correct bytes 2023-04-14 10:19:54 -04:00
Kevin Matz 107a239287 leave the widget open when scanning 2023-04-14 10:18:57 -04:00
Kevin Matz 49b25f0a86 close the port under certain error conditions 2023-04-14 10:18:29 -04:00
Kevin Matz 379ae89cb6 clumsy synchronous read for opening 2023-04-14 10:17:23 -04:00
Kevin Matz 66410fdf22 look for changes to attached ports 2023-04-14 10:14:09 -04:00
Kevin Matz 18e710dc56 simplify open/close semantics 2023-04-14 10:10:10 -04:00
Kevin Matz 80bfce67b7 setting parameters may optionally set the user data 2023-04-14 10:09:26 -04:00
Kevin Matz a58ffc5c4a remove promises/futures for now 2023-04-14 10:07:58 -04:00
Kevin Matz 0bb177c68e get/set user configuration data 2023-04-14 09:55:10 -04:00
Kevin Matz da32cf4240 feature sets are encoded in the high byte of the firmware version 2023-04-14 09:54:05 -04:00
Kevin Matz 1bf3f23a66 return the calculated value that was actually set 2023-04-14 09:53:30 -04:00
Kevin Matz 7fa3bde8b3 example for interacting with widgets 2023-04-09 20:57:34 -04:00
Kevin Matz 5d7f169692 don't change the mode when opening 2023-04-09 20:51:07 -04:00
Kevin Matz 47a71072ce introduce a model for tracking attached widgets 2023-04-09 20:46:28 -04:00
Kevin Matz 4833b37739 copy dimmer data directly to the data vector 2023-04-09 20:42:05 -04:00
Kevin Matz e435e04945 do the pointer arithmetic before casting 2023-04-09 20:20:17 -04:00
Kevin Matz 02d4d3a8b8 method for retrieving the serial port info 2023-04-09 20:18:16 -04:00
Kevin Matz a132719db9 use a signed write buffer and only cast it to unsigned once 2023-04-09 20:14:57 -04:00
Kevin Matz ca24b7878c only write the message data if there is any 2023-04-09 20:09:03 -04:00
Kevin Matz 7663b84ec5 discard used bytes without the offset 2023-04-09 20:07:41 -04:00
Kevin Matz 4c0bf5ded8 use the MessageData creator to instantiate the correct specialized type 2023-04-09 20:06:43 -04:00
Kevin Matz d7d64fa13c single assignment assembly of the length 2023-04-09 19:58:56 -04:00
Kevin Matz 63e16701b9 explicitly compare as unsigned value 2023-04-09 19:57:44 -04:00
Kevin Matz ea3bf9e5e8 skip a few parsing steps if the buffer is empty 2023-04-09 19:56:32 -04:00
Kevin Matz 32c6ccb60c leave the port always connected 2023-04-09 19:52:24 -04:00
Kevin Matz 244da5c8c1 no need to pass around zero data messages 2023-04-09 19:51:01 -04:00
Kevin Matz c16e999969 use a helper to make the different MessageData specializations 2023-04-09 19:43:54 -04:00
Kevin Matz f69c50d66d available widgets will return the widgets themselves 2023-04-09 19:42:21 -04:00
Kevin Matz c801ffb32b set the window title from the app metadata 2023-04-09 19:35:34 -04:00
Kevin Matz 3cb89f51d3 mark stream functions as virtual in the derivative classes 2023-04-09 19:34:27 -04:00
Kevin Matz b6e080f7b4 give the No-Op message it's own class 2023-04-09 19:33:03 -04:00
Kevin Matz be4064bb14 declare a storage type for enums 2023-04-09 19:32:35 -04:00
Kevin Matz 6387944c68 the usb mode can be accessible by super classes 2023-04-09 19:31:56 -04:00
Kevin Matz 55317f7513 BCD encode/decode the serial number 2023-04-09 19:26:41 -04:00
Kevin Matz c89f55c107 message i/o for the serial port 2023-04-06 20:07:52 -04:00
Kevin Matz 3ab0c1141c include the fw version in widget info struct 2023-04-06 19:55:08 -04:00
Kevin Matz c12044326f accept hello on closed port 2023-04-06 12:39:18 -04:00
Kevin Matz e1e9c9d473 enable label=0 as ping/echo 2023-04-06 12:37:25 -04:00
Kevin Matz 9168f1b98c a (probably incorrect) interpretation of writing firmware 2023-04-06 10:43:42 -04:00
Kevin Matz 0617cc3bf6 only send a reboot message if operating as a USB Host 2023-04-06 10:42:39 -04:00
Kevin Matz 388beecf8d take the promise's future whilst still holding the metadata mutex 2023-04-06 10:11:33 -04:00
Kevin Matz b92f159d02 default into bridge mode on init/connect 2023-04-06 09:53:42 -04:00
Kevin Matz 153786efa4 get the initial parameter values when the device is opened. 2023-04-06 09:45:39 -04:00
Kevin Matz 97ae66e231 Only consider the port to be open if a serial number can be read within 500 milliseconds. 2023-04-06 09:44:54 -04:00
Kevin Matz e316c71a83 messages that expect a reply may optionally block to wait for it. 2023-04-06 09:43:41 -04:00
Kevin Matz a355edd722 use the USB mode to determine connection state 2023-04-06 09:40:00 -04:00
Kevin Matz c2f1dcce07 rename the dimmer data change callback token 2023-04-06 08:34:35 -04:00
Kevin Matz fa2625f77d thread protect metadata with a mutex 2023-04-06 08:33:57 -04:00
Kevin Matz 024d965f83 manage the qserialport open state 2023-04-05 18:07:48 -04:00
Kevin Matz 640fa888c7 search serial ports for connected devices 2023-04-05 12:42:18 -04:00
Kevin Matz 5a4e827f08 DMX data I/O 2023-04-05 12:41:13 -04:00
Kevin Matz 55c9060ff6 use the same defaults as the Enttec 2023-04-05 12:39:12 -04:00
Kevin Matz 54244d4988 metadata setter functions 2023-04-05 09:19:16 -04:00
Kevin Matz 10ee3c1d22 setting device parameters is a const operation for the host 2023-04-05 08:55:18 -04:00
Kevin Matz b6a81c2bd2 host may tell device to prep for recieving a firmware 2023-04-05 08:54:43 -04:00
Kevin Matz 98e903159c user data length must match the lenght of data sent 2023-04-05 08:43:26 -04:00
Kevin Matz d496e71f8a get/set widget metadata 2023-04-05 08:42:50 -04:00
Kevin Matz 1b272f0d23 rename USB host side methods 2023-04-05 08:10:59 -04:00
Kevin Matz aaa6e04817 bring enums into the local namespace 2023-04-05 07:53:22 -04:00
Kevin Matz 72a66be0b5 let superclasses decide if they want firmware updates 2023-04-05 06:56:23 -04:00
Kevin Matz 0b7143f809 get/set widget parameters 2023-04-03 20:12:01 -04:00
Kevin Matz a2ec07e16c get/set serial number 2023-04-03 20:11:25 -04:00
Kevin Matz c6c09be86a always fail at writing firmware 2023-04-03 20:09:55 -04:00
Kevin Matz 2cfbf384de get all of the firmware version bits at once 2023-04-03 20:09:06 -04:00
Kevin Matz 8fdd8573af meta-object register the API enums 2023-04-03 16:53:22 -04:00
Kevin Matz f6c45d3b7f QFlags for the RX errors 2023-04-03 16:46:06 -04:00
Kevin Matz d68c37fa82 use QSerialPort for IO 2023-04-03 16:45:32 -04:00
Kevin Matz 9f6968c915 keep widget properties 2023-04-03 16:43:37 -04:00
Kevin Matz 13bb3a39f0 incorporate the label value into the message data struct directly 2023-04-03 16:42:48 -04:00
Kevin Matz 3bea3097d0 optional endianness support 2023-04-03 15:39:42 -04:00
Kevin Matz 76a7078cef build files for a Qt platform driver for the ENTTEC DMX USB Pro 2023-04-03 09:59:18 -04:00
Kevin Matz 0c36bd34b7 put the widget directly in the ENTTEC namespace 2023-04-03 09:58:26 -04:00
Kevin Matz d54b714981 private linking only for Qt modules 2023-04-03 09:24:40 -04:00
Kevin Matz ebb8c284f9 enable automoc and friends for all Qt platform drivers 2023-04-03 08:58:28 -04:00
Kevin Matz 12fddd4caa unify project version 2023-04-03 08:57:54 -04:00
Kevin Matz 94c4af768e move the Qt sACN platform driver to a subdirectory 2023-04-03 08:47:49 -04:00
Kevin Matz cb2bc6548d stubs for communicating with a widget 2023-04-02 22:47:50 -04:00
Kevin Matz 8f3d704d5a stream IO for the API 2023-04-02 19:42:32 -04:00
Kevin Matz 8e41fc72c2 use the bufferstream library 2023-04-02 13:24:55 -04:00
Kevin Matz 1ea49ebde2 use bufferstreams instead of the ACN::PDU stream 2023-04-02 13:24:26 -04:00
Kevin Matz 099ea5a98a split the buffer stream out of ACN::PDU 2023-04-02 13:13:26 -04:00
Kevin Matz dcbd735476 personalities get a name 2023-04-02 10:35:07 -04:00
Kevin Matz 3e805960d7 protocol definitions for ENTTEC DMX USB Pro API 1.44 2023-04-02 10:34:49 -04:00
Kevin Matz 7d1c756d89 patch Personalities, not devices. 2023-04-01 15:31:22 -04:00
Kevin Matz 3ee4cbb858 basic operations for managing a patch 2023-04-01 11:17:56 -04:00
Kevin Matz 6486775626 allow devices to be sorted by startaddress 2023-04-01 11:17:29 -04:00
Kevin Matz 55a120dfed namespace stubs for CITP protocol layers 2023-03-31 19:51:42 -04:00
Kevin Matz 189be8baf7 notes on how to access the standards documents 2023-03-31 11:22:56 -04:00
Kevin Matz 88f06738ab add a bibliography file for OSC 2023-03-31 11:22:28 -04:00
Kevin Matz 7b436915d8 initial build framework for CITP 2023-03-31 11:22:01 -04:00
Kevin Matz e1c1c1b579 move OSC to the prococols directory 2023-03-31 10:52:33 -04:00
Kevin Matz 847b6d931d rename platform library with consitant project name 2023-03-31 09:55:02 -04:00
Kevin Matz 396aac84f3 reorder for readability 2023-03-31 01:52:08 -04:00
Kevin Matz aaacd8539b build as a shared library 2023-03-31 01:51:14 -04:00
Kevin Matz 40a3342317 use preferred naming convention for build 2023-03-31 01:49:38 -04:00
Kevin Matz b55a8fdd83 link by module namespace referance 2023-03-31 01:46:35 -04:00
Kevin Matz db81ecefdc add namespace aliases to cmake 2023-03-31 01:42:40 -04:00
Kevin Matz c2ab4d9b22 remove unnecessary cmake directives 2023-03-31 01:37:37 -04:00
Kevin Matz f9efddf583 cleanup comments 2023-03-30 20:15:35 -04:00
Kevin Matz 6cd9a9e6df correctly scale return value, with comment about linearity 2023-03-30 20:14:59 -04:00
Kevin Matz cd884a8593 mark standards parameters as static 2023-03-30 20:13:26 -04:00
Kevin Matz b6e9425083 evaluate non-dim from millivolts 2023-03-30 19:16:54 -04:00
Kevin Matz 8979dd4699 Hysteresis aware method for calculating non-dim switch state 2023-03-30 14:55:12 -04:00
Kevin Matz 9c5654b34d retain low-end resolution 2023-03-30 14:40:29 -04:00
Kevin Matz 2c72607dc3 E1.3 0-10V support 2023-03-30 13:56:14 -04:00
Kevin Matz 3ef5e0498b set default merge mode 2022-12-12 13:43:40 -05:00
Kevin Matz 7dfb0a81d7 universe arbitration is a helper not a subclass 2022-12-12 13:39:10 -05:00
Kevin Matz 64e6ee1c0a add refresh function to consolidate maintenance tasks 2022-12-12 11:32:26 -05:00
Kevin Matz f5d99a03e6 remove unused base class function 2022-12-12 11:31:29 -05:00
Kevin Matz d87fbcf25d unify the callback handlers 2022-12-12 11:29:41 -05:00
Kevin Matz 64140eb757 cache the dominant universe 2022-12-11 21:30:37 -05:00
Kevin Matz e219e64d28 doc updates 2022-12-11 21:30:26 -05:00
Kevin Matz 472b6a1c39 retriever of sources metadata 2022-12-11 21:24:27 -05:00
Kevin Matz a2bd30dbe5 better to just call the callback doer in a lambda 2022-12-11 21:23:34 -05:00
Kevin Matz 74de2c07af don't pay attention to universes that may not be dominant 2022-12-11 21:22:17 -05:00
Kevin Matz 27179b4e00 use a single vector for all callback tokens 2022-12-11 16:53:54 -05:00
Kevin Matz b5f3e63b36 add callbacks for member changes 2022-12-11 16:48:42 -05:00
Kevin Matz 59dbad2264 reorder header 2022-12-11 16:48:06 -05:00
Kevin Matz b84d7bafe9 explicitly call the base class function 2022-12-11 16:46:11 -05:00
Kevin Matz 059c115c15 do data callbacks after synchronization 2022-12-11 16:45:37 -05:00
Kevin Matz ce51e17ddd use the existing metadata 2022-12-11 16:13:10 -05:00
Kevin Matz 6da7001c67 refactor class and file name 2022-12-11 16:04:44 -05:00
Kevin Matz 89716e1af5 make HTP the default merge mode 2022-12-11 15:58:40 -05:00
Kevin Matz 12b34cef6e refactor class and file name 2022-12-11 15:58:28 -05:00
Kevin Matz 7676cd54a2 configurable merge mode for received universes 2022-12-11 14:55:05 -05:00
Kevin Matz b18fb9547f menues doen't behave well with icons 2022-12-11 13:51:59 -05:00
Kevin Matz b48c90bfee merge mode is a property of the universe not the receiver 2022-12-11 10:00:24 -05:00
Kevin Matz 628459840d additional action icons 2022-12-11 09:59:59 -05:00
Kevin Matz a3eb42523e stubs for the OSC library 2022-12-11 09:58:26 -05:00
Kevin Matz 293b2060b8 don't hold the control lock when doing callbacks 2022-12-10 13:34:45 -05:00
Kevin Matz 99e42dba17 shorten activity blink off time 2022-12-10 13:21:31 -05:00
Kevin Matz 0b02bc3c14 toggle'able hold-last-look 2022-12-10 13:20:39 -05:00
Kevin Matz 986dc9b89e remove hold-last-look behaviors from receiver to universe 2022-12-10 12:24:17 -05:00
Kevin Matz 849da8cecb take the control lock when running callbacks 2022-12-10 11:30:23 -05:00
Kevin Matz 8ce86353c9 opportunistically perform maintenance tasks 2022-12-10 11:15:02 -05:00
Kevin Matz dbcde09616 more of making thing constant 2022-12-10 11:12:44 -05:00
Kevin Matz 52758cc904 use the volatile global enabled flag 2022-12-10 11:08:18 -05:00
Kevin Matz 37367cb6b4 explicit constructors and virtual destructors 2022-12-10 11:07:13 -05:00
Kevin Matz 19df7540c1 set organization name before loading settings 2022-12-10 11:05:31 -05:00
Kevin Matz eb47f6543f correct locking semantics 2022-12-09 23:09:52 -05:00
Kevin Matz 8221c90e24 remove commented code 2022-12-09 23:09:26 -05:00
Kevin Matz 16416ff10f mark as const 2022-12-09 23:09:11 -05:00
Kevin Matz 45cee71b19 become more constant 2022-12-09 21:27:27 -05:00
Kevin Matz c4cf9768ab use scoped mutex locking 2022-12-09 21:08:35 -05:00
Kevin Matz 1f57ef1029 use seperate sockets for rx and tx 2022-12-09 21:00:38 -05:00
Kevin Matz fe8bc039bc rename private variable 2022-12-09 18:17:04 -05:00
Kevin Matz c9dec23ea9 initialize the DMP data segment from the sending thread 2022-12-09 16:08:16 -05:00
Kevin Matz 0c9c85830e use dynamic casts if RTTI is an available feature 2022-12-09 16:04:31 -05:00
Kevin Matz 4975c7e809 rearrange in header order 2022-12-09 15:16:03 -05:00
Kevin Matz 1d1be459c8 rename mutex for consistency 2022-12-09 11:54:52 -05:00
Kevin Matz e6146e9efe standard timing intervals are constant 2022-12-09 11:46:18 -05:00
Kevin Matz 7f8e9ce4ac shared lock for universe access control 2022-12-09 11:18:27 -05:00
Kevin Matz d11e136247 use locking guards 2022-12-09 10:29:01 -05:00
Kevin Matz f1d3fe0256 use a shared mutex to lock the dimmer data 2022-12-09 10:28:02 -05:00
Kevin Matz b7ca5c36f5 reuse the sACN framing layer PDU 2022-12-09 09:26:42 -05:00
Kevin Matz 4fa5a2794a use initializer list to create sender 2022-12-09 09:25:29 -05:00
Kevin Matz 4d7413b949 move all of transmitting to the helper class 2022-12-08 21:22:20 -05:00
Kevin Matz 47bc2c9ffd immediately delete the terminated universe 2022-12-08 21:21:04 -05:00
Kevin Matz 466efd7477 move tx to a helper class 2022-12-08 19:09:39 -05:00
Kevin Matz d67b3b8f40 adapt to the DMP rx/tx mechanism 2022-12-08 18:05:33 -05:00
Kevin Matz f9896ff5f3 add synchronization reset helper 2022-12-08 18:04:37 -05:00
Kevin Matz 85a7ec4762 strictly enforce recieved sequence numbers 2022-12-08 17:56:02 -05:00
Kevin Matz 8a83107ee7 follow rename in base class 2022-12-08 17:52:24 -05:00
Kevin Matz 0ac22b232d inherit from DMP::Component 2022-12-08 17:50:29 -05:00
Kevin Matz 46938de0f5 refactor for readability 2022-12-08 17:47:37 -05:00
Kevin Matz 6237a9ef37 refactor for naming consistency 2022-12-08 17:41:07 -05:00
Kevin Matz 67e4472751 refactor to less ambigous name 2022-12-08 17:40:15 -05:00
Kevin Matz e34a5acb5c metadata is a more descriptive property name 2022-12-08 10:12:25 -05:00
Kevin Matz 63ee24d334 add a non-standard 0 value failure reason 2022-12-07 21:58:10 -05:00
Kevin Matz 382f2d26fd add some accesses'ors for the range metadata 2022-12-07 21:57:32 -05:00
Kevin Matz 2b5db0febd initialize the address_type members 2022-12-07 21:56:49 -05:00
Kevin Matz ce3e1ebf7c capitalize the Range struct 2022-12-07 21:56:17 -05:00
Kevin Matz 433e91f70c refactor over DMP Component 2022-12-07 21:53:39 -05:00
Kevin Matz 8f42e0bca6 a base class for DMP components 2022-12-07 21:51:15 -05:00
Kevin Matz 26ae455a07 check that the block was created before assigning parents 2022-12-06 18:05:12 -05:00
Kevin Matz 7434ac3d91 fix off-by-one error 2022-12-06 18:04:37 -05:00
Kevin Matz f496b89e44 prefer the block setting code path 2022-12-06 18:04:19 -05:00
Kevin Matz 97ef3d5078 allocate loop control variables before the loop 2022-12-06 15:38:20 -05:00
Kevin Matz b07d505bce only send 3 terminated universe frames 2022-12-06 14:52:06 -05:00
Kevin Matz 8a2e838916 only reset the sequence number for received terminated universes 2022-12-06 14:51:43 -05:00
Kevin Matz 1cad61ffc7 protected convenience calls can be inlined 2022-12-06 14:51:03 -05:00
Kevin Matz 635dc36ada cleanup documentation 2022-12-06 14:50:20 -05:00
Kevin Matz 39a6bcbfbf rearrange header 2022-12-06 14:04:18 -05:00
Kevin Matz f9f0d1298e any optimization of separate handling is overshadowed by duplicating the code 2022-12-06 13:37:40 -05:00
Kevin Matz ed40a3b666 catch the range exception instead of implementing seperate bounds checks 2022-12-06 13:36:27 -05:00
Kevin Matz e7297c42ff use the same callback runner for all callback types 2022-12-06 13:35:18 -05:00
Kevin Matz 5ad7b5505e give all universe callbacks the same signature 2022-12-06 13:30:40 -05:00
Kevin Matz 738ced6964 fix off-by-one in range check 2022-12-06 13:24:09 -05:00
Kevin Matz ca75a9973c the status indicator widget 2022-12-06 00:16:00 -05:00
Kevin Matz 837bf1ad98 universe windows are non modal, but don't exist longer than the multiverse window 2022-12-06 00:12:18 -05:00
Kevin Matz 6cf9d98e8f add about messages 2022-12-05 11:26:37 -05:00
Kevin Matz b031ccbcd7 extend the copyright year 2022-12-05 11:26:03 -05:00
Kevin Matz 0b17605873 formatting cleanup 2022-12-04 20:53:46 -05:00
Kevin Matz a24a6e8b38 make sure the context menue gets deleted 2022-12-04 20:44:36 -05:00
Kevin Matz 55bcdc8db6 cleanup whitespace 2022-12-04 12:06:16 -05:00
Kevin Matz 304c74ae39 add const keyword to accepted submissions 2022-12-04 12:05:58 -05:00
Kevin Matz 3c3b1d9955 rename signal for consistency across dialog types 2022-12-04 11:56:12 -05:00
Kevin Matz 0e2b2ce039 rename methods for consistency 2022-12-04 11:53:48 -05:00
Kevin Matz 170eb40f50 let the parent window set the dialog title 2022-12-04 11:53:12 -05:00
Kevin Matz 4e536a6f1e open dialogues and menus asynchronously 2022-12-04 11:30:49 -05:00
Kevin Matz 302cf63ca6 consistency across ternary returns 2022-12-04 10:36:37 -05:00
Kevin Matz 501167c8ee use the Qt macros for Qt containers 2022-12-04 10:34:57 -05:00
Kevin Matz 4652623bb7 set the toolbar as the parent of the toolbar widgets 2022-12-04 10:33:56 -05:00
Kevin Matz af3cf519f1 add const keyword 2022-12-04 00:32:12 -05:00
Kevin Matz 14a3b644f8 remove unused functions 2022-12-04 00:30:28 -05:00
Kevin Matz bc9bb9b5a7 initialize private members as null pointers 2022-12-04 00:16:14 -05:00
Kevin Matz c1cc0ef6ed construct QObjects with parent 2022-12-03 23:31:13 -05:00
Kevin Matz b267627376 remove unnecessary slot 2022-12-03 23:28:50 -05:00
Kevin Matz 93e5680f85 rename to remove implied relationship to model/view 2022-12-03 23:28:24 -05:00
Kevin Matz 8f3c59783f move the multiverse model to the platform library 2022-12-03 22:51:55 -05:00
Kevin Matz fe53e57a6d remove unnecessary signals/slots 2022-12-03 22:42:44 -05:00
Kevin Matz 18e14b99d8 add const keyword to getters 2022-12-03 22:38:31 -05:00
Kevin Matz c46ca4b0cc move the universe model to the platform library 2022-12-03 21:33:23 -05:00
Kevin Matz 6e6b47edb5 finish renaming the window 2022-12-03 21:21:08 -05:00
Kevin Matz 8c54793edd remove unnecessary signals and slots 2022-12-03 21:12:31 -05:00
Kevin Matz da484ef028 construct the model around the universe 2022-12-03 20:46:10 -05:00
Kevin Matz 798affa3fb refactor the window name to not imply a Model/View relationship 2022-12-03 20:36:02 -05:00
Kevin Matz f11536e152 ensure that the existing inspector is shown 2022-12-03 11:09:44 -05:00
Kevin Matz 7a877f97ad close the inspector when the universe disapears 2022-12-03 11:04:17 -05:00
Kevin Matz 0fb5db0574 allow only one inspector per universe 2022-12-03 11:04:00 -05:00
Kevin Matz 3da5529b39 only retrieve a non-default value if the index is valid 2022-12-03 11:02:40 -05:00
Kevin Matz 31f460913c keep a pointer to the universe being viewed 2022-12-03 11:00:48 -05:00
Kevin Matz 13a775d0fd remove stale declaration of previously removed private method 2022-12-03 11:00:09 -05:00
Kevin Matz 4241cd0c38 prefer QTimer api for readability 2022-12-03 10:59:22 -05:00
Kevin Matz 37010fa8d4 add a watchdog to keep the universe status updated 2022-12-02 16:35:27 -05:00
Kevin Matz 3135c0ae6c rename call back registration function for consistancy 2022-12-02 16:35:05 -05:00
Kevin Matz a03ef79d5d add note about the need to collapse the superposition of status 2022-12-02 15:47:51 -05:00
Kevin Matz e0394e4413 don't supply and undefined source_name 2022-12-02 15:35:48 -05:00
Kevin Matz be17ea9bb5 remove redundant local variable 2022-12-02 15:35:08 -05:00
Kevin Matz a443299204 set timeout state based solely on age 2022-12-02 15:33:22 -05:00
Kevin Matz 9548145629 rename function to remove implication of rx use only 2022-12-02 15:32:26 -05:00
Kevin Matz f500d6d376 add a table column for status 2022-12-02 15:28:06 -05:00
Kevin Matz 2cb021b184 reset the sequence number on termination 2022-12-02 13:09:58 -05:00
Kevin Matz 6a977bbf01 prefer the copy_n method for readability 2022-12-02 13:09:18 -05:00
Kevin Matz 11c6013a00 unify formatting and comments 2022-12-02 13:08:08 -05:00
Kevin Matz 704c3a2985 allocate for the container to prevent taking the referance of a temporary 2022-12-02 13:07:24 -05:00
Kevin Matz db7bf1dcb3 add status change callbacks for listeners that want to be notified 2022-12-02 13:06:22 -05:00
Kevin Matz 2491054e1d cleanup universe status setting 2022-12-02 13:04:06 -05:00
Kevin Matz 85bd5accd0 rename signal to be more descriptive 2022-12-02 12:59:26 -05:00
Kevin Matz 4745458b87 rename callback list to better indicate the type of callback 2022-12-02 12:57:01 -05:00
Kevin Matz 6a2d1242be rename variable to reflect validity for both rx and tx 2022-12-02 12:52:52 -05:00
Kevin Matz 8e082fb112 mark override functions as virtual 2022-11-30 09:55:18 -05:00
Kevin Matz 874f6482a3 initialize varibles in the constructor 2022-11-30 09:54:24 -05:00
Kevin Matz 41530d86c1 comment cleanup 2022-11-30 09:54:01 -05:00
Kevin Matz d4c0665330 generated IP addresses are static 2022-11-30 09:53:08 -05:00
Kevin Matz 79ed3e5a8b use a propper hash combiner 2022-11-30 09:52:50 -05:00
Kevin Matz 0ba1ecc9ac cleanup the the linking and including hierarchy 2022-11-30 09:52:25 -05:00
Kevin Matz 32557b5c31 the ACN library has become an interface library 2022-11-30 09:49:27 -05:00
Kevin Matz 15281fc3a7 build a library for the PDU 2022-11-30 09:49:00 -05:00
Kevin Matz 277fdf9c45 set the version number in the library 2022-11-30 09:47:09 -05:00
Kevin Matz f17487b30f universes manage their own synchronization 2022-11-29 10:11:47 -05:00
Kevin Matz f12b055aa0 remove intermediate project names 2022-11-28 17:24:38 -05:00
Kevin Matz c9377418fa move OTP to a submodule 2022-11-28 16:41:28 -05:00
Kevin Matz 3e648dc152 move RDM and RDMnet to submodules 2022-11-28 16:37:38 -05:00
Kevin Matz 3e2d22e77e move ACN to a submodule 2022-11-28 15:57:52 -05:00
Kevin Matz a86d968569 move sACN to a subproject 2022-11-28 15:06:48 -05:00
Kevin Matz 9e7b45d38c only declare project name 2022-11-28 14:55:45 -05:00
Kevin Matz 212e1ce0af make artistic a subproject 2022-11-28 14:55:31 -05:00
Kevin Matz 13d1251511 move DMX to submodule 2022-11-28 14:55:05 -05:00
Kevin Matz 375ec153fe build as shared libraries 2022-11-28 14:19:18 -05:00
Kevin Matz 9b49e63cab mark the linking to LCP as private 2022-11-28 14:19:01 -05:00
Kevin Matz 2f1d29ecfa move UUID into a submodule 2022-11-28 14:18:21 -05:00
Kevin Matz a8377b6635 move Art-Net to a subproject 2022-11-28 13:41:57 -05:00
Kevin Matz e2b46c5bbf version bump 2022-11-28 11:37:25 -05:00
Kevin Matz a74bd32598 mark some class headers as public 2022-11-28 11:37:10 -05:00
Kevin Matz a81bc5b1fd accept the front of over-length data 2022-11-28 11:36:01 -05:00
Kevin Matz fdb2980d0e cleanup docs and whitespace 2022-11-28 11:23:13 -05:00
Kevin Matz 71edfaf7e9 consolidate status changes 2022-11-28 11:22:06 -05:00
Kevin Matz 73ee83cd9e scope the age variable to the function 2022-11-28 11:17:35 -05:00
Kevin Matz 3e77990490 make sure the age is big enough on all platforms 2022-11-28 11:16:19 -05:00
Kevin Matz 686b11f1f0 formating cleanup 2022-11-28 09:42:02 -05:00
Kevin Matz c4baa96960 let rxAge() return negative if invalid 2022-11-28 09:41:27 -05:00
Kevin Matz 3f78a9de01 use Qt API, not STL, for map operation 2022-11-28 09:39:11 -05:00
Kevin Matz 01c569ffa4 change appearance based on status 2022-11-28 09:27:08 -05:00
Kevin Matz c786b422e2 getter for the universe status 2022-11-28 09:26:49 -05:00
Kevin Matz 5a90c9fbbc if status() causes a status change, the lock will be needed in setStatus() 2022-11-28 09:25:12 -05:00
Kevin Matz 62131a9dc5 trust the Qt macros to be optimized 2022-11-28 09:22:53 -05:00
Kevin Matz 41c46e7bc5 remove redundant copy constructors 2022-11-28 09:21:25 -05:00
Kevin Matz 0292051392 holding last look is the behavior of a universe, not the receiver 2022-11-26 12:50:54 -05:00
Kevin Matz 3835cee9bd refactor the discovery worker thread 2022-11-26 12:48:02 -05:00
Kevin Matz 11d202d51a allow RLP communications protocols to be enabled/disabled 2022-11-26 12:41:46 -05:00
Kevin Matz 617b082f96 be aware of enabled RLP communications protocols 2022-11-26 12:40:42 -05:00
Kevin Matz 52cdef90dd only generate discovery frames for enabled RLP communications protocols 2022-11-26 12:38:26 -05:00
Kevin Matz 79ba3a18b7 only send packets for enabled IP protocols 2022-11-26 12:36:42 -05:00
Kevin Matz ab0c2f0e62 acknowledge IPv4 and IPv6 as communication protocols for RLP 2022-11-26 12:34:48 -05:00
Kevin Matz 3f717454eb documentation cleanup 2022-11-26 12:30:27 -05:00
Kevin Matz d57a6a31ad the source name is not a per/universe property 2022-11-24 14:57:11 -05:00
Kevin Matz 4ad13317fd update the sender source name when the UACN changes 2022-11-24 11:35:25 -05:00
Kevin Matz 758740704c add actions for the receiver merge mode 2022-11-24 11:35:04 -05:00
Kevin Matz 4a1c50cdf1 hold the last seen time separate from the list of valid times 2022-11-24 11:34:33 -05:00
Kevin Matz 24ba41a73f it's the name of the source, not a description of the universe 2022-11-24 11:31:51 -05:00
Kevin Matz 29fd1fc9ed privatize the active data slot variable 2022-11-23 14:28:27 -05:00
Kevin Matz 9e94c406fd init variable in constructor, not header 2022-11-23 14:27:40 -05:00
Kevin Matz 9889a8943c the minimum length sACN data is just the start code 2022-11-23 14:22:37 -05:00
Kevin Matz 3695ae80fe improve code readability 2022-11-23 13:50:15 -05:00
Kevin Matz 88c4bb628f fix private variable init order 2022-11-23 13:49:51 -05:00
Kevin Matz e12d11e62e tighten up the use of time 2022-11-23 13:49:35 -05:00
Kevin Matz e157090e35 lock the data mutex for the minimum necessary instructions 2022-11-23 13:47:51 -05:00
Kevin Matz da668011fe report the status of the dominant universe 2022-11-22 23:27:25 -05:00
Kevin Matz d41ecc65bc the status byte can be private 2022-11-22 23:26:33 -05:00
Kevin Matz c00e0e1d9a use virtual setStatus to change operational states 2022-11-22 12:15:24 -05:00
Kevin Matz 5c2548a5d3 if DMX has timed out, check if sACN has also 2022-11-22 12:13:52 -05:00
Kevin Matz 8a3eb928dd set DMX_LOST status from E111_DATA_LOSS_TIMEOUT 2022-11-22 12:05:25 -05:00
Kevin Matz 6be8dfbebd set the operational status for sending universes 2022-11-21 17:46:17 -05:00
Kevin Matz cf1df8d373 send 3 stream_terminated messages during orderly shutdown 2022-11-21 17:45:43 -05:00
Kevin Matz 09dc532984 check for timeout before returning the status 2022-11-21 17:42:08 -05:00
Kevin Matz 357f8d5359 override the base implementation to be thread safer 2022-11-21 17:41:28 -05:00
Kevin Matz 81e68203ff set the status of terminated universes 2022-11-21 14:40:38 -05:00
Kevin Matz f27faa4fe6 always attempt to delete terminated universes 2022-11-21 14:40:04 -05:00
Kevin Matz fdbff859c7 be verbose in the status name 2022-11-21 14:37:02 -05:00
Kevin Matz 6ad6aa28f8 don't remove last universe if holding the look 2022-11-21 14:25:12 -05:00
Kevin Matz a5958448ff set/timeout ops modify the operational state 2022-11-21 13:41:22 -05:00
Kevin Matz 53ac273c0b there is no distinction between network data lost and terminated 2022-11-21 13:39:14 -05:00
Kevin Matz e76fd2f603 add a property to track the operating state of the universe 2022-11-21 13:24:21 -05:00
Kevin Matz 718a3dd22f Add status enum to be used later to track the operational state of a universe. 2022-11-21 12:41:59 -05:00
Kevin Matz 07cc2e1c0a give the Multiverseview to each UniverseView as parent 2022-11-21 12:40:31 -05:00
Kevin Matz b0a16a8cb7 Add theme icons to actions 2022-11-20 23:01:30 -05:00
Kevin Matz 8fd8ef977d doc improvements 2022-11-20 23:00:38 -05:00
Kevin Matz 54ead231ad ensure that UDP and TCP are being sent on the correct transport 2022-11-20 17:30:23 -05:00
Kevin Matz 4a52f7302d implement a more robust method of determining dominant member 2022-11-20 15:22:51 -05:00
Kevin Matz 3672b1d49f Remove note about deleting callback tokens, It's done for all stale callbacks. 2022-11-20 15:16:12 -05:00
Kevin Matz 1ad91bdefe doc updates 2022-11-20 15:15:14 -05:00
Kevin Matz ae838bee21 reuse the same RLP header for every message 2022-11-20 15:10:53 -05:00
Kevin Matz 10031d3a05 implement a separate sender for each transport protocol 2022-11-20 15:09:03 -05:00
Kevin Matz 2d93bbe709 only handle discovery frames if discovery is enabled 2022-11-19 17:23:13 -05:00
Kevin Matz e14556fe16 call discovery callbacks after disabling discovery 2022-11-19 17:22:36 -05:00
Kevin Matz b26bf158c0 never add a universe for discovery 2022-11-19 17:22:03 -05:00
Kevin Matz 99d919d613 move discovery state tracking form platform driver to protocol receiver 2022-11-19 16:43:41 -05:00
Kevin Matz 2003e22110 IPv6 uses MDL not IGMP 2022-11-19 16:08:24 -05:00
Kevin Matz df8356e7dc wait until after names are assigned to print debug statement 2022-11-19 16:07:33 -05:00
Kevin Matz b748dd9a01 separate "name" (FCTN or UACN) from UACN 2022-11-19 15:56:59 -05:00
Kevin Matz b7f045cd6b Enable changing the ACN EPI 19 user assignable name 2022-11-19 15:47:51 -05:00
Kevin Matz d84dde0cb2 add a modal line editior dialog 2022-11-19 15:46:47 -05:00
Kevin Matz e4119c5189 add missing property getter for "Hold Last Look" 2022-11-19 15:45:31 -05:00
Kevin Matz 488328a05e refactor persistent settings 2022-11-19 15:44:34 -05:00
Kevin Matz e98b6f5734 make the add universe dialog modal 2022-11-19 15:43:31 -05:00
Kevin Matz e528abe5e1 make the ACN EPI 19 Fixed name only asignable at initilization 2022-11-19 15:43:02 -05:00
Kevin Matz 0cad3288d7 move metatype declaration ajacent to type definition 2022-11-19 15:36:15 -05:00
Kevin Matz 01e3549861 Add a note to documentation about subclassing vs wrapping for QSacnUniverse 2022-11-19 12:34:52 -05:00
Kevin Matz 5c0a74e338 verify subscription by validity of universe pointer 2022-11-19 12:34:20 -05:00
Kevin Matz 33347894af toggle'able sACN discovery 2022-11-19 12:33:49 -05:00
Kevin Matz 498129c325 rearrange the menu bar 2022-11-19 12:32:32 -05:00
Kevin Matz a0a1c13cb3 enhanced IGMP debug messages 2022-11-19 12:27:02 -05:00
Kevin Matz 24e7b5eb4c retrieving a universe doesn't modify the receiver 2022-11-19 12:26:07 -05:00
Kevin Matz ef38fae104 Without a way to query the subscription state of an IGMP group, use a boolean to track the state of sACN discovery. 2022-11-19 12:24:46 -05:00
Kevin Matz ffb82f3029 edit'able source names from the tree view 2022-11-18 15:55:52 -05:00
Kevin Matz 693b361244 add citation 2022-11-18 13:50:07 -05:00
Kevin Matz 8fb0666ff0 fix indent 2022-11-18 12:16:35 -05:00
Kevin Matz a0cb4c18a4 uniformly check if user entered value is valid 2022-11-18 12:15:19 -05:00
Kevin Matz e889e36ccd remove unused header 2022-11-18 11:32:22 -05:00
Kevin Matz d60721cf79 Enable setting priority from the universe model tree 2022-11-18 11:32:07 -05:00
Kevin Matz 056063ab2c remove the universe name from the window title now that it's in the toolbar 2022-11-17 12:41:15 -05:00
Kevin Matz 4ddd786053 initialize the description edit in a uniform way 2022-11-17 12:40:33 -05:00
Kevin Matz 85e631424a include universe number in metadata source_name 2022-11-17 12:39:43 -05:00
Kevin Matz 7def265788 header cleanup 2022-11-17 12:29:57 -05:00
Kevin Matz 9b918b858e add universe description edit 2022-11-16 09:35:38 -05:00
Kevin Matz bfb46eb0df header cleanup 2022-11-16 09:28:08 -05:00
Kevin Matz 186f7d9134 setDescription slot parameter as QString 2022-11-16 09:27:28 -05:00
Kevin Matz 091dea3ed5 remove spurious menu entry 2022-11-16 08:54:24 -05:00
Kevin Matz 0f481ae2a7 stubs for Artnet I/O 2022-11-15 11:42:27 -05:00
Kevin Matz 4123e85328 nodes contain a set of ports 2022-11-15 11:41:25 -05:00
Kevin Matz 161f8644ec ArtDmx has two modes 2022-11-15 11:40:47 -05:00
Kevin Matz b4f27c88f7 explicit constructors 2022-11-15 11:39:54 -05:00
Kevin Matz 8e7489082b introduce ports and universes 2022-11-15 11:37:52 -05:00
Kevin Matz 5f46377e95 remove unnecessary headers 2022-11-15 11:32:53 -05:00
Kevin Matz bca742c37f Documentation improvement and cleanup 2022-11-15 11:32:29 -05:00
Kevin Matz 248aca3f70 fixed by commit b20e463177 2022-11-14 10:04:12 -05:00
Kevin Matz b20e463177 preserver stream state checking when reading 8bit values 2022-11-14 10:00:02 -05:00
Kevin Matz 0b5435065e correctly accept empty universe discovery lists 2022-11-14 09:16:53 -05:00
Kevin Matz db3c782bef remove unnecessary header includes 2022-11-11 11:08:24 -05:00
Kevin Matz 933279e068 cleanup doxygen warnings 2022-11-11 11:08:05 -05:00
Kevin Matz ea1b281cde Remove depricated doxygen config statements. 2022-11-11 11:06:35 -05:00
Kevin Matz ce1e17e9fc use reinterpret_cast on data() instead of c style cast on constData() that silently removed the const. 2022-06-11 11:25:18 -04:00
Kevin Matz 5220fa5b3d use std::make_shared to construct the shared pointer 2022-06-11 11:23:41 -04:00
Kevin Matz 1904dacea9 globally configurable Art-Net OEM value 2022-06-09 16:51:14 -04:00
Kevin Matz 27e668457f class hierarchy of Device, Controller, and Node 2022-06-09 16:41:55 -04:00
Kevin Matz 2942d2800d add a header 2022-06-08 16:06:30 -04:00
Kevin Matz 5710631724 the Art-Net packet definitions 2022-06-08 16:05:06 -04:00
Kevin Matz 7826d17d40 set flag value if initialized via the manufactuer high bit 2022-06-08 10:42:44 -04:00
Kevin Matz f03b2140fa always write the flag bit 2022-06-08 10:39:10 -04:00
Kevin Matz b0c291b146 Prepare to support the Art-Net protocol. 2022-06-04 10:54:45 -04:00
Kevin Matz fa24d64c72 receive a ProbeRequest 2022-06-04 10:52:42 -04:00
Kevin Matz 5d4f6654cd protocol I/Os are protected functions 2022-06-04 10:52:06 -04:00
Kevin Matz ca671229f7 protocol recievers are protected members 2022-06-04 10:51:39 -04:00
Kevin Matz 77113b9139 add flag for when device is connected to a broker 2022-06-04 10:50:39 -04:00
Kevin Matz 74b95cf007 specify the byte width of the IP address specification enum 2022-06-04 10:49:24 -04:00
Kevin Matz 497f45a0df flag to probe for inactive only 2022-06-04 10:48:46 -04:00
Kevin Matz ce8baafcf3 add definition for slot count 2022-06-04 10:29:00 -04:00
Kevin Matz 00d825ccf8 turn pdu_flags into a POD struct 2022-06-04 10:27:25 -04:00
Kevin Matz a4978c514e minor optimization for streaming the native type 2022-05-31 15:25:07 -04:00
Kevin Matz 53868f7162 remove obsolete CLASS_DIAGRAMS directive 2022-05-28 17:01:18 -04:00
Kevin Matz b95f6a72d6 keep a transaction number 2021-09-19 22:37:57 -04:00
Kevin Matz 1842c69b02 un-document unused variable 2021-09-19 12:22:24 -04:00
Kevin Matz 7c1f529833 LLRP frame handling 2021-09-19 12:22:07 -04:00
Kevin Matz 95916aea56 check for existing segments before creating 2021-09-19 12:19:53 -04:00
Kevin Matz c5c4df1888 create LLRP frame block 2021-09-19 10:23:33 -04:00
Kevin Matz 6b960d8913 inherit from an EPT Client base class that does nothing. 2021-09-19 10:21:55 -04:00
Kevin Matz 9fe57bc363 unpack structure for portability 2021-09-19 09:29:17 -04:00
Kevin Matz 4cd8289a85 remove packed struct for portability 2021-09-19 09:03:41 -04:00
Kevin Matz 6cc9f6e025 and stream condition checks 2021-09-17 09:35:47 -04:00
Kevin Matz b8a113759b remove extranious stream read 2021-09-17 09:35:35 -04:00
Kevin Matz 6a24a9ed57 don't rely on packed structures 2021-09-17 08:49:28 -04:00
Kevin Matz c30d6a33f2 use the mkshared constructor 2021-09-17 08:48:39 -04:00
Kevin Matz 0afb725606 enabe MOC for qt platform driver 2021-09-14 09:52:43 -04:00
Kevin Matz ad6098c553 corrected macro definition 2021-09-14 09:17:14 -04:00
Kevin Matz f21a071860 zero is the default width case 2021-09-11 14:29:40 -04:00
Kevin Matz 7ae5896bc8 correct more type defines 2021-09-11 14:29:17 -04:00
Kevin Matz ae2a03531b uint -> unsigned int for MinGW 2021-09-11 13:54:32 -04:00
Kevin Matz 567e70d42b cmake track generated doxygen work output 2021-09-11 12:22:27 -04:00
Kevin Matz e1f4ec5b2c clean up doxygen warnings 2021-09-11 11:50:47 -04:00
Kevin Matz 4b1c4f51af remove test setup 2021-09-11 10:59:31 -04:00
Kevin Matz 2b9f2c4dfe build as static 2021-09-11 10:59:17 -04:00
Kevin Matz ccc99b3097 UniverseView windows don't need a parent 2021-09-11 10:48:19 -04:00
Kevin Matz 04eb87ed82 comparison operators that makes sense 2021-09-11 10:01:18 -04:00
Kevin Matz 8e50d9292d callback tokens to signal when the caller has gone away 2021-09-11 09:44:16 -04:00
Kevin Matz abc7b35b70 initial window size 2021-09-11 09:42:30 -04:00
Kevin Matz bb71d2ca61 put tests last in config order 2021-09-10 17:49:39 -04:00
Kevin Matz abbfe6ee0d configurable hold-last-look 2021-09-10 16:26:31 -04:00
Kevin Matz 7039696807 connect data change refresh for component sources 2021-09-10 16:05:09 -04:00
Kevin Matz 7c53a1d9ca double click expansion conflicts with inspection behavior 2021-09-10 13:06:57 -04:00
Kevin Matz 6f6ec334ca purge zombie sources 2021-09-10 12:48:33 -04:00
Kevin Matz 220a70d51b age in MS since the last receipt 2021-09-10 12:47:11 -04:00
Kevin Matz 69bca798c4 poly-source universe API cleanup 2021-09-10 12:00:50 -04:00
Kevin Matz dfeff5c344 fix off-by-one error 2021-09-10 11:49:36 -04:00
Kevin Matz b5c099af35 whitespace cleanups 2021-09-10 11:47:48 -04:00
Kevin Matz 7b0cb3d268 unused API call 2021-09-10 09:58:17 -04:00
Kevin Matz d3da663a44 manage universes with shared pointers 2021-09-10 09:55:34 -04:00
Kevin Matz 15ad9a565b send at least one empty discovery message if have previously announced universes 2021-09-08 16:16:45 -04:00
Kevin Matz 053339d2d1 complete the removal of a terminated universe 2021-09-08 16:15:40 -04:00
Kevin Matz ef59af7207 corrected comment 2021-09-08 16:14:39 -04:00
Kevin Matz eaaad9ae4e self-check if editable 2021-09-08 15:47:10 -04:00
Kevin Matz b609966df7 no longer need to be friends with DMX::Universe 2021-09-08 15:46:51 -04:00
Kevin Matz f29c082d4f qobjects automatically disconnect during destruction 2021-09-08 15:19:36 -04:00
Kevin Matz 7229657f4d row removals 2021-09-08 13:28:49 -04:00
Kevin Matz b64f04443c given edit data for column, find the first matching row number 2021-09-08 13:28:05 -04:00
Kevin Matz 1aee17d70e disconnect universes before deleting 2021-09-08 13:25:43 -04:00
Kevin Matz 66870b970a emit model data changed on universe changed 2021-09-08 12:47:40 -04:00
Kevin Matz 511c4224ef verity capability before edit 2021-09-08 10:33:54 -04:00
Kevin Matz 4f7d77c803 emit changed signal with every change 2021-09-08 10:31:28 -04:00
Kevin Matz a699210d77 add column for channel count 2021-09-08 09:55:38 -04:00
Kevin Matz 719eca89d7 universes with no active slots aren't sent, so don't announce 2021-09-08 09:54:26 -04:00
Kevin Matz 85e85edbbd sort by universe number 2021-09-08 09:40:27 -04:00
Kevin Matz 33479b37ee insert correct quantity of rows 2021-09-08 09:37:32 -04:00
Kevin Matz 63a87f4f1a sanity checks 2021-09-08 09:35:56 -04:00
Kevin Matz 5863e6b1f9 disabled actions for node operation 2021-09-08 09:34:47 -04:00
Kevin Matz f7acefccd0 bulk remove children 2021-09-07 11:18:49 -04:00
Kevin Matz ac3dc494d0 cleanup flags after new doublclick method 2021-09-07 11:18:31 -04:00
Kevin Matz 1e2bb87493 shared pointers for discovered universe metadata 2021-09-07 11:17:52 -04:00
Kevin Matz 3e8cc72a61 span first column for top level items 2021-09-07 09:59:01 -04:00
Kevin Matz 62e15b4bd6 append child count to override data 2021-09-07 09:58:44 -04:00
Kevin Matz 0a3f14e75f add discoveries to model 2021-09-07 09:39:45 -04:00
Kevin Matz ee2c46625f drop unused slot 2021-09-07 09:38:57 -04:00
Kevin Matz 1744e34598 return correctly constructed string 2021-09-07 09:37:34 -04:00
Kevin Matz 111aa59417 emit signal if discovery has completed 2021-09-07 09:16:01 -04:00
Kevin Matz f3f6b0d35f clear entries from CID on first page of discovery 2021-09-07 09:14:34 -04:00
Kevin Matz 42368ea01f double click doesn't need a delegate 2021-09-06 22:01:12 -04:00
Kevin Matz f6a50926c3 slot for discovery inserts 2021-09-06 15:09:45 -04:00
Kevin Matz 891a2e9dfa create/subscribe actions 2021-09-06 15:09:08 -04:00
Kevin Matz 12843e8264 modal dialog 2021-09-06 15:08:29 -04:00
Kevin Matz c1efa991fb enable UI actions with appropriate selection 2021-09-06 15:08:16 -04:00
Kevin Matz 5693689267 give the delegate the node for connecting signals 2021-09-06 13:44:03 -04:00
Kevin Matz 745d62f4a1 qt6 versionless targets 2021-09-06 13:28:07 -04:00
Kevin Matz b3ee265745 rework directory structure 2021-09-06 12:54:28 -04:00
Kevin Matz 0090fa5706 drop models from library 2021-09-06 07:02:22 -04:00
Kevin Matz d021366fc8 fixed length strings get written null-terminated by default 2021-09-05 12:37:58 -04:00
Kevin Matz 25227ff083 track relative address offests 2021-09-04 17:41:02 -04:00
Kevin Matz c6386b0272 add rest of discovery information from PDU 2021-09-04 17:39:46 -04:00
Kevin Matz 5fe5b71148 ordered discovered universes 2021-09-04 17:38:55 -04:00
Kevin Matz 777732e378 less than operator overloads 2021-09-04 17:37:58 -04:00
Kevin Matz 7a1bcd74ca return a fake provenance if no dominant sources 2021-09-04 17:37:25 -04:00
Kevin Matz bf50231f6f compound universes can know what universe they are 2021-09-04 17:28:00 -04:00
Kevin Matz 7fb9bb8f9a also range check setting by percent 2021-09-03 17:45:32 -04:00
Kevin Matz b3db8ecd47 "gray-out" slots beyond the last active slot 2021-09-03 17:44:53 -04:00
Kevin Matz e538cc74d3 virtual method for getting active slot count 2021-09-03 17:43:50 -04:00
Kevin Matz bbe4432737 virtualize ediditability query 2021-09-03 17:37:12 -04:00
Kevin Matz 76582b1cc4 terminate and unsubscribe during destruction 2021-09-03 17:35:41 -04:00
Kevin Matz 3ea916d61e extra bytes for unsigned math 2021-09-02 13:46:56 -04:00
Kevin Matz 6ade2e1ef6 simultaneous rx/tx on both IPv4 and IPv6 2021-09-02 13:43:35 -04:00
Kevin Matz 2d83dd3e5c wrap the sACN multicast address mapping functions 2021-09-02 13:24:37 -04:00
Kevin Matz c11f6c0510 set user-entered values 2021-09-02 13:23:58 -04:00
Kevin Matz de786159ff set ItemIsEditable if the universe is tx 2021-09-02 13:23:19 -04:00
Kevin Matz b186c949f9 don't directly construct shared pointers 2021-09-02 13:22:50 -04:00
Kevin Matz 7f0919f60c document behavior that is non-standard 2021-09-02 13:21:11 -04:00
Kevin Matz 79be37d38a track rx universes seperately from tx universes 2021-09-02 13:20:37 -04:00
Kevin Matz f3f5aa3d3e implement QSacnUniverse in proper body 2021-09-02 13:18:40 -04:00
Kevin Matz 41686e32af publicly indicate if universe is tx or rx 2021-09-02 13:17:47 -04:00
Kevin Matz 64046967ce arduino platform drivers belong closer to the arduino build system 2021-09-02 13:15:15 -04:00
Kevin Matz 3dd300902f doc updates 2021-09-01 20:28:24 -04:00
Kevin Matz 5c0cc41af5 worker thread for streaming sACN 2021-09-01 12:53:53 -04:00
Kevin Matz b3256be388 sending sACN is not virtual 2021-09-01 12:41:15 -04:00
Kevin Matz dfeaf646e1 don't send inactive universes 2021-09-01 12:40:21 -04:00
Kevin Matz 5459b48388 forgotten include 2021-09-01 12:39:48 -04:00
Kevin Matz f5055020ed cleanup memory allocators 2021-09-01 12:39:15 -04:00
Kevin Matz a8e997258b override setValue functions 2021-09-01 12:37:38 -04:00
Kevin Matz 958083f6a3 copy instead of iterate 2021-09-01 12:35:40 -04:00
Kevin Matz 3aa1142ade initialize property in initializer list 2021-09-01 12:34:10 -04:00
Kevin Matz 4d052c84e8 set functions are virtual 2021-09-01 12:33:42 -04:00
Kevin Matz 681f659934 read/write directly to vector data 2021-09-01 12:32:04 -04:00
Kevin Matz ca89b60e21 copy by referance in lambda 2021-09-01 12:31:19 -04:00
Kevin Matz 26136e1397 transport already has a root block 2021-09-01 12:30:23 -04:00
Kevin Matz 1ec57961f7 index off-by-one error 2021-09-01 12:29:11 -04:00
Kevin Matz 6822fe2d52 use stream operator overload for Block 2021-09-01 12:28:04 -04:00
Kevin Matz 33b1d4eda7 parent isn't set until after istream completes 2021-09-01 12:27:02 -04:00
Kevin Matz e0d142797a fix flags+length writing 2021-09-01 12:24:48 -04:00
Kevin Matz 4e38144e9f default universe priority 2021-09-01 12:24:02 -04:00
Kevin Matz 6f4ce45413 explicit referances in block processing 2021-09-01 12:23:30 -04:00
Kevin Matz 535cc66cd1 range element width has only one user 2021-09-01 12:22:53 -04:00
Kevin Matz b356d052b3 cast range types before writing 2021-09-01 12:21:22 -04:00
Kevin Matz 7c40861383 platform driver needs a refactor before output can be const keyword 2021-09-01 12:20:35 -04:00
Kevin Matz c811469c95 platform implementation for sending PDU::Stream RLP datagrams 2021-09-01 12:13:59 -04:00
Kevin Matz 044c95b168 virtual alt SC function 2021-08-31 13:25:02 -04:00
Kevin Matz 740bd851c0 don't modify data being set 2021-08-31 13:24:32 -04:00
Kevin Matz 337a8a650d increment sequence number every message 2021-08-31 12:24:21 -04:00
Kevin Matz 355f2b3365 reduce redundant code 2021-08-31 11:59:48 -04:00
Kevin Matz 00b1c1299f mutex for NULL Start Code data 2021-08-31 11:33:02 -04:00
Kevin Matz ae5732e2fd smart pointers for PDU header and data segments 2021-08-31 11:13:36 -04:00
Kevin Matz 858e8b4770 purge universe discovery bitrot 2021-08-31 11:09:20 -04:00
Kevin Matz 68bc049b8b discovery on both IPv4 and IPv6 2021-08-31 10:28:00 -04:00
Kevin Matz decf1f7cb1 sACN universe sending 2021-08-31 10:27:30 -04:00
Kevin Matz b9e752e87e make universes aware of their sending source 2021-08-31 09:27:53 -04:00
Kevin Matz 207c6b8b71 reclassify null start data as Protected 2021-08-31 09:03:18 -04:00
Kevin Matz 7c9c62d0fe make uniform the passing address_type 2021-08-31 09:01:57 -04:00
Kevin Matz 09b2210616 threaded worker loop for periodic discovery broadcast 2021-08-30 13:59:08 -04:00
Kevin Matz d421a5c577 transmit sACN Universe Discovery lists 2021-08-30 13:36:17 -04:00
Kevin Matz f0d9402db9 transmit sACN EXTENDED frames 2021-08-30 13:32:20 -04:00
Kevin Matz 9c10bd7124 calculate multicast addresses from universe numbers 2021-08-30 13:30:36 -04:00
Kevin Matz b8e6dcdcef enhanced IP address 2021-08-30 12:53:17 -04:00
Kevin Matz 7495897b4a rlp writing 2021-08-30 10:15:39 -04:00
Kevin Matz d9e42f5c2f universes have a destination IP, defaulting to the appropriate multicast address 2021-08-30 10:00:54 -04:00
Kevin Matz 9b0ff5821b reuse IP address types from SDT 2021-08-30 09:56:48 -04:00
Kevin Matz cf9ae4c89b intermediate transport class for both TCP and UDP 2021-08-30 09:55:34 -04:00
Kevin Matz 8f7463b67f keep a list of discovered universes 2021-08-29 11:44:27 -04:00
Kevin Matz 7a0fbb0e41 note that 0 sequences can expose bugs 2021-08-29 11:18:45 -04:00
Kevin Matz 66b6c8e7e4 initilize active slots to 0 2021-08-29 11:17:53 -04:00
Kevin Matz 7a49267125 use std::copy for efficiency 2021-08-29 11:06:00 -04:00
Kevin Matz b934477b19 don't front-pad property data for non-zero addresses 2021-08-29 09:49:00 -04:00
Kevin Matz 9ab058393e remove redundant length check 2021-08-29 09:07:05 -04:00
Kevin Matz dbed27aacf remove redundant length check 2021-08-29 09:03:42 -04:00
Kevin Matz 08c07d1c9c also track sequence number for syncronizaton 2021-08-29 01:12:11 -04:00
Kevin Matz 5101e0d22c track sequence numbers per universe 2021-08-29 01:01:20 -04:00
Kevin Matz 3bad5e0d3e evaluate sequence numbers using standard method 2021-08-29 01:00:50 -04:00
Kevin Matz 324ac934e8 document PDU properties 2021-08-29 00:59:36 -04:00
Kevin Matz ca990976e5 directly compare DATA::data_headers 2021-08-29 00:58:57 -04:00
Kevin Matz a99a191e13 new name for the ArpitratingUniverse class 2021-08-28 15:28:52 -04:00
Kevin Matz 6def8d52b9 reuse DATA::data_header to maintain record of metadata 2021-08-28 14:47:30 -04:00
Kevin Matz ea3abc97b5 transmit sequence number 2021-08-28 12:54:46 -04:00
Kevin Matz 287ae5f6cc markdown block quotes from the standard 2021-08-28 12:54:26 -04:00
Kevin Matz bb290ab7cb validate incoming sequence number 2021-08-28 12:23:20 -04:00
Kevin Matz eaac6084a2 required documentation of merge 2021-08-28 11:24:01 -04:00
Kevin Matz 39cb36c46e fail on receiving invalid universe number 2021-08-28 10:27:13 -04:00
Kevin Matz c808341eff universes have provenances or metadata, not sources 2021-08-28 10:17:53 -04:00
Kevin Matz 9b4f698b9a data header options inherit from pdu_steam_object 2021-08-28 09:10:28 -04:00
Kevin Matz 0614524d0b also enforce priority value constraint on sending 2021-08-28 09:08:40 -04:00
Kevin Matz bd05b0439a DMP action stubs 2021-08-28 09:07:50 -04:00
Kevin Matz 6a9247ccea refactor dmp header component names 2021-08-28 09:07:06 -04:00
Kevin Matz 54df9ae8f6 track the count of active data slots 2021-08-28 09:04:06 -04:00
Kevin Matz 031888f192 don't force sACN namespace to be all caps 2021-08-28 09:01:33 -04:00
Kevin Matz 6cde71a672 more bibs 2021-08-28 08:53:43 -04:00
Kevin Matz 193899b4d7 Provenance can be a POD struct 2021-08-27 12:09:19 -04:00
Kevin Matz 2a26e7fa49 DMP layer of sACN is too constrained to require inheritance of DMP::Device 2021-08-27 09:02:44 -04:00
Kevin Matz 8c99f4ff1f enforce value constraints when reading stream 2021-08-27 08:56:39 -04:00
Kevin Matz f40ac836ce DMP property get/set 2021-08-26 17:00:15 -04:00
Kevin Matz 8d6a765c3a refactor receive function names to reflect PDU type, not vector 2021-08-26 16:57:30 -04:00
Kevin Matz 05f629440b DMP Properties don't need a class 2021-08-26 16:56:28 -04:00
Kevin Matz 8392d2e433 data pending synchronization can be a nullptr until feature is used 2021-08-26 16:49:58 -04:00
Kevin Matz 3c78f0570d force synchronization on next update, not next access 2021-08-26 16:46:06 -04:00
Kevin Matz 390b2eadf6 cite multi-line quotes 2021-08-26 12:30:44 -04:00
Kevin Matz 60ac42d024 flat search box 2021-08-26 12:30:17 -04:00
Kevin Matz c724f414b1 remove gradients 2021-08-26 12:15:46 -04:00
Kevin Matz 381d5fe4f9 use slide images from git tree 2021-08-26 12:00:45 -04:00
Kevin Matz 53602709fa begin adding citations to standards 2021-08-26 11:54:30 -04:00
Kevin Matz 8de1af8e89 use doxygen todo list 2021-08-26 11:54:08 -04:00
Kevin Matz 44c87055fd copyright info 2021-08-26 11:53:25 -04:00
Kevin Matz b92593c653 SVG doxygen graphs 2021-08-26 11:52:52 -04:00
Kevin Matz 03300620fc custom doxygen content 2021-08-26 11:35:50 -04:00
Kevin Matz c05e47abf2 add bibliographies 2021-08-26 11:35:26 -04:00
Kevin Matz da90565ed5 move doxygen output to build tree 2021-08-26 03:25:01 -04:00
Kevin Matz ce74d93cad doxygen built files 2021-08-25 17:21:45 -04:00
Kevin Matz 7f9764ac43 have cmake build doxygen files 2021-08-25 17:21:14 -04:00
Kevin Matz f948932c90 additional documentation 2021-08-25 17:20:33 -04:00
Kevin Matz d5bef37ddf rename ACN::RLP::Appliance -> ACN::RLP::Component 2021-08-25 10:57:31 -04:00
Kevin Matz 883dbc7f8c introduce ACN::PDU::Message 2021-08-25 10:33:53 -04:00
Kevin Matz dee458bf88 remove unnecessary virtuals 2021-08-25 10:33:15 -04:00
Kevin Matz 5ea90a2beb rename DMP::Component -> DMP::Appliance 2021-08-25 10:31:19 -04:00
Kevin Matz 30f61fa019 mark global heade as private 2021-08-25 10:29:48 -04:00
Kevin Matz c41d26bf7f register RLP vectors 2021-08-25 00:23:11 -04:00
Kevin Matz a62c41e6bc protocols move out of top level directory 2021-08-24 18:30:44 -04:00
Kevin Matz db5f007d22 project restructure + version bump 0.2.0 2021-08-24 18:10:20 -04:00
Kevin Matz 8d24b8b7a1 copied from an example. remove project copyright. 2021-08-22 19:20:27 -04:00
Kevin Matz 3a801fba51 test a random CID 2021-08-22 19:15:06 -04:00
Kevin Matz 0ad24fab4b good enough to pass the tests. more testing required to find the real problem. 2021-08-22 19:13:31 -04:00
Kevin Matz ec6edf19e0 cast to wider type before bitshift 2021-08-22 17:41:46 -04:00
Kevin Matz b147ca0578 raw bytes don't need to be a pointer 2021-08-22 17:41:12 -04:00
Kevin Matz cc40307adb be tolerant of curly braces on input, but don't use them on output. 2021-08-22 16:57:46 -04:00
Kevin Matz 026a4a16de test creating v4 uuid 2021-08-22 16:01:46 -04:00
Kevin Matz 9d5c985254 close the loop and compare to original string 2021-08-22 15:51:24 -04:00
Kevin Matz 7a0a8765a6 basic UUID test 2021-08-22 15:46:28 -04:00
Kevin Matz 5f72daa789 get version from correct octet 2021-08-22 15:46:02 -04:00
Kevin Matz ce575e983e set correct bits for UUID type 2021-08-22 15:34:55 -04:00
Kevin Matz a496cca21e add overview test of sACN data receive using packet example from the standard. 2021-08-22 15:33:32 -04:00
Kevin Matz 32da243239 tests get their own CmakeLists 2021-08-22 15:32:46 -04:00
Kevin Matz edd351fce0 change hard-coded length values to UUID_LENGTH constant 2021-08-22 12:11:48 -04:00
Kevin Matz 00a32ea2f9 seed srand with current UUID timestamp 2021-08-22 12:10:50 -04:00
Kevin Matz 922deb81da return nullptrs, not 0s 2021-08-22 12:04:30 -04:00
Kevin Matz 7f338164b6 IP stack receivers are public 2021-08-22 12:02:19 -04:00
Kevin Matz 8acfa70f00 link Tests against library 2021-08-22 12:01:38 -04:00
Kevin Matz 571e6a73ec DMP not allowed directly in root 2021-08-20 15:39:52 -04:00
Kevin Matz 5e9f957731 cleanup stream output 2021-08-20 12:04:39 -04:00
Kevin Matz 92e14a2b5c source name is a string 2021-08-20 11:41:20 -04:00
Kevin Matz 03347a5e21 OTP Transform PDU i/o 2021-08-20 11:34:26 -04:00
Kevin Matz 914be1f3f9 stream operators for signed integers 2021-08-20 10:13:00 -04:00
Kevin Matz 1c9f1e3769 stream operator overloads for pdu_stream_object. 2021-08-20 09:58:59 -04:00
Kevin Matz e5539ffc37 overridden virtual function should not be marked virtual in the derived class 2021-08-19 18:28:23 -04:00
Kevin Matz 00cb16a5e5 the OTP Advertisement PDU 2021-08-19 18:21:47 -04:00
Kevin Matz d77cf14dae OTP Layer PDU i/o 2021-08-19 16:27:20 -04:00
Kevin Matz 6c12f7d8f0 rename otp.h 2021-08-19 13:11:32 -04:00
Kevin Matz 1371d17af1 use istream.ignore() for a tighter loop 2021-08-19 12:56:12 -04:00
Kevin Matz 818a0891a3 the OTP PDU, Much simpler than the ACN PDU. 2021-08-19 12:55:25 -04:00
Kevin Matz a5710d1bb5 build platform library as shared lib 2021-08-18 17:11:34 -04:00
Kevin Matz 8c75d11199 build as shared library 2021-08-18 17:09:33 -04:00
Kevin Matz 45298929c8 sub implement component destructor 2021-08-18 17:08:56 -04:00
Kevin Matz c67c13a556 move stream operators for RDM::UIDs out of header 2021-08-18 17:08:35 -04:00
Kevin Matz 10606810cb SDT i/o cleanup, with class files for session and channel 2021-08-18 16:55:57 -04:00
Kevin Matz a746cb9022 cleanup EPI18 PDU i/o 2021-08-18 15:51:47 -04:00
Kevin Matz 68020b0213 use std::isteam ignore for loop efficiency 2021-08-18 15:46:07 -04:00
Kevin Matz 507a2352ff use Google Test framework for unit tests 2021-08-18 11:45:51 -04:00
Kevin Matz c552fba1ba remove non-pod global static from header 2021-08-17 16:29:27 -04:00
Kevin Matz 936f47903b qt node cleanup sacn namespace refactors 2021-08-17 16:20:32 -04:00
Kevin Matz 01deea9613 EPT PDU i/o 2021-08-17 16:18:40 -04:00
Kevin Matz ba3e17361d use pdu_stream string i/o 2021-08-17 14:20:17 -04:00
Kevin Matz 6e47d8690f LLRP rdm command uses the RPT COMMAND type PDU 2021-08-17 14:19:56 -04:00
Kevin Matz edb0880bdf RPT PDU i/o 2021-08-17 14:19:17 -04:00
Kevin Matz 2eac9cf7fb allow reading a string from the all available bytes. 2021-08-17 14:18:25 -04:00
Kevin Matz 9dbfb9bac6 remove redundant stream health checks 2021-08-17 12:33:43 -04:00
Kevin Matz 112326b695 pdu_stream can read/write fixed length strings 2021-08-17 12:24:19 -04:00
Kevin Matz 1d5e36ae10 stream read/write for RDM UIDs 2021-08-16 22:23:06 -04:00
Kevin Matz a8011898e8 rdmnet component 2021-08-16 21:54:48 -04:00
Kevin Matz f7ac8f2a10 user intermediate component class 2021-08-16 21:54:26 -04:00
Kevin Matz 7feb63ecd6 broker protocol PDUs 2021-08-16 21:53:48 -04:00
Kevin Matz fd0eeef934 pdu_stream can read/write UUID 2021-08-16 21:20:19 -04:00
Kevin Matz ed82a617e6 OTP PDU do not inherit from ACN PDU 2021-08-16 19:16:24 -04:00
Kevin Matz 069dd3ede8 PDU hieracy flowchart 2021-08-16 18:18:59 -04:00
Kevin Matz 9a619b0e63 component fctn 2021-08-16 12:13:13 -04:00
Kevin Matz d3f1edc33a EPT client implementation is optional 2021-08-16 12:09:11 -04:00
Kevin Matz d5f05f6fbe note for potential future work 2021-08-16 11:36:36 -04:00
Kevin Matz 2fa42b1c53 C++20 template concepts in the comments 2021-08-16 11:07:31 -04:00
Kevin Matz 69cb85ef9f allow pdu segment creators to be called with invalid streams 2021-08-16 10:44:25 -04:00
Kevin Matz 4440cb6f37 Use the DMX null start code timeout constant to prune received universes 2021-08-16 10:15:42 -04:00
Kevin Matz a6620f1def review of stream condition checking throughout iStream path 2021-08-16 10:14:39 -04:00
Kevin Matz abb5bf1466 header, namespace, and whitespace cleanup 2021-08-15 23:36:29 -04:00
Kevin Matz db6a22f4a5 static uuid as fromatted char array 2021-08-15 21:51:24 -04:00
Kevin Matz 0391910de5 protocol statics as char array 2021-08-15 21:47:27 -04:00
Kevin Matz 3e0cfe4868 make DMP generic enough for all vectors 2021-08-15 21:43:36 -04:00
Kevin Matz 53b6345038 only create SET data in SET vector'd DMP 2021-08-15 13:18:35 -04:00
Kevin Matz 53406333e3 template header/data reading in PDU 2021-08-15 13:16:59 -04:00
Kevin Matz 83e7a8d711 avoid potential cast to null 2021-08-15 12:11:50 -04:00
Kevin Matz 95853bd25b remove api bloat 2021-08-15 12:10:18 -04:00
Kevin Matz e6507c738d rename RLP vector registration function 2021-08-15 11:22:01 -04:00
Kevin Matz c033be5b6e PDU::Block will only ever contain valid PDU 2021-08-15 11:14:09 -04:00
Kevin Matz 52d57c282c add block diagram to readme 2021-08-15 10:39:52 -04:00
Kevin Matz 518f9826de cmake cleanups 2021-08-14 20:40:53 -04:00
Kevin Matz 7981acc57b check validity of PDU 2021-08-14 10:43:51 -04:00
Kevin Matz 41d7553e1e read PDU blocks through iStream virtual 2021-08-14 09:28:28 -04:00
Kevin Matz d883f061a4 uniform header/data names 2021-08-14 09:26:42 -04:00
Kevin Matz 13cba79652 transport EPI owns the root block 2021-08-13 17:19:16 -04:00
Kevin Matz 0c96f9a69c llrp rdm into rdm responder 2021-08-13 16:07:40 -04:00
Kevin Matz 587f5a8d33 use RDM::Message for VECTOR_RDM_CMD_RDM_DATA 2021-08-13 12:22:29 -04:00
Kevin Matz fa13e03ffa LLRP wire format 2021-08-13 12:03:11 -04:00
Kevin Matz 2c304859b9 validate flags on stream input 2021-08-13 12:02:06 -04:00
Kevin Matz 13651c7cf2 header and namespace cleanup 2021-08-13 10:09:56 -04:00
Kevin Matz edd3056341 RDMnet PDUs do not use standard ACN PDU flags. 2021-08-13 10:09:14 -04:00
Kevin Matz e54bd78929 in UUID namespace, define for length of a UUID 2021-08-13 10:05:32 -04:00
Kevin Matz 2ebc026af7 assignable ID numbers at construction 2021-08-13 10:04:30 -04:00
Kevin Matz 16217879c1 whitespace cleanup 2021-08-13 09:58:20 -04:00
Kevin Matz ee6bed286e initialize at construction 2021-08-13 09:57:53 -04:00
Kevin Matz 966e3f3909 enforce limit to manufacturer ID value 2021-08-12 15:42:28 -04:00
Kevin Matz 0f00adbc55 define component with CID 2021-08-12 15:12:34 -04:00
Kevin Matz ea9b7f909d PDU for rdmnet protocols 2021-08-12 13:46:25 -04:00
Kevin Matz 9605766ddb namespace and class outline 2021-08-12 13:13:00 -04:00
Kevin Matz f93091a1ba update readme 2021-08-12 11:09:20 -04:00
Kevin Matz fc26e5e833 variable name to match standard 2021-08-12 00:44:41 -04:00
Kevin Matz 33476e0ceb status collection PIDs 2021-08-12 00:31:00 -04:00
Kevin Matz a18501f607 Appendix B and C defines 2021-08-11 14:36:53 -04:00
Kevin Matz 0124123a71 psudo discovery from Appendix E 2021-08-11 14:36:41 -04:00
Kevin Matz 57c3247eaf individual queues for status types 2021-08-11 10:29:41 -04:00
Kevin Matz ec80a809db smart pointers for messages 2021-08-11 10:28:03 -04:00
Kevin Matz 32c5a37f57 comms status PID 2021-08-11 09:42:55 -04:00
Kevin Matz e273f2ffa6 remove unnecessary template parameter declarations 2021-08-11 09:42:43 -04:00
Kevin Matz 6684a90b4e discovery PIDs 2021-08-11 01:26:51 -04:00
Kevin Matz ef0f38f8ad don't respond to broadcast SET PIDs 2021-08-11 00:04:08 -04:00
Kevin Matz 75b4b13997 reset device PID 2021-08-11 00:02:41 -04:00
Kevin Matz de435d5021 remove stray overrides 2021-08-10 16:08:26 -04:00
Kevin Matz 141786ad67 nak and length check brevity 2021-08-10 16:08:09 -04:00
Kevin Matz 8e7a96ac82 responder counts failed message construction 2021-08-10 15:35:27 -04:00
Kevin Matz 3a86b10a8b sensors handle their own responses 2021-08-10 14:50:53 -04:00
Kevin Matz 2bb4460444 message IO is the responsibility of the message 2021-08-10 13:33:24 -04:00
Kevin Matz 5bc4c58b0e controller does not subclass responder 2021-08-10 13:02:58 -04:00
Kevin Matz 9127ad45b9 UID comparison operator 2021-08-10 01:24:52 -04:00
Kevin Matz 41eba231a5 check that message to send is valid 2021-08-10 00:57:09 -04:00
Kevin Matz 1bd99631a9 sensor PIDs 2021-08-10 00:56:43 -04:00
Kevin Matz 0202dc9422 dmx related PIDs 2021-08-09 17:50:36 -04:00
Kevin Matz 086cd0520c vtable completeness 2021-08-09 15:39:35 -04:00
Kevin Matz af47a4cfb7 action-able basic PIDs 2021-08-09 15:04:49 -04:00
Kevin Matz e4fb0736fc DMX user helper classes 2021-08-09 15:03:09 -04:00
Kevin Matz d449d8efe5 let cmake handle versioning 2021-08-09 15:01:41 -04:00
Kevin Matz 1d996f46a4 more rdm support 2021-08-08 17:25:41 -04:00
Kevin Matz 970617b9cd Appendix A: Defined Parameters (Normative) 2021-08-07 16:27:09 -04:00
Kevin Matz cd2f5cae7f namespace cleanup 2021-08-07 13:49:05 -04:00
Kevin Matz d78a5db381 namespace cleanup 2021-08-07 13:48:34 -04:00
Kevin Matz 47a5adcc8a DMX protocol defines in own header 2021-08-07 13:48:00 -04:00
Kevin Matz c8041ca857 Appendix A: Defined Parameters 2021-08-07 13:36:09 -04:00
Kevin Matz ac8fe68141 Appendix A: Defined Parameters (Normative) 2021-08-07 13:10:51 -04:00
Kevin Matz cfd0aa647d protocol specific timeout periods 2021-08-06 12:36:31 -04:00
Kevin Matz 7ad1962d3b support universe syncronization 2021-08-06 12:36:04 -04:00
Kevin Matz d7eb23a120 #pragma once header guard 2021-08-06 12:15:02 -04:00
Kevin Matz 23a4647342 refactor to unmask virtual functions 2021-08-06 12:14:29 -04:00
Kevin Matz eaf49608e5 remove platfrom driver from standards namespace 2021-08-06 12:11:44 -04:00
Kevin Matz c6a969bd9a add new classes to listing 2021-08-02 19:21:36 -04:00
Kevin Matz af8dcc92aa can create Timestamp UUIDs 2021-08-02 16:29:18 -04:00
Kevin Matz c01cd08b86 Clang -Wall -Wextra cleanups 2021-08-02 10:10:55 -04:00
Kevin Matz 4ce0bf73ea rx merging on sACN universe priority 2021-08-02 10:09:14 -04:00
Kevin Matz f9c01e5ad3 configurable data view modes 2021-07-31 14:06:58 -04:00
Kevin Matz 31108b7499 DMX::Universe calculates it's rx rate 2021-07-31 10:33:26 -04:00
Kevin Matz dd1f4ff4ff set the source before setting data so that source is available to data change callbacks. 2021-07-31 10:14:26 -04:00
Kevin Matz 92b211056e join multicast group on all interfaces 2021-07-30 14:40:03 -04:00
Kevin Matz b2facb639a display values in monospace font 2021-07-30 14:33:51 -04:00
Kevin Matz 4ce12e250d initial universe data table model 2021-07-30 13:57:27 -04:00
Kevin Matz 96fa743548 set FCTN name instead of UACN 2021-07-30 09:31:16 -04:00
Kevin Matz 82f5a61cb2 assign initial UACN throughout the stack 2021-07-30 09:26:21 -04:00
Kevin Matz 6c20317de3 documentation and namespace cleanup 2021-07-30 09:11:32 -04:00
Kevin Matz 51de1b2164 remove hard coded int 2021-07-30 08:37:39 -04:00
Kevin Matz 50023e765b rename low-level stream object base class 2021-07-30 08:37:16 -04:00
Kevin Matz c67472a8be io stream ops for preamble 2021-07-30 08:21:56 -04:00
Kevin Matz 79502361fd mark overrides for template struct members 2021-07-30 08:21:38 -04:00
Kevin Matz 00d769c22e improving PDU write support 2021-07-29 23:40:46 -04:00
Kevin Matz aaca7ea461 stream IO refactoring 2021-07-29 19:27:13 -04:00
Kevin Matz d69566bbc9 include additional debug info during debug builds 2021-07-29 17:36:06 -04:00
Kevin Matz 7351e3c0df uniform PDU::Stream io functions 2021-07-29 11:20:03 -04:00
Kevin Matz 1d4d99c9c2 output properties 2021-07-29 10:53:12 -04:00
Kevin Matz 833b33b751 better use of nullptr 2021-07-28 10:44:11 -04:00
Kevin Matz 51fb16455d refactor method names + doc updates 2021-07-28 10:43:43 -04:00
Kevin Matz 4702ac333d make it explicit when RTTI is beneficial 2021-07-28 10:37:33 -04:00
Kevin Matz 5851e1d55e frame_header write operator 2021-07-26 22:48:48 -04:00
Kevin Matz 8512da0b32 move constructor out of header 2021-07-26 22:48:01 -04:00
Kevin Matz 42022baecf documenaton churn 2021-07-26 22:47:25 -04:00
Kevin Matz 0cb3c72226 a few more doxygen blocks 2021-07-26 21:15:48 -04:00
Kevin Matz c6aef08e55 move pdu_stream class to it's own .h/.cpp files 2021-07-26 21:15:20 -04:00
Kevin Matz 7e58ce5562 pdustream output operators 2021-07-26 17:22:24 -04:00
Kevin Matz 555122bd3b refactor to include NCS variant support 2021-07-26 15:41:02 -04:00
Kevin Matz 44f5992340 input stream overload operators 2021-07-26 13:34:11 -04:00
Kevin Matz 237943489f stream read operators for integer data types 2021-07-26 13:33:14 -04:00
Kevin Matz d1ee4e1dae reorder alphabetically 2021-07-26 13:31:47 -04:00
Kevin Matz 86576ad4e3 don't create a universe for sACN discovery 2021-06-30 13:04:17 -04:00
Kevin Matz e49ba6c53f wrap the subscribed universes with a class that has convenient signals and slots. 2021-06-23 11:10:12 -04:00
Kevin Matz 0c6eb37e38 rename set methods to differentiate what gets callbacks 2021-06-23 10:21:11 -04:00
Kevin Matz c3c0cc884a doc updates 2021-06-22 21:26:03 -04:00
Kevin Matz 32ef49554b delete the universe before removing the number from the map 2021-06-22 21:25:40 -04:00
Kevin Matz 84476d33c7 prepare pdu_stream class for output buffering too 2021-06-21 10:26:46 -04:00
Kevin Matz ce28e5c645 implicit public base constructors 2021-06-21 10:25:09 -04:00
Kevin Matz e7ec979b9b correctly set QObject parent 2021-06-21 10:18:36 -04:00
Kevin Matz 9f407da475 seperate termination from ending semantics 2021-06-20 09:35:56 -04:00
Kevin Matz 619430fe50 allow source to be directly modified 2021-06-20 09:35:03 -04:00
Kevin Matz b2316fe388 source can create and terminate universes 2021-06-20 09:12:14 -04:00
Kevin Matz f223d5fea4 property setters 2021-06-20 09:10:32 -04:00
Kevin Matz 21e61c7ac4 explicit default constructor 2021-06-20 09:10:21 -04:00
Kevin Matz c58163fb8d universe source can be set without setting data 2021-06-20 09:09:03 -04:00
Kevin Matz 2858d64218 more ways to set universe data 2021-06-19 22:37:34 -04:00
Kevin Matz 88c7787f41 add bounds check to slot address 2021-06-19 21:12:40 -04:00
Kevin Matz b5e4c05922 support for component names 2021-06-19 20:27:11 -04:00
Kevin Matz a761675e89 initialize ACN appliance with persistent CID 2021-06-19 15:31:22 -04:00
Kevin Matz 941f2c74dd formatting cleanup 2021-06-19 15:30:27 -04:00
Kevin Matz 41183883f6 include curly braces on string input-output 2021-06-19 15:30:07 -04:00
Kevin Matz cee9ce5ed0 constructor becomes interface between QUuid and UUID types 2021-06-19 10:47:37 -04:00
Kevin Matz 474cc28d0e remove redundant calls to rdstate() 2021-06-19 10:18:09 -04:00
Kevin Matz 65df669754 read from stream by data type, not length 2021-06-18 08:46:30 -04:00
Kevin Matz 2c74470b16 initial import of sACN Node platform driver for Qt 2021-06-02 09:32:04 -04:00
Kevin Matz 603080b9f3 build as a static library 2021-06-02 09:30:22 -04:00
Kevin Matz 50f29808ef support discovering available sACN universes 2021-06-02 09:29:27 -04:00
Kevin Matz 15b611983a class structure for sACN transmitter and tranciever 2021-06-02 09:21:24 -04:00
Kevin Matz 418553fd3c cmake notes 2021-05-28 14:27:32 -04:00
Kevin Matz 76eca2aebf return Arduino platform ESP arch support to tree 2021-05-28 14:04:55 -04:00
302 changed files with 38580 additions and 2744 deletions

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "docs/doxygen_theme_flat_design"]
path = docs/doxygen_theme_flat_design
url = https://github.com/kcwongjoe/doxygen_theme_flat_design.git

View File

@ -1,47 +1,41 @@
cmake_minimum_required(VERSION 3.14)
project(ESTA VERSION 1.0.0 LANGUAGES CXX)
cmake_minimum_required(VERSION 3.20)
set(DEFAULT_BUILD_TYPE "Release")
project(lcp VERSION 0.3.1)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
if (CMAKE_BUILD_TYPE STREQUAL "Debug")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -D_GLIBCXX_DEBUG")
endif()
set(SOURCE_FILES
acn/acn.h
acn/appliance.cpp
acn/appliance.h
acn/component.h
acn/dmp.cpp
acn/dmp.h
acn/pdu.cpp
acn/pdu.h
acn/rlp-tcp.cpp
acn/rlp-tcp.h
acn/rlp-udp.cpp
acn/rlp-udp.h
acn/rlp.cpp
acn/rlp.h
acn/sdt-udp.cpp
acn/sdt-udp.h
acn/sdt.cpp
acn/sdt.h
dmx/universe.cpp
dmx/universe.h
sacn/data.cpp
sacn/data.h
sacn/extended.h
sacn/receiver.cpp
sacn/receiver.h
sacn/sacn.h
sacn/universe.cpp
sacn/universe.h
uuid/uuid.cpp
uuid/uuid.h
)
add_subdirectory(example)
add_subdirectory(lib)
add_subdirectory(platform)
add_subdirectory(protocol)
add_subdirectory(test)
add_library(${PROJECT_NAME} SHARED ${SOURCE_FILES})
#if (CMAKE_BUILD_TYPE MATCHES "^[Rr]elease")
option(BUILD_DOC "Build documentation" ON)
find_package(Doxygen)
if (DOXYGEN_FOUND)
# set input and output files
set(DOXYGEN_IN ${CMAKE_CURRENT_SOURCE_DIR}/docs/Doxyfile.in)
set(DOXYGEN_OUT ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile)
# request to configure the file
configure_file(${DOXYGEN_IN} ${DOXYGEN_OUT} @ONLY)
message("Doxygen build started")
# note the option ALL which allows to build the docs together with the application
add_custom_target(docs ALL
COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYGEN_OUT}
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
BYPRODUCTS ${CMAKE_CURRENT_BINARY_DIR}/docs
COMMENT "Generating API documentation with Doxygen"
VERBATIM )
else (DOXYGEN_FOUND)
message("Doxygen need to be installed to generate the doxygen documentation")
endif (DOXYGEN_FOUND)
#endif()
set_target_properties(${PROJECT_NAME} PROPERTIES VERSION ${PROJECT_VERSION})
set_target_properties(${PROJECT_NAME} PROPERTIES SOVERSION 1)
target_compile_definitions(${PROJECT_NAME} PRIVATE ESTA_LIBRARY)

View File

@ -1,4 +1,4 @@
Copyright (c) 2020 Kevin Matz
Copyright (c) 2020-2022 Kevin Matz
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

110
README.md
View File

@ -1,15 +1,50 @@
# LibESTA
# OpenLCP
A collection of C++ libraries for ESTA sponsored ANSI standards.
A collection of C++ libraries for Lighting Control Protocols, focusing on ESTA
sponsored ANSI standards.
<img alt="ESTA Protocols Block Diagram"
src="https://git.company235.com/official/OpenLCP/raw/branch/master/docs/establockdiagram.png"
width="80%">
## Object Architecture
The library encompases both the structured data being communicated between
controllable components, and the control objects that expose the data in
meaningful ways, without needing to know or understand the underlying protocols.
### Objects
Control objects are structured thusly;
<img alt="Architecture Flowchart"
src="https://git.company235.com/official/OpenLCP/raw/branch/master/docs/classdiagram.png"
width="100%">
### Data
Serveral of the ESTA protocols use the E1.17 Protocol Data Unit to transfer
data. The `PDU` are a series of nested hierarchical data structures. `PDU` may
encapsulate one or more `PDU` of an embedded protocol, or be a data endpoint
for that protocol. The following slide describes the relational hierocracy of
the `PDU` within `ACN`, `sACN`, `RDMnet`, and `OTP`. While similiar in
structure and concept, the `PDU` in `RDMnet` do not inherit directly from the
`PDU` in `ACN`, and the `PDU` in `OTP` are notably simpler, as data segments
may not be inherited from sibling `PDU` in the data.
<img alt="PDU Hierarch Flowchart"
src="https://git.company235.com/official/OpenLCP/raw/branch/master/docs/pduflowchart.png"
width="90%">
## Supported Standards
| Protocol Name | Standard | Implementation Status |
| :- | :-: | :-: |
| 0 to 10 V Analog Control | E1.3 | |
| 0 to 10 V Analog Control | E1.3 | |
| USITT DMX512-A | E1.11 | Data Abstraction |
| ACN Root Layer Protocol (RLP) | E1.17 | Rx |
| ACN Session Data Transport Protocol (SDT) | E1.17 | |
| ACN Root Layer Protocol (RLP) | E1.17 | |
| ACN Session Data Transport Protocol (SDT) | E1.17 | i/o |
| ACN Device Management Protocol (DMP) | E1.17 | Limited |
| ACN Device Description Language (DDL) | E1.17 | |
| EPI 10 - Autogeneration of Multicast Address on IPv4 Networks | E1.17 | |
@ -18,27 +53,68 @@ A collection of C++ libraries for ESTA sponsored ANSI standards.
| EPI 13 - Allocation of Internet Protocol Version 4 Addresses to ACN Hosts | E1.17 | |
| EPI 15 - ACN Allocation of Multicast Addresses on IPv4 Networks | E1.17 | |
| EPI 16 - ESTA Registered Names and Identifiers | E1.17 | |
| EPI 17 - ACN Root Layer Protocol Operation on UDP | E1.17 | Rx |
| EPI 17 - ACN Root Layer Protocol Operation on UDP | E1.17 | |
| EPI 18 - Operation of SDT on UDP Networks | E1.17 | |
| EPI 19 - ACN Discovery on IP Networks | E1.17 | |
| EPI 19 - ACN Discovery on IP Networks | E1.17 | Names |
| EPI 20 - MTU Size for ACN on IPv4 Networks | E1.17 | |
| EPI 22 - DDL Core Modules for ACN Devices | E1.17 | |
| Remote Device Management (RDM) | E1.20 | |
| Remote Device Management (RDM) | E1.20 | Responder |
| RDM Subdevice | E1.20 | ✓ |
| RDM Sensors | E1.20 | ✓ |
| RDM Discovery | E1.20 | ✓ |
| RDM Required PIDs | E1.20 | ✓ |
| RDM Status collection PIDs | E1.20 | ✓ |
| EPI 23 - Device Identification Subdevice | E1.30-1 | |
| EPI 25 - Time Reference in ACN Systems Using SNTP and NTP | E1.30-3 | |
| EPI 26 - DDL Extensions for DMX and RDM Devices | E1.30-4 | |
| EPI 32 - Identification of Draft DDL Modules | E1.30-10 | |
| EPI 33 - ACN RLP Operation on TCP | E1.30-11 | |
| Streaming ACN (sACN) | E1.31 | Data |
| RDMNet | E1.33 | |
| RDM Dimmer Message Sets | E1.37-1 | |
| RDM IPv4 & DNS Configuration Messages | E1.37-2 | |
| RDM Gateway & Splitter Configuration Messages | E1.37-7 | |
| EPI 33 - ACN RLP Operation on TCP | E1.30-11 | ✓ |
| Streaming ACN (sACN) | E1.31 | Data/Discovery |
| sACN Receiver | E1.31 | ✓ |
| sACN Source | E1.31 | ✓ |
| sACN Data | E1.31 | ✓ |
| sACN Sync | E1.31 | Rx |
| sACN Discovery | E1.31 | ✓ |
| sACN Preview | E1.31 | - |
| RDMNet | E1.33 | i/o |
| RDM Dimmer Message Sets | E1.37-1 | Normative |
| RDM IPv4 & DNS Configuration Messages | E1.37-2 | Normative |
| RDM Gateway & Splitter Configuration Messages | E1.37-7 | Normative |
### Non-Standard Protocols
| Protocol | Designer | Version | Implementation Status |
| :- | :- | :-: | :-: |
| Art-Net | Artistic Licence Holdings Ltd. | Protocol 4 v1.4 | |
| OSC | Open Sound Control | Spec 1.1 | ✓ |
### Dependent Protocols
| Protocol Name | Standard | Referenced By | Implementation Status |
| :- | :-: |:-: | :-: |
|UUID Universally Unique Identifier | RFC 4122 | RLP, DDL | Partial |
|Service Location Protocol (SLP) | RFC 2609 | EPI 19 | |
|Trivial File Transfer Protocol (TFTP) | RFC 1350 | EPI 25 | |
| Universally Unique Identifier (UUID) | RFC 4122 | RLP, DDL | Partial |
| Service Location Protocol (SLP) | RFC 2609 | EPI 19 | |
| Trivial File Transfer Protocol (TFTP) | RFC 1350 | EPI 25 | |
| Network Time Protocol (NTP) | RFC 5909 | OSC | |
| Serial Line Internet Protocol (SLIP) | RFC 1055 | OSC | |
## License
Copyright &copy; 2020-2023 Kevin Matz
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,35 +0,0 @@
# Library for E1.17 ACN
## `Architecture for Control Networks`
## Top level include
Include `acn.h` for full protocol support.
> ```#include "acn.h"```
## Logical components
### Protocol Handling
* ACN Architecture
* `pdu.h`: [`ACN::PDU`]
* `rlp.h`: [`ACN::RLP`]
* Device Management Protocol
* `dmp.h`: [`ACN::DMP`]
* Session Data Transport
* `sdt.h`: [`ACN::SDT`]
### EPI Support
* EPI 17. ACN Root Layer Protocol
Operation on UDP
* `rlp-udp.h`: [`ACN::RLP::UDP`]
* EPI 18. Operation of SDT on UDP Networks
* `sdt-udp.h`: [`ACN::SDT::UDP`]
* EPI 33. ACN Root Layer Protocol Operation on TCP
* `rlp-tcp.h`: [`ACN::RLP::TCP`]

View File

@ -1,60 +0,0 @@
/*
acn.h
Copyright (c) 2020 Kevin Matz (kevin.matz@gmail.com)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#pragma once
#include "appliance.h"
#include "component.h"
#include "dmp.h"
#include "pdu.h"
#include "rlp.h"
#include "rlp-tcp.h"
#include "rlp-udp.h"
#include "sdt.h"
#include "sdt-udp.h"
// ANSI E1.17- 2015, Architecture for Control Networks
namespace ACN {
// EPI 16 Protocol Identifier Database
// from https://tsp.esta.org/tsp/working_groups/CP/epi16ids.php
// as of 1/14/21
// Session Data Transport Protocol
static const uint32_t PLASA_SDT = 0x00000001;
// Device Management Protocol
static const uint32_t PLASA_DMP = 0x00000002;
// Lightweight streaming protocol for transport of DMX512
static const uint32_t PLASA_E1_31 = 0x00000004;
// RDMnet
static const uint32_t PLASA_E1_33 = 0x00000005;
// Empty data used for health checking connections in E1.33
static const uint32_t PLASA_NULL = 0x00000006;
// Open Lighting Architecture
static const uint32_t OpenLightingProject_OLA = 0x00000007;
// Extended functionality for sACN
static const uint32_t PLASA_E1_31_EXTENDED = 0x00000008;
// E1.59 Object Transform Protocol (OTP)
const static uint32_t ESTA_OTP = 0x00000009;
} // ACN

View File

@ -1,152 +0,0 @@
/*
appliance.h
Copyright (c) 2021 Kevin Matz (kevin.matz@gmail.com)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include "appliance.h"
#include "dmp.h"
#include "sdt.h"
#include "rlp.h"
#include "rlp-udp.h"
#include "rlp-tcp.h"
namespace ACN {
Appliance::Appliance(UUID::uuid cid)
: Component(cid)
{
registerRlpVectorHandler(SDT::SDT_PROTOCOL_ID,
std::bind(&Appliance::rootSdtHandler, this,
std::placeholders::_1));
registerRlpVectorHandler(DMP::DMP_PROTOCOL_ID,
std::bind(&Appliance::rootDmpHandler, this,
std::placeholders::_1));
};
/**
EPI 17
*/
void Appliance::UdpStreamHandler(PDU::Stream stream) {
// verify the UDP preamble
RLP::UDP::preamble_t preamble(stream);
// Implementations shall check the ACN Packet Identifier and preamble size.
if (!preamble)
stream->setstate(stream->rdstate() | std::ios_base::failbit);
// implementations shall compute the size and position of the PDU block from
// the preamble size and postamble size provided. ... ignoring any extra
// octets in the preamble or postamble.
for(int i = RLP::UDP::PREAMBLE_MINIMUM_SIZE; i < preamble.length; i++)
stream->read8();
if (!stream->good())
return;
auto block = PDU::readBlock<RLP::Pdu>(stream);
if (stream->fail())
return;
for(auto const &root : *block) {
RlpReceiver(root);
}
}
/**
EPI 33
*/
void Appliance::TcpStreamHandler(PDU::Stream stream) {
// verify the TCP preamble
RLP::TCP::preamble_t preamble(stream);
// implementations shall check the ACN Packet Identifier. If the ACN Packet
// Identifier is not correct the receiver shall close the connection.
if (!preamble)
stream->setstate(stream->rdstate() | std::ios_base::failbit);
if (!stream->good())
return;
auto block = PDU::readBlock<RLP::Pdu>(stream);
if (stream->fail())
return;
for(auto const &root : *block) {
RlpReceiver(root);
}
}
/**
Dispatch a recieved RLP PDU to the appropriate vector handlers.
*/
void Appliance::RlpReceiver(std::shared_ptr<RLP::Pdu> root) {
if (!rlp_vectors_.count(root->vector()))
return;
for(auto const &handler : rlp_vectors_[root->vector()]) {
handler(root);
}
}
/**
Add callback handler for a given RLP vector.
*/
void Appliance::registerRlpVectorHandler(uint32_t vect,
PDU::Handler<RLP::Pdu> handle) {
rlp_vectors_[vect].push_back(handle);
}
/**
Deregister RLP protocol handlers for the given vector.
*/
void Appliance::deregisterRlpVector(uint32_t vect) {
rlp_vectors_.erase(vect);
}
/**
Deregister _ALL_ RLP protocol handlers.
*/
void Appliance::deregisterRlpVector() {
rlp_vectors_.clear();
}
/**
*/
void Appliance::rootSdtHandler(std::shared_ptr<RLP::Pdu> rlp) {
}
/**
*/
void Appliance::rootDmpHandler(std::shared_ptr<RLP::Pdu> rlp) {
}
}; // ACN

View File

@ -1,79 +0,0 @@
/*
appliance.h
Copyright (c) 2021 Kevin Matz (kevin.matz@gmail.com)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#pragma once
#include "component.h"
#include "rlp.h"
#include "sdt.h"
#include "pdu.h"
#include "../uuid/uuid.h"
#include <map>
#include <memory>
#include <vector>
// appliance: In DDL an appliance is a piece of equipment described by a root device and all its children and descendents. In DMP systems an appliance corresponds to a component that exposes one or more devices (since the rules require that all devices are descendants of a single root device). See also root device.
// component: The process, program or application corresponding to a single ACN endpoint. All messages in ACN are sent and received by a component. See [Arch] for a more complete definition.
// root device: An instance of a device described in DDL that has no parent device. See also appliance.
namespace ACN {
class Appliance
: public Component
{
public:
Appliance(UUID::uuid = UUID::uuid());
protected:
// EPI 17
virtual void UdpStreamHandler(PDU::Stream);
// EPI 33
virtual void TcpStreamHandler(PDU::Stream);
void RlpReceiver(std::shared_ptr<RLP::Pdu>);
void registerRlpVectorHandler(uint32_t, PDU::Handler<RLP::Pdu>);
void deregisterRlpVector(uint32_t);
void deregisterRlpVector();
// process SDT frames
virtual void rootSdtHandler(std::shared_ptr<RLP::Pdu>);
// SDT 4.4 SDT Base Layer Messages
// Join, Get Sessions, and Sessions are ad-hoc messages, that is, they are
// not sent within channels. These messages identify their intended recipient
// by CID.
virtual void JoinSession() {};
virtual void GetSessions() {};
virtual void Sessions() {};
// process DMP frames
virtual void rootDmpHandler(std::shared_ptr<RLP::Pdu>);
private:
std::map<uint32_t, std::vector<PDU::Handler<RLP::Pdu>>> rlp_vectors_;
std::vector<std::shared_ptr<SDT::Session>> sessions_;
};
}; // ACN

View File

@ -1,119 +0,0 @@
/*
dmp.cpp
Copyright (c) 2020 Kevin Matz (kevin.matz@gmail.com)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include "dmp.h"
namespace ACN {
namespace DMP {
address_type::address_type(uint8_t val) {
z_reserved = (val >> 7) & 0b1;
relative = (val >> 6) & 0b1;
type = (data_type)((val >> 4) & 0b11);
x_reserved = (val >> 2) & 0b11;
width = (address_length)(val & 0b11);
}
range::range(PDU::Stream stream, data_type t, address_length l) :
incriment(0),
count(0)
{
address = read(stream, l);
if (t == SINGLE) return;
incriment = read(stream, l);
count = read(stream, l);
}
uint32_t range::read(PDU::Stream stream, address_length length) {
switch (length) {
case ONE:
return stream->read8();
case TWO:
return stream->read16();
case FOUR:
return stream->read32();
default:
return 0;
}
}
Pdu::Pdu(PDU::Stream stream)
: PDU::Pdu(stream, 1) // vectors are 1 octet
{
if (stream->fail()) return;
if (!buffer_->good()) return;
if (flags_.hasHeader)
setHeader(new address_type(buffer_->read8()));
if (flags_.hasData) {
switch(vector()) {
case SET_PROPERTY:
readSetData();
break;
default:
break;
}
}
}
void Pdu::readSetData() {
// header may be inherited. check that one exists
if (!header()) {
buffer_->setstate(buffer_->rdstate() | std::ios_base::failbit);
return;
}
auto header = (address_type*)this->header();
auto data = new dmp_set_data();
while(buffer_->good()) {
// Property Address
range pr(buffer_, header->type, header->width);
if (!buffer_->good() || pr.count * pr.incriment > buffer_->available()) {
buffer_->setstate(buffer_->rdstate() | std::ios_base::failbit);
break;
}
// Property Data
std::vector<uint8_t> pd;
pd.resize(pr.address, 0);
for (uint32_t i = 0; i < pr.count * pr.incriment; i ++)
pd.push_back(buffer_->read8());
// Property Fields
set_property fields(pr, pd);
data->properties.push_back(fields);
//set EOF if buffer insufficient for another property range
if (buffer_->available() < header->width * (header->type == SINGLE? 1 : 3))
buffer_->setstate(buffer_->rdstate() | std::ios_base::eofbit);
}
setData(data);
}
} // DMP
} // ACN

121
acn/dmp.h
View File

@ -1,121 +0,0 @@
/*
dmp.h
Copyright (c) 2020 Kevin Matz (kevin.matz@gmail.com)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#pragma once
#include <cstdint>
#include <memory>
#include <vector>
#include "pdu.h"
// Architecture for Control Networks Device Management Protocol
namespace ACN {
namespace DMP {
using std::uint8_t;
using std::uint32_t;
using std::vector;
using std::pair;
using PDU::Block;
// 5.1.4 Address and Data Types
enum data_type {
SINGLE = 0b00, // 0
RANGE = 0b01, // 1
ARRAY = 0b10, // 2
SERIES = 0b11 // 3
};
enum address_length {
ONE = 0b00, // 0
TWO = 0b01, // 1
FOUR = 0b10, // 2
ZERO = 0b11 // 3 (reserved)
};
struct address_type : PDU::pdu_header {
bool z_reserved : 1; // Z
bool relative : 1; // R
data_type type : 2; // D1, D0
uint8_t x_reserved : 2; // X1, X0
address_length width : 2; // A1, A0
address_type(uint8_t);
};
// 5.1.5
struct range {
uint32_t address;
uint32_t incriment;
uint32_t count;
range() {};
range(PDU::Stream, data_type, address_length);
private:
uint32_t read(PDU::Stream, address_length);
};
typedef pair<range, vector<uint8_t>> set_property;
struct dmp_set_data : PDU::pdu_data {
vector<set_property> properties;
};
// 7 Response Messages
enum failure_reason {
NONSPECIFIC = 1, // Non-specific or non-DMP reason.
NOT_PROPERTY = 2, // The address does not correspond to a property.
WRITE_ONLY = 3, // The propertys value may not be read.
NOT_WRITABLE = 4, // The propertys value may not be written.
DATA_ERROR = 5, // The data does not correspond to the property.
SUBSCIRPTION_NOT_SUPPORTED = 10, // Subscriptions on the specified property are not supported by the device.
NO_SUBSCRIPTIONS_SUPPORTED = 11, // Subscriptions not supported on any property.
INSUFFICIENT_RESOURCES = 12, // The component cannot support more subscriptions due to resource limitations
UNAVAILABLE = 13 // The propertys value is not available due to restrictions imposed by device specific functionality (e.g., access permission mechanisms).
};
// 13.1 Protocol Codes
static const uint32_t DMP_PROTOCOL_ID = 2; // PDU protocol value
// 13.2 Message Codes
static const uint8_t GET_PROPERTY = 1;
static const uint8_t SET_PROPERTY = 2;
static const uint8_t GET_PROPERTY_REPLY = 3;
static const uint8_t EVENT = 4;
static const uint8_t SUBSCRIBE = 7;
static const uint8_t UNSUBSCRIBE = 8;
static const uint8_t GET_PROPERTY_FAIL = 9;
static const uint8_t SET_PROPERTY_FAIL = 10;
static const uint8_t SUBSCRIBE_ACCEPT = 12;
static const uint8_t SUBSCRIBE_REJECT = 13;
static const uint8_t SYNC_EVENT = 17;
class Pdu
: public PDU::Pdu
{
public:
Pdu(PDU::Stream);
private:
void readSetData();
};
} // DMP
} // ACN

View File

@ -1,177 +0,0 @@
/*
pdu.cpp
Copyright (c) 2020 Kevin Matz (kevin.matz@gmail.com)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include "pdu.h"
#include <cmath>
namespace ACN {
namespace PDU {
Pdu::Pdu(Stream stream, size_t vector_size)
: flags_(stream->peek())
{
header_ = 0;
data_ = 0;
parent_ = 0;
inherit_ = 0; // pointer to previous PDU in block
// read length and vector off of the stream
// abort if the remaining PDU length isn't available
if (stream->available() < (flags_.hasLength ? 3 : 2)) {
stream->setstate(stream->rdstate() | std::ios_base::failbit);
return;
}
readLength(stream);
if (!length_ || length_ > std::pow(2, (8 * (flags_.hasLength ? 3 : 2)) - 4)) {
stream->setstate(stream->rdstate() | std::ios_base::failbit);
return;
}
// length includes the flags, length, and vector
// calculate the remaining length of the PDU
int len = length_ - (flags_.hasLength ? 3 : 2);
// abort if the remaining PDU length isn't available
if (!stream->good() || stream->available() < len) {
stream->setstate(stream->rdstate() | std::ios_base::failbit);
return;
}
// create a stream buffer for the header and data
buffer_ = Stream(new pdu_stream(stream->data(), len));
if (buffer_->available() != len) {
stream->setstate(stream->rdstate() | std::ios_base::failbit);
return;
}
// fast-forward the input stream
for (int i = 0; i < len; i++)
stream->get();
if (!stream->available())
stream->setstate(stream->rdstate() | std::ios_base::eofbit);
if (flags_.hasVector)
readVector(vector_size);
}
Pdu::~Pdu() {
if (header_) delete header_;
if (data_) delete data_;
}
const uint32_t Pdu::vector() {
if (flags_.hasVector)
return vector_;
if (inherit_ != nullptr)
return inherit_->vector();
return 0;
}
pdu_header * Pdu::header() {
if (flags_.hasHeader)
return header_;
if (inherit_ != nullptr)
return inherit_->header();
return 0;
}
pdu_data * Pdu::data() {
if (flags_.hasData)
return data_;
if (inherit_ != nullptr)
return inherit_->data();
return 0;
}
void Pdu::readLength(Stream stream) {
length_ = stream->read16() & 0x0fff; // high 4 bytes are flags
if (flags_.hasLength)
length_ = (length_ << 8 ) | stream->read8();
}
void Pdu::readVector(uint8_t vector_size) {
vector_ = 0;
for (int o = vector_size - 1; o >= 0; o--)
vector_ |= (buffer_->read8() << (8 * o));
}
pdu_flags::pdu_flags(uint8_t val) {
hasLength = (val >> 7) & 0b1;
hasVector = (val >> 6) & 0b1;
hasHeader = (val >> 5) & 0b1;
hasData = (val >> 4) & 0b1;
};
uint8_t pdu_stream::read8() {
uint8_t ret = 0;
if (available() < (int)sizeof(ret)) {
setstate(rdstate() | std::ios_base::failbit);
return 0;
}
ret = get();
if (!available())
setstate(rdstate() | std::ios_base::eofbit);
return ret;
}
uint16_t pdu_stream::read16() {
uint16_t ret = 0;
if (available() < (int)sizeof(ret)) {
setstate(rdstate() | std::ios_base::failbit);
return 0;
}
ret |= get() << 8;
ret |= get();
if (!available())
setstate(rdstate() | std::ios_base::eofbit);
return ret;
}
uint32_t pdu_stream::read32() {
uint32_t ret = 0;
if (available() < (int)sizeof(ret)) {
setstate(rdstate() | std::ios_base::failbit);
return 0;
}
ret |= get() << 24;
ret |= get() << 16;
ret |= get() << 6;
ret |= get();
if (!available())
setstate(rdstate() | std::ios_base::eofbit);
return ret;
}
} // PDU
} // ACN

171
acn/pdu.h
View File

@ -1,171 +0,0 @@
/*
pdu.h
Copyright (c) 2020 Kevin Matz (kevin.matz@gmail.com)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#pragma once
#include <cstdint>
#include <functional>
#include <istream>
#include <memory>
#include <vector>
namespace ACN {
namespace PDU {
using std::uint8_t;
using std::uint16_t;
using std::uint32_t;
using std::vector;
using std::shared_ptr;
// 2.4.1. Flags
// Flags is a 4-bit field containing flags L, V, H and D which declare how the PDU is packed.
struct pdu_flags {
bool hasLength : 1;
bool hasVector : 1;
bool hasHeader : 1;
bool hasData : 1;
pdu_flags(uint8_t);
};
// MAYBE: remove virtuals?
// Arduino doen't enable RTTI for run-time polymorphism.
struct pdu_header { virtual ~pdu_header() {} };
struct pdu_data { virtual ~pdu_data() {} };
/**
Memory buffer of uint8_t data.
*/
class pdu_buffer
: public std::basic_streambuf<uint8_t>
{
public:
pdu_buffer(uint8_t * p, size_t l) { setg(p, p, p + l); };
uint8_t * cur_ptr() { return gptr(); };
};
/**
Input stream of nested PDU
*/
class pdu_stream
: public std::basic_istream<uint8_t>
{
public:
pdu_stream(uint8_t * p, size_t l)
: std::basic_istream<uint8_t>(&_buffer)
, _buffer(p, l) { rdbuf(&_buffer); }
uint32_t available() { return _buffer.in_avail(); }
uint8_t * data() { return _buffer.cur_ptr(); };
uint8_t read8 ();
uint16_t read16();
uint32_t read32();
private:
pdu_buffer _buffer;
};
using Stream = shared_ptr<pdu_stream>;
/**
Base class PDU
All PDU share common structure of:
flags, length, vector,
and protocol specific header/data.
Flag values indicate if lenght, vector, header or data
are present in the PDU, or if they should be inherited from the
preceding PDU.
*/
class Pdu {
public:
Pdu(Stream, size_t vector_size);
~Pdu();
// getters
const pdu_flags flags() {return flags_;}
const uint32_t length() {return length_;}
const uint32_t vector(); // may inherit
pdu_header * header(); // may inherit
pdu_data * data(); // may inherit
shared_ptr<Pdu> parent() {return parent_;}
Stream buffer() {return buffer_;}
// setters
void setParent (shared_ptr<Pdu> pdu) {parent_ = pdu;}
void setInherit(shared_ptr<Pdu> pdu) {inherit_ = pdu;}
protected:
pdu_flags flags_;
uint32_t length_;
uint32_t vector_;
shared_ptr<Pdu> parent_;
shared_ptr<Pdu> inherit_;
Stream buffer_;
// private setters
void setHeader (pdu_header * h) {header_ = h;}
void setData (pdu_data * d) {data_ = d;}
private:
pdu_header * header_;
pdu_data * data_;
void readLength(Stream);
void readVector(uint8_t);
};
template <typename T>
using Handler = std::function<void(std::shared_ptr<T>)>;
template <typename T>
using Block = std::shared_ptr<std::vector<std::shared_ptr<T>>>;
/**
Template creator of a PDU Block.
@param std::shared_ptr<PDU::pdu_stream> The stream to read from.
@return std::shared_ptr<std::vector<std::shared_ptr<T>>> A block of PDU
*/
template<typename T>
Block<T> readBlock(Stream buffer, shared_ptr<PDU::Pdu> parent = 0) {
auto block = Block<T>(new vector<shared_ptr<T>>);
while(buffer->good()) {
shared_ptr<T> pdu(new T(buffer));
if (buffer->fail()) // stream failed during pdu constructor
break;
if (pdu->buffer()->fail()) // pdu buffer errors
continue;
if (parent != 0) // set parent
pdu->setParent(parent);
if (!block->empty()) // set inheritee
pdu->setInherit(block->back());
block->push_back(pdu); // add to block
}
return block;
}
} // PDU
} // ACN

View File

@ -1,69 +0,0 @@
/*
sdt-udp.h
Copyright (c) 2020 Kevin Matz (kevin.matz@gmail.com)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#pragma once
#include <cstdint>
#include "pdu.h"
// ACN EPI 18 - Operation of SDT on UDP Networks
namespace ACN {
namespace SDT {
namespace UDP {
// Table 3. SDT symbolic parameters
static const float MAK_TIMEOUT_FACTOR = 0.1;
static const uint8_t MAK_MAX_RETRIES = 2; // (3 tries total)
static const uint8_t AD_HOC_TIMEOUT = 200; // ms
static const uint8_t AD_HOC_RETRIES = 2; // (3 tries total)
static const float RECIPROCAL_TIMEOUT_FACTOR = 0.2;
static const uint8_t MIN_EXPIRY_TIME = 2; // s
static const float NAK_TIMEOUT_FACTOR = 0.1;
static const uint8_t NAK_MAX_RETRIES = 2; // (3 tries total)
static const uint8_t NAK_HOLDOFF_INTERVAL = 2; // ms
static const uint8_t NAK_MAX_TIME = 10 * NAK_HOLDOFF_INTERVAL;
static const uint8_t NAK_BLANKTIME = 3 * NAK_HOLDOFF_INTERVAL;
static const uint16_t SDT_MULTICAST_PORT = 5568; // IANA registered “sdt”
// defined in SDT, but needed here without including that header
enum ip_addr_spec_t {
SDT_ADDR_NULL = 0, // Address is not present (0 octets).
SDT_ADDR_IPV4 = 1, // Address specified is in IP v4 format
SDT_ADDR_IPV6 = 2 // Address specified is in IP v6 format
};
// 3 Address Specification
struct address_t {
uint8_t type = SDT_ADDR_NULL;
uint16_t port = SDT_MULTICAST_PORT;
union {
uint8_t ipv4[4];
uint8_t ipv6[16] = {0};
};
address_t() {};
address_t(PDU::Stream);
};
} // UDP
} // SDT
} // ACN

View File

@ -1,169 +0,0 @@
/*
sdt.cpp
Copyright (c) 2021 Kevin Matz (kevin.matz@gmail.com)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include "sdt.h"
namespace ACN {
namespace SDT {
params_t::params_t(PDU::Stream stream) {
Expiry = stream->read8();
reserved = stream->peek() & 0xef;
NAK_Outbound = stream->read8() >> 7;
reserved = stream->read8() & 0xef;
NAKholdoff = stream->read16();
NAKmodulus = stream->read16();
NAKmaxwait = stream->read16();
}
join_data_t::join_data_t(PDU::Stream stream) {
mid = stream->read16();
number = stream->read16();
reciprocal = stream->read16();
sequence = stream->read32();
reliable = stream->read32();
destination = UDP::address_t(stream);
parameters = params_t(stream);
expiry = stream->read8();
}
join_accept_data_t::join_accept_data_t(PDU::Stream stream) {
uint8_t buf[16];
stream->read(buf, sizeof(buf));
leader = UUID::uuid(buf);
number = stream->read16();
mid = stream->read16();
reliable = stream->read32();
reciprocal = stream->read16();
}
join_refuse_data_t::join_refuse_data_t(PDU::Stream stream) {
uint8_t buf[16];
stream->read(buf, sizeof(buf));
leader = UUID::uuid(buf);
number = stream->read16();
mid = stream->read16();
reliable = stream->read32();
code = stream->read8();
}
nak_data_t::nak_data_t(PDU::Stream stream) {
uint8_t buf[16];
stream->read(buf, sizeof(buf));
leader = UUID::uuid(buf);
number = stream->read16();
mid = stream->read16();
reliable = stream->read32();
missed_first = stream->read32();
missed_last = stream->read32();
}
wrapper_data_t::wrapper_data_t(PDU::Stream stream) {
number = stream->read16();
}
/**
compare to another sessionID
*/
bool SessionId::operator== (const SessionId & other) const {
return ((other.cid == cid) &
(other.number == number) &
(other.protocol == protocol));
}
/**
Constuct a sequenced channel with an owner and a direction.
@param owner the owner of the channel
@param direction the direction of channel communication
*/
Channel::Channel(std::shared_ptr<Component> owner, Direction direction) {
owner_ = owner;
direction_ = direction;
}
/**
deconstructor that closes the channel cleanly.
*/
Channel::~Channel() {
// TODO: close the channel.
}
/**
Construct a new session.
*/
Session::Session() {
}
/**
deconstructor that leaves the session gracefully.
*/
Session::~Session() {
// TODO: Disconnect the session.
}
/**
get the ID of the session
*/
SessionId Session::id() {
SessionId ret;
ret.cid = leader->cid();
ret.number = number_;
ret.protocol = protocol_;
return ret;
}
Pdu::Pdu(PDU::Stream stream)
: PDU::Pdu(stream, 1) // vectors are 1 octet
{
// if (stream->fail()) return;
// if (!buffer_->good()) return;
}
client_pdu_header_t::client_pdu_header_t(PDU::Stream stream) {
protocol = stream->read32();
association = stream->read16();
}
ClientPdu::ClientPdu(PDU::Stream stream)
: PDU::Pdu(stream, 2) // vectors are 2 octets (MID)
{
if (stream->fail()) return;
if (!buffer_->good()) return;
if (flags_.hasHeader)
setHeader(new client_pdu_header_t(buffer_));
}
}; // SDT
}; // ACN

313
acn/sdt.h
View File

@ -1,313 +0,0 @@
/*
sdt.h
Copyright (c) 2020 Kevin Matz (kevin.matz@gmail.com)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#pragma once
#include <cstdint>
#include <list>
#include <memory>
#include <unordered_map>
#include "component.h"
#include "pdu.h"
#include "sdt-udp.h" // EPI 18
// ANSI E1.17- 2015, Architecture for Control Networks
// Session Data Transport Protocol
namespace ACN {
namespace SDT {
// 3.1 Session Identity
struct SessionId {
UUID::uuid cid; // the component ID (CID) of the session leader
uint16_t number; // the session number the leader has assigned
uint32_t protocol; // the ID of the protocol carried by the session
bool operator== (const SessionId &) const;
};
// 3.3 Sequenced Channels
// Sequenced channels transport three categories of traffic:
enum Direction {
internal, // SDT internal traffic
downstream, // Session downstream traffic
upstream // Session upstream traffic
};
// 3.5.1.1 Member Identifiers
using MID = uint16_t;
// 4.4.1.2 Channel Parameter Block
struct params_t {
uint8_t Expiry; // number of seconds without traffic before leaving
struct {
uint8_t NAK_Outbound : 1; // NAK to channel (1) or destination address (0)
uint8_t reserved : 7;
};
uint16_t NAKholdoff; // calculation of a standoff time for sending NAKs
uint16_t NAKmodulus; // calculation of a standoff time for sending NAKs
uint16_t NAKmaxwait; // maximum milliseconds to wait before sending a NAK.
params_t() {};
params_t(PDU::Stream);
};
// 4.4.1 Join
struct join_data_t : PDU::pdu_data {
MID mid;
uint16_t number;
uint16_t reciprocal;
uint32_t sequence;
uint32_t reliable;
UDP::address_t destination;
params_t parameters;
uint8_t expiry;
join_data_t() {};
join_data_t(PDU::Stream);
};
// 4.4.2 Join Accept
struct join_accept_data_t : PDU::pdu_data {
UUID::uuid leader;
uint16_t number;
MID mid;
uint32_t reliable;
uint16_t reciprocal;
join_accept_data_t() {};
join_accept_data_t(PDU::Stream);
};
// 4.4.3 Join Refuse
// 4.4.4 Leaving
struct join_refuse_data_t : PDU::pdu_data {
UUID::uuid leader;
uint16_t number;
MID mid;
uint32_t reliable;
uint8_t code;
join_refuse_data_t() {};
join_refuse_data_t(PDU::Stream);
};
// 4.4.5 NAK
struct nak_data_t : PDU::pdu_data {
UUID::uuid leader;
uint16_t number;
MID mid;
uint32_t reliable;
uint32_t missed_first;
uint32_t missed_last;
nak_data_t() {};
nak_data_t(PDU::Stream);
};
// 4.4.6 Reliable Wrapper and Unreliable Wrapper
struct wrapper_data_t : PDU::pdu_data {
uint16_t number;
uint32_t sequence;
uint32_t reliable;
uint32_t oldest;
MID ack_range_begin;
MID ack_range_end;
uint16_t MAK_threshold;
wrapper_data_t() {};
wrapper_data_t(PDU::Stream);
};
// 4.4.7 SDT Client Block
struct client_pdu_header_t : PDU::pdu_header {
uint32_t protocol;
uint16_t association;
client_pdu_header_t() {};
client_pdu_header_t(PDU::Stream);
};
// Client Block PDU
class ClientPdu
: public PDU::Pdu
{
public:
ClientPdu(PDU::Stream);
};
// 4.4.8 Get Sessions
struct get_sessions_data_t : PDU::pdu_data {
UUID::uuid cid;
};
// 4.4.9.1 Channel Owner Info Block
struct channel_info_block_t {
MID mid;
UUID::uuid owner;
uint16_t number;
UDP::address_t destination;
UDP::address_t source;
uint16_t reciprocal;
uint16_t count;
std::list<uint32_t> protocols;
};
// 4.4.9 Sessions
struct sessions_data_t : PDU::pdu_data {
std::list<channel_info_block_t> list;
};
// 4.5.1 ACK
struct ack_data_t : PDU::pdu_data {
uint32_t reliable;
};
// 4.5.2 Channel Params
struct channel_params_data_t : PDU::pdu_data {
params_t parameters;
UDP::address_t address;
uint8_t expiry;
};
// 4.5.3 Connect
// 4.5.4 Connect Accept
// 4.5.6 Disconnect
struct connect_data_t : PDU::pdu_data {
uint32_t protocol;
};
// 4.5.5 Connect Refuse
// 4.5.7 Disconnecting
struct connect_refuse_data_t : PDU::pdu_data {
uint32_t protocol;
uint8_t code;
};
// 7.1 Protocol Code
static const uint32_t SDT_PROTOCOL_ID = 1; // PDU protocol value
// 7.2 PDU Vector Codes
enum sdt_vector_t {
REL_WRAP = 1,
UNREL_WRAP = 2,
CHANNEL_PARAMS = 3,
JOIN = 4,
JOIN_REFUSE = 5,
JOIN_ACCEPT = 6,
LEAVE = 7,
LEAVING = 8,
CONNECT = 9,
CONNECT_ACCEPT = 10,
CONNECT_REFUSE = 11,
DISCONNECT = 12,
DISCONNECTING = 13,
ACK = 14,
NAK = 15,
GET_SESSIONS = 16,
SESSIONS = 17,
};
// 7.4 Other Symbolic Parameters
// Table 6: Reason Codes
enum reason_code_t {
NONSPECIFIC = 1, // Non-specific, non-SDT reason.
ILLEGAL_PARAMETERS = 2, // Illegal channel parameters.
LOW_RESOURCES = 3, // Insufficient resources.
ALREADY_MEMBER = 4, // Multiple MIDs for single component.
BAD_ADDRESS_TYPE = 5, // Unrecognized transport address type.
NO_RECIPROCAL_CHANNEL = 6, // No upstream channel and cant create one
CHANNEL_EXPIRED = 7, // Channel has expired.
LOST_SEQUENCE = 8, // Unrecoverable packets missed.
SATURATED = 9, // Cant keep up, processor saturation.
TRANSPORT_ADDRESS_CHANGING = 10, // (e.g., IP number lease expired).
ASKED_TO_LEAVE = 11, // Asked to leave the channel.
NO_RECIPIENT = 12, // Component does not support protocol ID.
ONLY_UNICAST_SUPPORTED = 13 // Only unicast channels are supported
};
// Table 7: Address Specification Types
enum ip_addr_spec_t {
SDT_ADDR_NULL = 0, // Address is not present (0 octets).
SDT_ADDR_IPV4 = 1, // Address specified is in IP v4 format
SDT_ADDR_IPV6 = 2 // Address specified is in IP v6 format
};
// PDU type for this protocol
class Pdu
: public PDU::Pdu
{
public:
Pdu(PDU::Stream);
};
// Sequenced channels are unidirectional communication channels (unicast or
// multicast) from an owner component to one or more member components.
class Channel {
public:
Channel(std::shared_ptr<Component>, Direction);
~Channel();
private:
std::shared_ptr<Component> owner_;
Direction direction_ = internal;
};
// A session has a single leader and zero or more session members. The leader
// communicates to members using the downstream address. Members respond to the
// leader on the upstream address. A unique session identifier identifies a
// session.
class Session {
public:
Session();
~Session();
SessionId id();
// 4.4 SDT Base Layer Messages (non ad-hoc)
void join_accept() {};
void join_refuse() {};
void leaving() {};
void nak() {};
void reliable_wrapper() {};
void unreliable_wrapper() {};
// 4.5 SDT Wrapped Messages
void ack();
void channel_params();
void leave();
void connect();
void connect_accept();
void connect_refuse();
void disconnect();
void disconnecting();
protected:
std::shared_ptr<ACN::Component> leader;
std::shared_ptr<Channel> downstream;
std::unordered_map<MID, std::shared_ptr<Channel>> upstream;
private:
uint16_t number_;
uint32_t protocol_;
MID next_id_ = 1;
};
} // SDT
} // ACN

View File

@ -1,72 +0,0 @@
/*
universe.h
Copyright (c) 2020 Kevin Matz (kevin.matz@gmail.com)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#pragma once
#include <cstdint>
#include <functional>
#include <vector>
namespace DMX {
using std::uint8_t;
using std::uint16_t;
using std::array;
using std::function;
using std::vector;
// Table D1 - Reserved START Codes
static const uint8_t E111_NULL_START = 0;
static const uint8_t E111_ASC_TEXT_ASCII = 23;
static const uint8_t E111_ASC_TEST = 85;
static const uint8_t E111_ASC_TEXT_UTF8 = 144;
static const uint8_t E111_ASC_MANUFACTURER = 145;
static const uint8_t E111_ASC_SIP = 207;
class Universe; // forward declare the Univserse class
using DataHandler = function<void(Universe *)>;
using DimmerData = array<uint8_t, 513>;
/*
The Universe class
ANSI E1.11 describes may things about moving data over
serial EIA-485-A links. This class encapselates that data,
regardless of how it was transmitted.
*/
class Universe {
public:
Universe ();
DimmerData * data() { return &null_start_data_; }
void onData (const DataHandler callback);
void set (vector<uint8_t>);
uint8_t slot (uint16_t address) { return null_start_data_[address]; }
private:
DimmerData null_start_data_;
vector<DataHandler> callbacks_;
};
} // DMX

304
docs/Doxyfile.in Normal file
View File

@ -0,0 +1,304 @@
# Doxyfile 1.9.1
#---------------------------------------------------------------------------
# Project related configuration options
#---------------------------------------------------------------------------
DOXYFILE_ENCODING = UTF-8
PROJECT_NAME = OpenLCP
PROJECT_NUMBER = @PROJECT_VERSION@
#PROJECT_BRIEF = "A collection of C++ libraries for Lighting Control Protocols, focusing on ESTA sponsored ANSI standards."
PROJECT_LOGO =
OUTPUT_DIRECTORY = @CMAKE_CURRENT_BINARY_DIR@/docs
CREATE_SUBDIRS = YES
ALLOW_UNICODE_NAMES = YES
OUTPUT_LANGUAGE = English
BRIEF_MEMBER_DESC = YES
REPEAT_BRIEF = YES
ABBREVIATE_BRIEF = "The $name class" \
"The $name widget" \
"The $name file" \
is \
provides \
specifies \
contains \
represents \
a \
an \
the
ALWAYS_DETAILED_SEC = NO
INLINE_INHERITED_MEMB = NO
FULL_PATH_NAMES = YES
STRIP_FROM_PATH =
STRIP_FROM_INC_PATH =
SHORT_NAMES = NO
JAVADOC_AUTOBRIEF = YES
JAVADOC_BANNER = NO
QT_AUTOBRIEF = YES
MULTILINE_CPP_IS_BRIEF = NO
PYTHON_DOCSTRING = YES
INHERIT_DOCS = YES
SEPARATE_MEMBER_PAGES = NO
TAB_SIZE = 2
ALIASES =
OPTIMIZE_OUTPUT_FOR_C = NO
OPTIMIZE_OUTPUT_JAVA = NO
OPTIMIZE_FOR_FORTRAN = NO
OPTIMIZE_OUTPUT_VHDL = NO
OPTIMIZE_OUTPUT_SLICE = NO
EXTENSION_MAPPING =
MARKDOWN_SUPPORT = YES
TOC_INCLUDE_HEADINGS = 5
AUTOLINK_SUPPORT = YES
BUILTIN_STL_SUPPORT = YES
CPP_CLI_SUPPORT = NO
SIP_SUPPORT = NO
IDL_PROPERTY_SUPPORT = YES
DISTRIBUTE_GROUP_DOC = NO
GROUP_NESTED_COMPOUNDS = NO
SUBGROUPING = YES
INLINE_GROUPED_CLASSES = NO
INLINE_SIMPLE_STRUCTS = NO
TYPEDEF_HIDES_STRUCT = NO
LOOKUP_CACHE_SIZE = 0
NUM_PROC_THREADS = 4
#---------------------------------------------------------------------------
# Build related configuration options
#---------------------------------------------------------------------------
EXTRACT_ALL = NO
EXTRACT_PRIVATE = NO
EXTRACT_PRIV_VIRTUAL = NO
EXTRACT_PACKAGE = NO
EXTRACT_STATIC = NO
EXTRACT_LOCAL_CLASSES = YES
EXTRACT_LOCAL_METHODS = NO
EXTRACT_ANON_NSPACES = NO
RESOLVE_UNNAMED_PARAMS = YES
HIDE_UNDOC_MEMBERS = NO
HIDE_UNDOC_CLASSES = NO
HIDE_FRIEND_COMPOUNDS = NO
HIDE_IN_BODY_DOCS = NO
INTERNAL_DOCS = NO
CASE_SENSE_NAMES = NO
HIDE_SCOPE_NAMES = NO
HIDE_COMPOUND_REFERENCE= NO
SHOW_INCLUDE_FILES = YES
SHOW_GROUPED_MEMB_INC = NO
FORCE_LOCAL_INCLUDES = NO
INLINE_INFO = YES
SORT_MEMBER_DOCS = YES
SORT_BRIEF_DOCS = NO
SORT_MEMBERS_CTORS_1ST = NO
SORT_GROUP_NAMES = NO
SORT_BY_SCOPE_NAME = NO
STRICT_PROTO_MATCHING = NO
GENERATE_TODOLIST = YES
GENERATE_TESTLIST = YES
GENERATE_BUGLIST = YES
GENERATE_DEPRECATEDLIST= YES
ENABLED_SECTIONS =
MAX_INITIALIZER_LINES = 30
SHOW_USED_FILES = YES
SHOW_FILES = NO
SHOW_NAMESPACES = YES
FILE_VERSION_FILTER =
LAYOUT_FILE =
CITE_BIB_FILES = @CMAKE_CURRENT_SOURCE_DIR@/protocol/artistic/ARTISTIC.bib \
@CMAKE_CURRENT_SOURCE_DIR@/protocol/citp/CITP.bib \
@CMAKE_CURRENT_SOURCE_DIR@/protocol/enttec/ENTTEC.bib \
@CMAKE_CURRENT_SOURCE_DIR@/protocol/esta/ESTA.bib \
@CMAKE_CURRENT_SOURCE_DIR@/protocol/ietf/IETF.bib \
@CMAKE_CURRENT_SOURCE_DIR@/protocol/osc/OSC.bib
#---------------------------------------------------------------------------
# Configuration options related to warning and progress messages
#---------------------------------------------------------------------------
QUIET = YES
WARNINGS = YES
WARN_IF_UNDOCUMENTED = YES
WARN_IF_DOC_ERROR = YES
WARN_NO_PARAMDOC = YES
WARN_AS_ERROR = NO
WARN_FORMAT = "$file:$line: $text"
WARN_LOGFILE =
#---------------------------------------------------------------------------
# Configuration options related to the input files
#---------------------------------------------------------------------------
INPUT = @doxy_main_page@ \
@CMAKE_CURRENT_SOURCE_DIR@
INPUT_ENCODING = UTF-8
FILE_PATTERNS = *.cpp \
*.h \
*.md \
*.py
RECURSIVE = YES
EXCLUDE = @CMAKE_CURRENT_SOURCE_DIR@/docs \
@CMAKE_CURRENT_SOURCE_DIR@/example \
@CMAKE_CURRENT_SOURCE_DIR@/platform \
@CMAKE_CURRENT_SOURCE_DIR@/test
EXCLUDE_SYMLINKS = NO
EXCLUDE_PATTERNS =
EXCLUDE_SYMBOLS =
EXAMPLE_PATH =
EXAMPLE_PATTERNS = *
EXAMPLE_RECURSIVE = NO
IMAGE_PATH =
INPUT_FILTER =
FILTER_PATTERNS =
FILTER_SOURCE_FILES = NO
FILTER_SOURCE_PATTERNS =
USE_MDFILE_AS_MAINPAGE = @CMAKE_CURRENT_SOURCE_DIR@/README.md
#---------------------------------------------------------------------------
# Configuration options related to source browsing
#---------------------------------------------------------------------------
SOURCE_BROWSER = NO
INLINE_SOURCES = NO
STRIP_CODE_COMMENTS = NO
REFERENCED_BY_RELATION = NO
REFERENCES_RELATION = NO
REFERENCES_LINK_SOURCE = YES
SOURCE_TOOLTIPS = YES
USE_HTAGS = NO
VERBATIM_HEADERS = NO
#---------------------------------------------------------------------------
# Configuration options related to the alphabetical class index
#---------------------------------------------------------------------------
ALPHABETICAL_INDEX = YES
IGNORE_PREFIX =
#---------------------------------------------------------------------------
# Configuration options related to the HTML output
#---------------------------------------------------------------------------
GENERATE_HTML = YES
HTML_OUTPUT = html
HTML_FILE_EXTENSION = .html
HTML_HEADER = @CMAKE_CURRENT_SOURCE_DIR@/docs/doxygen_files/header.html
HTML_FOOTER = @CMAKE_CURRENT_SOURCE_DIR@/docs/doxygen_files/footer.html
HTML_STYLESHEET = @CMAKE_CURRENT_SOURCE_DIR@/docs/doxygen_files/doxygen.css
HTML_EXTRA_STYLESHEET = @CMAKE_CURRENT_SOURCE_DIR@/docs/doxygen_theme_flat_design/src/doxygen-style.css
HTML_EXTRA_FILES =
HTML_COLORSTYLE_HUE = 220
HTML_COLORSTYLE_SAT = 100
HTML_COLORSTYLE_GAMMA = 80
HTML_TIMESTAMP = NO
HTML_DYNAMIC_MENUS = YES
HTML_DYNAMIC_SECTIONS = YES
HTML_INDEX_NUM_ENTRIES = 100
GENERATE_DOCSET = NO
GENERATE_HTMLHELP = NO
CHM_FILE =
HHC_LOCATION =
GENERATE_CHI = NO
CHM_INDEX_ENCODING =
BINARY_TOC = NO
TOC_EXPAND = NO
GENERATE_QHP = NO
QCH_FILE =
QHP_NAMESPACE = org.doxygen.Project
QHP_VIRTUAL_FOLDER = doc
QHP_CUST_FILTER_NAME =
QHP_CUST_FILTER_ATTRS =
QHP_SECT_FILTER_ATTRS =
QHG_LOCATION =
GENERATE_ECLIPSEHELP = NO
DISABLE_INDEX = NO
GENERATE_TREEVIEW = NO
ENUM_VALUES_PER_LINE = 4
TREEVIEW_WIDTH = 250
EXT_LINKS_IN_WINDOW = NO
HTML_FORMULA_FORMAT = png
FORMULA_FONTSIZE = 10
FORMULA_MACROFILE =
USE_MATHJAX = NO
MATHJAX_FORMAT = HTML-CSS
MATHJAX_RELPATH = https://cdn.jsdelivr.net/npm/mathjax@2
MATHJAX_EXTENSIONS =
MATHJAX_CODEFILE =
SEARCHENGINE = YES
SERVER_BASED_SEARCH = NO
EXTERNAL_SEARCH = NO
SEARCHENGINE_URL =
SEARCHDATA_FILE = searchdata.xml
EXTERNAL_SEARCH_ID =
EXTRA_SEARCH_MAPPINGS =
#---------------------------------------------------------------------------
# Configuration options related to the LaTeX output
#---------------------------------------------------------------------------
GENERATE_LATEX = NO
#---------------------------------------------------------------------------
# Configuration options related to the RTF output
#---------------------------------------------------------------------------
GENERATE_RTF = NO
#---------------------------------------------------------------------------
# Configuration options related to the man page output
#---------------------------------------------------------------------------
GENERATE_MAN = NO
#---------------------------------------------------------------------------
# Configuration options related to the XML output
#---------------------------------------------------------------------------
GENERATE_XML = NO
#---------------------------------------------------------------------------
# Configuration options related to the DOCBOOK output
#---------------------------------------------------------------------------
GENERATE_DOCBOOK = NO
#---------------------------------------------------------------------------
# Configuration options for the AutoGen Definitions output
#---------------------------------------------------------------------------
GENERATE_AUTOGEN_DEF = NO
#---------------------------------------------------------------------------
# Configuration options related to Sqlite3 output
#---------------------------------------------------------------------------
#---------------------------------------------------------------------------
# Configuration options related to the Perl module output
#---------------------------------------------------------------------------
GENERATE_PERLMOD = NO
#---------------------------------------------------------------------------
# Configuration options related to the preprocessor
#---------------------------------------------------------------------------
ENABLE_PREPROCESSING = YES
MACRO_EXPANSION = YES
EXPAND_ONLY_PREDEF = YES
SEARCH_INCLUDES = YES
INCLUDE_PATH =
INCLUDE_FILE_PATTERNS =
PREDEFINED = __attribute__((x))=
EXPAND_AS_DEFINED =
SKIP_FUNCTION_MACROS = YES
#---------------------------------------------------------------------------
# Configuration options related to external references
#---------------------------------------------------------------------------
TAGFILES =
GENERATE_TAGFILE =
ALLEXTERNALS = NO
EXTERNAL_GROUPS = YES
EXTERNAL_PAGES = YES
#---------------------------------------------------------------------------
# Configuration options related to the dot tool
#---------------------------------------------------------------------------
DIA_PATH =
HIDE_UNDOC_RELATIONS = YES
HAVE_DOT = YES
DOT_NUM_THREADS = 0
DOT_FONTPATH =
CLASS_GRAPH = YES
COLLABORATION_GRAPH = NO
GROUP_GRAPHS = YES
UML_LOOK = NO
UML_LIMIT_NUM_FIELDS = 10
DOT_UML_DETAILS = NO
DOT_WRAP_THRESHOLD = 17
TEMPLATE_RELATIONS = YES
INCLUDE_GRAPH = YES
INCLUDED_BY_GRAPH = YES
CALL_GRAPH = YES
CALLER_GRAPH = YES
GRAPHICAL_HIERARCHY = YES
DIRECTORY_GRAPH = YES
DOT_IMAGE_FORMAT = svg
INTERACTIVE_SVG = NO
DOT_PATH =
DOTFILE_DIRS =
MSCFILE_DIRS =
DIAFILE_DIRS =
DOT_GRAPH_MAX_NODES = 50
MAX_DOT_GRAPH_DEPTH = 0
DOT_MULTI_TARGETS = NO
GENERATE_LEGEND = YES
DOT_CLEANUP = YES

BIN
docs/classdiagram.odg Normal file

Binary file not shown.

BIN
docs/classdiagram.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 518 KiB

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,17 @@
<!--BEGIN GENERATE_TREEVIEW-->
<div id="nav-path" class="navpath"><!-- id is needed for treeview function! -->
<ul>
$navpath
<li class="footer">Copyright &copy; 2020-2023 Kevin Matz -- Generated by doxygen $doxygenversion</li>
</ul>
</div>
<!--END GENERATE_TREEVIEW-->
<!--BEGIN !GENERATE_TREEVIEW-->
<address class="footer">
<small>
Copyright &copy; 2020-2023 Kevin Matz -- Generated by doxygen $doxygenversion
</small>
</address>
<!--END !GENERATE_TREEVIEW-->
</body>
</html>

View File

@ -0,0 +1,56 @@
<!-- HTML header for doxygen 1.9.1-->
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=9"/>
<meta name="generator" content="Doxygen $doxygenversion"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<!--BEGIN PROJECT_NAME--><title>$projectname: $title</title><!--END PROJECT_NAME-->
<!--BEGIN !PROJECT_NAME--><title>$title</title><!--END !PROJECT_NAME-->
<link href="$relpath^tabs.css" rel="stylesheet" type="text/css"/>
<script type="text/javascript" src="$relpath^jquery.js"></script>
<script type="text/javascript" src="$relpath^dynsections.js"></script>
$treeview
$search
$mathjax
<link href="$relpath^$stylesheet" rel="stylesheet" type="text/css" />
$extrastylesheet
</head>
<body>
<div id="top"><!-- do not remove this div, it is closed by doxygen! -->
<!--BEGIN TITLEAREA-->
<div id="titlearea">
<table cellspacing="0" cellpadding="0">
<tbody>
<tr style="height: 56px;">
<!--BEGIN PROJECT_LOGO-->
<td id="projectlogo"><img alt="Logo" src="$relpath^$projectlogo"/></td>
<!--END PROJECT_LOGO-->
<!--BEGIN PROJECT_NAME-->
<td id="projectalign" style="padding-left: 0.5em;">
<div id="projectname">$projectname
<!--BEGIN PROJECT_NUMBER-->&#160;<span id="projectnumber">$projectnumber</span><!--END PROJECT_NUMBER-->
</div>
<!--BEGIN PROJECT_BRIEF--><div id="projectbrief">$projectbrief</div><!--END PROJECT_BRIEF-->
</td>
<!--END PROJECT_NAME-->
<!--BEGIN !PROJECT_NAME-->
<!--BEGIN PROJECT_BRIEF-->
<td style="padding-left: 0.5em;">
<div id="projectbrief">$projectbrief</div>
</td>
<!--END PROJECT_BRIEF-->
<!--END !PROJECT_NAME-->
<!--BEGIN DISABLE_INDEX-->
<!--BEGIN SEARCHENGINE-->
<td>$searchbox</td>
<!--END SEARCHENGINE-->
<!--END DISABLE_INDEX-->
</tr>
</tbody>
</table>
</div>
<!--END TITLEAREA-->
<!-- end header part -->

@ -0,0 +1 @@
Subproject commit 7be08cbe5531f60607dffb2f8f76f6c070a9c81b

BIN
docs/establockdiagram.odg Normal file

Binary file not shown.

BIN
docs/establockdiagram.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

BIN
docs/pduflowchart.odg Normal file

Binary file not shown.

BIN
docs/pduflowchart.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 318 KiB

BIN
docs/vectorflowchart.odg Normal file

Binary file not shown.

3
example/CMakeLists.txt Normal file
View File

@ -0,0 +1,3 @@
add_subdirectory("sACN Explorer")
add_subdirectory("widgetExplorer")

View File

@ -0,0 +1,38 @@
project(sacnExplorer VERSION ${PROJECT_VERSION})
find_package(Qt6 COMPONENTS Network Widgets REQUIRED)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
add_executable(${PROJECT_NAME})
target_sources(${PROJECT_NAME}
PRIVATE
adduniversedialog.cpp
adduniversedialog.h
adduniversedialog.ui
lineeditdialog.cpp
lineeditdialog.h
lineeditdialog.ui
main.cpp
multiversewindow.cpp
multiversewindow.h
multiversewindow.ui
sacnexplorer.h
sacnexplorer.cpp
)
target_link_libraries(${PROJECT_NAME}
PRIVATE
LCP::Qt::sACN
Qt::Network
Qt::Widgets
)
set_target_properties(${PROJECT_NAME} PROPERTIES
MACOSX_BUNDLE_GUI_IDENTIFIER ${PROJECT_NAME}.company235.com
MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
)

View File

@ -0,0 +1,17 @@
#include "adduniversedialog.h"
#include "ui_adduniversedialog.h"
AddUniverseDialog::AddUniverseDialog(QWidget *parent, uint16_t universe)
: QDialog(parent)
, ui(new Ui::AddUniverseDialog)
{
ui->setupUi(this);
ui->spinBox->setValue(universe);
connect(this, &QDialog::accepted,
this, [this](){emit submit(ui->spinBox->value());});
}
AddUniverseDialog::~AddUniverseDialog()
{
delete ui;
}

View File

@ -0,0 +1,23 @@
#pragma once
#include <QDialog>
QT_BEGIN_NAMESPACE
namespace Ui {
class AddUniverseDialog;
}
QT_END_NAMESPACE
class AddUniverseDialog : public QDialog
{
Q_OBJECT
public:
explicit AddUniverseDialog(QWidget *parent = nullptr, uint16_t universe = 1);
virtual ~AddUniverseDialog();
signals:
void submit(const uint16_t universe);
private:
Ui::AddUniverseDialog *ui;
};

View File

@ -0,0 +1,77 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>AddUniverseDialog</class>
<widget class="QDialog" name="AddUniverseDialog">
<property name="modal">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="_3">
<item>
<layout class="QFormLayout" name="_2">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Universe</string>
</property>
<property name="buddy">
<cstring>spinBox</cstring>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QSpinBox" name="spinBox">
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>63999</number>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QDialogButtonBox" name="dialogButtonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>dialogButtonBox</sender>
<signal>accepted()</signal>
<receiver>AddUniverseDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>dialogButtonBox</sender>
<signal>rejected()</signal>
<receiver>AddUniverseDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -0,0 +1,23 @@
#include "lineeditdialog.h"
#include "ui_lineeditdialog.h"
LineEditDialog::LineEditDialog(QWidget *parent, QString label, int length,
QString value, QString placeholder) :
QDialog(parent),
ui(new Ui::LineEditDialog)
{
ui->setupUi(this);
ui->label->setText(label);
if (length > 0)
ui->lineEdit->setMaxLength(length);
ui->lineEdit->setText(value);
ui->lineEdit->setPlaceholderText(placeholder);
connect(this, &LineEditDialog::accepted,
this, [this]() {emit submit(ui->lineEdit->text());});
}
LineEditDialog::~LineEditDialog()
{
delete ui;
}

View File

@ -0,0 +1,26 @@
#ifndef LINEEDITDIALOG_H
#define LINEEDITDIALOG_H
#include <QDialog>
namespace Ui {
class LineEditDialog;
}
class LineEditDialog : public QDialog
{
Q_OBJECT
public:
explicit LineEditDialog(QWidget *parent = nullptr,
QString label = "line", int length = 0,
QString value = "", QString placeholder = "");
~LineEditDialog();
signals:
void submit(const QString);
private:
Ui::LineEditDialog *ui;
};
#endif // LINEEDITDIALOG_H

View File

@ -0,0 +1,81 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>LineEditDialog</class>
<widget class="QDialog" name="LineEditDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>82</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<property name="modal">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="lineEdit"/>
</item>
</layout>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>LineEditDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>LineEditDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -0,0 +1,7 @@
#include "sacnexplorer.h"
int main(int argc, char *argv[])
{
SacnExplorer a(argc, argv);
return a.exec();
}

View File

@ -0,0 +1,327 @@
#include "adduniversedialog.h"
#include "lineeditdialog.h"
#include "multiversewindow.h"
#include "multiversemodel.h"
#include "ui_multiversewindow.h"
#include "universewindow.h"
#include <QActionGroup>
#include <QMessageBox>
#include <QStringBuilder>
/**
* @brief MultiverseWindow::MultiverseWindow
* @param parent
*/
MultiverseWindow::MultiverseWindow(QWidget *parent, QSacnNode *node)
: QMainWindow(parent)
, ui(new Ui::MultiverseWindow)
, node(node)
, model(new MultiverseModel(this, node))
, sortProxy(new QSortFilterProxyModel(this))
{
// build the UI
ui->setupUi(this);
ui->menubar->setNativeMenuBar(false);
auto mergeMode = new QActionGroup(this);
mergeMode->addAction(ui->actionMergeModeHTP);
mergeMode->addAction(ui->actionMergeModeLTP);
// initial states
ui->actionTerminate->setEnabled(false);
ui->actionUnsubscribe->setEnabled(false);
ui->actionInspect->setEnabled(false);
ui->actionIPv4->setChecked(node->isEnabledIPv4());
ui->actionIPv6->setChecked(node->isEnabledIPv6());
ui->actionDiscovery->setChecked(node->discoveryEnabled());
// setup the model
sortProxy->setSourceModel(model);
ui->multiverseView->setModel(sortProxy);
ui->multiverseView->setSortingEnabled(true);
ui->multiverseView->sortByColumn(MultiverseModel::Column::Universe,
Qt::SortOrder::AscendingOrder);
ui->multiverseView->setContextMenuPolicy(Qt::CustomContextMenu);
ui->multiverseView->expandAll();
for (int i = 0; i < ui->multiverseView->model()->rowCount(); i++)
ui->multiverseView->setFirstColumnSpanned(i, QModelIndex(), true);
// model connections
connect(ui->multiverseView->selectionModel(), &QItemSelectionModel::currentChanged,
this, &MultiverseWindow::selectionChanged);
connect(ui->multiverseView, &QTreeView::customContextMenuRequested,
this, &MultiverseWindow::openContextMenu);
connect(ui->multiverseView, &QTreeView::doubleClicked,
this, &MultiverseWindow::openUniverseEditor);
// action connections
connect(ui->actionUACN, &QAction::triggered,
this, &MultiverseWindow::openUacnEditor);
connect(ui->actionIPv4, &QAction::toggled,
node, &QSacnNode::setIPv4);
connect(ui->actionIPv6, &QAction::toggled,
node, &QSacnNode::setIPv6);
connect(ui->actionDiscovery, &QAction::toggled,
node, [&, node](bool checked) {
checked ? node->discoveryStart() : node->discoveryStop();
});
connect(ui->actionCreate, &QAction::triggered,
this, [this, node]() {
auto dialog = new AddUniverseDialog(this, 1);
connect(dialog, &AddUniverseDialog::submit,
node, &QSacnNode::create);
dialog->setWindowTitle(tr("Create"));
dialog->setAttribute(Qt::WA_DeleteOnClose);
dialog->open();
});
connect(ui->actionTerminate, &QAction::triggered,
this, [&, node]() {
auto index = ui->multiverseView->currentIndex();
auto data = index.data(Qt::EditRole);
if (data.metaType().id() == qMetaTypeId<QSacnUniverse*>())
{
auto univ = data.value<QSacnUniverse*>();
if (univ->isEditable())
node->terminate(univ->number());
}
});
connect(ui->actionSubscribe, &QAction::triggered,
this, [this, node]() {
auto val = QVariant(1);
auto index = ui->multiverseView->currentIndex();
if (index.isValid())
{
auto actual = sortProxy->mapToSource(index);
auto item = static_cast<MultiverseItem*>(actual.internalPointer());
val = item->data(MultiverseModel::Column::Universe, Qt::DisplayRole);
}
auto dialog = new AddUniverseDialog(this, val.toInt());
connect(dialog, &AddUniverseDialog::submit,
node, &QSacnNode::subscribe);
dialog->setWindowTitle(tr("Subscribe"));
dialog->setAttribute(Qt::WA_DeleteOnClose);
dialog->open();
});
connect(ui->actionUnsubscribe, &QAction::triggered,
this, [&, node]() {
auto index = ui->multiverseView->currentIndex();
auto data = index.data(Qt::EditRole);
if (data.metaType().id() == qMetaTypeId<QSacnUniverse*>())
{
auto univ = data.value<QSacnUniverse*>();
if (!univ->isEditable())
node->unsubscribe(univ->number());
}
});
connect(ui->actionInspect, &QAction::triggered,
this, [this]() {
auto selected = ui->multiverseView->currentIndex();
openUniverseEditor(selected);
});
connect(ui->actionUniverseHoldLastLook, &QAction::toggled,
this, [this](bool state) {
auto selected = ui->multiverseView->currentIndex();
auto data = selected.data(Qt::EditRole);
auto univ = data.value<QSacnUniverse*>();
univ->setHoldLastLook(state);
});
connect(ui->actionMergeModeHTP, &QAction::toggled,
this, [this](bool state) {
auto selected = ui->multiverseView->currentIndex();
auto data = selected.data(Qt::EditRole);
auto univ = data.value<QSacnUniverse*>();
if (state)
univ->setMergeMode(sACN::UniverseArbitrator::MERGE_HTP);
else
univ->setMergeMode(sACN::UniverseArbitrator::MERGE_LTP);
});
connect(ui->actionAbout, &QAction::triggered,
this, [this](){
QString title = tr("About") % " " % qAppName();
QString text = QApplication::organizationName() % "\n"
% "https://" % QApplication::organizationDomain() % "\n\n"
% QApplication::applicationName() % " is a demonstration of the capabilities "
% "of the sACN implimentation in the OpenLCP protocol library using the "
% "QsACN platform driver." % "\n\n"
% "© 2020-2022 Kevin Matz" % "\n\n"
% "Permission is hereby granted, free of charge, to any person obtaining a copy"
% "of this software and associated documentation files (the \"Software\"), "
% "to deal in the Software without restriction, including without limitation "
% "the rights to use, copy, modify, merge, publish, distribute, sublicense, "
% "and/or sell copies of the Software, and to permit persons to whom the "
% "Software is furnished to do so, subject to the following conditions:" % "\n\n"
% "The above copyright notice and this permission notice shall be included in "
% "all copies or substantial portions of the Software." % "\n\n"
% "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR "
% "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, "
% "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE "
% "AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER "
% "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING "
% "FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER "
% "DEALINGS IN THE SOFTWARE.";
QMessageBox::about(this, title, text);
});
connect(ui->actionAbout_Qt, &QAction::triggered,
this, [this](){QMessageBox::aboutQt(this);});
}
/**
* @brief MultiverseWindow::~MultiverseWindow
*/
MultiverseWindow::~MultiverseWindow()
{
qDeleteAll(mInspectors);
delete ui;
}
void MultiverseWindow::closeEvent(QCloseEvent *)
{
// close all universe windows
foreach (const auto & inspector, mInspectors)
inspector->close();
}
/**
* @brief MultiverseWindow::selectionChanged
* @param selected
* @param deselected
*/
void MultiverseWindow::selectionChanged(const QModelIndex &current,
const QModelIndex &previous)
{
Q_UNUSED(previous)
ui->actionTerminate->setEnabled(false);
ui->actionUnsubscribe->setEnabled(false);
ui->actionInspect->setEnabled(false);
ui->actionUniverseHoldLastLook->setEnabled(false);
ui->menuMerge_Mode->setEnabled(false);
auto data = current.data(Qt::EditRole);
// Data universes:
if (data.metaType().id() == qMetaTypeId<QSacnUniverse*>())
{
auto univ = data.value<QSacnUniverse*>();
ui->actionInspect->setEnabled(true);
ui->actionUniverseHoldLastLook->setChecked(univ->getHoldLastLook());
switch (univ->getMergeMode()){
case sACN::UniverseArbitrator::MERGE_HTP:
ui->actionMergeModeHTP->setChecked(true);
break;
case sACN::UniverseArbitrator::MERGE_LTP:
ui->actionMergeModeLTP->setChecked(true);
break;
default:
break;
}
if (univ->isEditable())
ui->actionTerminate->setEnabled(true);
else
{
ui->actionUnsubscribe->setEnabled(true);
if (univ->hasSources())
{
ui->actionUniverseHoldLastLook->setEnabled(true);
ui->menuMerge_Mode->setEnabled(true);
}
}
return;
}
}
/**
* @brief MultiverseWindow::openContextMenu
* @param pos
*/
void MultiverseWindow::openContextMenu(const QPoint &pos)
{
auto menu = new QMenu(ui->multiverseView);
menu->setAttribute(Qt::WA_DeleteOnClose, true);
auto globalpos = ui->multiverseView->mapToGlobal(pos);
auto data = ui->multiverseView->indexAt(pos).data(Qt::EditRole);
if (data.metaType().id() == qMetaTypeId<QSacnUniverse*>())
{ // Data universes:
menu->addAction(ui->actionInspect);
menu->addSeparator();
auto univ = data.value<QSacnUniverse*>();
if (univ->isEditable())
menu->addAction(ui->actionTerminate);
else
menu->addAction(ui->actionUnsubscribe);
menu->popup(globalpos);
}
else if (data.metaType().id() ==
qMetaTypeId<sACN::EXTENDED::DISCOVERY::discoveredUniverse*>())
{ // Discovery universes:
menu->addAction(ui->actionSubscribe);
menu->popup(globalpos);
}
else
// unknow metaType
menu->deleteLater();
}
/**
* @brief MultiverseWindow::openUniverseEditor
* @param index
*/
void MultiverseWindow::openUniverseEditor(const QModelIndex &index)
{
auto data = index.data(Qt::EditRole);
// Data universes:
if (data.metaType().id() == qMetaTypeId<QSacnUniverse*>())
{
auto univ = data.value<QSacnUniverse*>();
foreach (const auto & inspector, mInspectors)
if (inspector->universe() == univ->get())
return inspector->show();
auto view = new UniverseWindow(nullptr, univ->get());
mInspectors.insert(view);
connect(view, &QObject::destroyed, this, [this, view](){mInspectors.remove(view);});
connect(univ, &QObject::destroyed, view, &QMainWindow::close);
view->setWindowTitle(QString("%1 '%2' : "+tr("Universe")+" %3").arg(
univ->isEditable() ? tr("Editing") : tr("Viewing"),
univ->sourceName(),
QString::number(univ->number())));
view->setAttribute(Qt::WA_DeleteOnClose, true);
view->show();
return;
}
// Discovery universes:
if (data.metaType().id() ==
qMetaTypeId<sACN::EXTENDED::DISCOVERY::discoveredUniverse*>())
{
auto disc = data.value<sACN::EXTENDED::DISCOVERY::discoveredUniverse*>();
auto dialog = new AddUniverseDialog(this, disc->universe);
connect(dialog, &AddUniverseDialog::submit,
node, &QSacnNode::subscribe);
dialog->setWindowTitle(tr("Create"));
dialog->setAttribute(Qt::WA_DeleteOnClose);
dialog->open();
return;
}
}
/**
* @brief MultiverseWindow::openUacnEditor
*/
void MultiverseWindow::openUacnEditor()
{
auto editor = new LineEditDialog(this, "Name", 63,
node->userName().c_str(), node->fixedName().c_str());
connect(editor, &LineEditDialog::submit,
this, [&](const QString name) {node->assignUserName(name.toStdString());});
editor->setWindowTitle("User Assigned Component Name");
editor->setAttribute(Qt::WA_DeleteOnClose);
editor->open();
}

View File

@ -0,0 +1,68 @@
/*
multiverseview.h
Copyright (c) 2021 Kevin Matz (kevin.matz@gmail.com)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#pragma once
#include "multiversemodel.h"
#include <qsacnnode.h>
#include <universewindow.h>
#include <QMainWindow>
#include <QSortFilterProxyModel>
QT_BEGIN_NAMESPACE
namespace Ui
{
class MultiverseWindow;
}
QT_END_NAMESPACE
/**
* @brief The MultiverseView class
*/
class MultiverseWindow
: public QMainWindow
{
Q_OBJECT
public:
explicit MultiverseWindow(QWidget *parent = nullptr,
QSacnNode* node = nullptr);
virtual ~MultiverseWindow();
protected:
void closeEvent(QCloseEvent *event) override;
private slots:
void selectionChanged(const QModelIndex &current, const QModelIndex &previous);
void openContextMenu(const QPoint &pos);
void openUniverseEditor(const QModelIndex &index);
void openUacnEditor();
private:
Ui::MultiverseWindow *ui;
QSacnNode * node;
MultiverseModel * model;
QSortFilterProxyModel *sortProxy;
QSet<UniverseWindow*> mInspectors;
};

View File

@ -0,0 +1,304 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MultiverseWindow</class>
<widget class="QMainWindow" name="MultiverseWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>600</height>
</rect>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QTreeView" name="multiverseView">
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="uniformRowHeights">
<bool>true</bool>
</property>
<property name="expandsOnDoubleClick">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<widget class="QMenu" name="menuNode">
<property name="title">
<string>Node</string>
</property>
<addaction name="actionIPv4"/>
<addaction name="actionIPv6"/>
<addaction name="actionUACN"/>
</widget>
<widget class="QMenu" name="menusUniverse">
<property name="title">
<string>Universe</string>
</property>
<widget class="QMenu" name="menuMerge_Mode">
<property name="enabled">
<bool>false</bool>
</property>
<property name="title">
<string>Merge Mode</string>
</property>
<addaction name="actionMergeModeHTP"/>
<addaction name="actionMergeModeLTP"/>
</widget>
<addaction name="actionInspect"/>
<addaction name="actionUniverseHoldLastLook"/>
<addaction name="menuMerge_Mode"/>
</widget>
<widget class="QMenu" name="menuReceiver">
<property name="title">
<string>Receiver</string>
</property>
<addaction name="actionSubscribe"/>
<addaction name="actionUnsubscribe"/>
<addaction name="separator"/>
<addaction name="actionDiscovery"/>
</widget>
<widget class="QMenu" name="menuSource">
<property name="title">
<string>Source</string>
</property>
<addaction name="actionCreate"/>
<addaction name="actionTerminate"/>
</widget>
<widget class="QMenu" name="menuHelp">
<property name="title">
<string>Help</string>
</property>
<addaction name="actionAbout"/>
<addaction name="actionAbout_Qt"/>
</widget>
<addaction name="menuNode"/>
<addaction name="menuReceiver"/>
<addaction name="menuSource"/>
<addaction name="menusUniverse"/>
<addaction name="menuHelp"/>
</widget>
<widget class="QStatusBar" name="statusbar"/>
<widget class="QToolBar" name="toolBar">
<property name="enabled">
<bool>true</bool>
</property>
<property name="windowTitle">
<string>toolBar</string>
</property>
<property name="iconSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonFollowStyle</enum>
</property>
<attribute name="toolBarArea">
<enum>TopToolBarArea</enum>
</attribute>
<attribute name="toolBarBreak">
<bool>false</bool>
</attribute>
<addaction name="actionSubscribe"/>
<addaction name="actionUnsubscribe"/>
<addaction name="separator"/>
<addaction name="actionCreate"/>
<addaction name="actionTerminate"/>
<addaction name="separator"/>
<addaction name="actionInspect"/>
</widget>
<action name="actionSubscribe">
<property name="icon">
<iconset theme="call-start">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>Subscribe</string>
</property>
<property name="toolTip">
<string>Subscribe to an sACN Universe.</string>
</property>
<property name="shortcut">
<string>+</string>
</property>
</action>
<action name="actionUnsubscribe">
<property name="enabled">
<bool>false</bool>
</property>
<property name="icon">
<iconset theme="call-stop">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>Unsubscribe</string>
</property>
<property name="toolTip">
<string>Unsubscribe from the sACN Universe.</string>
</property>
<property name="shortcut">
<string>-</string>
</property>
</action>
<action name="actionCreate">
<property name="icon">
<iconset theme="document-new">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>Create</string>
</property>
<property name="toolTip">
<string>Create an sACN Universe to transmit.</string>
</property>
<property name="shortcut">
<string>N</string>
</property>
</action>
<action name="actionTerminate">
<property name="enabled">
<bool>false</bool>
</property>
<property name="icon">
<iconset theme="edit-delete">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>Terminate</string>
</property>
<property name="toolTip">
<string>Terminate transmission of the sACN Universe.</string>
</property>
<property name="shortcut">
<string>X</string>
</property>
</action>
<action name="actionUACN">
<property name="icon">
<iconset theme="network-server">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>User Assigned Component Name</string>
</property>
<property name="toolTip">
<string>Set this Component's UACN</string>
</property>
</action>
<action name="actionInspect">
<property name="enabled">
<bool>false</bool>
</property>
<property name="icon">
<iconset theme="edit-find">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>Inspect</string>
</property>
<property name="toolTip">
<string>View the data of the Universe.</string>
</property>
<property name="shortcut">
<string>Space</string>
</property>
</action>
<action name="actionIPv4">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>IPv4</string>
</property>
<property name="toolTip">
<string>Enable IPv4</string>
</property>
<property name="shortcut">
<string>Ctrl+4</string>
</property>
</action>
<action name="actionIPv6">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>IPv6</string>
</property>
<property name="toolTip">
<string>Enable IPv6</string>
</property>
<property name="shortcut">
<string>Ctrl+6</string>
</property>
</action>
<action name="actionDiscovery">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Discovery</string>
</property>
<property name="toolTip">
<string>Enable Discovery in the Receiver. Discovery messages are always transmitted.</string>
</property>
<property name="shortcut">
<string>Ctrl+D</string>
</property>
</action>
<action name="actionUniverseHoldLastLook">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Hold Last Look</string>
</property>
<property name="toolTip">
<string>Retain at least 1 inacitve source on this universe.</string>
</property>
</action>
<action name="actionMergeModeLTP">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>LTP</string>
</property>
<property name="toolTip">
<string>Latest Takes Precedence</string>
</property>
</action>
<action name="actionMergeModeHTP">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>HTP</string>
</property>
<property name="toolTip">
<string>Highest Takes Precedence</string>
</property>
</action>
<action name="actionAbout">
<property name="icon">
<iconset theme="help-about"/>
</property>
<property name="text">
<string>About</string>
</property>
</action>
<action name="actionAbout_Qt">
<property name="text">
<string>About Qt</string>
</property>
</action>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,67 @@
#include <QDebug>
#include "sacnexplorer.h"
SacnExplorer::SacnExplorer(int argc, char *argv[])
: QApplication(argc, argv)
, node_(nullptr)
, window_(nullptr)
{
setOrganizationName("Company235");
setOrganizationDomain("company235.com");
setApplicationName(tr("sACN Explorer"));
settings_ = new QSettings(this);
// Persistant configuration
qDebug() << "Loaded application settings from" << settings_->fileName();
settings_->beginGroup("ACN");
auto cid = settings_->value("CID", QUuid::createUuid()).toUuid();
auto uacn = settings_->value("UACN", "").toString();
auto ipv4 = settings_->value("IPv4", true).toBool();
auto ipv6 = settings_->value("IPv6", true).toBool();
settings_->endGroup();
settings_->beginGroup("sACN");
settings_->beginGroup("receiver");
auto discovery = settings_->value("discovery", true).toBool();
settings_->endGroup();
settings_->endGroup();
// sACN Node
node_ = new QSacnNode(this, cid, applicationName(), ipv4, ipv6);
node_->assignUserName(uacn.toStdString());
qDebug() << "sACN node" << QString(node_->name().c_str()) << "started with CID" << cid.toString();
if (discovery)
node_->discoveryStart();
// Multiverse Explorer
window_ = new MultiverseWindow(nullptr, node_);
window_->setWindowTitle(QApplication::applicationName());
window_->show();
}
SacnExplorer::~SacnExplorer()
{
saveSettings();
delete window_;
}
/**
* @brief SacnExplorer::saveSettings
*/
void SacnExplorer::saveSettings()
{
qDebug() << "Saving application settings to" << settings_->fileName();
settings_->beginGroup("ACN");
settings_->setValue("CID", node_->cid().string().c_str());
settings_->setValue("UACN", node_->userName().c_str());
settings_->setValue("IPv4", node_->isEnabledIPv4());
settings_->setValue("IPv6", node_->isEnabledIPv6());
settings_->endGroup();
settings_->beginGroup("sACN");
settings_->beginGroup("receiver");
settings_->setValue("discovery", node_->discoveryEnabled());
settings_->endGroup();
settings_->endGroup();
}

View File

@ -0,0 +1,24 @@
#pragma once
#include "multiversewindow.h"
#include "qsacnnode.h"
#include <QApplication>
#include <QSettings>
class SacnExplorer
: public QApplication
{
Q_OBJECT
public:
explicit SacnExplorer(int argc, char *argv[]);
virtual ~SacnExplorer();
private:
void saveSettings();
QSettings *settings_;
QSacnNode *node_;
MultiverseWindow *window_;
};

View File

@ -0,0 +1,38 @@
project(widgetExplorer VERSION ${PROJECT_VERSION})
find_package(Qt6 COMPONENTS Gui Network SerialPort Widgets REQUIRED)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
add_executable(${PROJECT_NAME})
target_sources(${PROJECT_NAME}
PRIVATE
devicewindow.cpp
devicewindow.h
devicewindow.ui
main.cpp
metadatadialog.h
metadatadialog.cpp
metadatadialog.ui
widgetexplorer.cpp
widgetexplorer.h
)
target_link_libraries(${PROJECT_NAME}
PRIVATE
LCP::Qt::sACN
LCP::Qt::DmxWidget
Qt::Gui
Qt::Network
Qt::SerialPort
Qt::Widgets
)
set_target_properties(${PROJECT_NAME} PROPERTIES
MACOSX_BUNDLE_GUI_IDENTIFIER ${PROJECT_NAME}.company235.com
MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
)

View File

@ -0,0 +1,178 @@
#include "devicewindow.h"
#include "metadatadialog.h"
#include "ui_devicewindow.h"
#include <QMessageBox>
#include <QStringBuilder>
DeviceWindow::DeviceWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::DeviceWindow)
, model(new WidgetModel(this))
, sortProxy(new QSortFilterProxyModel(this))
{
// build the UI
ui->setupUi(this);
ui->menubar->setNativeMenuBar(false); // workaround for poor Qt6 support in KDE 5
// setup the model
sortProxy->setSourceModel(model);
ui->widgetView->setModel(sortProxy);
ui->widgetView->setSortingEnabled(true);
ui->widgetView->sortByColumn(WidgetModel::PortName, Qt::SortOrder::AscendingOrder);
ui->widgetView->setColumnWidth(WidgetModel::Manufacturer, 128);
ui->widgetView->setColumnWidth(WidgetModel::Description, 196);
ui->widgetView->setColumnWidth(WidgetModel::SerialNumber, 96);
// model connections
connect(ui->widgetView->selectionModel(), &QItemSelectionModel::currentChanged,
this, &DeviceWindow::selectionChanged);
connect(model, &WidgetModel::dataChanged, this, [this](){
auto selected = ui->widgetView->currentIndex();
if (selected.isValid() && model->rowCount())
ui->widgetView->selectRow(selected.row());
});
connect(ui->actionOpen, &QAction::triggered,
this, [this]() {
auto selected = ui->widgetView->currentIndex();
if (selected.isValid())
selected.data(Qt::EditRole).value<DmxWidget*>()->open();
});
connect(ui->actionClose, &QAction::triggered,
this, [this]() {
auto selected = ui->widgetView->currentIndex();
if (selected.isValid())
selected.data(Qt::EditRole).value<DmxWidget*>()->close();
});
connect(ui->actionDMX, &QAction::triggered,
this, [this]() {
auto selected = ui->widgetView->currentIndex();
if (!selected.isValid())
return;
openDmxWindow(selected);
});
connect(ui->actionRDM, &QAction::triggered,
this, [this]() {
auto selected = ui->widgetView->currentIndex();
if (!selected.isValid())
return;
/// \todo Add an action to monitor RDM.
});
connect(ui->actionParameter, &QAction::triggered,
this, [this]() {
auto selected = ui->widgetView->currentIndex();
if (!selected.isValid())
return;
MetadataDialog dlg(selected.data(Qt::EditRole).value<std::shared_ptr<DmxWidget>>().get(), this);
dlg.exec();
});
connect(ui->actionUserData, &QAction::triggered,
this, [this]() {
auto selected = ui->widgetView->currentIndex();
if (!selected.isValid())
return;
/// \todo Add an action to manage user configuraion data.
});
connect(ui->actionFirmware, &QAction::triggered,
this, [this]() {
auto selected = ui->widgetView->currentIndex();
if (!selected.isValid())
return;
/// \todo Add a notice about firmware support.
});
// action connections
connect(ui->actionRefreshList, &QAction::triggered, model, &WidgetModel::rescanPorts);
connect(ui->actionRefreshList, &QAction::triggered, this, [this](){
ui->widgetView->clearSelection();
selectionChanged(QModelIndex(), QModelIndex());
});
connect(ui->actionAbout, &QAction::triggered,
this, [this](){
QString title = tr("About") % " " % qAppName();
QString text = QApplication::organizationName() % "\n"
% "https://" % QApplication::organizationDomain() % "\n\n"
% QApplication::applicationName() % " is a demonstration of the capabilities "
% "of the DMX-USB-Pro driver implimentation in the OpenLCP protocol library."
% "\n\n" % "© 2023 Kevin Matz" % "\n\n"
% "Permission is hereby granted, free of charge, to any person obtaining a copy"
% "of this software and associated documentation files (the \"Software\"), "
% "to deal in the Software without restriction, including without limitation "
% "the rights to use, copy, modify, merge, publish, distribute, sublicense, "
% "and/or sell copies of the Software, and to permit persons to whom the "
% "Software is furnished to do so, subject to the following conditions:" % "\n\n"
% "The above copyright notice and this permission notice shall be included in "
% "all copies or substantial portions of the Software." % "\n\n"
% "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR "
% "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, "
% "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE "
% "AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER "
% "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING "
% "FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER "
% "DEALINGS IN THE SOFTWARE.";
QMessageBox::about(this, title, text);
});
connect(ui->actionAbout_Qt, &QAction::triggered,
this, [this](){QMessageBox::aboutQt(this);});
}
DeviceWindow::~DeviceWindow()
{
qDeleteAll(inspectors_);
delete ui;
}
void DeviceWindow::selectionChanged(const QModelIndex &current, const QModelIndex &previous)
{
Q_UNUSED(previous)
bool valid = false;
bool connected = false;
bool hasdmx = false;
bool hasrdm = false;
if (current.isValid())
{
auto index = model->index(current.row(), 0, current.parent());
auto wdgt = index.data(Qt::EditRole).value<std::shared_ptr<DmxWidget>>();
valid = true;
connected = wdgt->isConnected();
hasdmx = connected & wdgt->featureDMX();
hasrdm = connected & wdgt->featureRDM();
}
ui->menuWidget->setEnabled(valid);
ui->actionOpen->setEnabled(!connected);
ui->actionClose->setEnabled(connected);
ui->actionDMX->setEnabled(hasdmx);
ui->actionRDM->setEnabled(hasrdm);
ui->actionParameter->setEnabled(connected);
ui->actionUserData->setEnabled(connected);
ui->actionFirmware->setEnabled(false);
}
/**
* @brief DeviceWindow::openDmxWindow
* @param index
*/
void DeviceWindow::openDmxWindow(const QModelIndex &index)
{
auto wdgt = index.data(Qt::EditRole).value<std::shared_ptr<DmxWidget>>();
auto univ = std::static_pointer_cast<DMX::Universe>(wdgt);
foreach (const auto & inspector, inspectors_)
if (inspector->universe() == univ)
return inspector->show();
auto view = new UniverseWindow(nullptr, univ);
inspectors_.insert(view);
connect(view, &QObject::destroyed, this, [this, view]() {
inspectors_.remove(view);
});
view->setWindowTitle(QString("%1 '%2'").arg(
wdgt->isEditable() ? tr("Editing") : tr("Viewing"),
wdgt->portName()));
view->setAttribute(Qt::WA_DeleteOnClose, true);
view->show();
}

View File

@ -0,0 +1,30 @@
#pragma once
#include <QMainWindow>
#include <QSortFilterProxyModel>
#include <universewindow.h>
#include <widgetmodel.h>
namespace Ui {
class DeviceWindow;
}
class DeviceWindow : public QMainWindow
{
Q_OBJECT
public:
explicit DeviceWindow(QWidget *parent = nullptr);
~DeviceWindow();
private slots:
void selectionChanged(const QModelIndex &current, const QModelIndex &previous);
void openDmxWindow(const QModelIndex &index);
private:
Ui::DeviceWindow *ui;
WidgetModel *model;
QSortFilterProxyModel *sortProxy;
QSet<UniverseWindow*> inspectors_;
};

View File

@ -0,0 +1,323 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>DeviceWindow</class>
<widget class="QMainWindow" name="DeviceWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>600</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QTableView" name="widgetView">
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<attribute name="horizontalHeaderStretchLastSection">
<bool>true</bool>
</attribute>
</widget>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>25</height>
</rect>
</property>
<widget class="QMenu" name="menuHelp">
<property name="title">
<string>Help</string>
</property>
<addaction name="actionAbout"/>
<addaction name="actionAbout_Qt"/>
</widget>
<widget class="QMenu" name="menuPort">
<property name="title">
<string>Port</string>
</property>
<addaction name="actionOpen"/>
<addaction name="actionClose"/>
<addaction name="separator"/>
<addaction name="actionRefreshList"/>
</widget>
<widget class="QMenu" name="menuWidget">
<property name="title">
<string>Widget</string>
</property>
<addaction name="actionDMX"/>
<addaction name="actionRDM"/>
<addaction name="separator"/>
<addaction name="actionParameter"/>
<addaction name="actionUserData"/>
<addaction name="separator"/>
<addaction name="actionFirmware"/>
</widget>
<widget class="QMenu" name="menuView">
<property name="title">
<string>View</string>
</property>
<widget class="QMenu" name="menuToolbars">
<property name="title">
<string>Toolbars</string>
</property>
<addaction name="actionPortToolbar"/>
<addaction name="actionWidgetToolbar"/>
</widget>
<addaction name="menuToolbars"/>
</widget>
<addaction name="menuPort"/>
<addaction name="menuWidget"/>
<addaction name="menuView"/>
<addaction name="menuHelp"/>
</widget>
<widget class="QStatusBar" name="statusbar"/>
<widget class="QToolBar" name="toolBarPort">
<property name="windowTitle">
<string>Port</string>
</property>
<property name="iconSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonFollowStyle</enum>
</property>
<attribute name="toolBarArea">
<enum>TopToolBarArea</enum>
</attribute>
<attribute name="toolBarBreak">
<bool>false</bool>
</attribute>
<addaction name="actionRefreshList"/>
<addaction name="actionOpen"/>
<addaction name="actionClose"/>
<addaction name="separator"/>
<addaction name="separator"/>
<addaction name="separator"/>
</widget>
<widget class="QToolBar" name="toolBarWidget">
<property name="windowTitle">
<string>Widget</string>
</property>
<property name="iconSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonFollowStyle</enum>
</property>
<attribute name="toolBarArea">
<enum>TopToolBarArea</enum>
</attribute>
<attribute name="toolBarBreak">
<bool>false</bool>
</attribute>
<addaction name="actionDMX"/>
<addaction name="actionRDM"/>
<addaction name="actionParameter"/>
</widget>
<action name="actionAbout">
<property name="icon">
<iconset theme="help-about">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>About</string>
</property>
</action>
<action name="actionAbout_Qt">
<property name="text">
<string>About Qt</string>
</property>
</action>
<action name="actionRefreshList">
<property name="icon">
<iconset theme="view-refresh">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>Refresh List</string>
</property>
</action>
<action name="actionOpen">
<property name="enabled">
<bool>false</bool>
</property>
<property name="icon">
<iconset theme="call-start">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>Open</string>
</property>
</action>
<action name="actionClose">
<property name="enabled">
<bool>false</bool>
</property>
<property name="icon">
<iconset theme="call-stop">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>Close</string>
</property>
</action>
<action name="actionParameter">
<property name="enabled">
<bool>false</bool>
</property>
<property name="icon">
<iconset theme="preferences-other">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>Parameters</string>
</property>
<property name="toolTip">
<string>Tx Parameters</string>
</property>
</action>
<action name="actionUserData">
<property name="enabled">
<bool>false</bool>
</property>
<property name="icon">
<iconset theme="document-properties">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>User Data</string>
</property>
</action>
<action name="actionFirmware">
<property name="enabled">
<bool>false</bool>
</property>
<property name="icon">
<iconset theme="system-run">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>Upload Firmware</string>
</property>
</action>
<action name="actionDMX">
<property name="enabled">
<bool>false</bool>
</property>
<property name="icon">
<iconset theme="edit-find">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>DMX Universe</string>
</property>
</action>
<action name="actionRDM">
<property name="enabled">
<bool>false</bool>
</property>
<property name="icon">
<iconset theme="computer">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>RDM Devices</string>
</property>
</action>
<action name="actionPortToolbar">
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
<property name="icon">
<iconset theme="drive-removable-media">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>Port</string>
</property>
<property name="toolTip">
<string>Show Port Toolbar</string>
</property>
</action>
<action name="actionWidgetToolbar">
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
<property name="icon">
<iconset theme="media-flash">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>Widget</string>
</property>
<property name="toolTip">
<string>Show Widget Toolbar</string>
</property>
</action>
</widget>
<resources/>
<connections>
<connection>
<sender>actionWidgetToolbar</sender>
<signal>triggered(bool)</signal>
<receiver>toolBarWidget</receiver>
<slot>setVisible(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>498</x>
<y>53</y>
</hint>
</hints>
</connection>
<connection>
<sender>actionPortToolbar</sender>
<signal>triggered(bool)</signal>
<receiver>toolBarPort</receiver>
<slot>setVisible(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>98</x>
<y>53</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -0,0 +1,7 @@
#include "widgetexplorer.h"
int main(int argc, char *argv[])
{
WidgetExplorer a(argc, argv);
return a.exec();
}

View File

@ -0,0 +1,47 @@
#include "metadatadialog.h"
#include "ui_metadatadialog.h"
#include <pro.h>
#include <QPushButton>
MetadataDialog::MetadataDialog(DmxWidget *device, QWidget *parent)
: QDialog(parent)
, ui(new Ui::MetadataDialog)
, dev(device)
{
ui->setupUi(this);
ui->breakSpinBox->setSingleStep(ENTTEC::Pro::DMX_BREAK_INTERVAL);
ui->breakSpinBox->setMinimum(ENTTEC::Pro::DMX_BREAK_INTERVAL * ENTTEC::Pro::DMX_BREAK_MIN);
ui->breakSpinBox->setMaximum(ENTTEC::Pro::DMX_BREAK_INTERVAL * ENTTEC::Pro::DMX_BREAK_MAX);
ui->breakSpinBox->setValue(device->txBreakTime());
ui->mabSpinBox->setSingleStep(ENTTEC::Pro::DMX_MAB_INTERVAL);
ui->mabSpinBox->setMinimum(ENTTEC::Pro::DMX_MAB_INTERVAL * ENTTEC::Pro::DMX_MAB_MIN);
ui->mabSpinBox->setMaximum(ENTTEC::Pro::DMX_MAB_INTERVAL * ENTTEC::Pro::DMX_MAB_MAX);
ui->mabSpinBox->setValue(device->txMabTime());
ui->rateSpinBox->setSingleStep(1);
ui->rateSpinBox->setMinimum(0); // zero rate for fastest tx possible
ui->rateSpinBox->setMaximum(ENTTEC::Pro::DMX_RATE_MAX);
ui->rateSpinBox->setValue(device->txRate());
connect(ui->buttonBox->button(QDialogButtonBox::RestoreDefaults), &QPushButton::clicked,
this, [this](){
ui->breakSpinBox->setValue(17 * ENTTEC::Pro::DMX_BREAK_INTERVAL); // match OEM
ui->mabSpinBox->setValue(10 * ENTTEC::Pro::DMX_MAB_INTERVAL); // match OEM
ui->rateSpinBox->setValue(40); // match OEM
});
connect(this, &QDialog::accepted, this, [this](){
dev->setTxBreakTime(ui->breakSpinBox->value());
dev->setTxMabTime(ui->mabSpinBox->value());
dev->setTxRate(ui->rateSpinBox->value());
dev->setParameters();
});
}
MetadataDialog::~MetadataDialog()
{
delete ui;
}

View File

@ -0,0 +1,22 @@
#pragma once
#include <QDialog>
#include <dmxwidget.h>
namespace Ui {
class MetadataDialog;
}
class MetadataDialog : public QDialog
{
Q_OBJECT
public:
explicit MetadataDialog(DmxWidget *device, QWidget *parent = nullptr);
~MetadataDialog();
private:
Ui::MetadataDialog *ui;
DmxWidget * dev;
};

View File

@ -0,0 +1,130 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MetadataDialog</class>
<widget class="QDialog" name="MetadataDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>467</width>
<height>151</height>
</rect>
</property>
<property name="windowTitle">
<string>Tx Parameters</string>
</property>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="1">
<widget class="QDoubleSpinBox" name="breakSpinBox">
<property name="toolTip">
<string>The BREAK generated by a transmitter is defined as a mark-to-space transition followed by a low of this duration, followed by a low to high transition.</string>
</property>
<property name="correctionMode">
<enum>QAbstractSpinBox::CorrectToNearestValue</enum>
</property>
<property name="suffix">
<string> μs</string>
</property>
<property name="decimals">
<number>1</number>
</property>
<property name="singleStep">
<double>10.670000000000000</double>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="labelMAB">
<property name="toolTip">
<string>The MARK separating the BREAK and the START Code</string>
</property>
<property name="text">
<string>MAB Time</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QDoubleSpinBox" name="mabSpinBox">
<property name="toolTip">
<string>The MARK separating the BREAK and the START Code</string>
</property>
<property name="suffix">
<string> μs</string>
</property>
<property name="decimals">
<number>1</number>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="labelRate">
<property name="text">
<string>Rate</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QSpinBox" name="rateSpinBox">
<property name="suffix">
<string> fps</string>
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::RestoreDefaults|QDialogButtonBox::Save</set>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="labelBreak">
<property name="toolTip">
<string>The BREAK indicates the start of a new packet.</string>
</property>
<property name="text">
<string>Break Time</string>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>MetadataDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>MetadataDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -0,0 +1,37 @@
#include <QDebug>
#include "widgetexplorer.h"
WidgetExplorer::WidgetExplorer(int argc, char *argv[])
: QApplication(argc, argv)
, window_(nullptr)
{
setOrganizationName("Company235");
setOrganizationDomain("company235.com");
setApplicationName(tr("widgetExplorer"));
settings_ = new QSettings(this);
// Persistant configuration
qDebug() << "Loaded application settings from" << settings_->fileName();
// Device list
window_ = new DeviceWindow();
window_->setWindowTitle(QApplication::applicationName());
window_->show();
}
WidgetExplorer::~WidgetExplorer()
{
saveSettings();
delete window_;
}
/**
* @brief SacnExplorer::saveSettings
*/
void WidgetExplorer::saveSettings()
{
qDebug() << "Saving application settings to" << settings_->fileName();
}

View File

@ -0,0 +1,21 @@
#pragma once
#include "devicewindow.h"
#include <QApplication>
#include <QSettings>
class WidgetExplorer
: public QApplication
{
Q_OBJECT
public:
explicit WidgetExplorer(int argc, char *argv[]);
virtual ~WidgetExplorer();
private:
void saveSettings();
QSettings *settings_;
DeviceWindow *window_;
};

1
lib/CMakeLists.txt Normal file
View File

@ -0,0 +1 @@
add_subdirectory(bufferstream)

View File

@ -0,0 +1,14 @@
project(bufferstream VERSION ${PROJECT_VERSION})
add_library(${PROJECT_NAME} SHARED)
add_library(LCP::BufferStream ALIAS ${PROJECT_NAME})
target_sources(${PROJECT_NAME}
PUBLIC
bufferstream.h
PRIVATE
bufferstream.cpp
)
set_target_properties(${PROJECT_NAME} PROPERTIES VERSION ${PROJECT_VERSION})
target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})

View File

@ -0,0 +1,443 @@
/*
bufferstream.cpp
Copyright (c) 2023 Kevin Matz (kevin.matz@gmail.com)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include "bufferstream.h"
/**
* @brief bufferstream::bufferstream
* @param p
* @param l
* @param o
*/
bufferstream::bufferstream(uint8_t * p, std::streamsize l, endian o)
: std::basic_streambuf<uint8_t>()
, std::basic_iostream<uint8_t>(this)
, order_(o)
{
setg(p, p, p + l);
setp(p, p + l);
}
/**
* @brief bufferstream::available
* @return
*/
size_t bufferstream::available()
{
return in_avail();
}
/**
* @brief bufferstream::data
* @return
*/
uint8_t *bufferstream::data()
{
return gptr();
}
/**
* @brief bufferstream::size
* @return
*/
size_t bufferstream::size()
{
return pptr() - pbase();
}
/**
* @brief bufferstream::base
* @return
*/
uint8_t *bufferstream::base()
{
return pbase();
}
/**
* @brief bufferstream::operator >>
* @param val
* @return
*/
bufferstream &bufferstream::operator>> (uint8_t &val)
{
val = readType<uint8_t>();
return *this;
};
/**
* @brief bufferstream::operator >>
* @param val
* @return
*/
bufferstream &bufferstream::operator>> (uint16_t &val)
{
val = readType<uint16_t>();
return *this;
};
/**
* @brief bufferstream::operator >>
* @param val
* @return
*/
bufferstream &bufferstream::operator>> (uint32_t &val)
{
val = readType<uint32_t>();
return *this;
};
/**
* @brief bufferstream::operator >>
* @param val
* @return
*/
bufferstream &bufferstream::operator>> (uint64_t &val)
{
val = readType<uint64_t>();
return *this;
};
/**
* @brief bufferstream::operator >>
* @param val
* @return
*/
bufferstream &bufferstream::operator>> (int8_t &val)
{
val = readType<int8_t>();
return *this;
}
/**
* @brief bufferstream::operator >>
* @param val
* @return
*/
bufferstream &bufferstream::operator>> (int16_t &val)
{
val = readType<int16_t>();
return *this;
}
/**
* @brief bufferstream::operator >>
* @param val
* @return
*/
bufferstream &bufferstream::operator>> (int32_t &val)
{
val = readType<int32_t>();
return *this;
}
/**
* @brief bufferstream::operator >>
* @param val
* @return
*/
bufferstream &bufferstream::operator>> (int64_t &val)
{
val = readType<int64_t>();
return *this;
}
/**
* @brief bufferstream::operator <<
* @param val
* @return
*/
bufferstream &bufferstream::operator<< (const int8_t &val)
{
put(val);
return *this;
}
/**
* @brief bufferstream::operator <<
* @param val
* @return
*/
bufferstream &bufferstream::operator<< (const int16_t &val)
{
writeType<int16_t>(val);
return *this;
}
/**
* @brief bufferstream::operator <<
* @param val
* @return
*/
bufferstream &bufferstream::operator<< (const int32_t &val)
{
writeType<int32_t>(val);
return *this;
}
/**
* @brief bufferstream::operator <<
* @param val
* @return
*/
bufferstream &bufferstream::operator<< (const int64_t &val)
{
writeType<int64_t>(val);
return *this;
}
/**
* @brief bufferstream::operator <<
* @param val
* @return
*/
bufferstream &bufferstream::operator<< (const uint8_t &val)
{
writeType<uint8_t>(val);
return *this;
}
/**
* @brief bufferstream::operator <<
* @param val
* @return
*/
bufferstream &bufferstream::operator<< (const uint16_t &val)
{
writeType<uint16_t>(val);
return *this;
}
/**
* @brief bufferstream::operator <<
* @param val
* @return
*/
bufferstream &bufferstream::operator<< (const uint32_t &val)
{
writeType<uint32_t>(val);
return *this;
}
/**
* @brief bufferstream::operator <<
* @param val
* @return
*/
bufferstream &bufferstream::operator<< (const uint64_t &val)
{
writeType<uint64_t>(val);
return *this;
}
/**
* @brief bufferstream::operator >>
* @param val
* @return
*/
bufferstream &bufferstream::operator>> (float &val)
{
val = readType<float>();
return *this;
};
/**
* @brief bufferstream::operator >>
* @param val
* @return
*/
bufferstream &bufferstream::operator>> (double &val)
{
val = readType<double>();
return *this;
};
/**
* @brief bufferstream::operator <<
* @param val
* @return
*/
bufferstream &bufferstream::operator<< (const float &val)
{
writeType<float>(val);
return *this;
}
/**
* @brief bufferstream::operator <<
* @param val
* @return
*/
bufferstream &bufferstream::operator<< (const double &val)
{
writeType<double>(val);
return *this;
}
/**
* @brief bufferstream::operator >>
* @param obj
* @return
*/
bufferstream &bufferstream::operator>> (streamable &obj)
{
obj.iStream(shared_from_this());
return *this;
}
/**
* @brief bufferstream::operator <<
* @param obj
* @return
*/
bufferstream &bufferstream::operator<< (const streamable &obj)
{
obj.oStream(shared_from_this());
return *this;
}
/**
* @brief bufferstream::operator >>
* @param str
* @return
*/
bufferstream &bufferstream::operator>> (std::string &str)
{
readString(str, 0, true); // variable length, null-terminated
return *this;
}
/**
* @brief bufferstream::operator <<
* @param str
* @return
*/
bufferstream &bufferstream::operator<< (const std::string &str)
{
writeString(str, 0, true); // variable length, null-terminated
return *this;
}
/**
* @brief bufferstream::readString
* @param str std::string to which the read string will be appended.
* @param fixed_length this many bytes will be read from the stream. If 0, all
* available bytes on the stream will be considered in appending the string.
* @param terminated If set, read the stream into the string until a 0 is read, otherwise
* the entire non-fixed length will be read. Has no effect on fixed-length strings.
*/
void bufferstream::readString(std::string &str, const int fixed_length, const bool terminated)
{
if (fixed_length) // FIXED LENGTH
{
uint8_t buffer[fixed_length];
read(buffer, fixed_length);
if (gcount() != fixed_length)
return setstate(std::ios_base::failbit);
str += std::string(reinterpret_cast<char*>(buffer));
} // end FIXED LENGTH
else // VARIABLE LENGTH
{
if (terminated) // null terminated
{
char c;
while (good())
{
c = readType<char>();
if (c == 0)
break;
str += c;
}
}
else // all remaining bytes
{
str += std::string(reinterpret_cast<char*>(data()), available());
setstate(std::ios_base::eofbit);
}
} // end VARIABLE LENGTH
}
/**
* @brief bufferstream::writeString
* @param str
* @param fixed_length write this length to the stream, padding with null
* if str is shorter than fixed_length. If 0, will write the unrestricted
* contents of str, including it's null termination.
* @param terminated If true, the last byte of fixed_length is guaranteed to
* be a 0 (null) byte.
*/
void bufferstream::writeString(const std::string &str, const size_t fixed_length,
const bool terminated)
{
// fixed_length == 0 means dynamic length.
bool fixed = fixed_length > 0 ? true : false;
// fixed length strings restricted to fixed_lengh characters
size_t maxlen = fixed ? fixed_length : str.size() + 1; // null terminated
// write no more of the string than is available
size_t wrtlen = str.size() < maxlen ? str.size() : maxlen;
// terminated fixed-length strings get a guaranteed padding byte
if (fixed && terminated && wrtlen == maxlen)
--wrtlen;
// output the correct ammount of data from the string
write(reinterpret_cast<const uint8_t*>(str.data()), wrtlen);
// output any required padding bytes
for (size_t i = wrtlen; i < maxlen; i++)
put(0);
}

View File

@ -0,0 +1,184 @@
/*
bufferstream.h
Copyright (c) 2023 Kevin Matz (kevin.matz@gmail.com)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#pragma once
#include <iostream>
#include <memory>
#include <string>
struct streamable;
/**
* @brief Input/Output stream for unsigned 8-bit buffers
*
* By default, wide types will be handled as big-endian, the network byte order.
*/
class bufferstream
: public std::enable_shared_from_this<bufferstream>
, private std::basic_streambuf<uint8_t>
, public std::basic_iostream<uint8_t>
{
public:
/**
* @brief The endian enum is not available until C++20.
*
* \note This implimentation may be compiler dependant.
*
* \note After upgrading the project to C++20 or better, drop this if favor of
* the standard implimentation in <bit.h>
*/
enum class endian
{
little = __ORDER_LITTLE_ENDIAN__,
big = __ORDER_BIG_ENDIAN__,
native = __BYTE_ORDER__,
};
bufferstream(uint8_t *p, std::streamsize l, endian o = endian::big); // default network byte-order
// input sequence
size_t available();
uint8_t *data();
// output sequence
size_t size();
uint8_t *base();
// unsigned integer
bufferstream &operator>> (uint8_t &val);
bufferstream &operator>> (uint16_t &val);
bufferstream &operator>> (uint32_t &val);
bufferstream &operator>> (uint64_t &val);
bufferstream &operator<< (const uint8_t &val);
bufferstream &operator<< (const uint16_t &val);
bufferstream &operator<< (const uint32_t &val);
bufferstream &operator<< (const uint64_t &val);
// signed integer
bufferstream &operator>> (int8_t &val);
bufferstream &operator>> (int16_t &val);
bufferstream &operator>> (int32_t &val);
bufferstream &operator>> (int64_t &val);
bufferstream &operator<< (const int8_t &val);
bufferstream &operator<< (const int16_t &val);
bufferstream &operator<< (const int32_t &val);
bufferstream &operator<< (const int64_t &val);
// floating point
bufferstream &operator>> (float &val);
bufferstream &operator>> (double &val);
bufferstream &operator<< (const float &val);
bufferstream &operator<< (const double &val);
// stream objects
bufferstream &operator>> (streamable &obj);
bufferstream &operator<< (const streamable &obj);
// null-terminated strings
bufferstream &operator>> (std::string &str);
bufferstream &operator<< (const std::string &str);
// strings
void readString(std::string& str, const int fixed_length = 0, const bool terminated = true);
void writeString(const std::string& str, const size_t fixed_length = 0,
const bool terminated = true);
// reinterpreted i/o
/**
* @brief readType
* @return T
*/
template<typename T>
T readType()
{
T ret = 0;
uint_fast8_t width = sizeof(T);
if (in_avail() < width)
setstate(std::ios_base::failbit);
if (!good())
return ret;
if (width == 1)
{
ret = static_cast<T>(get());
}
else
{
auto bytes = reinterpret_cast<uint8_t*>(&ret);
if (order_ == endian::native)
for (int_fast8_t i = 0; i < width; i++)
bytes[i] = get();
else
for (int_fast8_t i = width; --i >= 0;)
bytes[i] = get();
}
if (!in_avail())
setstate(std::ios_base::eofbit);
return ret;
}
/**
* @brief writeType
* @param val
*/
template<typename T>
void writeType (const T& val)
{
uint_fast8_t width = sizeof(T);
if (width == 1) {
put(static_cast<uint8_t>(val));
return;
}
auto bytes = reinterpret_cast<const uint8_t*>(&val);
if (order_ == endian::native)
for (int_fast8_t i = 0; i < width; i++)
put(bytes[i]);
else
for (int_fast8_t i = width; --i >= 0;)
put(bytes[i]);
}
private:
endian order_;
};
/**
* @brief The base for data objects that can be read from and written to a bufferstream.
*/
struct streamable
{
virtual ~streamable() {};
/**
* @brief streamSize
* @return length (count of octets) on the wire
*/
virtual size_t streamSize() const = 0;
/**
* @brief fill structure data from input stream
*/
virtual void iStream(std::shared_ptr<bufferstream>) = 0;
/**
* @brief write structure data to output stream
*/
virtual void oStream(std::shared_ptr<bufferstream>) const = 0;
};

1
platform/CMakeLists.txt Normal file
View File

@ -0,0 +1 @@
add_subdirectory(qt)

View File

@ -0,0 +1,11 @@
project(${PROJECT_NAME}-qt VERSION ${PROJECT_VERSION})
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
# network and gui interface for an sACN node
add_subdirectory(sacn)
# Enttec DMX-USB-Pro serial driver
add_subdirectory(dmxwidget)

View File

@ -0,0 +1,25 @@
project(${PROJECT_NAME}-dmxwidget VERSION ${PROJECT_VERSION})
find_package(Qt6 COMPONENTS SerialPort REQUIRED)
add_library(${PROJECT_NAME} SHARED)
add_library(LCP::Qt::DmxWidget ALIAS ${PROJECT_NAME})
target_sources(${PROJECT_NAME}
PUBLIC
dmxwidget.h
widgetmodel.h
PRIVATE
dmxwidget.cpp
widgetmodel.cpp
)
target_link_libraries(${PROJECT_NAME}
PUBLIC
ENTTEC::Pro
PRIVATE
Qt::SerialPort
)
set_target_properties(${PROJECT_NAME} PROPERTIES VERSION ${PROJECT_VERSION})
target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})

View File

@ -0,0 +1,197 @@
/*
dmxwidget.cpp
Copyright (c) 2023 Kevin Matz (kevin.matz@gmail.com)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include "dmxwidget.h"
#include <QDebug>
DmxWidget::DmxWidget(QObject *parent)
: QObject{parent}
, port_(new QSerialPort(this))
{
connect(this, &DmxWidget::serialDataRead, this, &DmxWidget::parseMessageBuffer);
connect(port_, &QSerialPort::errorOccurred, this, &DmxWidget::serialErrorOccured);
connect(port_, &QSerialPort::readyRead, this, [this]() {
message_rx_buffer_.append(port_->readAll());
emit serialDataRead();
});
}
DmxWidget::~DmxWidget()
{
DmxWidget::close();
}
void DmxWidget::open()
{
auto wait_for = [this](bool &reply) {
reply = false; // reset the reply state variale
for (int i = 0; i < 10; i++) { // serial reply may not be first in the read buffer
port_->waitForReadyRead(100); // wait for the port read buffer to have content
if (reply)
break; // break the loop if a reply was received
}
return reply;
};
if (!port_->open(QIODeviceBase::ReadWrite)) // open the port
return;
ENTTEC::Widget::open(); // open the base class widget
getSerialNumber(); // request a serial number
getParameters(0); // get the metadata while we're here
if (wait_for(reply_serial)) // widget is found if a serial number reply is received
{
setModeBridge(); // widget defaults to bridge, so sync our state
emit connectedChanged(isConnected());
}
else
close();
}
void DmxWidget::close()
{
port_->close();
ENTTEC::Widget::close();
emit connectedChanged(isConnected());
}
/**
* @brief DmxWidget::parseMessageBuffer
*/
void DmxWidget::parseMessageBuffer()
{
OperatingMode other_side = USBunknown;
switch (usb_mode) {
case ENTTEC::Widget::USBdevice:
other_side = USBhost;
break;
case ENTTEC::Widget::USBhost:
other_side = USBdevice;
break;
default:
break;
}
uint16_t length;
const int fixed_length = 5; // start + label + 2xlength + end
auto fast_forward = [this](int offset = 0) {
if (message_rx_buffer_.isEmpty())
return false;
if (offset == 0)
offset = message_rx_buffer_.indexOf(ENTTEC::Pro::START_DELIMITER);
if (offset < 0)
message_rx_buffer_.clear(); // start delimiter not found
else if (offset > 0)
message_rx_buffer_.remove(0, offset); // delimiter now at start of buffer
return (message_rx_buffer_.size() >= fixed_length
&& (uint8_t)message_rx_buffer_[0] == ENTTEC::Pro::START_DELIMITER);
};
while(fast_forward())
{
// read and check the data length
length = message_rx_buffer_[2] | message_rx_buffer_[3] << 8;
if (message_rx_buffer_.size() < length + fixed_length)
return; // message in buffer is incomplete
// look for the end delimeter
if ((uint8_t)message_rx_buffer_[length + fixed_length - 1] != ENTTEC::Pro::END_DELIMITER) {
fast_forward(length + fixed_length - 1); // discard corrupted bytes
continue;
}
// make a new message data
auto msg = MessageDataFactory((MESSAGE_LABEL)message_rx_buffer_[1], other_side);
// fill it with data
std::shared_ptr<bufferstream> stream(new bufferstream(
reinterpret_cast<uint8_t*>(message_rx_buffer_.data()+fixed_length-1),
length, bufferstream::endian::little));
msg->iStream(stream);
// ship it
routeRxMessage(msg);
// discard processed bytes
fast_forward(length + fixed_length);
}
}
void DmxWidget::sendMessage(std::shared_ptr<ENTTEC::Pro::MessageData> msg) const
{
uint16_t length = msg->streamSize();
const int fixed_length = 5;
char buffer[length + fixed_length];
std::shared_ptr<bufferstream> stream(new bufferstream(reinterpret_cast<uint8_t*>(buffer),
sizeof(buffer),
bufferstream::endian::little));
*stream << ENTTEC::Pro::START_DELIMITER;
*stream << msg->label;
*stream << length;
msg->oStream(stream);
*stream << ENTTEC::Pro::END_DELIMITER;
port_->write(buffer, sizeof(buffer));
}
/**
* @brief DmxWidget::serialErrorOccured
* @param error
*/
void DmxWidget::serialErrorOccured(QSerialPort::SerialPortError error)
{
switch (error) {
case QSerialPort::WriteError:
case QSerialPort::ReadError:
case QSerialPort::ResourceError:
case QSerialPort::TimeoutError:
close();
break;
default:
break;
}
}
/**
* @brief DmxWidget::availableWidgets
* @return
*/
QList<std::shared_ptr<DmxWidget>> DmxWidget::availableWidgets()
{
QList<std::shared_ptr<DmxWidget>> wdgts;
// evaluate all system serial ports
const auto ports = QSerialPortInfo::availablePorts();
for (const QSerialPortInfo &port : ports) {
// try to open a widget on each port
auto widget = std::make_shared<DmxWidget>();
widget->setPort(port);
widget->open();
if (widget->isConnected())
wdgts.push_back(widget); // add valid widgets to the list
else
widget->close();
}
return wdgts;
}

View File

@ -0,0 +1,86 @@
/*
qwidget.h
Copyright (c) 2023 Kevin Matz (kevin.matz@gmail.com)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#pragma once
#include <QFlags>
#include <QObject>
#include <QSerialPort>
#include <QSerialPortInfo>
#include <widget.h>
using DMX::DeviceClass;
using ENTTEC::Pro::FIRMWARE_TYPE;
using ENTTEC::Pro::MESSAGE_LABEL;
using ENTTEC::Pro::DMX_RX_MODE;
/**
* @brief The DmxWidget class
*/
class DmxWidget
: public QObject
, public ENTTEC::Widget
{
Q_OBJECT
public:
explicit DmxWidget(QObject *parent = nullptr);
virtual ~DmxWidget();
virtual void open() override;
virtual void close() override;
void setPort(const QSerialPortInfo &port) { port_->setPort(port); emit portNameChanged(); }
void setPortName(const QString &name) { port_->setPortName(name); emit portNameChanged(); }
QString portName() const { return port_->portName(); }
QSerialPortInfo portInfo() const { return QSerialPortInfo(*port_); }
DeviceClass deviceClass() const { return device_class_; }
Q_ENUM(DeviceClass)
Q_ENUM(FIRMWARE_TYPE)
Q_ENUM(MESSAGE_LABEL)
Q_ENUM(DMX_RX_MODE)
Q_DECLARE_FLAGS(RxErrors, ENTTEC::Pro::RX_ERROR)
Q_PROPERTY(bool isConnected READ isConnected NOTIFY connectedChanged)
Q_PROPERTY(QString portName READ portName NOTIFY portNameChanged)
static QList<std::shared_ptr<DmxWidget>> availableWidgets();
signals:
void connectedChanged(bool);
void portNameChanged();
void serialDataRead();
protected:
virtual void sendMessage(std::shared_ptr<ENTTEC::Pro::MessageData>) const override;
private:
QSerialPort *port_;
QByteArray message_rx_buffer_;
private slots:
void parseMessageBuffer();
void serialErrorOccured(QSerialPort::SerialPortError);
};
Q_DECLARE_OPERATORS_FOR_FLAGS(DmxWidget::RxErrors)

View File

@ -0,0 +1,162 @@
#include <QMetaEnum>
#include "widgetmodel.h"
WidgetModel::WidgetModel(QObject *parent)
: QAbstractItemModel(parent)
{
rescanPorts();
}
WidgetModel::~WidgetModel() {
for(auto &wdgt: widgets_)
disconnect(wdgt.get(), nullptr, this, nullptr);
}
QVariant WidgetModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (role == Qt::DisplayRole && orientation == Qt::Horizontal) {
switch (static_cast<Column>(section)) {
case PortName:
return tr("Port");
case Manufacturer:
return tr("Manufacturer");
case Description:
return tr("Description");
case SerialNumber:
return tr("Serial");
case FirmwareVersion:
return tr("Firmware");
case OperatingMode:
return tr("Mode");
default:
return QVariant();
}
}
return QVariant();
}
QModelIndex WidgetModel::index(int row, int column, const QModelIndex &parent) const
{
if (parent.isValid() || row >= widgets_.size())
return QModelIndex();
return createIndex(row, column, widgets_.at(row).get());
}
QModelIndex WidgetModel::parent(const QModelIndex &index) const
{
Q_UNUSED(index)
return QModelIndex();
}
int WidgetModel::rowCount(const QModelIndex &parent) const
{
if (parent.isValid())
return 0;
return widgets_.size();
}
int WidgetModel::columnCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return QMetaEnum::fromType<Column>().keyCount();
}
QVariant WidgetModel::data(const QModelIndex &index, int role) const
{
if (!checkIndex(index, CheckIndexOption::IndexIsValid))
return QVariant();
switch (role) {
case Qt::DisplayRole:
{
auto wdgt = widgets_.at(index.row());
auto info = wdgt->portInfo();
switch (static_cast<Column>(index.column()))
{
case PortName:
return info.portName();
case Manufacturer:
return info.manufacturer();
case Description:
return info.description();
case SerialNumber:
return wdgt->serialNumber();
case FirmwareVersion:
return QString::number(wdgt->firmwareVersion() >> 8)
+ "."
+ QString::number(wdgt->firmwareVersion() & 0xff);
case OperatingMode:
switch (wdgt->deviceClass()) {
case DMX::CONTROLLER:
return tr("Controller");
case DMX::RECEIVER:
return tr("Receiver");
case DMX::RESPONDER:
return tr("Responder");
default:
return QVariant();
}
default:
return QVariant();
}
}
case Qt::TextAlignmentRole:
switch (static_cast<Column>(index.column())) {
case SerialNumber:
case FirmwareVersion:
return Qt::AlignCenter;
default:
return QVariant();
}
case Qt::EditRole:
switch (static_cast<Column>(index.column())) {
case OperatingMode:
/// \todo Create a combobox delegate to change the DMX Widget operating mode.
default:
return QVariant::fromValue(widgets_.at(index.row()));
}
default:
return QVariant();
}
}
/**
* @brief WidgetModel::rescanPorts
*/
void WidgetModel::rescanPorts()
{
// remove disconnected widgets
for(qsizetype i = 0; i < widgets_.size();)
if (!widgets_.at(i)->isConnected())
{
beginRemoveRows(QModelIndex(), i, i+1);
disconnect(widgets_.at(i).get(), nullptr, this, nullptr);
widgets_.remove(i);
endRemoveRows();
}
else
i++;
// look for new widgets
auto newWidgets = DmxWidget::availableWidgets();
if (!newWidgets.isEmpty())
{
beginInsertRows(QModelIndex(), widgets_.size(), widgets_.size()+newWidgets.size()-1);
widgets_.append(newWidgets);
endInsertRows();
for(auto &wdgt: newWidgets)
connect(wdgt.get(), &DmxWidget::connectedChanged, this, [this](){
// refresh the whole view. Could be optimized by looking for this widget in the rows.
emit dataChanged(createIndex(0,0), createIndex(rowCount(),columnCount()));
});
}
}

View File

@ -0,0 +1,44 @@
#pragma once
#include "dmxwidget.h"
#include <QAbstractItemModel>
class WidgetModel : public QAbstractItemModel
{
Q_OBJECT
public:
explicit WidgetModel(QObject *parent = nullptr);
virtual ~WidgetModel();
// Header:
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
// Basic functionality:
QModelIndex index(int row, int column,
const QModelIndex &parent = QModelIndex()) const override;
QModelIndex parent(const QModelIndex &index) const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
/// @brief The Column enum
enum Column {
PortName,
Manufacturer,
Description,
SerialNumber,
FirmwareVersion,
OperatingMode,
};
Q_ENUM(Column)
public slots:
void rescanPorts();
private:
QList<std::shared_ptr<DmxWidget>> widgets_;
};

View File

@ -0,0 +1,39 @@
project(${PROJECT_NAME}-sacn VERSION ${PROJECT_VERSION})
find_package(Qt6 COMPONENTS Gui Network Widgets REQUIRED)
add_library(${PROJECT_NAME} SHARED)
add_library(LCP::Qt::sACN ALIAS ${PROJECT_NAME})
target_sources(${PROJECT_NAME}
PUBLIC
multiversemodel.h
qsacnnode.h
qsacnuniverse.h
universemodel.h
universestatuswidget.h
universewindow.h
PRIVATE
multiverseitem.h
multiverseitem.cpp
multiversemodel.cpp
qsacn_global.h
qsacnnode.cpp
qsacnuniverse.cpp
universemodel.cpp
universestatuswidget.cpp
universewindow.cpp
universewindow.ui
)
target_link_libraries(${PROJECT_NAME}
PUBLIC
LCP::sACN
PRIVATE
Qt::Gui
Qt::Network
Qt::Widgets
)
set_target_properties(${PROJECT_NAME} PROPERTIES VERSION ${PROJECT_VERSION})
target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})

View File

@ -0,0 +1,335 @@
#include "multiverseitem.h"
#include "multiversemodel.h"
#include "universestatuswidget.h"
#include <QFont>
#include <QMetaEnum>
#include <QMetaType>
#include <QObject>
/**
* @brief MultiverseItem::MultiverseItem
* @param universe
* @param parent
*/
MultiverseItem::MultiverseItem(MultiverseItem* parent,
QSacnUniverse* universe,
QSacnDiscoveredUniverse *discovery)
: universe_(universe)
, discovery_(discovery)
, parentItem_(parent)
, override_data_(QVariant())
, mIndicator(nullptr)
{
createChildren();
if(parent)
parent->appendChild(this);
if (universe)
mIndicator = new UniverseStatusWidget(nullptr, universe->get());
}
/**
* @brief MultiverseItem::~MultiverseItem
*/
MultiverseItem::~MultiverseItem()
{
delete mIndicator;
qDeleteAll(childItems_);
}
/**
* @brief MultiverseItem::appendChild
* @param item
*/
void MultiverseItem::appendChild(MultiverseItem* item)
{
childItems_.append(item);
}
/**
* @brief MultiverseItem::removeChild
* @param child
*/
void MultiverseItem::removeChild(MultiverseItem* child)
{
for (int i = 0; i < childItems_.size(); ++i)
if (childItems_.at(i) == child)
childItems_.remove(i);
}
/**
* @brief MultiverseItem::removeChildren
*/
void MultiverseItem::removeChildren()
{
qDeleteAll(childItems_);
childItems_.clear();
}
/**
* @brief MultiverseItem::setOverrideData
* @param data
*/
void MultiverseItem::setOverrideData(QVariant data)
{
override_data_ = data;
}
/**
* @brief MultiverseItem::createChildren
*/
void MultiverseItem::createChildren()
{
if (!universe_)
return;
foreach (const auto & metadata, universe_->sources())
{
auto universe = universe_->sourceUniverse(metadata);
if (universe)
new MultiverseItem(this, universe);
}
}
/**
* @brief MultiverseItem::child
* @param row
* @return
*/
MultiverseItem* MultiverseItem::child(int row) const
{
return (row < 0 || row >= childItems_.size()) ? nullptr : childItems_.at(row);
}
/**
* @brief MultiverseItem::childCount
* @return
*/
int MultiverseItem::childCount() const
{
return childItems_.count();
}
/**
* @brief MultiverseItem::childRow
* @param universe
* @return
*/
int MultiverseItem::childRow(int column, const QVariant &data) const
{
for (int row = 0; row < childItems_.size(); row++)
{
auto childData = childItems_.at(row)->data(column, Qt::EditRole);
if (data == childData)
return row;
}
return -1;
}
/**
* @brief MultiverseItem::row
* @return
*/
int MultiverseItem::row() const
{
if (parentItem_)
return parentItem_->childItems_.indexOf(const_cast<MultiverseItem*>(this));
return 0;
}
/**
* @brief MultiverseItem::data
* @param column
* @return
*/
QVariant MultiverseItem::data(int column, int role) const
{
switch (role) {
case Qt::DisplayRole:
{
if (!universe_ && !discovery_)
{
if (column)
return QVariant();
return QString("%1 (%2)").arg(
override_data_.toString(),
QString::number(childCount()));
}
if (universe_)
{
switch (static_cast<MultiverseModel::Column>(column))
{
case MultiverseModel::Universe:
return universe_->number();
case MultiverseModel::Status:
return mIndicator? mIndicator->toolTip() : QVariant();
case MultiverseModel::Priority:
{
auto priority = universe_->priority();
return priority <= 200 ? priority : QVariant();
}
case MultiverseModel::Channels:
{
auto active = universe_->activeSlots();
return active ? active - 1 : QVariant();
}
case MultiverseModel::SourceName:
return universe_->sourceName();
default:
return QVariant();
}
}
if (discovery_)
{
switch (static_cast<MultiverseModel::Column>(column))
{
case MultiverseModel::Universe:
return discovery_->universe;
case MultiverseModel::SourceName:
return QString::fromUtf8(discovery_->description.c_str());
default:
return QVariant();
}
}
}
case Qt::FontRole:
{
QFont font;
if (!universe_ && !discovery_)
{
font.setBold(true);
return font;
}
if (universe_ && !universe_->isEditable())
{
switch (universe_->status())
{
case sACN::Universe::DMX_NULL:
font.setItalic(true);
return font;
// case sACN::Universe::DMX_ACTIVE:
// font.setWeight(QFont::Medium);
// return font;
case sACN::Universe::DMX_LOST:
font.setWeight(QFont::Light);
return font;
case sACN::Universe::sACN_TERMINATED:
font.setWeight(QFont::Light);
font.setItalic(true);
return font;
default:
return QVariant();
}
}
return QVariant();
}
case Qt::EditRole:
if (universe_)
{
if (!universe_->isEditable())
return QVariant::fromValue(universe_);
switch (static_cast<MultiverseModel::Column>(column))
{
case MultiverseModel::Priority:
return universe_->priority();
default:
return QVariant::fromValue(universe_);
}
}
if (discovery_)
return QVariant::fromValue(discovery_);
return data(column, Qt::DisplayRole);
default:
return QVariant();
}
}
/**
* @brief MultiverseItem::setData
* @param column
* @param value
* @param role
* @return
*/
bool MultiverseItem::setData(int column, const QVariant &value, int role)
{
if (!universe_->isEditable())
return false;
if (role != Qt::EditRole)
return false;
switch (column)
{
case MultiverseModel::Priority:
{
if (!value.canConvert<uint>())
return false;
auto p = value.toUInt();
if (p < 0 || p > 200)
return false;
universe_->setPriority(p);
return true;
}
default:
return false;
}
}
/**
* @brief MultiverseItem::flags
* @param column
* @return
*/
Qt::ItemFlags MultiverseItem::flags(int column, Qt::ItemFlags base) const
{
if (!universe_ && !discovery_)
{
if (column)
return Qt::NoItemFlags;
return base & ~Qt::ItemIsSelectable;
}
switch (static_cast<MultiverseModel::Column>(column))
{
case MultiverseModel::Universe:
case MultiverseModel::Status:
case MultiverseModel::Channels:
case MultiverseModel::SourceName:
return base;
case MultiverseModel::Priority:
if (universe_ && universe_->isEditable())
return base | Qt::ItemIsEditable;
else
return base;
default:
return Qt::NoItemFlags;
}
}
/**
* @brief MultiverseItem::parentItem
* @return
*/
MultiverseItem * MultiverseItem::parentItem() const
{
return parentItem_;
}

View File

@ -0,0 +1,46 @@
#pragma once
#include "extended.h"
#include <QVariant>
#include <QVector>
class QSacnUniverse; // forward declare
using QSacnDiscoveredUniverse = sACN::EXTENDED::DISCOVERY::discoveredUniverse;
Q_DECLARE_METATYPE(QSacnDiscoveredUniverse*)
/**
* @brief The MultiverseItem class
*/
class MultiverseItem
{
public:
explicit MultiverseItem(MultiverseItem* parentItem = nullptr,
QSacnUniverse* universe = nullptr,
QSacnDiscoveredUniverse* discovery = nullptr);
virtual ~MultiverseItem();
void appendChild(MultiverseItem* child);
void removeChild(MultiverseItem* child);
void removeChildren();
void setOverrideData(QVariant data);
void createChildren();
MultiverseItem * child(int row) const;
int childCount() const;
int childRow(int column, const QVariant &data) const;
QVariant data(int column, int role = Qt::DisplayRole) const;
bool setData(int column, const QVariant &value, int role = Qt::EditRole);
Qt::ItemFlags flags(int column, Qt::ItemFlags base) const;
int row() const;
MultiverseItem * parentItem() const;
private:
QVector<MultiverseItem*> childItems_;
QSacnUniverse* universe_;
QSacnDiscoveredUniverse* discovery_;
MultiverseItem* parentItem_;
QVariant override_data_;
QWidget *mIndicator;
};

View File

@ -0,0 +1,321 @@
#include "multiverseitem.h"
#include "multiversemodel.h"
#include "qsacnnode.h"
#include <QMetaEnum>
/**
* @brief MultiverseModel::MultiverseModel
* @param parent
* @param node
*/
MultiverseModel::MultiverseModel(QObject *parent, QSacnNode *node)
: QAbstractItemModel(parent)
, node_(node)
, rootItem_(new MultiverseItem())
{
// create items for the category headers
QMetaEnum categories = QMetaEnum::fromType<Catagory>();
for (int itr = 0; itr < categories.keyCount(); itr++)
{
auto header = new MultiverseItem(rootItem_);
header->setOverrideData(QString(categories.key(itr)));
auto idx = MultiverseModel::index(itr, 0, QModelIndex());
categoryIndexes.insert(static_cast<Catagory>(categories.value(itr)), idx);
}
// connect node changes
connect(node, &QSacnNode::discoveryUpdates,
this, &MultiverseModel::doDiscovery);
connect(node, &QSacnNode::subscribing,
this, [this](QSacnUniverse *universe){
insert_(categoryIndexes.value(Receiver), universe);
});
connect(node, &QSacnNode::unsubscribing,
this, [this](QSacnUniverse *universe){
remove_(categoryIndexes.value(Receiver), universe);
});
connect(node, &QSacnNode::creating,
this, [this](QSacnUniverse *universe){
insert_(categoryIndexes.value(Source), universe);
});
connect(node, &QSacnNode::terminating,
this, [this](QSacnUniverse *universe){
remove_(categoryIndexes.value(Source), universe);
});
}
/**
* @brief MultiverseModel::~MultiverseModel
*/
MultiverseModel::~MultiverseModel()
{
delete rootItem_;
}
/**
* @brief MultiverseModel::getItem
* @param index
* @return
*/
MultiverseItem * MultiverseModel::getItem(const QModelIndex &index) const
{
if (index.isValid())
{
auto item = static_cast<MultiverseItem*>(index.internalPointer());
if (item)
return item;
}
return rootItem_;
}
/**
* @brief MultiverseModel::headerData
* @param section
* @param orientation
* @param role
* @return
*/
QVariant MultiverseModel::headerData(int section, Qt::Orientation orientation,
int role) const
{
if (role == Qt::DisplayRole && orientation == Qt::Horizontal) {
switch (static_cast<Column>(section)) {
case Universe:
return tr("Universe");
case Status:
return tr("Status");
case Priority:
return tr("Priority");
case Channels:
return tr("Channels");
case SourceName:
return tr("Source Name");
}
}
return QVariant();
}
/**
* @brief MultiverseModel::index
* @param row
* @param column
* @param parent
* @return
*/
QModelIndex MultiverseModel::index(int row, int column,
const QModelIndex &parent) const
{
if (parent.isValid() && parent.column() != 0)
return QModelIndex();
MultiverseItem *parentItem = getItem(parent);
if (!parentItem)
return QModelIndex();
auto childItem = parentItem->child(row);
if (childItem)
return createIndex(row, column, childItem);
return QModelIndex();
}
/**
* @brief MultiverseModel::parent
* @param index
* @return
*/
QModelIndex MultiverseModel::parent(const QModelIndex &index) const
{
if (!checkIndex(index, CheckIndexOption::IndexIsValid |
CheckIndexOption::DoNotUseParent))
return QModelIndex();
auto childItem = getItem(index);
auto parentItem = childItem ? childItem->parentItem() : nullptr;
if (parentItem == rootItem_ || !parentItem)
return QModelIndex();
return createIndex(parentItem->row(), 0, parentItem);
}
/**
* @brief MultiverseModel::rowCount
* @param parent
* @return
*/
int MultiverseModel::rowCount(const QModelIndex &parent) const
{
auto parentItem = getItem(parent);
return parentItem ? parentItem->childCount() : 0;
}
/**
* @brief MultiverseModel::columnCount
* @param parent
* @return
*/
int MultiverseModel::columnCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return QMetaEnum::fromType<Column>().keyCount();
}
/**
* @brief MultiverseModel::data
* @param index
* @param role
* @return
*/
QVariant MultiverseModel::data(const QModelIndex &index, int role) const
{
if (!checkIndex(index, CheckIndexOption::IndexIsValid))
return QVariant();
return getItem(index)->data(index.column(), role);
}
/**
* @brief MultiverseModel::setData
* @param index
* @param value
* @param role
* @return
*/
bool MultiverseModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (!checkIndex(index, CheckIndexOption::IndexIsValid))
return false;
if (getItem(index)->setData(index.column(), value, role))
{
emit dataChanged(index, index, QVector<int>() << role);
return true;
}
return false;
}
/**
* @brief MultiverseModel::flags
* @param index
* @return
*/
Qt::ItemFlags MultiverseModel::flags(const QModelIndex &index) const
{
if (!checkIndex(index, CheckIndexOption::IndexIsValid))
return Qt::NoItemFlags;
auto item = getItem(index);
if (!item)
return Qt::NoItemFlags;
return item->flags(index.column(), QAbstractItemModel::flags(index));
}
/**
* @brief MultiverseModel::insert
* @param universe
* @param parent
* @return
*/
void MultiverseModel::insert_(const QModelIndex &parent,
QSacnUniverse* universe,
QSacnDiscoveredUniverse *discovery)
{
auto item = getItem(parent);
beginInsertRows(parent, item->childCount(), item->childCount());
auto child = new MultiverseItem(item, universe, discovery);
endInsertRows();
auto index = QPersistentModelIndex(createIndex(item->childCount() - 1, 0,
child));
auto refreshRow = [this] (QPersistentModelIndex index) {
auto begin = index.sibling(index.row(), 0);
auto end = begin.siblingAtColumn(columnCount() - 1);
emit dataChanged(begin, end);
};
connect(universe, &QSacnUniverse::dataChanged,
this, [refreshRow, index](){ refreshRow(index); });
connect(universe, &QSacnUniverse::statusChanged,
this, [refreshRow, index](){ refreshRow(index); });
connect(universe, &QSacnUniverse::sourceListChanged,
this, [this, universe, child, index, refreshRow]() {
// remove the old children
beginRemoveRows(index, 0, child->childCount() - 1);
child->removeChildren();
endRemoveRows();
// add the new children
beginInsertRows(index, 0, universe->sources().size() - 1);
child->createChildren();
endInsertRows();
// refresh row of new child when data arrives
for (int i = 0; i < child->childCount(); i++)
{
auto grandchild = child->child(i);
auto index = QPersistentModelIndex(createIndex(i, 0, grandchild));
auto data = grandchild->data(Column::Universe, Qt::EditRole);
if (data.metaType().id() != qMetaTypeId<QSacnUniverse*>())
return;
auto universe = data.value<QSacnUniverse*>();
connect(universe, &QSacnUniverse::dataChanged,
this, [refreshRow, index](){ refreshRow(index); });
connect(universe, &QSacnUniverse::statusChanged,
this, [refreshRow, index](){ refreshRow(index); });
}
});
}
/**
* @brief MultiverseModel::remove
* @param parent
* @param data
*/
void MultiverseModel::remove_(const QModelIndex &parent,
const QSacnUniverse *universe)
{
auto item = static_cast<MultiverseItem*>(parent.internalPointer());
auto row = item->childRow(Column::Universe, QVariant::fromValue(universe));
if (row < 0)
return;
auto child = item->child(row);
if (!child)
return;
beginRemoveRows(parent, row, row);
item->removeChild(child);
endRemoveRows();
}
void MultiverseModel::doDiscovery()
{
auto parentIndex = categoryIndexes.value(MultiverseModel::Discovery);
auto parent = static_cast<MultiverseItem*>(parentIndex.internalPointer());
beginRemoveRows(parentIndex, 0, parent->childCount() - 1);
parent->removeChildren();
endRemoveRows();
beginInsertRows(parentIndex, 0, node_->discovered.size() - 1);
for (auto& discovery : node_->discovered)
new MultiverseItem(parent, nullptr, discovery.get());
endInsertRows();
}

View File

@ -0,0 +1,68 @@
#pragma once
#include <QAbstractItemModel>
#include "multiverseitem.h"
#include "qsacn_global.h"
#include "qsacnuniverse.h"
class QSacnNode; // forward declare node class
/**
* @brief The MultiverseModel class
*/
class QT_EXPORT MultiverseModel
: public QAbstractItemModel
{
Q_OBJECT
public:
explicit MultiverseModel(QObject *parent = nullptr,
QSacnNode *node = nullptr);
virtual ~MultiverseModel();
// QAbstractItemModel overrides:
QVariant headerData(int section, Qt::Orientation orientation,
int role = Qt::DisplayRole) const override;
QModelIndex index(int row, int column,
const QModelIndex &parent = QModelIndex()) const override;
QModelIndex parent(const QModelIndex &index) const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index,
int role = Qt::DisplayRole) const override;
bool setData(const QModelIndex &index, const QVariant &value,
int role = Qt::EditRole) override;
Qt::ItemFlags flags(const QModelIndex &index) const override;
/// @brief The Catagory enum
enum Catagory {
Discovery,
Receiver,
Source
};
Q_ENUM(Catagory)
/// @brief The Column enum
enum Column {
Universe,
Status,
Priority,
Channels,
SourceName
};
Q_ENUM(Column)
private slots:
void doDiscovery();
private:
MultiverseItem * getItem(const QModelIndex &index) const;
QMap<MultiverseModel::Catagory, QModelIndex> categoryIndexes;
QSacnNode *node_;
MultiverseItem *rootItem_;
void insert_(const QModelIndex &parent,
QSacnUniverse *universe = nullptr,
QSacnDiscoveredUniverse *discovery = nullptr);
void remove_(const QModelIndex &parent,
const QSacnUniverse *universe);
};

View File

@ -0,0 +1,9 @@
#pragma once
#include <QtCore/qglobal.h>
#if defined(QsACN_LIBRARY)
# define QT_EXPORT Q_DECL_EXPORT
#else
# define QT_EXPORT Q_DECL_IMPORT
#endif

View File

@ -0,0 +1,302 @@
#include "qsacnnode.h"
#include <QDebug>
#include <QMetaEnum>
#include <QNetworkDatagram>
#include <QNetworkInterface>
/**
* @brief QSacnNode::QSacnNode
* @param parent
* @param cid
* @param fctn
*/
QSacnNode::QSacnNode(QObject *parent, QUuid cid, QString fctn, bool ipv4, bool ipv6)
: Component(UUID::uuid(cid.toString().toStdString()), fctn.toStdString(), ipv4, ipv6)
, QObject(parent)
, rx_socket_(new QUdpSocket(this))
, tx_socket_(new QUdpSocket(this))
{
rx_socket_->bind(QHostAddress::AnyIPv4, sACN::ACN_SDT_MULTICAST_PORT);
tx_socket_->bind(QHostAddress::AnyIPv4);
connect(rx_socket_, &QUdpSocket::readyRead,
this, &QSacnNode::udpReceive);
sACN::Receiver::onDiscovered([this](){ emit discoveryUpdates(); });
}
/**
* @brief QSacnNode::~QSacnNode
*/
QSacnNode::~QSacnNode()
{
if (Receiver::discoveryEnabled())
QSacnNode::unsubscribe(sACN::E131_DISCOVERY_UNIVERSE);
foreach (const auto num, rx_universes.keys())
QSacnNode::unsubscribe(num);
foreach (const auto num, tx_universes.keys())
QSacnNode::terminate(num);
}
void QSacnNode::setIPv4(const bool enable)
{
Node::setIPv4(enable);
QList<QHostAddress> groups;
foreach (const auto & universe, rx_universes)
groups.append(IPv4MulticastAddress(universe->number()));
if (discoveryEnabled())
groups.append(IPv4MulticastAddress(sACN::E131_DISCOVERY_UNIVERSE));
if (enable)
{
qDebug() << "Enabling IPv4";
foreach (const auto & ip, groups)
{
qDebug() << "Joining IGMP Group" << ip.toString();
for (QNetworkInterface &iface : QNetworkInterface::allInterfaces())
rx_socket_->joinMulticastGroup(ip, iface);
}
}
else
{
qDebug() << "Disabling IPv4";
foreach (const auto & ip, groups)
{
qDebug() << "Leaving IGMP Group" << ip.toString();
for (QNetworkInterface &iface : QNetworkInterface::allInterfaces())
rx_socket_->leaveMulticastGroup(ip, iface);
}
}
}
void QSacnNode::setIPv6(const bool enable)
{
Node::setIPv6(enable);
QList<QHostAddress> groups;
foreach (const auto & universe, rx_universes)
groups.append(IPv6MulticastAddress(universe->number()));
if (discoveryEnabled())
groups.append(IPv6MulticastAddress(sACN::E131_DISCOVERY_UNIVERSE));
if (enable)
{
qDebug() << "Enabling IPv6";
foreach (const auto & ip, groups)
{
qDebug() << "Joining MLD Group" << ip.toString();
for (QNetworkInterface &iface : QNetworkInterface::allInterfaces())
rx_socket_->joinMulticastGroup(ip, iface);
}
}
else
{
qDebug() << "Disabling IPv6";
foreach (const auto & ip, groups)
{
qDebug() << "Leaving MLD Group" << ip.toString();
for (QNetworkInterface &iface : QNetworkInterface::allInterfaces())
rx_socket_->leaveMulticastGroup(ip, iface);
}
}
}
/**
* @brief QSacnNode::subscribe
* @param num
*/
void QSacnNode::subscribe(const uint16_t num)
{
if (Receiver::universe(num)) // already subscribed
return;
switch (num) {
case sACN::E131_DISCOVERY_UNIVERSE:
{
if (discoveryEnabled())
return;
qDebug() << "Enabling sACN Discovery";
}
break;
default:
{
qDebug() << "Subscribing to universe" << QString::number(num);
Receiver::subscribe(num);
rx_universes.emplace(num, new QSacnUniverse(this, Receiver::universe(num)));
emit subscribing(rx_universes.value(num));
}
break;
}
if (enable_IPv4)
{
qDebug() << "Joining IGMP Group" << IPv4MulticastAddress(num).toString();
for (QNetworkInterface &iface : QNetworkInterface::allInterfaces())
rx_socket_->joinMulticastGroup(IPv4MulticastAddress(num), iface);
}
if (enable_IPv6)
{
qDebug() << "Joining MLD Group" << IPv6MulticastAddress(num).toString();
for (QNetworkInterface &iface : QNetworkInterface::allInterfaces())
rx_socket_->joinMulticastGroup(IPv6MulticastAddress(num), iface);
}
}
/**
* @brief QSacnNode::unsubscribe
* @param num
*/
void QSacnNode::unsubscribe(const uint16_t num)
{
switch (num) {
case sACN::E131_DISCOVERY_UNIVERSE:
{
if (!discoveryEnabled())
return;
qDebug() << "Disabling sACN Discovery";
}
break;
default:
{
if (Receiver::universe(num)) {
qDebug() << "Unsubscribing from universe " << QString::number(num);
emit unsubscribing(rx_universes.value(num));
rx_universes.take(num)->deleteLater();
Receiver::unsubscribe(num);
}
}
break;
}
if (enable_IPv4)
{
qDebug() << "Leaving IGMP Group" << IPv4MulticastAddress(num).toString();
for (QNetworkInterface &iface : QNetworkInterface::allInterfaces())
rx_socket_->leaveMulticastGroup(IPv4MulticastAddress(num), iface);
}
if (enable_IPv6)
{
qDebug() << "Leaving MLD Group" << IPv6MulticastAddress(num).toString();
for (QNetworkInterface &iface : QNetworkInterface::allInterfaces())
rx_socket_->leaveMulticastGroup(IPv6MulticastAddress(num), iface);
}
}
/**
* @brief QSacnNode::create
* @param num
*/
void QSacnNode::create(const uint16_t num)
{
if (Source::universe(num)) // already created
return;
qDebug() << "Creating universe " << QString::number(num);
Source::create(num);
tx_universes.emplace(num, new QSacnUniverse(this, Source::universe(num)));
emit creating(tx_universes.value(num));
}
/**
* @brief QSacnNode::terminate
* @param num
*/
void QSacnNode::terminate(const uint16_t num)
{
if (Source::universe(num)) {
qDebug() << "Terminating universe " << QString::number(num);
Source::terminate(num);
emit terminating(tx_universes.value(num));
delete tx_universes.take(num);
}
}
/**
* @brief QSacnNode::UdpPayloadReceiver
*/
void QSacnNode::udpReceive()
{
while (rx_socket_->hasPendingDatagrams())
{
auto datagram = rx_socket_->receiveDatagram();
// expecting IANA registered Session Data Transport traffic
if (datagram.destinationPort() != sACN::ACN_SDT_MULTICAST_PORT)
return;
// only receive over configured protocols
switch (datagram.senderAddress().protocol()) {
case QUdpSocket::IPv4Protocol:
if (!enable_IPv4)
return;
break;
case QUdpSocket::IPv6Protocol:
if (!enable_IPv6)
return;
break;
default:
return;
}
// wrap a PDU i/o stream around the QNetworkDatagram data buffer
auto data = datagram.data();
auto stream = std::make_shared<bufferstream>(
reinterpret_cast<uint8_t*>(data.data()), data.length());
UdpPayloadReceiver(stream);
}
}
/**
* @brief QSacnNode::sendUDP
* @param stream
* @param ip
*/
void QSacnNode::sendUDP(const ACN::PDU::Stream stream,
const ACN::SDT::UDP::ipAddress& ip) const
{
QHostAddress addr;
switch (ip.type) {
case ACN::SDT::SDT_ADDR_IPV4:
if (!enable_IPv4)
return;
addr = QHostAddress(ip.address.ipv4.value);
break;
case ACN::SDT::SDT_ADDR_IPV6:
if (!enable_IPv6)
return;
addr = QHostAddress(ip.address.ipv6.bytes);
break;
default:
return;
}
tx_socket_->writeDatagram(reinterpret_cast<char*>(stream->base()), stream->size(), addr, ip.port);
}
/**
* @brief QSacnNode::sendTCP
* @param stream
* @param ip
*/
void QSacnNode::sendTCP(const ACN::PDU::Stream stream,
const ACN::SDT::UDP::ipAddress& ip) const
{
Q_UNUSED(stream)
Q_UNUSED(ip)
qDebug() << "sACN uses UDP only. Stop trying to send TCP!";
}

View File

@ -0,0 +1,69 @@
#pragma once
#include "qsacn_global.h"
#include "qsacnuniverse.h"
#include "node.h"
#include <QHash>
#include <QHostAddress>
#include <QUdpSocket>
#include <QUuid>
/**
* @brief The QSacnNode class
*/
class QT_EXPORT QSacnNode
: public QObject
, public sACN::Node
{
Q_OBJECT
public:
explicit QSacnNode(QObject *parent = nullptr,
QUuid = QUuid::createUuid(), QString fctn = "OpenLCP QSacnNode",
bool ipv4 = true, bool ipv6 = true);
virtual ~QSacnNode();
// sACN::RLP::Component
void sendUDP(const ACN::PDU::Stream, const ACN::SDT::UDP::ipAddress&) const override;
void sendTCP(const ACN::PDU::Stream, const ACN::SDT::UDP::ipAddress&) const override;
public slots:
void setIPv4(const bool) override;
void setIPv6(const bool) override;
void subscribe(const uint16_t) override;
void unsubscribe(const uint16_t) override;
void create(const uint16_t) override;
void terminate(const uint16_t) override;
signals:
void discoveryUpdates();
void subscribing(QSacnUniverse*);
void unsubscribing(QSacnUniverse*);
void creating(QSacnUniverse*);
void terminating(QSacnUniverse*);
private:
void udpReceive();
QUdpSocket * rx_socket_;
QUdpSocket * tx_socket_;
QHash<uint16_t, QSacnUniverse*> rx_universes;
QHash<uint16_t, QSacnUniverse*> tx_universes;
public:
/// \cite sACN 9.3.1 Allocation of IPv4 Multicast Addresses
///
/// Multicast addresses are from the IPv4 Local Scope.
static QHostAddress IPv4MulticastAddress(const uint16_t universe) {
sACN::ipAddress ip = sACN::IPv4MulticastAddress(universe);
return QHostAddress(ip.address.ipv4.value);
};
/// \cite sACN 9.3.2 Allocation of IPv6 Multicast Addresses
static QHostAddress IPv6MulticastAddress(const uint16_t universe) {
sACN::ipAddress ip = sACN::IPv6MulticastAddress(universe);
return QHostAddress(ip.address.ipv6.bytes);
};
};

View File

@ -0,0 +1,312 @@
#include "qsacnuniverse.h"
/**
* @brief QSacnUniverse::QSacnUniverse
* @param parent
* @param universe
*/
QSacnUniverse::QSacnUniverse(QObject *parent, std::shared_ptr<sACN::Universe> universe)
: QObject(parent)
, universe_(universe)
, status_watchdog_(new QTimer(this))
{
if (!universe)
return;
data_change_token = universe_->onDataChange([this](DMX::Universe*){emit dataChanged();});
status_change_token = universe_->onStatusChange([this](DMX::Universe*){emit statusChanged();});
if (universe->arbitrator())
list_change_token = universe_->arbitrator()->onSourceListChange([this](DMX::Universe*){syncSources();});
// set the status watchdog to update the status if the universe
// isn't showing frequent activity
status_watchdog_->callOnTimeout([this](){universe_->status();});
status_watchdog_->setInterval(DMX::E111_DATA_LOSS_TIMEOUT);
status_watchdog_->start();
connect(this, &QSacnUniverse::dataChanged,
this, [this](){status_watchdog_->start();});
connect(this, &QSacnUniverse::statusChanged,
this, [this](){status_watchdog_->start();});
};
/**
* @brief QSacnUniverse::~QSacnUniverse
*/
QSacnUniverse::~QSacnUniverse()
{
// delete QSacnUniverse sources
qDeleteAll(sources_);
}
/**
* @brief QSacnUniverse::description
* @return
*/
const QString QSacnUniverse::sourceName() const
{
auto metadata = universe_->metadata();
return metadata ? QString::fromUtf8(metadata->source_name.c_str()) : QString();
}
/**
* @brief QSacnUniverse::number
* @return
*/
uint16_t QSacnUniverse::number() const
{
auto metadata = universe_->metadata();
return metadata ? metadata->universe : 0;
}
/**
* @brief QSacnUniverse::priority
* @return
*/
uint8_t QSacnUniverse::priority() const
{
auto metadata = universe_->metadata();
return metadata ? metadata->priority : 0;
}
/**
* @brief QSacnUniverse::rxRate
* @return
*/
double QSacnUniverse::rxRate() const
{
return universe_ ? universe_->rxRate() : -1;
}
/**
* @brief QSacnUniverse::status
* @return
*/
uint8_t QSacnUniverse::status() const
{
return universe_ ? universe_->status() : sACN::Universe::DMX_NULL;
}
/**
* @brief QSacnUniverse::slot
* @param slot
* @return
*/
uint8_t QSacnUniverse::slot(const uint16_t slot) const
{
return universe_ ? universe_->slot(slot) : 0;
}
/**
* @brief QSacnUniverse::isEditable
* @return
*/
bool QSacnUniverse::isEditable() const
{
return universe_ ? universe_->isEditable() : false;
}
/**
* @brief QSacnUniverse::hasSources
* @return
*/
bool QSacnUniverse::hasSources() const
{
return universe_ ? universe_->hasSources() : false;
}
/**
* @brief QSacnUniverse::sources
* @return
*/
const QList<sACN::DATA::data_header> QSacnUniverse::sources() const
{
QList<sACN::DATA::data_header> ret;
if (!universe_)
return ret;
for (const auto & src : universe_->sources())
ret.append(src);
return ret;
}
/**
* @brief QSacnUniverse::sourceUniverse
* @param metadata
* @return
*/
QSacnUniverse* QSacnUniverse::sourceUniverse(const sACN::DATA::data_header& metadata) const
{
if (!universe_)
return nullptr;
if (!sources_.count(metadata))
return nullptr;
return sources_.value(metadata);
}
/**
* @brief QSacnUniverse::activeSlots
* @return
*/
uint16_t QSacnUniverse::activeSlots() const
{
return universe_ ? universe_->activeSlots() : 0;
}
/**
* @brief QSacnUniverse::getHoldLastLook
* @return
*/
bool QSacnUniverse::getHoldLastLook() const
{
return universe_ && universe_->arbitrator()
? universe_->arbitrator()->getHoldLastLook()
: false;
}
/**
* @brief QSacnUniverse::getMergeMode
* @return
*/
sACN::UniverseArbitrator::MergeMode QSacnUniverse::getMergeMode() const
{
return universe_ && universe_->arbitrator()
? universe_->arbitrator()->getMergeMode()
: sACN::UniverseArbitrator::MERGE_OTHER;
}
/**
* @brief QSacnUniverse::get
* @return
*/
std::shared_ptr<sACN::Universe> QSacnUniverse::get() const
{
return universe_;
}
/**
* @brief QSacnUniverse::setHoldLastLook
* @param state
*/
void QSacnUniverse::setHoldLastLook(bool state)
{
if (isEditable() && universe_ && universe_->arbitrator())
universe_->arbitrator()->setHoldLastLook(state);
}
/**
* @brief QSacnUniverse::setMergeMode
* @param mode
*/
void QSacnUniverse::setMergeMode(const sACN::UniverseArbitrator::MergeMode mode)
{
if (isEditable() && universe_ && universe_->arbitrator())
universe_->arbitrator()->setMergeMode(mode);
}
/**
* @brief QSacnUniverse::setPriority
* @param p
*/
void QSacnUniverse::setPriority(uint8_t p)
{
if (!isEditable())
return;
universe_->metadata()->priority = p;
emit dataChanged();
}
/**
* @brief QSacnUniverse::setSyncAddress
* @param a
*/
void QSacnUniverse::setSyncAddress(uint16_t a)
{
if (!isEditable())
return;
universe_->metadata()->sync_address = a;
emit dataChanged();
}
/**
* @brief QSacnUniverse::setValue
* @param addr
* @param level
*/
void QSacnUniverse::setValue (const uint16_t addr, const uint8_t level)
{
setValue(addr, 1, &level);
}
/**
* @brief QSacnUniverse::setValue
* @param addr
* @param size
* @param profile
*/
void QSacnUniverse::setValue (const uint16_t addr, const uint16_t size,
const uint8_t* profile)
{
if (!isEditable())
return;
universe_->sACN::Universe::setValue(addr, size, profile);
emit dataChanged();
}
/**
* @brief QSacnUniverse::syncSources
*/
void QSacnUniverse::syncSources()
{
// create a new list of sources
QMap<sACN::DATA::data_header, QSacnUniverse*> newSources;
auto headers = universe_->sources();
for (const auto & metadata : headers)
{
if (sources_.contains(metadata))
// move the QSacnUniverse to the new list
newSources.insert(metadata, sources_.take(metadata));
else
{ // make a new QSacnUniverse to add to the new list
auto universe = universe_->arbitrator()->sourceUniverse(metadata);
newSources.insert(metadata, new QSacnUniverse(this, universe));
}
}
// delete any remaining sources, ie. not moved to the new list
qDeleteAll(sources_);
// accept the new list
sources_ = newSources;
emit sourceListChanged();
}

View File

@ -0,0 +1,66 @@
#pragma once
#include <QObject>
#include <QList>
#include <QMap>
#include <QTimer>
#include "qsacn_global.h"
#include "universe.h"
/**
* @brief The QSacnUniverse class
*
* Rather than QSacnNode reimplemnting the sACN::Node api for a QSacnUniverse that uses
* sACN::Universe as a base class, the QSacnUniverse class wraps a sACN::Universe.
*/
class QT_EXPORT QSacnUniverse
: public QObject
{
Q_OBJECT
public:
explicit QSacnUniverse(QObject *parent = nullptr,
std::shared_ptr<sACN::Universe> universe = nullptr);
virtual ~QSacnUniverse();
const QString sourceName() const;
uint16_t number() const;
uint8_t priority() const;
double rxRate() const;
uint8_t status() const;
uint8_t slot(const uint16_t) const;
uint16_t activeSlots() const;
bool isEditable() const;
bool hasSources() const;
const QList<sACN::DATA::data_header> sources() const;
QSacnUniverse* sourceUniverse(const sACN::DATA::data_header&) const;
bool getHoldLastLook() const;
sACN::UniverseArbitrator::MergeMode getMergeMode() const;
std::shared_ptr<sACN::Universe> get() const;
public slots:
void setHoldLastLook(bool state);
void setMergeMode(const sACN::UniverseArbitrator::MergeMode);
void setPriority(uint8_t p);
void setSyncAddress(uint16_t a);
void setValue (const uint16_t addr, const uint8_t level);
void setValue (const uint16_t addr, const uint16_t size,
const uint8_t* profile);
void syncSources();
signals:
void dataChanged();
void sourceListChanged();
void statusChanged();
private:
std::shared_ptr<sACN::Universe> universe_;
QMap<sACN::DATA::data_header, QSacnUniverse*> sources_;
std::shared_ptr<void> data_change_token;
std::shared_ptr<void> list_change_token;
std::shared_ptr<void> status_change_token;
QTimer *status_watchdog_;
};
Q_DECLARE_METATYPE(QSacnUniverse*)

View File

@ -0,0 +1,213 @@
#include "universemodel.h"
#include <QBrush>
#include <QFont>
#include <QChar>
#include <QMetaEnum>
/**
* @brief UniverseModel::UniverseModel
* @param parent
* @param universe
*/
UniverseModel::UniverseModel(QObject *parent, std::shared_ptr<DMX::Universe> universe)
: QAbstractTableModel(parent)
, universe_(universe)
, data_mode_(Decimal)
{
if (!universe_)
return;
data_change_token = universe_->onDataChange([this] (DMX::Universe*) {
emit dataChanged(index(0,0), index(rowCount(), columnCount()));
});
}
/**
* @brief UniverseModel::headerData
* @param section
* @param orientation
* @param role
* @return
*/
QVariant UniverseModel::headerData(int section, Qt::Orientation orientation, int role) const
{
switch (role)
{
case Qt::DisplayRole:
switch (orientation)
{
case Qt::Horizontal:
return section + 1;
case Qt::Vertical:
return section * 10;
}
default:
return QVariant();
}
}
/**
* @brief UniverseModel::rowCount
* @param parent
* @return
*/
int UniverseModel::rowCount(const QModelIndex &parent) const
{
if (parent.isValid())
return 0;
return 52;
}
/**
* @brief UniverseModel::columnCount
* @param parent
* @return
*/
int UniverseModel::columnCount(const QModelIndex &parent) const
{
if (parent.isValid())
return 0;
return 10;
}
/**
* @brief UniverseModel::data
* @param index
* @param role
* @return
*/
QVariant UniverseModel::data(const QModelIndex &index, int role) const
{
if (!checkIndex(index, CheckIndexOption::IndexIsValid))
return QVariant();
if (!universe_)
return QVariant();
uint16_t slot = (index.row() * 10) + (index.column() + 1);
switch (role) {
case Qt::DisplayRole:
{
if (slot == 0 || slot > 512)
return QVariant();
switch (data_mode_)
{
case Decimal:
return universe_->slot(slot);
case Hex:
return QString("%1")
.arg(universe_->slot(slot), 2, 16, QChar('0'))
.toUpper();
case Percent:
return QString("%1%").arg((universe_->slot(slot) / 255.0F) * 100,
0, 'f', 0, '0');
}
return QVariant();
}
case Qt::FontRole:
return QFont("monospace");
case Qt::TextAlignmentRole:
return int(Qt::AlignCenter | Qt::AlignVCenter);
case Qt::ForegroundRole:
{
if (slot > universe_->activeSlots() - 1)
return QBrush(Qt::gray);
return QVariant();
}
default:
return QVariant();
}
}
/**
* @brief UniverseModel::setData
* @param index
* @param value
* @param role
* @return
*/
bool UniverseModel::setData(const QModelIndex &index, const QVariant &value,
int role)
{
if (!universe_->isEditable())
return false;
if (data(index, role) == value)
return false;
uint16_t slot = (index.row() * 10) + (index.column() + 1);
uint data;
bool ok;
switch (data_mode_)
{
case Decimal:
data = value.toUInt(&ok);
break;
case Hex:
data = value.toString().toUInt(&ok, 16);
break;
case Percent:
data = 255 * (value.toDouble(&ok) / 100);
break;
}
if (!ok || data > 255)
return false;
universe_->setValue(slot, data);
emit dataChanged(index, index, QVector<int>() << role);
return true;
}
/**
* @brief UniverseModel::flags
* @param index
* @return
*/
Qt::ItemFlags UniverseModel::flags(const QModelIndex &index) const
{
if (!checkIndex(index, CheckIndexOption::IndexIsValid))
return Qt::NoItemFlags;
if (!universe_)
return Qt::NoItemFlags;
uint16_t slot = (index.row() * 10) + (index.column() + 1);
if (slot == 0 || slot > 512)
return Qt::NoItemFlags;
auto f = QAbstractItemModel::flags(index);
return universe_->isEditable() ? f | Qt::ItemIsEditable : f;
}
/**
* @brief UniverseModel::setDataMode
* @param mode
*/
void UniverseModel::setDataMode(const UniverseModel::data_modes mode)
{
data_mode_ = mode;
emit dataChanged(index(0,0), index(rowCount(), columnCount()));
}
/**
* @brief UniverseModel::dataMode
* @return
*/
UniverseModel::data_modes UniverseModel::dataMode()
{
return data_mode_;
}

View File

@ -0,0 +1,41 @@
#pragma once
#include <QAbstractTableModel>
#include <universe.h>
class UniverseModel
: public QAbstractTableModel
{
Q_OBJECT
public:
explicit UniverseModel(QObject *parent = nullptr,
std::shared_ptr<DMX::Universe> universe = nullptr);
// Model overrides:
QVariant headerData(int section, Qt::Orientation orientation,
int role = Qt::DisplayRole) const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index,
int role = Qt::DisplayRole) const override;
bool setData(const QModelIndex &index, const QVariant &value,
int role = Qt::EditRole) override;
Qt::ItemFlags flags(const QModelIndex& index) const override;
/// @brief The data_modes enum
enum data_modes {
Decimal,
Hex,
Percent
};
Q_ENUM(data_modes)
data_modes dataMode();
public slots:
void setDataMode(const UniverseModel::data_modes mode);
private:
std::shared_ptr<DMX::Universe> universe_;
data_modes data_mode_;
std::shared_ptr<void> data_change_token;
};

View File

@ -0,0 +1,155 @@
/*
universestatuswidget.cpp
Copyright (c) 2022 Kevin Matz (kevin.matz@gmail.com)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include "universestatuswidget.h"
#include <QPainter>
#include <QTimer>
UniverseStatusWidget::UniverseStatusWidget(QWidget *parent,
std::shared_ptr<DMX::Universe> universe)
: QWidget{parent}
, mUniverse(universe)
, mBlinkState(true)
{
setMinimumWidth(16);
setSizePolicy(QSizePolicy::Minimum, QSizePolicy::MinimumExpanding);
// initial state
setToolTip(statusText());
statusChanged();
// status feedback
data_change_token = mUniverse->onDataChange([this] (DMX::Universe*) {
dataActivity();
});
status_change_token = mUniverse->onStatusChange([this] (DMX::Universe*) {
statusChanged();
setToolTip(statusText());
});
}
/**
* @brief StatusIndicatorWidget::statusChanged
*/
void UniverseStatusWidget::statusChanged()
{
switch (mUniverse->status()) {
case DMX::Universe::DMX_LOST:
{
mBlinkState = !mBlinkState;
QTimer::singleShot(250, this, [this]() {
statusChanged();
});
break;
}
default:
mBlinkState = true;;
}
update();
}
/**
* @brief StatusIndicatorWidget::dataActivity
*/
void UniverseStatusWidget::dataActivity()
{
if (!mBlinkState)
return; // already in a blink state
QTimer::singleShot(100, this, [this]() {
mBlinkState = true;
update();
});
mBlinkState = false;
update();
}
/**
* @brief StatusIndicatorWidget::statusText
* @return
*/
const QString UniverseStatusWidget::statusText() const
{
switch (mUniverse->status())
{
case DMX::Universe::DMX_NULL:
return tr("Pending");
case DMX::Universe::DMX_ACTIVE:
return tr("Active");
case DMX::Universe::DMX_LOST:
return tr("Lost");
case DMX::Universe::RX_TIMEOUT:
return tr("Missing");
case sACN::Universe::sACN_TERMINATED:
return tr("Terminated");
default:
return tr("Undefined");
}
}
void UniverseStatusWidget::paintEvent(QPaintEvent *)
{
QColor stroke(127,127,127); // neutral
QColor fill(64,64,64); // grey
switch (mUniverse->status()) {
case DMX::Universe::DMX_NULL:
fill.setRgb(250,250,250); // white
break;
case DMX::Universe::DMX_ACTIVE:
fill.setRgb(32,255,64); // green
break;
case DMX::Universe::DMX_LOST:
fill.setRgb(255,255,64); // yellow
break;
case DMX::Universe::RX_TIMEOUT:
fill.setRgb(255,128,32); // amber
break;
case sACN::Universe::sACN_TERMINATED:
fill.setRgb(255,32,32); // red
break;
default:
break;
}
fill.setAlpha(mBlinkState ? 255 : 42);
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
painter.setPen(stroke);
painter.setBrush(fill);
const int size = qMin(width(), height());
switch (mUniverse->status()) {
case DMX::Universe::DMX_NULL:
case DMX::Universe::DMX_ACTIVE:
case DMX::Universe::DMX_LOST:
case DMX::Universe::RX_TIMEOUT:
case sACN::Universe::sACN_TERMINATED:
default:
painter.drawEllipse((width()-size)/2, (height()-size)/2, size, size); // circle
}
}

View File

@ -0,0 +1,51 @@
/*
universestatuswidget.h
Copyright (c) 2022 Kevin Matz (kevin.matz@gmail.com)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#pragma once
#include <QWidget>
#include <universe.h>
class UniverseStatusWidget
: public QWidget
{
Q_OBJECT
public:
explicit UniverseStatusWidget(QWidget *parent = nullptr,
std::shared_ptr<DMX::Universe> universe = nullptr);
const QString statusText() const;
protected:
void paintEvent(QPaintEvent *) override;
private slots:
void statusChanged();
void dataActivity();
private:
std::shared_ptr<DMX::Universe> mUniverse;
bool mBlinkState;
std::shared_ptr<void> data_change_token;
std::shared_ptr<void> status_change_token;
};

View File

@ -0,0 +1,140 @@
/*
universewindow.h
Copyright (c) 2022 Kevin Matz (kevin.matz@gmail.com)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include "universewindow.h"
#include "ui_universewindow.h"
#include "universemodel.h"
#include "universestatuswidget.h"
#include <QActionGroup>
#include <QComboBox>
#include <QLabel>
#include <QMetaEnum>
/**
* @brief UniverseWindow::UniverseWindow
* @param parent
*/
UniverseWindow::UniverseWindow(QWidget *parent, std::shared_ptr<DMX::Universe> universe)
: QMainWindow(parent)
, ui(new Ui::UniverseWindow())
, universe_(universe)
, status_watchdog_(new QTimer(this))
{
ui->setupUi(this);
// set the status watchdog to update the status if the universe
// isn't showing frequent activity
status_watchdog_->callOnTimeout([this](){update_status_();});
status_watchdog_->setInterval(DMX::E111_DATA_LOSS_TIMEOUT / 10);
status_watchdog_->start();
auto model = new UniverseModel(this, universe);
ui->tableView->setModel(model);
// update the status bar whenever the universe refreshes
connect(model, &UniverseModel::dataChanged, this, [this]() {
update_status_();
status_watchdog_->start();
});
// add data format combobox to toolbar
auto formatLabel = new QLabel(ui->toolBarDMX);
ui->toolBarDMX->addWidget(formatLabel);
formatLabel->setText(tr("Data Format") + ": ");
auto dataModes = new QComboBox(ui->toolBarDMX);
ui->toolBarDMX->addWidget(dataModes);
const QMetaEnum types = QMetaEnum::fromType<UniverseModel::data_modes>();
for (int i=0; i < types.keyCount(); ++i)
dataModes->addItem(types.key(i));
auto formatGroup = new QActionGroup(ui->centralwidget);
formatGroup->addAction(ui->actionViewDecimal);
formatGroup->addAction(ui->actionViewHex);
formatGroup->addAction(ui->actionViewPercent);
connect(dataModes, &QComboBox::currentTextChanged,
this, [this, model, types, formatGroup](QString mode) {
auto data_mode = static_cast<UniverseModel::data_modes>(types.keyToValue(mode.toLocal8Bit()));
model->setDataMode(data_mode);
formatGroup->blockSignals(true);
switch (data_mode) {
case UniverseModel::Decimal:
ui->actionViewDecimal->setChecked(true);
break;
case UniverseModel::Hex:
ui->actionViewHex->setChecked(true);
break;
case UniverseModel::Percent:
ui->actionViewPercent->setChecked(true);
break;
default:
break;
}
formatGroup->blockSignals(false);
});
connect(ui->actionViewDecimal, &QAction::toggled, this, [dataModes](bool state) {
if (state)
dataModes->setCurrentIndex((int)UniverseModel::Decimal);
});
connect(ui->actionViewHex, &QAction::toggled, this, [dataModes](bool state) {
if (state)
dataModes->setCurrentIndex((int)UniverseModel::Hex);
});
connect(ui->actionViewPercent, &QAction::toggled, this, [dataModes](bool state) {
if (state)
dataModes->setCurrentIndex((int)UniverseModel::Percent);
});
// status indicator
auto indicator = new UniverseStatusWidget(this, universe_);
ui->statusbar->addPermanentWidget(indicator);
update_status_();
}
/**
* @brief UniverseWindow::~UniverseWindow
*/
UniverseWindow::~UniverseWindow()
{
delete ui;
}
/**
* @brief UniverseWindow::updateStatusBar
*/
void UniverseWindow::update_status_()
{
if (!universe_->isEditable())
{
// status bar
ui->statusbar->clearMessage();
QString message = QString("%1 " + tr("Hz")).arg(
QString::number(universe_->rxRate()));
ui->statusbar->showMessage(message, 2500);
}
}

View File

@ -1,7 +1,7 @@
/*
extended.h
universewindow.h
Copyright (c) 2020 Kevin Matz (kevin.matz@gmail.com)
Copyright (c) 2022 Kevin Matz (kevin.matz@gmail.com)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@ -23,35 +23,35 @@
*/
#pragma once
#include "sacn.h"
#include <universe.h>
#include <QMainWindow>
#include <QTimer>
namespace SACN {
using namespace ACN;
QT_BEGIN_NAMESPACE
namespace Ui {
class UniverseWindow;
}
QT_END_NAMESPACE
namespace EXTENDED {
using std::uint8_t;
using std::uint16_t;
class UniverseWindow
: public QMainWindow
{
Q_OBJECT
public:
explicit UniverseWindow(QWidget *parent = nullptr,
std::shared_ptr<DMX::Universe> universe = nullptr);
virtual ~UniverseWindow();
// 6.3 E1.31 Synchronization Packet Framing Layer
struct frame_sync_header : PDU::pdu_header {
uint8_t sequence_number;
uint16_t sync_address;
uint8_t reserved[2];
/**
* @brief universe
* @return
*/
std::shared_ptr<DMX::Universe> universe() { return universe_; }
private:
Ui::UniverseWindow *ui;
std::shared_ptr<DMX::Universe> universe_;
void update_status_();
QTimer *status_watchdog_;
};
// 6.4 E1.31 Universe Discovery Packet Framing Layer
struct frame_discovery_header : PDU::pdu_header {
uint8_t source_name[64];
uint8_t reserved[4];
};
// Table 8-9: E1.31 Universe Discovery Packet Universe Discovery Layer
struct discovery_list_header : PDU::pdu_header {
uint8_t page;
uint8_t last_page;
};
} // EXTENDED
} // SACN

View File

@ -0,0 +1,169 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>UniverseWindow</class>
<widget class="QMainWindow" name="UniverseWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>800</height>
</rect>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QTableView" name="tableView">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="verticalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOn</enum>
</property>
<property name="sizeAdjustPolicy">
<enum>QAbstractScrollArea::AdjustToContents</enum>
</property>
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
<attribute name="horizontalHeaderDefaultSectionSize">
<number>60</number>
</attribute>
</widget>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>25</height>
</rect>
</property>
<property name="nativeMenuBar">
<bool>false</bool>
</property>
<widget class="QMenu" name="menuView">
<property name="title">
<string>View</string>
</property>
<widget class="QMenu" name="menuToolbars">
<property name="title">
<string>Toolbars</string>
</property>
<addaction name="actionToolbarDMX"/>
</widget>
<addaction name="menuToolbars"/>
</widget>
<widget class="QMenu" name="menuDMX">
<property name="title">
<string>DMX</string>
</property>
<widget class="QMenu" name="menuData_Format">
<property name="title">
<string>Data Format</string>
</property>
<addaction name="actionViewDecimal"/>
<addaction name="actionViewHex"/>
<addaction name="actionViewPercent"/>
</widget>
<addaction name="menuData_Format"/>
</widget>
<addaction name="menuDMX"/>
<addaction name="menuView"/>
</widget>
<widget class="QStatusBar" name="statusbar"/>
<widget class="QToolBar" name="toolBarDMX">
<property name="windowTitle">
<string>DMX</string>
</property>
<attribute name="toolBarArea">
<enum>TopToolBarArea</enum>
</attribute>
<attribute name="toolBarBreak">
<bool>false</bool>
</attribute>
</widget>
<action name="actionViewHex">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Hex</string>
</property>
</action>
<action name="actionViewDecimal">
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
<property name="text">
<string>Decimal</string>
</property>
</action>
<action name="actionViewPercent">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Percent</string>
</property>
</action>
<action name="actionToolbarDMX">
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
<property name="text">
<string>DMX</string>
</property>
</action>
</widget>
<resources/>
<connections>
<connection>
<sender>actionToolbarDMX</sender>
<signal>toggled(bool)</signal>
<receiver>toolBarDMX</receiver>
<slot>setVisible(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>199</x>
<y>33</y>
</hint>
</hints>
</connection>
<connection>
<sender>toolBarDMX</sender>
<signal>visibilityChanged(bool)</signal>
<receiver>actionToolbarDMX</receiver>
<slot>setChecked(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>199</x>
<y>33</y>
</hint>
<hint type="destinationlabel">
<x>-1</x>
<y>-1</y>
</hint>
</hints>
</connection>
</connections>
</ui>

32
protocol/CMakeLists.txt Normal file
View File

@ -0,0 +1,32 @@
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_AUTOUIC OFF)
set(CMAKE_AUTOMOC OFF)
set(CMAKE_AUTORCC OFF)
if (CMAKE_BUILD_TYPE STREQUAL "Debug")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -D_GLIBCXX_DEBUG")
endif()
add_compile_definitions(BUILT_WITH_CMAKE)
configure_file(config.h.in config.h)
# Entertainment Services and Technology Association
add_subdirectory(esta)
# Internet Engineering Task Force
add_subdirectory(ietf)
# Artistic License
add_subdirectory(artistic)
# Enttec
add_subdirectory(enttec)
# OpenSoundControl
add_subdirectory(osc)
# Controller Interface Transport Protocol
add_subdirectory(citp)

52
protocol/README.md Normal file
View File

@ -0,0 +1,52 @@
# OpenLCP Protocols
Copies of the specifications and standards are copyright their authors, and may be
downloaded from the publishers website.
## ESTA
[Entertainment Services and Technology Association ](esta.org)
[ESTA Technical Standards Program](tsp.esta.org)
[TSP Published Documents](tsp.esta.org/tsp/documents/published_docs.php)
## Artistic License
[Artistic License](artisticlicence.com)
[Art-Net](art-net.org.uk)
[Art-Net 4 Specification](artisticlicence.com/WebSiteMaster/User%20Guides/art-net.pdf)
## Open Sound Control
[OSC](opensoundcontrol.stanford.edu)
[Specification 1.0](opensoundcontrol.stanford.edu/spec-1_0.html)
[Specification 1.1](opensoundcontrol.stanford.edu/spec-1_1.html)
## ENTTEC
[ENTTEC](www.enttec.com)
[DMX USB Pro](www.enttec.com/product/lighting-communication-protocols/dmx512/dmx-usb-pro/)
[API V1.44](cdn.enttec.com/pdf/assets/70304/70304_DMX_USB_PRO_API.pdf)
## CITP
The [CITP Protocol](www.lewlight.com/citp-protocol) website appears to
have gone dormant, but the text is still available in the
[author's BitBucket](bitbucket.org/lars_wernlund/citp/downloads/)
## IETF
[The Internet Engineering Task Force](www.ietf.org)
[Various RFC documents](www.ietf.org/standards/rfcs/)

View File

@ -0,0 +1,9 @@
@String{ARTISTIC = "Artistic Licence Holdings Ltd."}
@manual{ARTNET,
key = "ARTNET",
title = "Specification for the Art-Net 4 Ethernet Communication Protocol",
organization = ARTISTIC,
year = 2021,
address = "Bovey Tracey, England"
}

View File

@ -0,0 +1,2 @@
# Art-Net 4
add_subdirectory(artnet)

View File

@ -0,0 +1,36 @@
project(${PROJECT_NAME}-artnet VERSION ${PROJECT_VERSION})
add_library(${PROJECT_NAME} SHARED)
add_library(LCP::ArtNet ALIAS ${PROJECT_NAME})
configure_file(../../config.h.in config.h)
target_sources(${PROJECT_NAME}
PUBLIC
controller.h
device.h
gateway.h
node.h
universe.h
PRIVATE
artnet.h
controller.cpp
device.cpp
gateway.cpp
node.cpp
packet.h
packet.cpp
port.h
port.cpp
universe.cpp
)
target_link_libraries(${PROJECT_NAME}
PRIVATE
LCP::ACN::SDT # for IP Address types
LCP::BufferStream
LCP::RDM
)
set_target_properties(${PROJECT_NAME} PROPERTIES VERSION ${PROJECT_VERSION})
target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})

View File

@ -0,0 +1,214 @@
# OpenLCP support for Art-Net
The Art-Net 4 Release 1.4dd2 (1/2/2021) describes three members of an Art-Net network:
- **Controller** : A lighting console.
- **Node** : A DMX to / from Art-Net device.
- **Media Server** : A Media Server.
The ArtPollReply Styles (Table 4) enumerates those, plus an additional 4 device types:
- **Route** : A network routing device.
- **Backup** : A backup device.
- **Config** : A configuration or diagnostic tool
- **Visual** : A visualizer
The behaviors of Route, Backup, Config, and Visual devices is not specified. The behaviors of Controller, Node, and Media Server are defined, and summarized in a table below.
In OpenLCP, the base class for all Art-Net devices is the ARTNET::Device class. ARTNET::Controller and ARTNET::Node inherit from ARTNET::Device virtually, allowing custom implementations that may need to inherit from ARTNET::Controller _and_ ARTNET::Node to do so with a common base ARTNET::Device.
- ARTNET::Device carries the implementation for:
- Rx
- ArtPoll
- ArtTimeCode
- ArtCommand
- ArtTrigger
- ArtDmx
- ArtNzs
- ArtVlc
- ArtRdm
- ArtRdmSub
- Tx
- ArtPollReply
- ArtDiagData
- ArtTimeCode
- ArtCommand
- ArtTrigger
- ArtDmx (Peer to Peer)
- ArtNzs
- ArtVlc
- ArtRdm
- ArtRdmSub
- ARTNET::Controller extends ARTNET::Device with:
- Rx
- ArtPollReply
- ArtIpProgReply
- ArtDiagData
- ArtFirmwareReply
- ArtTodData
- Tx
- ArtPoll
- ArtIpProg
- ArtAddress
- ArtDmx (Controller to Peer)
- ArtSync
- ArtInput
- ArtFirmwareMaster
- ArtTodRequest
- ArtTodControl
- ARTNET::Node extends ARTNET::Device with:
- Rx
- ArtIpProg
- ArtAddress
- ArtSync
- ArtInput
- ArtFirmwareMaster
- ArtTodRequest
- ArtTodControl
- Tx
- ArtIpProgReply
- ArtFirmwareReply
- ArtTodData
## Art-Net Table of Behaviors
<table>
<tr>
<th rowspan=3>OpCode</th>
<th colspan=3>Controller</th>
<th colspan=3>Node</th>
<th colspan=3>Media Server</th>
</tr>
<tr>
<th rowspan=2>RX Behavior</th><th colspan=2>TX</th>
<th rowspan=2>RX Behavior</th><th colspan=2>TX</th>
<th rowspan=2>RX Behavior</th><th colspan=2>TX</th>
</tr>
<tr>
<th>uni</th><th>brd</th>
<th>uni</th><th>brd</th>
<th>uni</th><th>brd</th>
</tr>
<tr>
<td>ArtPoll</td>
<td>ArtPollReply</td><td></td><td>TX</td>
<td>ArtPollReply</td><td></td><td></td>
<td>ArtPollReply</td><td></td><td></td>
</tr>
<tr>
<td>ArtPollReply</td>
<td></td><td></td><td>TX</td>
<td></td><td></td><td>TX</td>
<td></td><td></td><td>TX</td>
</tr>
<tr>
<td>ArtIpProg</td>
<td></td><td>TX</td><td></td>
<td>ArtIpProgReply</td><td></td><td></td>
<td>ArtIpProgReply</td><td></td><td></td>
</tr>
<tr>
<td>ArtIpProgReply</td>
<td></td><td></td><td></td>
<td></td><td>TX</td><td></td>
<td></td><td>TX</td><td></td>
</tr>
<tr>
<td>ArtAddress</td>
<td></td><td>TX</td><td></td>
<td>ArtPollReply</td><td></td><td></td>
<td>ArtPollReply</td><td></td><td></td>
</tr>
<tr>
<td>ArtDiagData</td>
<td></td><td colspan=2>per ArtPoll</td>
<td></td><td colspan=2>per ArtPoll</td>
<td></td><td colspan=2>per ArtPoll</td>
</tr>
<tr>
<td>ArtTimeCode</td>
<td colspan=9>Application Specific</td>
</tr>
<tr>
<td>ArtCommand</td>
<td colspan=9>Application Specific</td>
</tr>
<tr>
<td>ArtTrigger</td>
<td colspan=9>Application Specific</td>
</tr>
<tr>
<td>ArtDmx</td>
<td>Application Specific</td><td>TX</td><td></td>
<td>Application Specific</td><td>TX</td><td></td>
<td>Application Specific</td><td>TX</td><td></td>
</tr>
<tr>
<td>ArtSync</td>
<td></td><td></td><td>TX</td>
<td>Sync</td><td></td><td></td>
<td>Sync</td><td></td><td></td>
</tr>
<tr>
<td>ArtNzs</td>
<td>Application Specific</td><td>TX</td><td></td>
<td>Application Specific</td><td>TX</td><td></td>
<td>Application Specific</td><td>TX</td><td></td>
</tr>
<tr>
<td>ArtVlc</td>
<td colspan=9>As ArtNzs</td>
</tr>
<tr>
<td>ArtInput</td>
<td></td><td>TX</td><td></td>
<td>ArtPollReply</td><td></td><td></td>
<td>ArtPollReply</td><td></td><td></td>
</tr>
<tr>
<td>ArtFirmwareMaster</td>
<td></td><td>TX</td><td></td>
<td>OpFirmwareReply</td><td></td><td></td>
<td>OpFirmwareReply</td><td></td><td></td>
</tr>
<tr>
<td>ArtFirmwareReply</td>
<td>Next OpFirmwareMaster</td><td></td><td></td>
<td></td><td>TX</td><td></td>
<td></td><td>TX</td><td></td>
</tr>
<tr>
<td>ArtTodRequest</td>
<td></td><td></td><td>TX</td>
<td>ArtTodData</td><td></td><td></td>
<td></td><td></td><td></td>
</tr>
<tr>
<td>ArtTodData</td>
<td></td><td></td><td></td>
<td></td><td></td><td></td>
<td></td><td></td><td></td>
</tr>
<tr>
<td>ArtTodControl</td>
<td></td><td></td><td>TX</td>
<td>ArtTodData</td><td></td><td></td>
<td></td><td></td><td></td>
</tr>
<tr>
<td>ArtRdm</td>
<td></td><td>TX</td><td>tx</td>
<td></td><td>TX</td><td>tx</td>
<td></td><td>TX</td><td>tx</td>
</tr>
<tr>
<td>ArtRdmSub</td>
<td></td><td>TX</td><td></td>
<td></td><td>TX</td><td></td>
<td></td><td></td><td></td>
</tr>
</table>
Art-Net™ is a trade mark of Artistic Licence Holdings Ltd. The Art-Net protocol and associated documentation is copyright Artistic Licence Holdings Ltd.

View File

@ -0,0 +1,541 @@
/*
artnet.h
Copyright (c) 2022 Kevin Matz (kevin.matz@gmail.com)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#pragma once
#include <cstdint>
/**
* @brief \cite ARTNET Art-Net is an Ethernet protocol based on the TCP/IP protocol
* suite.
*
* Its purpose is to allow transfer of large amounts of DMX512 data
* over a wide area using standard networking technology.
*/
namespace ARTNET {
/// @brief \cite ARTNET Universe Addressing
///
/// > The Port-Address of each DMX512 Universe is encoded as a 15-bit number
struct PortAddress {
union {
uint16_t value;
struct {
union {
uint8_t subuni;
struct {
uint8_t universe : 4; //!< A single DMX512 frame of 512 channels is referred to as a Universe.
uint8_t subnet : 4; //!< A group of 16 consecutive universes is referred to as a sub-net.
};
};
struct {
uint8_t net : 7; //!< A group of 16 consecutive Sub-Nets or 256 consecutive Universes is referred to as a net. There are 128 Nets in total.
bool reserved : 1;
};
};
};
};
/// @brief \cite ARTNET Protocol Operation
///
/// > The UDP port used as both source and destination is 0x1936.
static const uint16_t UDP_PORT = 6454;
/// @brief Packet ID
///
/// > Array of 8 characters, the final character is a null termination.
/// > Value = A r t - N e t 0x00
static const uint8_t PACKET_IDENTIFIER[] = { 0x41, 0x72, 0x74, 0x2D,
0x4E, 0x65, 0x74, 0x00};
/// @brief \cite ARTNET Art-Net protocol revision number.
///
/// Current value 14. Controllers should ignore communication with nodes
/// using a protocol version lower than 14.
static const uint16_t VERSION = 14;
/// @brief \cite ARTNET Table 1 - OpCodes
///
/// The OpCode defines the class of data following within the UDP packet.
/// The following table details the legal OpCode values used in Art-Net packets
enum OpCode : uint16_t {
OpNull = 0x0000, //!< no-op. Ignore this packet.
OpPoll = 0x2000, //!< an ArtPoll packet, no other data is contained in this UDP packet.
OpPollReply = 0x2100, //!< an ArtPollReply Packet. It contains device status information.
OpDiagData = 0x2300, //!< Diagnostics and data logging packet.
OpCommand = 0x2400, //!< Used to send text based parameter commands.
// OpOutput = 0x5000, //!< an ArtDmx data packet. It contains zero start code DMX512 information for a single Universe.
OpDmx = 0x5000, //!< an ArtDmx data packet. It contains zero start code DMX512 information for a single Universe.
OpNzs = 0x5100, //!< an ArtNzs data packet. It contains non-zero start code (except RDM) DMX512 information for a single Universe.
OpSync = 0x5200, //!< an ArtSync data packet. It is used to force synchronous transfer of ArtDmx packets to a nodes output.
OpAddress = 0x6000, //!< an ArtAddress packet. It contains remote programming information for a Node.
OpInput = 0x7000, //!< an ArtInput packet. It contains enable disable data for DMX inputs.
OpTodRequest = 0x8000, //!< an ArtTodRequest packet. It is used to request a Table of Devices (ToD) for RDM discovery.
OpTodData = 0x8100, //!< an ArtTodData packet. It is used to send a Table of Devices (ToD) for RDM discovery.
OpTodControl = 0x8200, //!< an ArtTodControl packet. It is used to send RDM discovery control messages.
OpRdm = 0x8300, //!< an ArtRdm packet. It is used to send all non discovery RDM messages.
OpRdmSub = 0x8400, //!< an ArtRdmSub packet. It is used to send compressed, RDM Sub-Device data.
OpVideoSetup = 0xA010, //!< an ArtVideoSetup packet. It contains video screen setup information for nodes that implement the extended video features.
OpVideoPalette = 0xA020, //!< an ArtVideoPalette packet. It contains colour palette setup information for nodes that implement the extended video features.
OpVideoData = 0xA040, //!< an ArtVideoData packet. It contains display data for nodes that implement the extended video features.
OpMacMaster = 0xF000, //!< depreciated
OpMacSlave = 0xF100, //!< depreciated
OpFirmwareMaster = 0xF200, //!< an ArtFirmwareMaster packet. It is used to upload new firmware or firmware extensions to the Node.
OpFirmwareReply = 0xF300, //!< an ArtFirmwareReply packet. It is returned by the node to acknowledge receipt of an ArtFirmwareMaster packet or ArtFileTnMaster packet.
OpFileTnMaster = 0xF400, //!< Uploads user file to node.
OpFileFnMaster = 0xF500, //!< Downloads user file from node.
OpFileFnReply = 0xF600, //!< Server to Node acknowledge for download packets.
OpIpProg = 0xF800, //!< an ArtIpProg packet. It is used to re- programme the IP, Mask and Port address of the Node.
OpIpProgReply = 0xF900, //!< an ArtIpProgReply packet. It is returned by the node to acknowledge receipt of an ArtIpProg packet.
OpMedia = 0x9000, //!< an ArtMedia packet. It is Unicast by a Media Server and acted upon by a Controller.
OpMediaPatch = 0x9100, //!< an ArtMediaPatch packet. It is Unicast by a Controller and acted upon by a Media Server.
OpMediaControl = 0x9200, //!< an ArtMediaControl packet. It is Unicast by a Controller and acted upon by a Media Server.
OpMediaContrlReply = 0x9300, //!< an ArtMediaControlReply packet. It is Unicast by a Media Server and acted upon by a Controller.
OpTimeCode = 0x9700, //!< an ArtTimeCode packet. It is used to transport time code over the network.
OpTimeSync = 0x9800, //!< Used to synchronise real time date and clock
OpTrigger = 0x9900, //!< Used to send trigger macros
OpDirectory = 0x9A00, //!< Requests a node's file list
OpDirectoryReply = 0x9B00 //!< Replies to OpDirectory with file list
};
/// OEM is unknown or non-registered type
static const uint16_t oem_unknown = 0x00ff;
/// Used by ArtTrigger for general purpose codes
static const uint16_t oem_gobal = 0xffff;
/// @brief \cite ARTNET Table 3 - NodeReport Codes
enum NodeReport : uint16_t {
RcDebug = 0x0000, //!< Booted in debug mode (Only used in development)
RcPowerOk = 0x0001, //!< Power On Tests successful
RcPowerFail = 0x0002, //!< Hardware tests failed at Power On
RcSocketWr1 = 0x0003, //!< Last UDP from Node failed due to truncated length, Most likely caused by a collision.
RcParseFail = 0x0004, //!< Unable to identify last UDP transmission. Check OpCode and packet length.
RcUdpFail = 0x0005, //!< Unable to open Udp Socket in last transmission attempt
RcShNameOk = 0x0006, //!< Confirms that Short Name programming via ArtAddress, was successful.
RcLoNameOk = 0x0007, //!< Confirms that Long Name programming via ArtAddress, was successful.
RcDmxError = 0x0008, //!< DMX512 receive errors detected.
RcDmxUdpFull = 0x0009, //!< Ran out of internal DMX transmit buffers.
RcDmxRxFull = 0x000A, //!< Ran out of internal DMX Rx buffers.
RcSwitchErr = 0x000B, //!< Rx Universe switches conflict.
RcConfigErr = 0x000C, //!< Product configuration does not match firmware.
RcDmxShort = 0x000D, //!< DMX output short detected. See GoodOutput field.
RcFirmwareFail = 0x000E, //!< Last attempt to upload new firmware failed.
RcUserFail = 0x000F, //!< User changed switch settings when address locked by remote programming. User changes ignored.
RcFactoryRes = 0x0010 //!< Factory reset has occurred.
};
/// \cite ARTNET Table 4 - Style Codes
enum Style : uint8_t {
StNode = 0x00, //!< A DMX to / from Art-Net device
StController = 0x01, //!< A lighting console.
StMedia = 0x02, //!< A Media Server.
StRoute = 0x03, //!< A network routing device.
StBackup = 0x04, //!< A backup device.
StConfig = 0x05, //!< A configuration or diagnostic tool
StVisual = 0x06 //!< A visualiser
};
/// \cite ARTNET Table 5 - Priority Codes
enum Priority : uint8_t {
DpLow = 0x10, //!< Low priority message.
DpMed = 0x40, //!< Medium priority message.
DpHigh = 0x80, //!< High priority message.
DpCritial = 0xE0, //!< Critical priority message.
DpVolatile = 0xF0 //!< Volatile message.
};
/// Set the communication behavior during ArtPoll
struct TalkToMe {
union {
uint8_t value = 0;
struct {
bool depreciated : 1;
bool reply_on_change : 1; //!< Send ArtPollReply whenever Node conditions change.
bool diag_enable : 1; //!< Send me diagnostics messages.
bool diag_unicast : 1; //!< Diagnostics messages are unicast. (if bit 2).
bool VLC_disable : 1; //!< Disable VLC transmission.
uint8_t reserved : 3;
};
};
};
/**
* @brief The OEM word
*
* Describes the equipment vendor and the feature set available.
* Bit 15 high indicates extended features available.
*/
struct OEM {
union {
uint16_t value = oem_unknown;
struct {
uint16_t manufacturer : 15;
bool extended_features : 1;
};
};
};
/**
* @brief The Authority enum
*/
enum Authority : uint8_t {
AuthorityUnkown = 0b00, //!< Port-Address Programming Authority unknown.
AuthorityLocal = 0b01, //!< Port-Address set by front panel controls.
AuthorityRemote = 0b10, //!< All or part of Port-Address programmed by network
AuthorityUnused = 0b11, //!< not used
};
/**
* @brief The Indicator enum
*/
enum Indicator : uint8_t {
IndicatorUnknown = 0b00, //!< Indicator state unknown.
IndicatorIdentify = 0b01, //!< Indicators in Locate / Identify Mode.
IndicatorMute = 0b10, //!< Indicators in Mute Mode.
IndicatorNormal = 0b11, //!< Indicators in Normal Mode.
};
/**
* @brief The FailoverMode enum
*/
enum FailoverMode : uint8_t {
FailoverHoldLast = 0b00, //!< Hold last state.
FailoverAllZeros = 0b01, //!< All output to zero.
FailoverAllFull = 0b10, //!< All output to full.
FailoverScenePlayback = 0b11, //!< Playback failover scene.
};
/**
* @brief The General_Status register
*/
struct GeneralStatus {
union {
uint8_t value1 = 0;
struct {
bool ubea_valid : 1; //!< UBEA present and not corrupt.
bool rdm_capable : 1; //!< Capable of Remote Device Management (RDM).
bool boot_failsafe : 1; //!< Booted from ROM
bool reserved1 : 1; //!< not implimented
Authority authority : 2; //!< Port-Address Programming Authority
Indicator indicator : 2; //!< Indicator State
};
};
union {
uint8_t value2 = 0b00010000; // 15-bit port-addresses (Art-Net 3 or 4)
struct {
bool web_config_available : 1; //!< supports web browser configuration.
bool DHCP_active : 1; //!< Nodes IP is DHCP configured.
bool DHCP_capable : 1; //!< Node is DHCP capable.
bool port_address_15b : 1; //!< Node supports 15 bit Port-Address
bool sACN_capable : 1; //!< Node is able to switch between Art-Net and sACN.
bool squawking : 1; //!< is sqawking
bool io_remote_set : 1; //!> Node supports switching of output style using ArtCommand.
bool remote_RDM : 1; //!> Node supports control of RDM using ArtCommand.
};
};
union {
uint8_t value3 = 0;
struct {
uint8_t reserved3 : 5; //!< Not used, set to zero
bool failover_capable : 1; //!< Node supports fail-over.
FailoverMode failover_mode : 2; //!< network data loss behavior.
};
};
};
/**
* @brief The KnownProtocols enum
*
* Protocols elegible to be reported in the PortTypes array
*/
enum KnownProtocols : uint8_t {
Protocol_DMX = 0b000000,
Protocol_MIDI = 0b000001,
Protocol_Avab = 0b000010,
Protocol_CMX = 0b000011,
Protocol_ADB = 0b000100,
Protocol_ArtNet = 0b000101,
};
/**
* @brief The PortTypes struct
*
* An array of 4 PortTypes are reported in the ArtPollReply packet.
*/
struct PortTypes {
union {
uint8_t value = 0;
struct {
KnownProtocols protocol : 6;
bool can_input : 1;
bool can_output : 1;
};
};
};
/**
* @brief The GoodInput struct
*
* An array of 4 GoodInput are reported in the ArtPollReply packet.
*/
struct GoodInput {
union {
uint8_t value = 0;
struct {
uint8_t reserved : 2; //!< Unused and transmitted as zero.
bool rx_errors : 1; //!< Receive errors detected.
bool disabled : 1; //!< Input is disabled.
bool DMX_text : 1; //!< includes DMX512 text packets.
bool DMX_SIP : 1; //!< includes DMX512 SIPs.
bool DMX_test : 1; //!< includes DMX512 test packets.
bool active : 1; //!< Data received.
};
};
};
/**
* @brief The GoodOutput struct
*
* An array of 4 GoodOutput are reported in the ArtPollReply packet.
*/
struct GoodOutput {
union {
uint8_t valueA = 0;
struct {
bool sACN_enabled : 1; //!< Output is selected to transmit sACN.
bool merge_LTP : 1; //!< Merge Mode is LTP.
bool output_short : 1; //!< DMX output short detected on power up.
bool merge_ArtNet : 1; //!< Output is merging ArtNet data.
bool DMX_text : 1; //!< includes DMX512 text packets.
bool DMX_SIP : 1; //!< includes DMX512 SIPs.
bool DMX_test : 1; //!< includes DMX512 test packets.
bool active : 1; //!< Data received.
};
};
union {
uint8_t valueB = 0;
struct {
uint8_t reserved : 6; //!< Not used, set to zero
bool output_continuous : 1; //!< oposite of changes-only
bool rdm_disabled : 1; //!< RDM disabled
};
};
};
/**
* @brief The ActivityReport struct
*
* reported in the ArtPollReply packet as SwMacro and SwRemote
*/
struct ActivityReport {
union {
uint8_t value = 0;
struct {
bool active_1 : 1;
bool active_2 : 1;
bool active_3 : 1;
bool active_4 : 1;
bool active_5 : 1;
bool active_6 : 1;
bool active_7 : 1;
bool active_8 : 1;
};
};
};
/**
* @brief The AddressCommand enum
*/
enum AddressCommand : uint8_t {
// Node configuration commands:
AcNone = 0x00, //!< No action
AcCancelMerge = 0x01, //!< if in merge mode, cancel
AcLedNormal = 0x02, //!< set front panel indicators to normal
AcLedMute = 0x03, //!< switch off front panel indicators
AcLedLocate = 0x04, //!< flash front panel indicators
AcResetRxFlags = 0x05, //!< reset SIP, Text, Test and Error flags
// Fail-over configuration commands:
AcFailHold = 0x08, //!< hold last look
AcFailZero = 0x09, //!< output zeros on data loss
AcFailFull = 0x0a, //!< output full on data loss
AcFailScene = 0x0b, //!< output scene on data loss
AcFailRecord = 0x0c, //!< record current output state as failover scene
// Port configuration commands:
AcMergeLtp0 = 0x10, //!< set port 0 to merge LTP
AcMergeLtp1 = 0x11, //!< set port 1 to merge LTP
AcMergeLtp2 = 0x12, //!< set port 2 to merge LTP
AcMergeLtp3 = 0x13, //!< set port 3 to merge LTP
AcMergeHtp0 = 0x50, //!< set port 0 to merge HTP
AcMergeHtp1 = 0x51, //!< set port 1 to merge HTP
AcMergeHtp2 = 0x52, //!< set port 2 to merge HTP
AcMergeHtp3 = 0x53, //!< set port 3 to merge HTP
AcArtNetSel0 = 0x60, //!< set port 0 protocol to Art-Net
AcArtNetSel1 = 0x61, //!< set port 1 protocol to Art-Net
AcArtNetSel2 = 0x62, //!< set port 2 protocol to Art-Net
AcArtNetSel3 = 0x63, //!< set port 3 protocol to Art-Net
AcAcnSel0 = 0x70, //!< set port 0 protocol to sACN
AcAcnSel1 = 0x71, //!< set port 1 protocol to sACN
AcAcnSel2 = 0x72, //!< set port 2 protocol to sACN
AcAcnSel3 = 0x73, //!< set port 3 protocol to sACN
AcClearOp0 = 0x90, //!< clear output buffer for port 0
AcClearOp1 = 0x91, //!< clear output buffer for port 1
AcClearOp2 = 0x92, //!< clear output buffer for port 2
AcClearOp3 = 0x93, //!< clear output buffer for port 3
AcStyleDelta0 = 0xa0, //!< set port 0 to changes-only
AcStyleDelta1 = 0xa1, //!< set port 1 to changes-only
AcStyleDelta2 = 0xa2, //!< set port 2 to changes-only
AcStyleDelta3 = 0xa3, //!< set port 3 to changes-only
AcStyleConst0 = 0xb0, //!< set port 0 to constant output
AcStyleConst1 = 0xb1, //!< set port 1 to constant output
AcStyleConst2 = 0xb2, //!< set port 2 to constant output
AcStyleConst3 = 0xb3, //!< set port 3 to constant output
AcRdEnable0 = 0xc0, //!< enable RDM for port 0
AcRdEnable1 = 0xc1, //!< enable RDM for port 1
AcRdEnable2 = 0xc2, //!< enable RDM for port 2
AcRdEnable3 = 0xc3, //!< enable RDM for port 3
AcRdmDisable0 = 0xd0, //!< disable RDM for port 0
AcRdmDisable1 = 0xd1, //!< disable RDM for port 1
AcRdmDisable2 = 0xd2, //!< disable RDM for port 2
AcRdmDisable3 = 0xd3, //!< disable RDM for port 3
};
/**
* @brief The TimecodeType enum
*/
enum TimecodeType : uint8_t {
Film = 0, //!< 24 fps
EBU = 1, //!< 25 fps
DF = 3, //!< 29.97 fps
SMPTE = 4, //!< 30 fps
};
/**
* @brief The Timecode struct
*/
struct Timecode {
uint8_t frame; //!< 24-30, depending on type
uint8_t seconds; //!< 0-59
uint8_t minutes; //!< 0-59
uint8_t hours; //!< 0-24
TimecodeType type; //!< framerate
};
// Table 7 ArtTrigger Key Values.
/// The SubKey field contains an ASCII character which the receiving device
/// should process as if it were a keyboard press.
static const uint8_t KeyAscii = 0;
/// The SubKey field contains the number of a Macro which the receiving
/// device should execute.
static const uint8_t KeyMacro = 1;
/// The SubKey field contains a soft-key number which the receiving device
/// should process as if it were a soft-key keyboard press.
static const uint8_t KeySoft = 2;
/// The SubKey field contains the number of a Show which the receiving
/// device should run.
static const uint8_t KeyShow = 3;
static const std::size_t Short_Name_Length = 18; //!< string length
static const std::size_t Long_Name_Length = 64; //!< string length
static const std::size_t Node_Report_Length = 64; //!< string length
/// \cite ARTNET
/// ...to allow transition between synchronous and non-synchronous modes, a
/// node shall time out to non-synchronous operation if an ArtSync is not
/// received for 4 seconds or more.
static const uint16_t SYNC_LOSS_TIMEOUT = 4000;
namespace VLC {
/// The DMX512 start code of this (ArtVlc) packet is set to 0x91.
static const uint8_t START_CODE = 0x91;
/// Magic number used to identify this packet.
static const uint8_t MagicNumber[] = {0x41, 0x4c, 0x45};
/// Payload contains a simple text string representing a URL.
static const uint16_t BeaconURL = 0x0000;
/// Payload contains a simple ASCII text message
static const uint16_t BeaconText = 0x0001;
} // namespace ARTNET::VLC
namespace FIRMWARE {
/// Type of firmware master packet
enum MasterType : uint8_t {
FirmFirst = 0x00, //!< The first packet of a firmware upload.
FirmCont = 0x01, //!< A consecutive continuation packet of a firmware upload.
FirmLast = 0x02, //!< The last packet of a firmware upload.
UbeaFirst = 0x03, //!< The first packet of a UBEA upload.
UbeaCont = 0x04, //!< A consecutive continuation packet of a UBEA upload.
UbeaLast = 0x05, //!< The last packet of a UBEA upload.
};
/// Type of firmware reply packet
enum ResponseType : uint8_t {
FirmBlockGood = 0x00, //!< Last packet recieved successfully.
FirmAllGood = 0x01, //!< All firmware receieved successfully.
FirmFail = 0xff, //!< Firmware upload failed.
};
} // namespace ARTNET::FIRMWARE
namespace RDM {
enum Version : uint8_t {
Draft = 0x00, //!< RDM DRAFT V1.0
Standard = 0x01, //!< RDM V1.0
};
enum TodRequestCommand : uint8_t {
TodFull = 0x00, //!< Send the Entire TOD
};
enum TodCommandResponse : uint8_t {
ResponseTodFull = 0x00, //!< entire or packet in sequence of the entire TOD.
ResponseTodNak = 0xff, //!< TOD is not available or discovery is incomplete.
};
enum TodControlCommand : uint8_t {
AtcNone = 0x00, //!< No action.
AtcFlush = 0x01, //!< The node flushes its TOD and instigates full discovery.
};
enum RdmCommand : uint8_t {
ArProcess = 0x00, //!< Process RDM Packet.
};
} // namespace ARTNET::RDM
/// @brief Communication Activity
///
/// On if any Art-Net packets detected on network, timeout after 6 seconds.
static const uint16_t DATA_LOSS_TIMEOUT = 6000;
} // namespace ARTNET

View File

@ -0,0 +1,123 @@
/*
controller.cpp
Copyright (c) 2022 Kevin Matz (kevin.matz@gmail.com)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include "controller.h"
namespace ARTNET {
Controller::Controller()
: Device(StController)
, minimum_polling_interval(2500)
, _periodic_polling_enabled(true)
, _pollster(std::thread(&Controller::_periodic_polling, this))
, last_poll(std::chrono::system_clock::from_time_t(0))
, _talktome_request({.VLC_disable=true})
, _priority_request(DpCritial)
{
setShortName("OpenLCP Controller");
setLongName("Generic OpenLCP Art-Net Controller");
}
Controller::~Controller()
{
if (_pollster.joinable())
{
_periodic_polling_enabled = false; // disable
{
std::shared_lock lk_ctl(_mtx_poll_control);
_poll_request.notify_all(); // wake-up
}
_pollster.join();
}
}
/**
* @brief Controller::setTalkToMeRequest
* @param talktome
*/
void Controller::setTalkToMeRequest(TalkToMe talktome)
{
std::unique_lock lk_data(_mtx_control_data);
_talktome_request = talktome;
}
/**
* @brief Controller::setPriorityRequest
* @param priority
*/
void Controller::setPriorityRequest(Priority priority)
{
std::unique_lock lk_data(_mtx_control_data);
_priority_request = priority;
}
/**
* @brief Controller::txArtPoll
*/
void Controller::txArtPoll()
{
auto data = std::make_shared<poll_data>();
{
std::shared_lock lk_data(_mtx_control_data);
data->talk_to_me = _talktome_request;
data->diagnostic_level = _priority_request;
}
auto packet = std::make_shared<ArtPoll>(data);
send(packet, broadcastIp());
// every poll tx gets considered in the minimum_poll_interval
{
std::unique_lock lk_ctl(_mtx_poll_control);
last_poll = std::chrono::system_clock::now();
}
}
void Controller::_periodic_polling()
{
std::chrono::nanoseconds elapsed;
std::unique_lock lk_thread(_mtx_poll_thread);
while (_periodic_polling_enabled) {
// enforce strict minimum update interval
{
std::shared_lock lk_ctl(_mtx_poll_control);
elapsed = std::chrono::system_clock::now() - last_poll;
}
if (elapsed < minimum_polling_interval)
std::this_thread::sleep_for(minimum_polling_interval - elapsed);
if(!_periodic_polling_enabled) // may have been disabled while enforcing minimum interval
break;
txArtPoll();
// sleep before the next cycle
_poll_request.wait_for(lk_thread, minimum_polling_interval);
}
}
} // namespace ARTNET

View File

@ -0,0 +1,70 @@
/*
controller.h
Copyright (c) 2022 Kevin Matz (kevin.matz@gmail.com)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#pragma once
#include "device.h"
#include "gateway.h"
#include <atomic>
#include <condition_variable>
#include <shared_mutex>
#include <thread>
namespace ARTNET {
/**
* @brief \cite ARTNET A generic term describing an Art-Net device with the
* primary task of generating control data.
*
* \cite ARTNET Controllers emulate the operation of Input Gateways.
*/
class Controller
: public Device
, public InputGateway
{
public:
explicit Controller();
virtual ~Controller();
void setTalkToMeRequest(TalkToMe);
void setPriorityRequest(Priority);
protected:
virtual void txArtPoll();
private:
const std::chrono::milliseconds minimum_polling_interval;
mutable std::shared_mutex _mtx_poll_control;
mutable std::mutex _mtx_poll_thread;
std::atomic<bool> _periodic_polling_enabled;
std::condition_variable_any _poll_request;
std::thread _pollster;
void _periodic_polling();
mutable std::shared_mutex _mtx_control_data;
std::chrono::system_clock::time_point last_poll;
TalkToMe _talktome_request; //!< TalkToMe to be uses in the ArtPoll
Priority _priority_request; //!< Priority to be used in the ArtPoll
};
} // namespace ARTNET

View File

@ -0,0 +1,589 @@
/*
device.cpp
Copyright (c) 2022 Kevin Matz (kevin.matz@gmail.com)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include "device.h"
namespace ARTNET {
/**
* @brief Device::Device
* @param style
*
* \note A ArtPollReply packet is required to be broadcast to the Directed Broadcast address by
* all Art-Net devices on power up. Since information in that packet requres a fully configured
* device, it must be specifically sent later.
*/
Device::Device(Style style)
: styleCode(style)
, diagnostic_reporting_threshold(DpCritial)
, device_status {.rdm_capable=true, .authority=AuthorityUnused}
, _shortName("OpenLCP Device")
, _longName("Generic OpenLCP Art-Net Device")
, _report_code(RcPowerOk)
, _poll_reply_count(0)
, _report_text("initialized")
{
}
/**
* @brief Device::receive
* @param buffer
* @param origin
*/
void Device::receive(ACN::PDU::Stream buffer, ipAddress &origin)
{
if (origin.port != UDP_PORT || origin.type != 1) // ACN::SDT::SDT_ADDR_IPV4
return buffer->setstate(std::ios_base::badbit);
auto packet = std::make_shared<Packet>();
packet->iStream(buffer);
if (buffer->fail() || buffer->bad())
{
_report_code = RcParseFail;
_report_text = "unable to read packet";
return;
}
packet->originAddress = origin.address.ipv4.value;
receive(packet);
}
/**
* @brief Device::receive
* @param packet
*/
void Device::receive(std::shared_ptr<Packet> packet)
{
switch (packet->opcode()) {
case OpPoll:
rxArtPoll(std::static_pointer_cast<ArtPoll>(packet));
break;
case OpPollReply:
rxArtPollReply(std::static_pointer_cast<ArtPollReply>(packet));
break;
case OpDiagData:
rxArtDiagData(std::static_pointer_cast<ArtDiagData>(packet));
break;
case OpCommand:
rxArtCommand(std::static_pointer_cast<ArtCommand>(packet));
break;
case OpDmx:
rxArtDmx(std::static_pointer_cast<ArtDmx>(packet));
break;
case OpNzs:
rxArtNzs(std::static_pointer_cast<ArtNzs>(packet));
break;
case OpSync:
rxArtSync(std::static_pointer_cast<ArtSync>(packet));
break;
case OpAddress:
rxArtAddress(std::static_pointer_cast<ArtAddress>(packet));
break;
case OpInput:
rxArtInput(std::static_pointer_cast<ArtInput>(packet));
break;
case OpTodRequest:
rxArtTodRequest(std::static_pointer_cast<ArtTodRequest>(packet));
break;
case OpTodData:
rxArtTodData(std::static_pointer_cast<ArtTodData>(packet));
break;
case OpTodControl:
rxArtTodControl(std::static_pointer_cast<ArtTodControl>(packet));
break;
case OpRdm:
rxArtRdm(std::static_pointer_cast<ArtRdm>(packet));
break;
case OpRdmSub:
rxArtRdmSub(std::static_pointer_cast<ArtRdmSub>(packet));
break;
case OpFirmwareMaster:
rxArtFirmwareMaster(std::static_pointer_cast<ArtFirmwareMaster>(packet));
break;
case OpFirmwareReply:
rxArtFirmwareReply(std::static_pointer_cast<ArtFirmwareReply>(packet));
break;
case OpIpProg:
rxArtIpProg(std::static_pointer_cast<ArtIpProg>(packet));
break;
case OpIpProgReply:
rxArtIpProgReply(std::static_pointer_cast<ArtIpProgReply>(packet));
break;
case OpTimeCode:
rxArtTimeCode(std::static_pointer_cast<ArtTimeCode>(packet));
break;
case OpTrigger:
rxArtTrigger(std::static_pointer_cast<ArtTrigger>(packet));
break;
default:
break;
}
}
/**
* @brief Device::setSender
* @param sender
* @return
*/
std::shared_ptr<void> Device::setSender(const std::function<void(std::shared_ptr<bufferstream>,
ipAddress)> sender)
{
// wrap the callback with a shared pointer
auto sp = std::make_shared<std::function<void(std::shared_ptr<bufferstream>,
ipAddress)>>(std::move(sender));
// store sender function (as a weak pointer)
_sender = sp;
// return token that caller must keep throughout it's scope
return sp;
}
/**
* @brief Device::send
* @param packet
* @param address
*/
void Device::send(const std::shared_ptr<Packet> packet, const ipAddress &address) const
{
auto buffer = std::vector<uint8_t>(packet->streamSize());
auto stream = std::make_shared<bufferstream>(buffer.data(), buffer.size());
packet->oStream(stream);
if (auto sp = _sender.lock()) // the owner is still holding the token
(*sp)(stream, address);
}
/**
* @brief Device::setStatus
* @param status
* @param text
*/
void Device::setReport(const NodeReport status, const std::string &text)
{
_report_code = status;
_report_text = text.substr(Node_Report_Length-8-1); // less required chars and null terminator
}
/**
* @brief Device::rxArtPoll
* @param packet
*/
void Device::rxArtPoll(std::shared_ptr<ArtPoll> packet)
{
auto data_opt = packet->data<poll_data>();
if (!data_opt.has_value())
return;
auto data = data_opt.value();
/// \bug The specification details resolution of diagostice behavior from multiple simultanious
/// controllers. However, the spec does not provide a mechanism for differentiating between
/// controllers. Until OSI layer 3 data is available, LTP the reporting settings.
diagnostic_reporting_behavior = data->talk_to_me;
diagnostic_reporting_threshold = data->diagnostic_level;
/// All device types send an ArtPollReply in response to receiving an ArtPoll.
txArtPollReply();
}
/**
* @brief Device::rxArtPollReply
* @param packet
*/
void Device::rxArtPollReply(std::shared_ptr<ArtPollReply> packet)
{
(void)packet;
}
/**
* @brief Device::rxArtDiagData
* @param packet
*/
void Device::rxArtDiagData(std::shared_ptr<ArtDiagData> packet)
{
(void)packet;
}
/**
* @brief Device::rxArtCommand
* @param packet
*/
void Device::rxArtCommand(std::shared_ptr<ArtCommand> packet)
{
(void)packet;
}
/**
* @brief Device::rxArtDmx
* @param packet
*/
void Device::rxArtDmx(std::shared_ptr<ArtDmx> packet)
{
(void)packet;
}
/**
* @brief Device::rxArtNzs
* @param packet
*/
void Device::rxArtNzs(std::shared_ptr<ArtNzs> packet)
{
if (packet->isVLC())
rxArtVlc(packet);
}
/**
* @brief Device::rxArtVlc
* @param packet
*/
void Device::rxArtVlc(std::shared_ptr<ArtNzs> packet)
{
auto data_opt = packet->data<nzs_data>();
if (!data_opt.has_value())
return;
auto data = std::make_shared<vlc_data>(data_opt.value().get());
}
/**
* @brief Device::rxArtSync
* @param packet
*/
void Device::rxArtSync(std::shared_ptr<ArtSync> packet)
{
(void)packet;
}
/**
* @brief Device::rxArtAddress
* @param packet
*/
void Device::rxArtAddress(std::shared_ptr<ArtAddress> packet)
{
(void)packet;
}
/**
* @brief Device::rxArtInput
* @param packet
*/
void Device::rxArtInput(std::shared_ptr<ArtInput> packet)
{
(void)packet;
}
/**
* @brief Device::rxArtTodRequest
* @param packet
*/
void Device::rxArtTodRequest(std::shared_ptr<ArtTodRequest> packet)
{
(void)packet;
}
/**
* @brief Device::rxArtTodData
* @param packet
*/
void Device::rxArtTodData(std::shared_ptr<ArtTodData> packet)
{
(void)packet;
}
/**
* @brief Device::rxArtTodControl
* @param packet
*/
void Device::rxArtTodControl(std::shared_ptr<ArtTodControl> packet)
{
(void)packet;
}
/**
* @brief Device::rxArtRdm
* @param packet
*/
void Device::rxArtRdm(std::shared_ptr<ArtRdm> packet)
{
(void)packet;
}
/**
* @brief Device::rxArtRdmSub
* @param packet
*/
void Device::rxArtRdmSub(std::shared_ptr<ArtRdmSub> packet)
{
(void)packet;
}
/**
* @brief Device::rxArtFirmwareMaster
* @param packet
*/
void Device::rxArtFirmwareMaster(std::shared_ptr<ArtFirmwareMaster> packet)
{
(void)packet;
}
/**
* @brief Device::rxArtFirmwareReply
* @param packet
*/
void Device::rxArtFirmwareReply(std::shared_ptr<ArtFirmwareReply> packet)
{
(void)packet;
}
/**
* @brief Device::rxArtIpProg
* @param packet
*/
void Device::rxArtIpProg(std::shared_ptr<ArtIpProg> packet)
{
(void)packet;
}
/**
* @brief Device::rxArtIpProgReply
* @param packet
*/
void Device::rxArtIpProgReply(std::shared_ptr<ArtIpProgReply> packet)
{
(void)packet;
}
/**
* @brief Device::rxArtTimeCode
* @param packet
*/
void Device::rxArtTimeCode(std::shared_ptr<ArtTimeCode> packet)
{
(void)packet;
}
/**
* @brief Device::rxArtTrigger
* @param packet
*/
void Device::rxArtTrigger(std::shared_ptr<ArtTrigger> packet)
{
(void)packet;
}
/**
* @brief Device::txArtPollReply
* @param reply
*/
void Device::txArtPollReply(std::shared_ptr<pollreply_data> reply)
{
if (!reply)
reply = std::make_shared<pollreply_data>();
/// \todo complete data field population of ArtPollReply
reply->my_ip = deviceIp().address.ipv4.value;
reply->fw_version = LIB_VERSION >> 16; // only able to send 16 bits, use the highest
// reply->net_sub_switch = ;
reply->status = device_status;
reply->short_name = _shortName;
reply->long_name = _longName;
reply->set_report(_report_code, _poll_reply_count, _report_text);
// reply->num_ports = ;
// reply->port_types = ;
// reply->good_input = ;
// reply->good_output = ;
// reply->SwIn = ;
// reply->SwOut = ;
// reply->SwMacro = ;
// reply->SwRemote = ;
reply->style = styleCode;
const auto mac = deviceMac();
std::copy(mac.cbegin(), mac.cend(), reply->mac_address);
// reply->bind_ip = reply->my_ip;
// reply->bind_index = ;
auto packet = std::make_shared<ArtPollReply>(reply);
send(packet, broadcastIp());
// reset report components
_report_code = RcPowerOk;
_poll_reply_count++;
_report_text = "nominal";
}
/**
* @brief Device::txArtDiagData
*/
void Device::txArtDiagData()
{
/// \todo impliment txArtDiagData
}
/**
* @brief Device::deviceMac
* @return
*
* The default implimentation is to return an invalid MAC
* \note Platform aware device types should return a valid MAC address.
*/
std::array<uint8_t, 6> Device::deviceMac() const
{
return std::array<uint8_t, 6>({0});
}
/**
* @brief Device::deviceIp
* @return
*
* The default implimentation is to return an invalid IP.
* \note Platform aware device types must return a valid IP address.
*/
ipAddress Device::deviceIp() const
{
ipAddress addr;
addr.type = 1; // ACN::SDT::SDT_ADDR_IPV4
addr.port = UDP_PORT;
addr.address.ipv4.value = 0;
return addr;
}
/**
* @brief Device::broadcastIp
* @return
*
* The default implimentation is to return the limited broadcast address.
* \note Platform aware device types must return their directed broadcast address.
*/
ipAddress Device::broadcastIp() const
{
ipAddress addr;
addr.type = 1; // ACN::SDT::SDT_ADDR_IPV4
addr.port = UDP_PORT;
addr.address.ipv4.value = 0xFFFFFFFF;
return addr;
}
/**
* @brief Device::deviceSubnetMask
* @return
*
* The default implimenation is to return an invalid mask.
* \note Platform aware device types must return their valid subnet mask.
*/
uint32_t Device::deviceSubnetMask() const
{
return -1;
}
/**
* @brief Device::deviceHasDHCP
* @return
*
* The default implimentation is to return false, as the default invalid IP is static.
* \note Platform aware device types must return their DHCP status.
*/
bool Device::deviceHasDHCP() const
{
return false;
}
/**
* @brief Device::shortName
* @return
*/
std::string Device::shortName() const
{
return _shortName;
}
/**
* @brief Device::setShortName
* @param newShortName
*/
void Device::setShortName(const std::string &newShortName)
{
_shortName = newShortName.substr(0, Short_Name_Length-1); // leave room for null terminator
_report_code = RcShNameOk;
if (newShortName.length() == _shortName.length())
_report_text = "short name accepted";
else
_report_text = "short name truncated";
}
/**
* @brief Device::longName
* @return
*/
std::string Device::longName() const
{
return _longName;
}
/**
* @brief Device::setLongName
* @param newLongName
*/
void Device::setLongName(const std::string &newLongName)
{
_longName = newLongName.substr(0, Long_Name_Length-1); // leave room for null terminator
_report_code = RcLoNameOk;
if (newLongName.length() == _longName.length())
_report_text = "long name accepted";
else
_report_text = "long name truncated";
}
} // namespace ARTNET

View File

@ -0,0 +1,108 @@
/*
device.h
Copyright (c) 2022 Kevin Matz (kevin.matz@gmail.com)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#pragma once
#include "packet.h"
#include <functional>
#include <udp.h>
namespace ARTNET {
using ACN::SDT::UDP::ipAddress;
/**
* @brief The Device class is the base class for ARTNET::Controller
* and ARTNET::Node
*/
class Device
{
public:
explicit Device(Style);
const Style styleCode; //!< \cite ARTNET The Style code defines the equipment style of the device.
void receive(ACN::PDU::Stream, ipAddress&);
void receive(std::shared_ptr<Packet>);
std::shared_ptr<void> setSender(const std::function<void(std::shared_ptr<bufferstream>,
ipAddress)>);
std::string shortName() const;
void setShortName(const std::string &newShortName);
std::string longName() const;
void setLongName(const std::string &newLongName);
protected:
TalkToMe diagnostic_reporting_behavior; //!< behavior flags
Priority diagnostic_reporting_threshold; //!< lowest priority dignostic message to send
GeneralStatus device_status; //!< general status register
void send(const std::shared_ptr<Packet> packet, const ipAddress &) const;
void setReport(const NodeReport, const std::string &);
void rxArtPoll(std::shared_ptr<ArtPoll>);
virtual void rxArtPollReply(std::shared_ptr<ArtPollReply>);
virtual void rxArtDiagData(std::shared_ptr<ArtDiagData>);
virtual void rxArtCommand(std::shared_ptr<ArtCommand>);
virtual void rxArtDmx(std::shared_ptr<ArtDmx>);
virtual void rxArtNzs(std::shared_ptr<ArtNzs>);
virtual void rxArtVlc(std::shared_ptr<ArtNzs>);
virtual void rxArtSync(std::shared_ptr<ArtSync>);
virtual void rxArtAddress(std::shared_ptr<ArtAddress>);
virtual void rxArtInput(std::shared_ptr<ArtInput>);
virtual void rxArtTodRequest(std::shared_ptr<ArtTodRequest>);
virtual void rxArtTodData(std::shared_ptr<ArtTodData>);
virtual void rxArtTodControl(std::shared_ptr<ArtTodControl>);
virtual void rxArtRdm(std::shared_ptr<ArtRdm>);
virtual void rxArtRdmSub(std::shared_ptr<ArtRdmSub>);
virtual void rxArtFirmwareMaster(std::shared_ptr<ArtFirmwareMaster>);
virtual void rxArtFirmwareReply(std::shared_ptr<ArtFirmwareReply>);
virtual void rxArtIpProg(std::shared_ptr<ArtIpProg>);
virtual void rxArtIpProgReply(std::shared_ptr<ArtIpProgReply>);
virtual void rxArtTimeCode(std::shared_ptr<ArtTimeCode>);
virtual void rxArtTrigger(std::shared_ptr<ArtTrigger>);
virtual void txArtPollReply(std::shared_ptr<pollreply_data> = nullptr);
virtual void txArtDiagData();
// OSI layer 2
virtual std::array<uint8_t, 6> deviceMac() const;
// OSI layer 3
virtual ipAddress deviceIp() const;
virtual ipAddress broadcastIp() const;
virtual uint32_t deviceSubnetMask() const;
virtual bool deviceHasDHCP() const;
private:
std::weak_ptr<const std::function<void(std::shared_ptr<bufferstream>, ipAddress)>> _sender;
std::string _shortName;
std::string _longName;
NodeReport _report_code;
uint _poll_reply_count;
std::string _report_text;
};
} // namespace ARTNET

View File

@ -0,0 +1,54 @@
/*
artnet/gateway.cpp
Copyright (c) 2023 Kevin Matz (kevin.matz@gmail.com)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include "gateway.h"
namespace ARTNET {
InputGateway::InputGateway()
{
}
InputGateway::~InputGateway()
{
}
OutputGateway::OutputGateway()
{
}
OutputGateway::~OutputGateway()
{
}
} // namespace ARTNET

View File

@ -0,0 +1,69 @@
/*
artnet/gateway.h
Copyright (c) 2023 Kevin Matz (kevin.matz@gmail.com)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#pragma once
#include <basicdevice.h>
#include <rdm_controller.h>
namespace ARTNET {
/**
* @brief \cite ARTNET The list of RDM devices maintained by both Input and Output Gateways.
*/
using TableofDevices = std::vector<std::shared_ptr<::RDM::BasicDevice>>;
/**
* @brief \cite ARTNET A device that inputs DMX512 onto the Art-Net network.
*/
class InputGateway
{
public:
explicit InputGateway();
virtual ~InputGateway();
private:
TableofDevices devices;
};
/**
* @brief \cite ARTNET A device that outputs DMX512 from the Art-Net network.
*/
class OutputGateway
: public ::RDM::Controller
{
public:
explicit OutputGateway();
virtual ~OutputGateway();
private:
TableofDevices devices;
};
} // namespace ARTNET

View File

@ -0,0 +1,146 @@
/*
node.cpp
Copyright (c) 2022 Kevin Matz (kevin.matz@gmail.com)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include "node.h"
namespace ARTNET {
Node::Node()
: Device(StNode)
{
setShortName("OpenLCP Node");
setLongName("Generic OpenLCP Art-Net Node");
}
/**
* @brief Node::rxArtIpProg
* @param packet
*/
void Node::rxArtIpProg(std::shared_ptr<ArtIpProg> packet)
{
auto data_opt = packet->data<ipprog_data>();
if (!data_opt.has_value())
return;
auto data = data_opt.value();
if (data->command.enable_programming)
{
if (data->command.enable_dhcp)
setDhcpEnable(true);
else
{
setDhcpEnable(false);
if (data->command.program_ip)
setDeviceIP(data->ip_address);
if (data->command.program_netmask)
setDeviceSubnetMask(data->subnet_mask);
}
}
txArtIpProgReply(packet->originAddress);
}
/**
* @brief Node::rxArtAddress
* @param packet
*/
void Node::rxArtAddress(std::shared_ptr<ArtAddress> packet)
{
auto data_opt = packet->data<address_data>();
if (!data_opt.has_value())
return;
auto data = data_opt.value();
if (data->short_name.length()) // The Node will ignore this value if the string is null.
setShortName(data->short_name);
if (data->long_name.length()) // The Node will ignore this value if the string is null.
setLongName(data->long_name);
/// \todo process additional fields in this packet
/// \cite ARTNET Reply by broadcasting ArtPollReply.
txArtPollReply();
}
/**
* @brief Node::txArtIpProgReply
* @param dest
* @param reply
*/
void Node::txArtIpProgReply(const uint32_t dest, std::shared_ptr<ipprogreply_data> reply)
{
if (!reply)
reply = std::make_shared<ipprogreply_data>();
reply->ip_address = deviceIp().address.ipv4.value;
reply->subnet_mask = deviceSubnetMask();
reply->status.dhcp_enabled = deviceHasDHCP();
auto packet = std::make_shared<ArtIpProgReply>(reply);
auto controller = ipAddress();
controller.address.ipv4.value = dest;
send(packet, controller);
}
/**
* @brief Node::setDhcpEnable
* @param state
*
* The default implimentation is to ignore the request.
* \note Platform aware nodes may reimpliment to accept the DHCP state.
*/
void Node::setDhcpEnable(const bool state)
{
(void)state;
}
/**
* @brief Node::setDeviceIP
* @param value
*
* The default implimentation is to ignore the request.
* \note Platform aware nodes may reimpliment to accept the IP address change.
*/
void Node::setDeviceIP(const uint32_t value)
{
(void)value;
}
/**
* @brief Node::setDeviceSubnetMask
* @param value
*
* The default implimentation is to ignore the request.
* \note Platform aware nodes may reimpliment to accept the Subnet Mask change.
*/
void Node::setDeviceSubnetMask(const uint32_t value)
{
(void)value;
}
} // namespace ARTNET

View File

@ -1,7 +1,7 @@
/*
data.h
node.h
Copyright (c) 2020 Kevin Matz (kevin.matz@gmail.com)
Copyright (c) 2022 Kevin Matz (kevin.matz@gmail.com)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@ -20,41 +20,37 @@
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
*/
#pragma once
#include "sacn.h"
#include "device.h"
#include "port.h"
#include <vector>
namespace SACN {
namespace DATA {
namespace ARTNET {
using std::uint8_t;
using std::uint16_t;
// Table 6-1: E1.31 Data Packet Framing Layer
struct frame_header : PDU::pdu_header {
uint8_t source_name[64];
uint8_t priority;
uint16_t sync_address;
uint8_t sequence_number;
uint8_t options;
uint16_t universe;
frame_header(PDU::Stream);
};
// 6.2.6 E1.31 Data Packet: Options
enum options_t : uint8_t {
PREVIEW_DATA = 0b10000000, // Bit 7 = Preview_Data
STREAM_TERMINATED = 0b01000000, // Bit 6 = Stream_Terminated
FORCE_SYNCHRONIZATION = 0b00100000, // Bit 5 = Force_Synchronization
};
class Pdu
: public PDU::Pdu
/**
* @brief \cite ARTNET A device that translates DMX512 to or from Art-Net
*/
class Node
: public Device
{
public:
Pdu(PDU::Stream);
explicit Node();
protected:
virtual void rxArtIpProg(std::shared_ptr<ArtIpProg>) override;
virtual void rxArtAddress(std::shared_ptr<ArtAddress>) override;
virtual void txArtIpProgReply(const uint32_t dest, std::shared_ptr<ipprogreply_data> = nullptr);
// OSI layer 3
virtual void setDhcpEnable(const bool);
virtual void setDeviceIP(const uint32_t);
virtual void setDeviceSubnetMask(const uint32_t);
private:
std::vector<Port*> _ports;
};
} // DATA
} // SACN
} // namespace ARTNET

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,921 @@
/*
packet.h
Copyright (c) 2022 Kevin Matz (kevin.matz@gmail.com)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#pragma once
#include "artnet.h"
#include "config.h"
#include "bufferstream.h"
#include "../../esta/rdm/uid.h"
#include <optional>
#include <vector>
namespace ARTNET {
/**
* @brief The packet_data struct
*/
struct packet_data
: public streamable
{
uint16_t version = VERSION; //!< Protocol Version
};
/**
* @brief The artpoll_data struct
*
* \cite ARTNET This packet is used to discover the presence of other
* Controllers, Nodes and Media Servers. The ArtPoll packet is only sent by
* a Controller. Both Controllers and Nodes respond to the packet.
*/
struct poll_data
: public packet_data
{
poll_data()
: diagnostic_level(DpLow)
{};
TalkToMe talk_to_me; //!< Set behaviour of Node
Priority diagnostic_level; //!< The lowest priority of diagnostics message
virtual size_t streamSize() const override { return 4; };
virtual void iStream(std::shared_ptr<bufferstream>) override;
virtual void oStream(std::shared_ptr<bufferstream>) const override;
};
/**
* @brief The artpollreply_data struct
*
* A device, in response to a Controllers ArtPoll, sends the ArtPollReply.
* This packet is also broadcast to the Directed Broadcast address by all
* Art-Net devices on power up.
*/
struct pollreply_data
: public packet_data
{
pollreply_data()
: udp_port(UDP_PORT)
, oem({.manufacturer=MY_ARTNET_MANUFACTUER_ID})
, esta_manufacturer(MY_ESTA_MANUFACTURER_ID)
{};
uint32_t my_ip = 0; //!< the Nodes IPv4 address
uint16_t udp_port; //!< The Port is always 0x1936
uint16_t fw_version; //!< Node's Firemware revision number
PortAddress net_sub_switch; //!< Net and Subnet switch positions
OEM oem; //!< the equipment vendor and the feature set
uint8_t ubea_version = 0; //!< the firmware version of the User Bios Extension Area (UBEA)
GeneralStatus status; //!< general status register
uint16_t esta_manufacturer; //!< manufacturer ID
std::string short_name = ""; //!< short name for the Node
std::string long_name = ""; //!< long name for the Node
uint16_t num_ports = 0; //!< the number of input or output ports. Max=4
PortTypes port_types[4]; //!< operation and protocol of each port.
GoodInput good_input[4]; //!< input status of the node.
GoodOutput good_output[4]; //!< output status of the node.
uint8_t SwIn[4]; //!< Bits 3-0 of the 15 bit Port-Address for each input port
uint8_t SwOut[4]; //!< Bits 3-0 of the 15 bit Port-Address for each output port
uint8_t SwVideo = 0; //!< This field is now deprecated.
ActivityReport SwMacro; //!< used for remote event triggering or cueing.
ActivityReport SwRemote; //!< used for remote event triggering or cueing.
Style style; //!< the equipment style of the device.
uint8_t mac_address[6] = {0}; //!< MAC Address
uint32_t bind_ip = 0; //!< the IP of the root device.
uint8_t bind_index = 1; //!< the order of bound devices.
/**
* @brief textual report of the Nodes operating status or operational errors.
* @return
*/
const std::string report() const { return _node_report; };
void set_report(NodeReport, uint, std::string);
virtual size_t streamSize() const override { return 226; };
virtual void iStream(std::shared_ptr<bufferstream>) override;
virtual void oStream(std::shared_ptr<bufferstream>) const override;
private:
std::string _node_report = "";
static const size_t _filler_length = 21; //!< length of filler at end of packet
};
/**
* @brief The artipprog_data struct
*
* Program the IPv4 address.
*/
struct ipprog_data
: public packet_data
{
ipprog_data()
: udp_port(UDP_PORT)
{};
union {
uint8_t _raw = 0;
struct {
bool program_ip : 1;
bool program_netmask : 1;
bool program_port : 1;
bool reset_ip_mask_port : 1;
uint8_t reserved : 2;
bool enable_dhcp : 1;
bool enable_programming : 1;
};
} command; //!< Action of this packet
uint32_t ip_address = 0; //!< IP Address to be programmed into Node
uint32_t subnet_mask = 0; //!< Subnet Mask to be programmed into Node
uint16_t udp_port; //!< Depreciated
virtual size_t streamSize() const override { return 24; };
virtual void iStream(std::shared_ptr<bufferstream>) override;
virtual void oStream(std::shared_ptr<bufferstream>) const override;
private:
static const size_t _spare_length = 8;
};
/**
* @brief The artipprogreply_data struct
*/
struct ipprogreply_data
: public packet_data
{
ipprogreply_data()
: udp_port(UDP_PORT)
{};
uint32_t ip_address = 0; //!< IP Address of the Node
uint32_t subnet_mask = 0; //!< Subnet Mask of the Node
uint16_t udp_port; //!< Depreciated
union {
uint8_t _raw = 0;
struct {
uint8_t reserved1 : 6;
bool dhcp_enabled : 1;
bool reserved2 : 1;
};
} status; //!< Status flags
virtual size_t streamSize() const override { return 24; };
virtual void iStream(std::shared_ptr<bufferstream>) override;
virtual void oStream(std::shared_ptr<bufferstream>) const override;
private:
static const size_t _spare_length = 7;
};
/**
* @brief The address_data struct
*/
struct address_data
: public packet_data
{
uint8_t net_switch = 0x7f; //!< Net and Subnet switch positions
uint8_t bind_index = 1; //!< the order of bound devices.
std::string short_name = ""; //!< short name for the Node
std::string long_name = ""; //!< long name for the Node
uint8_t SwIn[4] = {0x7f}; //!< Bits 3-0 of the 15 bit Port-Address for each input port
uint8_t SwOut[4] = {0x7f}; //!< Bits 3-0 of the 15 bit Port-Address for each output port
uint8_t sub_switch = 0x7f; //!< Subnet switch positions
uint8_t SwVideo = 0; //!< This field is now deprecated.
AddressCommand command; //!< Node configuration command.
virtual size_t streamSize() const override { return 97; };
virtual void iStream(std::shared_ptr<bufferstream>) override;
virtual void oStream(std::shared_ptr<bufferstream>) const override;
};
/**
* @brief The diagdata_data struct
*/
struct diagdata_data
: public packet_data
{
Priority priority; //!< priority of the diagnostic data
std::string data = ""; //!< ASCII text
virtual size_t streamSize() const override { return 8 + data.length() + 1; };
virtual void iStream(std::shared_ptr<bufferstream>) override;
virtual void oStream(std::shared_ptr<bufferstream>) const override;
private:
static const uint16_t _max_data_length = 512; //!< max data length, including null terminator
};
/**
* @brief The timecode_data struct
*/
struct timecode_data
: public packet_data
{
Timecode time; //!< time code
virtual size_t streamSize() const override { return 9; };
virtual void iStream(std::shared_ptr<bufferstream>) override;
virtual void oStream(std::shared_ptr<bufferstream>) const override;
};
/**
* @brief The command_data struct
*/
struct command_data
: public packet_data
{
uint16_t esta_manufacturer; //!< manufacturer ID
std::string data = ""; //!< ASCII text
virtual size_t streamSize() const override { return 6 + data.length() + 1; };
virtual void iStream(std::shared_ptr<bufferstream>) override;
virtual void oStream(std::shared_ptr<bufferstream>) const override;
private:
static const uint16_t _max_data_length = 512; //!< max data length, including null terminator
};
/**
* @brief The trigger_data struct
*/
struct trigger_data
: public packet_data
{
OEM oem; //!< the equipment vendor and the feature set
uint8_t key; //!< the trigger key
uint8_t subkey; //!< the trigger subkey
uint8_t data[512] = {0}; //!< data array
virtual size_t streamSize() const override { return 520; };
virtual void iStream(std::shared_ptr<bufferstream>) override;
virtual void oStream(std::shared_ptr<bufferstream>) const override;
};
/**
* @brief The dmx_data struct
*/
struct dmx_data
: public packet_data
{
uint8_t sequence; //!< to ensure that ArtDmx packets are used in the correct order.
uint8_t physical; //!< the physical input port from which DMX was input
PortAddress universe; //!< Net, Subnet, and universe number
std::vector<uint8_t> data = {0}; //!< data array, initialize null start
virtual size_t streamSize() const override { return 7 + data.size(); };
virtual void iStream(std::shared_ptr<bufferstream>) override;
virtual void oStream(std::shared_ptr<bufferstream>) const override;
};
/**
* @brief The sync_data struct
*/
struct sync_data
: public packet_data
{
virtual size_t streamSize() const override { return 4; };
virtual void iStream(std::shared_ptr<bufferstream>) override;
virtual void oStream(std::shared_ptr<bufferstream>) const override;
};
/**
* @brief The nzs_data struct
*/
struct nzs_data
: public packet_data
{
/**
* @brief nzs_data
* @param startcode
*/
nzs_data(uint8_t startcode = 0)
{ data = {startcode}; }
uint8_t sequence; //!< to ensure that ArtDmx packets are used in the correct order.
PortAddress universe; //!< Net, Subnet, and universe number
std::vector<uint8_t> data; //!< data array with startcode
virtual size_t streamSize() const override { return 7 + data.size(); };
virtual void iStream(std::shared_ptr<bufferstream>) override;
virtual void oStream(std::shared_ptr<bufferstream>) const override;
};
/**
* @brief The vlc_data struct
*/
struct vlc_data
: public nzs_data
{
/**
* @brief vlc_data
* @param other
*/
vlc_data(const nzs_data * other = nullptr)
: nzs_data(VLC::START_CODE)
{ if (other) _from_nzs(other); }
union {
uint8_t _raw = 0;
struct {
uint8_t padding : 5; //!< reserved
bool beacon : 1; //!< the transmitter should continuously repeat transmission of this packet until another is received
bool reply : 1; //!< is a reply packet that is in response to the request sent with matching number in the transaction number
bool IEEE : 1; //!< data in the payload area shall be interpreted as IEEE VLC data.
};
} flags; //!< flags for this packet
uint16_t transaction = 0; //!< transaction number which allows VLC transactions to be synchronised
uint16_t address = 0; //!< The slot number, range 1-512, of the device to which this packet is directed.
uint8_t depth = 0; //!< modulation depth expressed as a percentage in the range 1 to 100
uint16_t frequency = 0; //!< modulation frequency of the VLC transmitter expressed in Hz.
uint16_t modulation = 0; //!< modulation type number that the transmitter should use to transmit VLC.
uint16_t language = 0; //!< payload language code
uint16_t beacon_frequency; //!< the frequency in Hertz at which the VLC packet should be repeated.
std::vector<uint8_t> payload; //!< The actual VLC payload.
virtual size_t streamSize() const override { return 30 + data.size(); };
/// The ArtVlc packet is a specially formed ArtNzs packet.
/// compile() MUST BE CALLED prior to streaming.
void compile();
private:
const static size_t _payload_max_length = 480;
uint16_t _checksum() const;
bool _from_nzs(const nzs_data *);
};
/**
* @brief The input_data struct
*/
struct input_data
: public packet_data
{
uint8_t bind_index = 1; //!< the order of bound devices.
uint16_t num_ports = 0; //!< the number of input or output ports. Max=4
union InputFlags {
uint8_t _raw = 0; //!< raw byte
struct {
bool disabled : 1; //!< Set to disable this input.
uint8_t reserved : 7;
};
}; //!< flags for this packet
InputFlags status[4]; //!< input disable status of each port.
virtual size_t streamSize() const override { return 10; };
virtual void iStream(std::shared_ptr<bufferstream>) override;
virtual void oStream(std::shared_ptr<bufferstream>) const override;
};
/**
* @brief The firmwaremaster_data struct
*/
struct firmwaremaster_data
: public packet_data
{
FIRMWARE::MasterType type; //!< Defines the packet contents
uint8_t block_id; //!< Counts the consecutive blocks of firmware upload.
uint64_t length; //!< the file size (in words) of the file to be uploaded.
uint16_t block[256] = {0}; //!< the firmware or UBEA data block.
virtual size_t streamSize() const override { return 542; };
virtual void iStream(std::shared_ptr<bufferstream>) override;
virtual void oStream(std::shared_ptr<bufferstream>) const override;
private:
const size_t _spare_length = 20;
};
/**
* @brief The firmwarereply_data struct
*/
struct firmwarereply_data
: public packet_data
{
FIRMWARE::ResponseType type; //!< Defines the packet contents
virtual size_t streamSize() const override { return 26; };
virtual void iStream(std::shared_ptr<bufferstream>) override;
virtual void oStream(std::shared_ptr<bufferstream>) const override;
private:
const size_t _spare_length = 21;
};
/**
* @brief The todrequest_data struct
*/
struct todrequest_data
: public packet_data
{
std::vector<PortAddress> universes; //!< universes that must respond
virtual size_t streamSize() const override { return 14 + universes.size(); };
virtual void iStream(std::shared_ptr<bufferstream>) override;
virtual void oStream(std::shared_ptr<bufferstream>) const override;
private:
const size_t _max_count = 32;
};
/**
* @brief The toddata_data struct
*/
struct toddata_data
: public packet_data
{
RDM::Version rdm_version = RDM::Standard; //!< supports Draft or Standard RDM
uint8_t port = 1; //!< physical port index
uint8_t bind_index = 1; //!< sub-device
PortAddress universe; //!< responding universe
RDM::TodCommandResponse type = RDM::ResponseTodFull; //!< response type
uint16_t total_count = 0; //!< total device count
uint8_t block_count = 0; //!< index of these devices in total
std::vector<std::shared_ptr<::RDM::UID>> devices; //!< block of up to 200 devices
virtual size_t streamSize() const override { return 18 + devices.size() * 6; }
virtual void iStream(std::shared_ptr<bufferstream>) override;
virtual void oStream(std::shared_ptr<bufferstream>) const override;
};
/**
* @brief The todcontrol_data struct
*/
struct todcontrol_data
: public packet_data
{
PortAddress universe; //!< unverse that should action command
RDM::TodControlCommand command = RDM::AtcNone; //!< control command
virtual size_t streamSize() const override { return 14; };
virtual void iStream(std::shared_ptr<bufferstream>) override;
virtual void oStream(std::shared_ptr<bufferstream>) const override;
};
/**
* @brief The rdm_data struct
*/
struct rdm_data
: public packet_data
{
RDM::Version rdm_version = RDM::Standard; //!< supports Draft or Standard RDM
PortAddress universe; //!< responding universe
RDM::RdmCommand command = RDM::ArProcess; //!< response type
std::vector<uint8_t> data = {::RDM::SC_RDM}; //!< data array, initialize RDM start
virtual size_t streamSize() const override { return 13 + data.size(); };
virtual void iStream(std::shared_ptr<bufferstream>) override;
virtual void oStream(std::shared_ptr<bufferstream>) const override;
};
/**
* @brief The rdmsub_data struct
*/
struct rdmsub_data
: public packet_data
{
RDM::Version rdm_version = RDM::Standard; //!< supports Draft or Standard RDM
::RDM::UID uid; //!< UID of target RDM device.
uint8_t command_class; //!< Get, Set, GetResponse, SetResponse
::RDM::PID pid; //!< type of parameter
uint16_t subdevice = 0; //!< the first device information contained in packet
std::vector<uint16_t> data; //!< packed data array
virtual size_t streamSize() const override { return 20 + (data.size() * 2); };
virtual void iStream(std::shared_ptr<bufferstream>) override;
virtual void oStream(std::shared_ptr<bufferstream>) const override;
};
/**
* @brief The ARTNET packet
*
* All UDP packets accepted by the Node conform to the Art-Net protocol
* specification as defined... Any other packets are ignored.
*/
struct Packet
: public streamable
{
/// @brief Packet
/// @param opcode
/// @param data
Packet(OpCode opcode = OpNull, std::shared_ptr<packet_data> data = nullptr)
: _opcode(opcode)
, _data(data)
{}
/// @brief opcode
/// @return
OpCode opcode() const { return _opcode; }
/// @brief IP address where unicast replies will be sent.
uint32_t originAddress;
/// @brief data
/// @return
template<class T>
auto data() const
{
static_assert(std::is_base_of<packet_data, T>::value,
"type must inherit ARTNET::packet_data");
return _data ? std::optional<std::shared_ptr<T>>{std::static_pointer_cast<T>(_data)}
: std::nullopt;
}
size_t streamSize() const override;
void iStream(std::shared_ptr<bufferstream>) override;
void oStream(std::shared_ptr<bufferstream>) const override;
protected:
OpCode _opcode; //!< Op Code
std::shared_ptr<packet_data> _data; //!< packet data
};
/**
* @brief The ArtPoll Packet
*/
struct ArtPoll
: public Packet
{
/**
* @brief ArtPoll
* @param data
*/
ArtPoll(std::shared_ptr<poll_data> data = std::make_shared<poll_data>())
: Packet(OpPoll, data)
{}
};
/**
* @brief The ArtPollReply Packet
*/
struct ArtPollReply
: public Packet
{
/**
* @brief ArtPollReply
* @param data
*/
ArtPollReply(std::shared_ptr<pollreply_data> data = std::make_shared<pollreply_data>())
: Packet(OpPollReply, data)
{}
};
/**
* @brief The ArtIpProg Packet
*/
struct ArtIpProg
: public Packet
{
/**
* @brief ArtIpProg
* @param data
*/
ArtIpProg(std::shared_ptr<ipprog_data> data = std::make_shared<ipprog_data>())
: Packet(OpIpProg, data)
{}
};
/**
* @brief The ArtIpProgReply Packet
*/
struct ArtIpProgReply
: public Packet
{
/**
* @brief ArtIpProgReply
* @param data
*/
ArtIpProgReply(std::shared_ptr<ipprogreply_data> data = std::make_shared<ipprogreply_data>())
: Packet(OpIpProgReply, data)
{}
};
/**
* @brief The ArtAddress Packet
*/
struct ArtAddress
: public Packet
{
/**
* @brief ArtAddress
* @param data
*/
ArtAddress(std::shared_ptr<address_data> data = std::make_shared<address_data>())
: Packet(OpAddress, data)
{}
};
/**
* @brief The ArtDiagData Packet
*/
struct ArtDiagData
: public Packet
{
/**
* @brief ArtDiagData
* @param data
*/
ArtDiagData(std::shared_ptr<diagdata_data> data = std::make_shared<diagdata_data>())
: Packet(OpDiagData, data)
{}
};
/**
* @brief The ArtTimeCode Packet
*/
struct ArtTimeCode
: public Packet
{
/**
* @brief ArtTimeCode
* @param data
*/
ArtTimeCode(std::shared_ptr<timecode_data> data = std::make_shared<timecode_data>())
: Packet(OpTimeCode, data)
{}
};
/**
* @brief The ArtCommand class
*/
struct ArtCommand
: public Packet
{
/**
* @brief ArtCommand
* @param data
*/
ArtCommand(std::shared_ptr<command_data> data = std::make_shared<command_data>())
: Packet(OpCommand, data)
{}
};
/**
* @brief The ArtTrigger Packet
*/
struct ArtTrigger
: public Packet
{
/**
* @brief ArtTrigger
* @param data
*/
ArtTrigger(std::shared_ptr<trigger_data> data = std::make_shared<trigger_data>())
: Packet(OpTrigger, data)
{}
};
/**
* @brief The ArtDmx Packet
*/
struct ArtDmx
: public Packet
{
/**
* @brief ArtDmx
* @param data
*/
ArtDmx(std::shared_ptr<dmx_data> data = std::make_shared<dmx_data>())
: Packet(OpDmx, data)
{}
};
/**
* @brief The ArtSync Packet
*/
struct ArtSync
: public Packet
{
/**
* @brief ArtSync
* @param data
*/
ArtSync(std::shared_ptr<sync_data> data = std::make_shared<sync_data>())
: Packet(OpSync, data)
{}
};
/**
* @brief The ArtNzs Packet
*/
struct ArtNzs
: public Packet
{
/**
* @brief ArtNzs
* @param data
*/
ArtNzs(std::shared_ptr<nzs_data> data = std::make_shared<nzs_data>())
: Packet(OpNzs, data)
{}
/**
* @brief ArtNzs and ArtVlc share the same OpCode.
* @return
*/
bool isVLC() const
{
if (!_data)
return false;
auto data = std::static_pointer_cast<nzs_data>(_data);
return data->data[0] == VLC::START_CODE;
};
};
/**
* @brief The ArtVlc Packet
*/
struct ArtVlc
: public Packet
{
/**
* @brief ArtVlc
* @param data
*/
ArtVlc(std::shared_ptr<vlc_data> data = std::make_shared<vlc_data>())
: Packet(OpNzs, data)
{}
};
/**
* @brief The ArtInput Packet
*/
struct ArtInput
: public Packet
{
/**
* @brief ArtInput
* @param data
*/
ArtInput(std::shared_ptr<input_data> data = std::make_shared<input_data>())
: Packet(OpInput, data)
{}
};
/**
* @brief The ArtFirmwareMaster Packet
*/
struct ArtFirmwareMaster
: public Packet
{
/**
* @brief ArtFirmwareMaster
* @param data
*/
ArtFirmwareMaster(std::shared_ptr<firmwaremaster_data> data = std::make_shared<firmwaremaster_data>())
: Packet(OpFirmwareMaster, data)
{}
};
/**
* @brief The ArtFirmwareReply Packet
*/
struct ArtFirmwareReply
: public Packet
{
/**
* @brief ArtFirmwareReply
* @param data
*/
ArtFirmwareReply(std::shared_ptr<firmwarereply_data> data = std::make_shared<firmwarereply_data>())
: Packet(OpFirmwareReply, data)
{}
};
/**
* @brief The ArtTodRequest Packet
*/
struct ArtTodRequest
: public Packet
{
/**
* @brief ArtTodRequest
* @param data
*/
ArtTodRequest(std::shared_ptr<todrequest_data> data = std::make_shared<todrequest_data>())
: Packet(OpTodRequest, data)
{}
};
/**
* @brief The ArtTodData Packet
*/
struct ArtTodData
: public Packet
{
/**
* @brief ArtTodData
* @param data
*/
ArtTodData(std::shared_ptr<toddata_data> data = std::make_shared<toddata_data>())
: Packet(OpTodData, data)
{}
};
/**
* @brief The ArtTodControl Packet
*/
struct ArtTodControl
: public Packet
{
/**
* @brief ArtTodControl
* @param data
*/
ArtTodControl(std::shared_ptr<todcontrol_data> data = std::make_shared<todcontrol_data>())
: Packet(OpTodControl, data)
{}
};
/**
* @brief The ArtRdm Packet
*/
struct ArtRdm
: public Packet
{
/**
* @brief ArtRdm
* @param data
*/
ArtRdm(std::shared_ptr<rdm_data> data = std::make_shared<rdm_data>())
: Packet(OpRdm, data)
{}
};
/**
* @brief The ArtRdmSub Packet
*/
struct ArtRdmSub
: public Packet
{
/**
* @brief ArtRdmSub
* @param data
*/
ArtRdmSub(std::shared_ptr<rdmsub_data> data = std::make_shared<rdmsub_data>())
: Packet(OpRdmSub, data)
{}
};
} // namespace ARTNET

View File

@ -0,0 +1,34 @@
/*
port.cpp
Copyright (c) 2022 Kevin Matz (kevin.matz@gmail.com)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include "port.h"
namespace ARTNET {
Port::Port()
{
}
} // namespace ARTNET

View File

@ -0,0 +1,39 @@
/*
port.h
Copyright (c) 2022 Kevin Matz (kevin.matz@gmail.com)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#pragma once
namespace ARTNET {
/**
* @brief The Port class
*
* Input or Output port on a node
*/
class Port
{
public:
explicit Port();
};
} // namespace ARTNET

View File

@ -0,0 +1,34 @@
/*
universe.cpp
Copyright (c) 2022 Kevin Matz (kevin.matz@gmail.com)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include "universe.h"
namespace ARTNET {
universe::universe()
{
}
} // namespace ARTNET

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