Compare commits
993 Commits
Author | SHA1 | Date |
---|---|---|
Kevin Matz | b6300cf80b | |
Kevin Matz | 47ac0220b4 | |
Kevin Matz | db2d793805 | |
Kevin Matz | 822a3130b5 | |
Kevin Matz | f49250a610 | |
Kevin Matz | f92bed2a8a | |
Kevin Matz | 97802bbebf | |
Kevin Matz | 2b8be50db8 | |
Kevin Matz | 4b71f666e2 | |
Kevin Matz | 877700625f | |
Kevin Matz | b25cdb102b | |
Kevin Matz | 68ced05c57 | |
Kevin Matz | 840f42d8dc | |
Kevin Matz | 7b6946a1cc | |
Kevin Matz | 95a0d85b04 | |
Kevin Matz | 506deb7e5e | |
Kevin Matz | c6e8c2528d | |
Kevin Matz | 98ca641adc | |
Kevin Matz | d8a768848e | |
Kevin Matz | 75c5e107ea | |
Kevin Matz | a15b0d90f7 | |
Kevin Matz | 652e9e9017 | |
Kevin Matz | 188a403726 | |
Kevin Matz | 50f3145f9e | |
Kevin Matz | 6d257449f0 | |
Kevin Matz | 9114e227f7 | |
Kevin Matz | 33c6e35228 | |
Kevin Matz | a255b915b5 | |
Kevin Matz | 4a857f720a | |
Kevin Matz | e565437988 | |
Kevin Matz | 26645b5c4d | |
Kevin Matz | 6b82f0eda4 | |
Kevin Matz | a26f6f0843 | |
Kevin Matz | 4368320e9f | |
Kevin Matz | 30621d7bb1 | |
Kevin Matz | 2a777946f5 | |
Kevin Matz | eead2311ea | |
Kevin Matz | e76d466bf6 | |
Kevin Matz | 325f6d4ffa | |
Kevin Matz | 29e7d788e6 | |
Kevin Matz | 828e4f11a7 | |
Kevin Matz | c0dded8b94 | |
Kevin Matz | cf06c0fa9a | |
Kevin Matz | 9e36ab422c | |
Kevin Matz | 796ef5f8ca | |
Kevin Matz | 9a0fd158d6 | |
Kevin Matz | 88c9cfa7c9 | |
Kevin Matz | 9c1b14ac3e | |
Kevin Matz | a41d8bfc87 | |
Kevin Matz | bf5f9812aa | |
Kevin Matz | 455ce23681 | |
Kevin Matz | de346b15b4 | |
Kevin Matz | 74815726bf | |
Kevin Matz | f01b3c8a56 | |
Kevin Matz | c4ab516efe | |
Kevin Matz | 8dc6c36e36 | |
Kevin Matz | 41c89e06e0 | |
Kevin Matz | f7a79c5a40 | |
Kevin Matz | 7315070462 | |
Kevin Matz | 0c442b74a2 | |
Kevin Matz | 4a067ad66b | |
Kevin Matz | 13981ff727 | |
Kevin Matz | 14e4bdc5b5 | |
Kevin Matz | 17f9ee21d6 | |
Kevin Matz | 45c2e5c116 | |
Kevin Matz | c0e7464919 | |
Kevin Matz | 999026abb7 | |
Kevin Matz | bd15cbd5aa | |
Kevin Matz | 7029286883 | |
Kevin Matz | d5bd61c35b | |
Kevin Matz | 79e9e2f360 | |
Kevin Matz | 7e679f3016 | |
Kevin Matz | 0398b63907 | |
Kevin Matz | 39bba1f5b0 | |
Kevin Matz | 1d2a3b7941 | |
Kevin Matz | 4fa70b4ff2 | |
Kevin Matz | 16f3c66562 | |
Kevin Matz | ba2927e6ee | |
Kevin Matz | 654dfed224 | |
Kevin Matz | 7e4734f56a | |
Kevin Matz | 258910efe1 | |
Kevin Matz | 216a08f100 | |
Kevin Matz | fdf801c962 | |
Kevin Matz | 0bd30d1151 | |
Kevin Matz | 31ec3ade78 | |
Kevin Matz | bf61bd9c3e | |
Kevin Matz | ac1c5d64d7 | |
Kevin Matz | 4fd1c4de29 | |
Kevin Matz | 64901a27cb | |
Kevin Matz | f0d5556230 | |
Kevin Matz | f72ca7e9f6 | |
Kevin Matz | 6853a2af5c | |
Kevin Matz | 0c22c70b38 | |
Kevin Matz | 767c0a9e34 | |
Kevin Matz | fe4d94628c | |
Kevin Matz | 5b65fd8187 | |
Kevin Matz | 6022d4bbc6 | |
Kevin Matz | 745222cbf9 | |
Kevin Matz | e638b09be6 | |
Kevin Matz | b64b185d73 | |
Kevin Matz | 2d2ef07de7 | |
Kevin Matz | 34d74375cc | |
Kevin Matz | 706fe5a3f9 | |
Kevin Matz | 81746d635e | |
Kevin Matz | 63df16831e | |
Kevin Matz | 983ae8a412 | |
Kevin Matz | 49a446c5aa | |
Kevin Matz | e1faba699e | |
Kevin Matz | 28c7bba442 | |
Kevin Matz | 4cc4d16fba | |
Kevin Matz | 310eae05ec | |
Kevin Matz | 798fe44ca4 | |
Kevin Matz | 5f98768a8a | |
Kevin Matz | 4f76a2f96c | |
Kevin Matz | 946a2dd9f4 | |
Kevin Matz | 3745f44a5f | |
Kevin Matz | f4c91891e0 | |
Kevin Matz | 3882af3659 | |
Kevin Matz | 6c7d62ca6b | |
Kevin Matz | c0074e62b7 | |
Kevin Matz | 41b51f7aa9 | |
Kevin Matz | e1f0836a55 | |
Kevin Matz | 9985f21cb2 | |
Kevin Matz | 887ce0f5a4 | |
Kevin Matz | 91fa0c71e4 | |
Kevin Matz | ec98049b39 | |
Kevin Matz | a1b08fd22d | |
Kevin Matz | 9b27820873 | |
Kevin Matz | 29173eacf2 | |
Kevin Matz | ab17dc05ba | |
Kevin Matz | f06a5f97e6 | |
Kevin Matz | 35dc0f30d2 | |
Kevin Matz | 73615db6c4 | |
Kevin Matz | 7fffc6d0dd | |
Kevin Matz | 08ca66fcb3 | |
Kevin Matz | 176ddf1df6 | |
Kevin Matz | d80e4c6559 | |
Kevin Matz | 5455d805d5 | |
Kevin Matz | bc36227387 | |
Kevin Matz | 34bcd7b174 | |
Kevin Matz | fc48300964 | |
Kevin Matz | e951c25461 | |
Kevin Matz | f1612dc60e | |
Kevin Matz | db3da280da | |
Kevin Matz | dafa95ec76 | |
Kevin Matz | 7802f201e0 | |
Kevin Matz | 2fdef29242 | |
Kevin Matz | 54b2335c98 | |
Kevin Matz | 098d936897 | |
Kevin Matz | 9442b6a7ff | |
Kevin Matz | c40d1fcdf5 | |
Kevin Matz | 76d3cd00f8 | |
Kevin Matz | d91838dc5b | |
Kevin Matz | 960e228e59 | |
Kevin Matz | 929ca62016 | |
Kevin Matz | 402bcf9188 | |
Kevin Matz | cba2cf9fd3 | |
Kevin Matz | 38ff40e646 | |
Kevin Matz | e29b41c812 | |
Kevin Matz | 773375757e | |
Kevin Matz | 0bfab08041 | |
Kevin Matz | d7722f24f3 | |
Kevin Matz | fa8b0482da | |
Kevin Matz | e94db990ac | |
Kevin Matz | 031d00bed4 | |
Kevin Matz | 23572904d2 | |
Kevin Matz | 4023cdaa81 | |
Kevin Matz | 22141d9a03 | |
Kevin Matz | 8fd3cc83cf | |
Kevin Matz | 7a25eeada7 | |
Kevin Matz | 913a1f789b | |
Kevin Matz | 76b8f974e7 | |
Kevin Matz | 45a8c673f0 | |
Kevin Matz | 42ca307c4d | |
Kevin Matz | 1cce721179 | |
Kevin Matz | 877ab3ed4c | |
Kevin Matz | d51d3163f9 | |
Kevin Matz | abe41404cc | |
Kevin Matz | c52bf250b6 | |
Kevin Matz | 9e390cff8b | |
Kevin Matz | 46a860fca9 | |
Kevin Matz | 4d797f2c1c | |
Kevin Matz | e0fb4ccbb3 | |
Kevin Matz | a34ada6da5 | |
Kevin Matz | eb25959c08 | |
Kevin Matz | 373e6dbda5 | |
Kevin Matz | cbd362b341 | |
Kevin Matz | 3f366f2aa0 | |
Kevin Matz | c3bc6dc44e | |
Kevin Matz | 67e84624ee | |
Kevin Matz | b18823a009 | |
Kevin Matz | 1563e620b9 | |
Kevin Matz | 91128ca9f3 | |
Kevin Matz | 6df0a06a04 | |
Kevin Matz | 63aef39f28 | |
Kevin Matz | f1f153f09e | |
Kevin Matz | 417d8cd18b | |
Kevin Matz | 5223d1bb33 | |
Kevin Matz | b79ea8d976 | |
Kevin Matz | 3bc8c200f6 | |
Kevin Matz | 4417a8888f | |
Kevin Matz | f484c2c8bd | |
Kevin Matz | 5c1f343688 | |
Kevin Matz | 95a5be2e65 | |
Kevin Matz | 8b5da1d1ca | |
Kevin Matz | 75598b4359 | |
Kevin Matz | 9b4e9eda03 | |
Kevin Matz | f19f572daf | |
Kevin Matz | 2c394a1bc7 | |
Kevin Matz | 5f331f262e | |
Kevin Matz | 0ab8dc2052 | |
Kevin Matz | 00b8309107 | |
Kevin Matz | 4242854696 | |
Kevin Matz | 107a239287 | |
Kevin Matz | 49b25f0a86 | |
Kevin Matz | 379ae89cb6 | |
Kevin Matz | 66410fdf22 | |
Kevin Matz | 18e710dc56 | |
Kevin Matz | 80bfce67b7 | |
Kevin Matz | a58ffc5c4a | |
Kevin Matz | 0bb177c68e | |
Kevin Matz | da32cf4240 | |
Kevin Matz | 1bf3f23a66 | |
Kevin Matz | 7fa3bde8b3 | |
Kevin Matz | 5d7f169692 | |
Kevin Matz | 47a71072ce | |
Kevin Matz | 4833b37739 | |
Kevin Matz | e435e04945 | |
Kevin Matz | 02d4d3a8b8 | |
Kevin Matz | a132719db9 | |
Kevin Matz | ca24b7878c | |
Kevin Matz | 7663b84ec5 | |
Kevin Matz | 4c0bf5ded8 | |
Kevin Matz | d7d64fa13c | |
Kevin Matz | 63e16701b9 | |
Kevin Matz | ea3bf9e5e8 | |
Kevin Matz | 32c6ccb60c | |
Kevin Matz | 244da5c8c1 | |
Kevin Matz | c16e999969 | |
Kevin Matz | f69c50d66d | |
Kevin Matz | c801ffb32b | |
Kevin Matz | 3cb89f51d3 | |
Kevin Matz | b6e080f7b4 | |
Kevin Matz | be4064bb14 | |
Kevin Matz | 6387944c68 | |
Kevin Matz | 55317f7513 | |
Kevin Matz | c89f55c107 | |
Kevin Matz | 3ab0c1141c | |
Kevin Matz | c12044326f | |
Kevin Matz | e1e9c9d473 | |
Kevin Matz | 9168f1b98c | |
Kevin Matz | 0617cc3bf6 | |
Kevin Matz | 388beecf8d | |
Kevin Matz | b92f159d02 | |
Kevin Matz | 153786efa4 | |
Kevin Matz | 97ae66e231 | |
Kevin Matz | e316c71a83 | |
Kevin Matz | a355edd722 | |
Kevin Matz | c2f1dcce07 | |
Kevin Matz | fa2625f77d | |
Kevin Matz | 024d965f83 | |
Kevin Matz | 640fa888c7 | |
Kevin Matz | 5a4e827f08 | |
Kevin Matz | 55c9060ff6 | |
Kevin Matz | 54244d4988 | |
Kevin Matz | 10ee3c1d22 | |
Kevin Matz | b6a81c2bd2 | |
Kevin Matz | 98e903159c | |
Kevin Matz | d496e71f8a | |
Kevin Matz | 1b272f0d23 | |
Kevin Matz | aaa6e04817 | |
Kevin Matz | 72a66be0b5 | |
Kevin Matz | 0b7143f809 | |
Kevin Matz | a2ec07e16c | |
Kevin Matz | c6c09be86a | |
Kevin Matz | 2cfbf384de | |
Kevin Matz | 8fdd8573af | |
Kevin Matz | f6c45d3b7f | |
Kevin Matz | d68c37fa82 | |
Kevin Matz | 9f6968c915 | |
Kevin Matz | 13bb3a39f0 | |
Kevin Matz | 3bea3097d0 | |
Kevin Matz | 76a7078cef | |
Kevin Matz | 0c36bd34b7 | |
Kevin Matz | d54b714981 | |
Kevin Matz | ebb8c284f9 | |
Kevin Matz | 12fddd4caa | |
Kevin Matz | 94c4af768e | |
Kevin Matz | cb2bc6548d | |
Kevin Matz | 8f3d704d5a | |
Kevin Matz | 8e41fc72c2 | |
Kevin Matz | 1ea49ebde2 | |
Kevin Matz | 099ea5a98a | |
Kevin Matz | dcbd735476 | |
Kevin Matz | 3e805960d7 | |
Kevin Matz | 7d1c756d89 | |
Kevin Matz | 3ee4cbb858 | |
Kevin Matz | 6486775626 | |
Kevin Matz | 55a120dfed | |
Kevin Matz | 189be8baf7 | |
Kevin Matz | 88f06738ab | |
Kevin Matz | 7b436915d8 | |
Kevin Matz | e1c1c1b579 | |
Kevin Matz | 847b6d931d | |
Kevin Matz | 396aac84f3 | |
Kevin Matz | aaacd8539b | |
Kevin Matz | 40a3342317 | |
Kevin Matz | b55a8fdd83 | |
Kevin Matz | db81ecefdc | |
Kevin Matz | c2ab4d9b22 | |
Kevin Matz | f9efddf583 | |
Kevin Matz | 6cd9a9e6df | |
Kevin Matz | cd884a8593 | |
Kevin Matz | b6e9425083 | |
Kevin Matz | 8979dd4699 | |
Kevin Matz | 9c5654b34d | |
Kevin Matz | 2c72607dc3 | |
Kevin Matz | 3ef5e0498b | |
Kevin Matz | 7dfb0a81d7 | |
Kevin Matz | 64e6ee1c0a | |
Kevin Matz | f5d99a03e6 | |
Kevin Matz | d87fbcf25d | |
Kevin Matz | 64140eb757 | |
Kevin Matz | e219e64d28 | |
Kevin Matz | 472b6a1c39 | |
Kevin Matz | a2bd30dbe5 | |
Kevin Matz | 74de2c07af | |
Kevin Matz | 27179b4e00 | |
Kevin Matz | b5f3e63b36 | |
Kevin Matz | 59dbad2264 | |
Kevin Matz | b84d7bafe9 | |
Kevin Matz | 059c115c15 | |
Kevin Matz | ce51e17ddd | |
Kevin Matz | 6da7001c67 | |
Kevin Matz | 89716e1af5 | |
Kevin Matz | 12b34cef6e | |
Kevin Matz | 7676cd54a2 | |
Kevin Matz | b18fb9547f | |
Kevin Matz | b48c90bfee | |
Kevin Matz | 628459840d | |
Kevin Matz | a3eb42523e | |
Kevin Matz | 293b2060b8 | |
Kevin Matz | 99e42dba17 | |
Kevin Matz | 0b02bc3c14 | |
Kevin Matz | 986dc9b89e | |
Kevin Matz | 849da8cecb | |
Kevin Matz | 8ce86353c9 | |
Kevin Matz | dbcde09616 | |
Kevin Matz | 52758cc904 | |
Kevin Matz | 37367cb6b4 | |
Kevin Matz | 19df7540c1 | |
Kevin Matz | eb47f6543f | |
Kevin Matz | 8221c90e24 | |
Kevin Matz | 16416ff10f | |
Kevin Matz | 45cee71b19 | |
Kevin Matz | c4cf9768ab | |
Kevin Matz | 1f57ef1029 | |
Kevin Matz | fe8bc039bc | |
Kevin Matz | c9dec23ea9 | |
Kevin Matz | 0c9c85830e | |
Kevin Matz | 4975c7e809 | |
Kevin Matz | 1d1be459c8 | |
Kevin Matz | e6146e9efe | |
Kevin Matz | 7f8e9ce4ac | |
Kevin Matz | d11e136247 | |
Kevin Matz | f1d3fe0256 | |
Kevin Matz | b7ca5c36f5 | |
Kevin Matz | 4fa5a2794a | |
Kevin Matz | 4d7413b949 | |
Kevin Matz | 47bc2c9ffd | |
Kevin Matz | 466efd7477 | |
Kevin Matz | d67b3b8f40 | |
Kevin Matz | f9896ff5f3 | |
Kevin Matz | 85a7ec4762 | |
Kevin Matz | 8a83107ee7 | |
Kevin Matz | 0ac22b232d | |
Kevin Matz | 46938de0f5 | |
Kevin Matz | 6237a9ef37 | |
Kevin Matz | 67e4472751 | |
Kevin Matz | e34a5acb5c | |
Kevin Matz | 63ee24d334 | |
Kevin Matz | 382f2d26fd | |
Kevin Matz | 2b5db0febd | |
Kevin Matz | ce3e1ebf7c | |
Kevin Matz | 433e91f70c | |
Kevin Matz | 8f42e0bca6 | |
Kevin Matz | 26ae455a07 | |
Kevin Matz | 7434ac3d91 | |
Kevin Matz | f496b89e44 | |
Kevin Matz | 97ef3d5078 | |
Kevin Matz | b07d505bce | |
Kevin Matz | 8a2e838916 | |
Kevin Matz | 1cad61ffc7 | |
Kevin Matz | 635dc36ada | |
Kevin Matz | 39a6bcbfbf | |
Kevin Matz | f9f0d1298e | |
Kevin Matz | ed40a3b666 | |
Kevin Matz | e7297c42ff | |
Kevin Matz | 5ad7b5505e | |
Kevin Matz | 738ced6964 | |
Kevin Matz | ca75a9973c | |
Kevin Matz | 837bf1ad98 | |
Kevin Matz | 6cf9d98e8f | |
Kevin Matz | b031ccbcd7 | |
Kevin Matz | 0b17605873 | |
Kevin Matz | a24a6e8b38 | |
Kevin Matz | 55bcdc8db6 | |
Kevin Matz | 304c74ae39 | |
Kevin Matz | 3c3b1d9955 | |
Kevin Matz | 0e2b2ce039 | |
Kevin Matz | 170eb40f50 | |
Kevin Matz | 4e536a6f1e | |
Kevin Matz | 302cf63ca6 | |
Kevin Matz | 501167c8ee | |
Kevin Matz | 4652623bb7 | |
Kevin Matz | af3cf519f1 | |
Kevin Matz | 14a3b644f8 | |
Kevin Matz | bc9bb9b5a7 | |
Kevin Matz | c1cc0ef6ed | |
Kevin Matz | b267627376 | |
Kevin Matz | 93e5680f85 | |
Kevin Matz | 8f3c59783f | |
Kevin Matz | fe53e57a6d | |
Kevin Matz | 18e14b99d8 | |
Kevin Matz | c46ca4b0cc | |
Kevin Matz | 6e6b47edb5 | |
Kevin Matz | 8c54793edd | |
Kevin Matz | da484ef028 | |
Kevin Matz | 798affa3fb | |
Kevin Matz | f11536e152 | |
Kevin Matz | 7a877f97ad | |
Kevin Matz | 0fb5db0574 | |
Kevin Matz | 3da5529b39 | |
Kevin Matz | 31f460913c | |
Kevin Matz | 13a775d0fd | |
Kevin Matz | 4241cd0c38 | |
Kevin Matz | 37010fa8d4 | |
Kevin Matz | 3135c0ae6c | |
Kevin Matz | a03ef79d5d | |
Kevin Matz | e0394e4413 | |
Kevin Matz | be17ea9bb5 | |
Kevin Matz | a443299204 | |
Kevin Matz | 9548145629 | |
Kevin Matz | f500d6d376 | |
Kevin Matz | 2cb021b184 | |
Kevin Matz | 6a977bbf01 | |
Kevin Matz | 11c6013a00 | |
Kevin Matz | 704c3a2985 | |
Kevin Matz | db7bf1dcb3 | |
Kevin Matz | 2491054e1d | |
Kevin Matz | 85bd5accd0 | |
Kevin Matz | 4745458b87 | |
Kevin Matz | 6a2d1242be | |
Kevin Matz | 8e082fb112 | |
Kevin Matz | 874f6482a3 | |
Kevin Matz | 41530d86c1 | |
Kevin Matz | d4c0665330 | |
Kevin Matz | 79ed3e5a8b | |
Kevin Matz | 0ba1ecc9ac | |
Kevin Matz | 32557b5c31 | |
Kevin Matz | 15281fc3a7 | |
Kevin Matz | 277fdf9c45 | |
Kevin Matz | f17487b30f | |
Kevin Matz | f12b055aa0 | |
Kevin Matz | c9377418fa | |
Kevin Matz | 3e648dc152 | |
Kevin Matz | 3e2d22e77e | |
Kevin Matz | a86d968569 | |
Kevin Matz | 9e7b45d38c | |
Kevin Matz | 212e1ce0af | |
Kevin Matz | 13d1251511 | |
Kevin Matz | 375ec153fe | |
Kevin Matz | 9b49e63cab | |
Kevin Matz | 2f1d29ecfa | |
Kevin Matz | a8377b6635 | |
Kevin Matz | e2b46c5bbf | |
Kevin Matz | a74bd32598 | |
Kevin Matz | a81bc5b1fd | |
Kevin Matz | fdb2980d0e | |
Kevin Matz | 71edfaf7e9 | |
Kevin Matz | 73ee83cd9e | |
Kevin Matz | 3e77990490 | |
Kevin Matz | 686b11f1f0 | |
Kevin Matz | c4baa96960 | |
Kevin Matz | 3f78a9de01 | |
Kevin Matz | 01c569ffa4 | |
Kevin Matz | c786b422e2 | |
Kevin Matz | 5a90c9fbbc | |
Kevin Matz | 62131a9dc5 | |
Kevin Matz | 41c46e7bc5 | |
Kevin Matz | 0292051392 | |
Kevin Matz | 3835cee9bd | |
Kevin Matz | 11d202d51a | |
Kevin Matz | 617b082f96 | |
Kevin Matz | 52cdef90dd | |
Kevin Matz | 79ba3a18b7 | |
Kevin Matz | ab0c2f0e62 | |
Kevin Matz | 3f717454eb | |
Kevin Matz | d57a6a31ad | |
Kevin Matz | 4ad13317fd | |
Kevin Matz | 758740704c | |
Kevin Matz | 4a1c50cdf1 | |
Kevin Matz | 24ba41a73f | |
Kevin Matz | 29fd1fc9ed | |
Kevin Matz | 9e94c406fd | |
Kevin Matz | 9889a8943c | |
Kevin Matz | 3695ae80fe | |
Kevin Matz | 88c4bb628f | |
Kevin Matz | e12d11e62e | |
Kevin Matz | e157090e35 | |
Kevin Matz | da668011fe | |
Kevin Matz | d41ecc65bc | |
Kevin Matz | c00e0e1d9a | |
Kevin Matz | 5c2548a5d3 | |
Kevin Matz | 8a3eb928dd | |
Kevin Matz | 6be8dfbebd | |
Kevin Matz | cf1df8d373 | |
Kevin Matz | 09dc532984 | |
Kevin Matz | 357f8d5359 | |
Kevin Matz | 81e68203ff | |
Kevin Matz | f27faa4fe6 | |
Kevin Matz | fdbff859c7 | |
Kevin Matz | 6ad6aa28f8 | |
Kevin Matz | a5958448ff | |
Kevin Matz | 53ac273c0b | |
Kevin Matz | e76fd2f603 | |
Kevin Matz | 718a3dd22f | |
Kevin Matz | 07cc2e1c0a | |
Kevin Matz | b0a16a8cb7 | |
Kevin Matz | 8fd8ef977d | |
Kevin Matz | 54ead231ad | |
Kevin Matz | 4a52f7302d | |
Kevin Matz | 3672b1d49f | |
Kevin Matz | 1ad91bdefe | |
Kevin Matz | ae838bee21 | |
Kevin Matz | 10031d3a05 | |
Kevin Matz | 2d93bbe709 | |
Kevin Matz | e14556fe16 | |
Kevin Matz | b26bf158c0 | |
Kevin Matz | 99d919d613 | |
Kevin Matz | 2003e22110 | |
Kevin Matz | df8356e7dc | |
Kevin Matz | b748dd9a01 | |
Kevin Matz | b7f045cd6b | |
Kevin Matz | d84dde0cb2 | |
Kevin Matz | e4119c5189 | |
Kevin Matz | 488328a05e | |
Kevin Matz | e98b6f5734 | |
Kevin Matz | e528abe5e1 | |
Kevin Matz | 0cad3288d7 | |
Kevin Matz | 01e3549861 | |
Kevin Matz | 5c0a74e338 | |
Kevin Matz | 33347894af | |
Kevin Matz | 498129c325 | |
Kevin Matz | a0a1c13cb3 | |
Kevin Matz | 24e7b5eb4c | |
Kevin Matz | ef38fae104 | |
Kevin Matz | ffb82f3029 | |
Kevin Matz | 693b361244 | |
Kevin Matz | 8fb0666ff0 | |
Kevin Matz | a0cb4c18a4 | |
Kevin Matz | e889e36ccd | |
Kevin Matz | d60721cf79 | |
Kevin Matz | 056063ab2c | |
Kevin Matz | 4ddd786053 | |
Kevin Matz | 85e631424a | |
Kevin Matz | 7def265788 | |
Kevin Matz | 9b918b858e | |
Kevin Matz | bfb46eb0df | |
Kevin Matz | 186f7d9134 | |
Kevin Matz | 091dea3ed5 | |
Kevin Matz | 0f481ae2a7 | |
Kevin Matz | 4123e85328 | |
Kevin Matz | 161f8644ec | |
Kevin Matz | b4f27c88f7 | |
Kevin Matz | 8e7489082b | |
Kevin Matz | 5f46377e95 | |
Kevin Matz | bca742c37f | |
Kevin Matz | 248aca3f70 | |
Kevin Matz | b20e463177 | |
Kevin Matz | 0b5435065e | |
Kevin Matz | db3c782bef | |
Kevin Matz | 933279e068 | |
Kevin Matz | ea1b281cde | |
Kevin Matz | ce1e17e9fc | |
Kevin Matz | 5220fa5b3d | |
Kevin Matz | 1904dacea9 | |
Kevin Matz | 27e668457f | |
Kevin Matz | 2942d2800d | |
Kevin Matz | 5710631724 | |
Kevin Matz | 7826d17d40 | |
Kevin Matz | f03b2140fa | |
Kevin Matz | b0c291b146 | |
Kevin Matz | fa24d64c72 | |
Kevin Matz | 5d4f6654cd | |
Kevin Matz | ca671229f7 | |
Kevin Matz | 77113b9139 | |
Kevin Matz | 74b95cf007 | |
Kevin Matz | 497f45a0df | |
Kevin Matz | ce8baafcf3 | |
Kevin Matz | 00d825ccf8 | |
Kevin Matz | a4978c514e | |
Kevin Matz | 53868f7162 | |
Kevin Matz | b95f6a72d6 | |
Kevin Matz | 1842c69b02 | |
Kevin Matz | 7c1f529833 | |
Kevin Matz | 95916aea56 | |
Kevin Matz | c5c4df1888 | |
Kevin Matz | 6b960d8913 | |
Kevin Matz | 9fe57bc363 | |
Kevin Matz | 4cd8289a85 | |
Kevin Matz | 6cc9f6e025 | |
Kevin Matz | b8a113759b | |
Kevin Matz | 6a24a9ed57 | |
Kevin Matz | c30d6a33f2 | |
Kevin Matz | 0afb725606 | |
Kevin Matz | ad6098c553 | |
Kevin Matz | f21a071860 | |
Kevin Matz | 7ae5896bc8 | |
Kevin Matz | ae2a03531b | |
Kevin Matz | 567e70d42b | |
Kevin Matz | e1f4ec5b2c | |
Kevin Matz | 4b1c4f51af | |
Kevin Matz | 2b9f2c4dfe | |
Kevin Matz | ccc99b3097 | |
Kevin Matz | 04eb87ed82 | |
Kevin Matz | 8e50d9292d | |
Kevin Matz | abc7b35b70 | |
Kevin Matz | bb71d2ca61 | |
Kevin Matz | abbfe6ee0d | |
Kevin Matz | 7039696807 | |
Kevin Matz | 7c53a1d9ca | |
Kevin Matz | 6f6ec334ca | |
Kevin Matz | 220a70d51b | |
Kevin Matz | 69bca798c4 | |
Kevin Matz | dfeff5c344 | |
Kevin Matz | b5c099af35 | |
Kevin Matz | 7b0cb3d268 | |
Kevin Matz | d3da663a44 | |
Kevin Matz | 15ad9a565b | |
Kevin Matz | 053339d2d1 | |
Kevin Matz | ef59af7207 | |
Kevin Matz | eaaad9ae4e | |
Kevin Matz | b609966df7 | |
Kevin Matz | f29c082d4f | |
Kevin Matz | 7229657f4d | |
Kevin Matz | b64f04443c | |
Kevin Matz | 1aee17d70e | |
Kevin Matz | 66870b970a | |
Kevin Matz | 511c4224ef | |
Kevin Matz | 4f7d77c803 | |
Kevin Matz | a699210d77 | |
Kevin Matz | 719eca89d7 | |
Kevin Matz | 85e85edbbd | |
Kevin Matz | 33479b37ee | |
Kevin Matz | 63a87f4f1a | |
Kevin Matz | 5863e6b1f9 | |
Kevin Matz | f7acefccd0 | |
Kevin Matz | ac3dc494d0 | |
Kevin Matz | 1e2bb87493 | |
Kevin Matz | 3e8cc72a61 | |
Kevin Matz | 62e15b4bd6 | |
Kevin Matz | 0a3f14e75f | |
Kevin Matz | ee2c46625f | |
Kevin Matz | 1744e34598 | |
Kevin Matz | 111aa59417 | |
Kevin Matz | f3f6b0d35f | |
Kevin Matz | 42368ea01f | |
Kevin Matz | f6a50926c3 | |
Kevin Matz | 891a2e9dfa | |
Kevin Matz | 12843e8264 | |
Kevin Matz | c1efa991fb | |
Kevin Matz | 5693689267 | |
Kevin Matz | 745d62f4a1 | |
Kevin Matz | b3ee265745 | |
Kevin Matz | 0090fa5706 | |
Kevin Matz | d021366fc8 | |
Kevin Matz | 25227ff083 | |
Kevin Matz | c6386b0272 | |
Kevin Matz | 5fe5b71148 | |
Kevin Matz | 777732e378 | |
Kevin Matz | 7a1bcd74ca | |
Kevin Matz | bf50231f6f | |
Kevin Matz | 7fb9bb8f9a | |
Kevin Matz | b3db8ecd47 | |
Kevin Matz | e538cc74d3 | |
Kevin Matz | bbe4432737 | |
Kevin Matz | 76582b1cc4 | |
Kevin Matz | 3ea916d61e | |
Kevin Matz | 6ade2e1ef6 | |
Kevin Matz | 2d83dd3e5c | |
Kevin Matz | c11f6c0510 | |
Kevin Matz | de786159ff | |
Kevin Matz | b186c949f9 | |
Kevin Matz | 7f0919f60c | |
Kevin Matz | 79be37d38a | |
Kevin Matz | f3f5aa3d3e | |
Kevin Matz | 41686e32af | |
Kevin Matz | 64046967ce | |
Kevin Matz | 3dd300902f | |
Kevin Matz | 5c0cc41af5 | |
Kevin Matz | b3256be388 | |
Kevin Matz | dfeaf646e1 | |
Kevin Matz | 5459b48388 | |
Kevin Matz | f5055020ed | |
Kevin Matz | a8e997258b | |
Kevin Matz | 958083f6a3 | |
Kevin Matz | 3aa1142ade | |
Kevin Matz | 4d052c84e8 | |
Kevin Matz | 681f659934 | |
Kevin Matz | ca89b60e21 | |
Kevin Matz | 26136e1397 | |
Kevin Matz | 1ec57961f7 | |
Kevin Matz | 6822fe2d52 | |
Kevin Matz | 33b1d4eda7 | |
Kevin Matz | e0d142797a | |
Kevin Matz | 4e38144e9f | |
Kevin Matz | 6f4ce45413 | |
Kevin Matz | 535cc66cd1 | |
Kevin Matz | b356d052b3 | |
Kevin Matz | 7c40861383 | |
Kevin Matz | c811469c95 | |
Kevin Matz | 044c95b168 | |
Kevin Matz | 740bd851c0 | |
Kevin Matz | 337a8a650d | |
Kevin Matz | 355f2b3365 | |
Kevin Matz | 00b1c1299f | |
Kevin Matz | ae5732e2fd | |
Kevin Matz | 858e8b4770 | |
Kevin Matz | 68bc049b8b | |
Kevin Matz | decf1f7cb1 | |
Kevin Matz | b9e752e87e | |
Kevin Matz | 207c6b8b71 | |
Kevin Matz | 7c9c62d0fe | |
Kevin Matz | 09b2210616 | |
Kevin Matz | d421a5c577 | |
Kevin Matz | f0d9402db9 | |
Kevin Matz | 9c10bd7124 | |
Kevin Matz | b8e6dcdcef | |
Kevin Matz | 7495897b4a | |
Kevin Matz | d9e42f5c2f | |
Kevin Matz | 9b0ff5821b | |
Kevin Matz | cf9ae4c89b | |
Kevin Matz | 8f7463b67f | |
Kevin Matz | 7a0fbb0e41 | |
Kevin Matz | 66b6c8e7e4 | |
Kevin Matz | 7a49267125 | |
Kevin Matz | b934477b19 | |
Kevin Matz | 9ab058393e | |
Kevin Matz | dbed27aacf | |
Kevin Matz | 08c07d1c9c | |
Kevin Matz | 5101e0d22c | |
Kevin Matz | 3bad5e0d3e | |
Kevin Matz | 324ac934e8 | |
Kevin Matz | ca990976e5 | |
Kevin Matz | a99a191e13 | |
Kevin Matz | 6def8d52b9 | |
Kevin Matz | ea3abc97b5 | |
Kevin Matz | 287ae5f6cc | |
Kevin Matz | bb290ab7cb | |
Kevin Matz | eaac6084a2 | |
Kevin Matz | 39cb36c46e | |
Kevin Matz | c808341eff | |
Kevin Matz | 9b4f698b9a | |
Kevin Matz | 0614524d0b | |
Kevin Matz | bd05b0439a | |
Kevin Matz | 6a9247ccea | |
Kevin Matz | 54df9ae8f6 | |
Kevin Matz | 031888f192 | |
Kevin Matz | 6cde71a672 | |
Kevin Matz | 193899b4d7 | |
Kevin Matz | 2a26e7fa49 | |
Kevin Matz | 8c99f4ff1f | |
Kevin Matz | f40ac836ce | |
Kevin Matz | 8d6a765c3a | |
Kevin Matz | 05f629440b | |
Kevin Matz | 8392d2e433 | |
Kevin Matz | 3c78f0570d | |
Kevin Matz | 390b2eadf6 | |
Kevin Matz | 60ac42d024 | |
Kevin Matz | c724f414b1 | |
Kevin Matz | 381d5fe4f9 | |
Kevin Matz | 53602709fa | |
Kevin Matz | 8de1af8e89 | |
Kevin Matz | 44c87055fd | |
Kevin Matz | b92593c653 | |
Kevin Matz | 03300620fc | |
Kevin Matz | c05e47abf2 | |
Kevin Matz | da90565ed5 | |
Kevin Matz | ce74d93cad | |
Kevin Matz | 7f9764ac43 | |
Kevin Matz | f948932c90 | |
Kevin Matz | d5bef37ddf | |
Kevin Matz | 883dbc7f8c | |
Kevin Matz | dee458bf88 | |
Kevin Matz | 5ea90a2beb | |
Kevin Matz | 30f61fa019 | |
Kevin Matz | c41d26bf7f | |
Kevin Matz | a62c41e6bc | |
Kevin Matz | db5f007d22 | |
Kevin Matz | 8d24b8b7a1 | |
Kevin Matz | 3a801fba51 | |
Kevin Matz | 0ad24fab4b | |
Kevin Matz | ec6edf19e0 | |
Kevin Matz | b147ca0578 | |
Kevin Matz | cc40307adb | |
Kevin Matz | 026a4a16de | |
Kevin Matz | 9d5c985254 | |
Kevin Matz | 7a0a8765a6 | |
Kevin Matz | 5f72daa789 | |
Kevin Matz | ce575e983e | |
Kevin Matz | a496cca21e | |
Kevin Matz | 32da243239 | |
Kevin Matz | edd351fce0 | |
Kevin Matz | 00a32ea2f9 | |
Kevin Matz | 922deb81da | |
Kevin Matz | 7f338164b6 | |
Kevin Matz | 8acfa70f00 | |
Kevin Matz | 571e6a73ec | |
Kevin Matz | 5e9f957731 | |
Kevin Matz | 92e14a2b5c | |
Kevin Matz | 03347a5e21 | |
Kevin Matz | 914be1f3f9 | |
Kevin Matz | 1c9f1e3769 | |
Kevin Matz | e5539ffc37 | |
Kevin Matz | 00cb16a5e5 | |
Kevin Matz | d77cf14dae | |
Kevin Matz | 6c12f7d8f0 | |
Kevin Matz | 1371d17af1 | |
Kevin Matz | 818a0891a3 | |
Kevin Matz | a5710d1bb5 | |
Kevin Matz | 8c75d11199 | |
Kevin Matz | 45298929c8 | |
Kevin Matz | c67c13a556 | |
Kevin Matz | 10606810cb | |
Kevin Matz | a746cb9022 | |
Kevin Matz | 68020b0213 | |
Kevin Matz | 507a2352ff | |
Kevin Matz | c552fba1ba | |
Kevin Matz | 936f47903b | |
Kevin Matz | 01deea9613 | |
Kevin Matz | ba3e17361d | |
Kevin Matz | 6e47d8690f | |
Kevin Matz | edb0880bdf | |
Kevin Matz | 2eac9cf7fb | |
Kevin Matz | 9dbfb9bac6 | |
Kevin Matz | 112326b695 | |
Kevin Matz | 1d5e36ae10 | |
Kevin Matz | a8011898e8 | |
Kevin Matz | f7ac8f2a10 | |
Kevin Matz | 7feb63ecd6 | |
Kevin Matz | fd0eeef934 | |
Kevin Matz | ed82a617e6 | |
Kevin Matz | 069dd3ede8 | |
Kevin Matz | 9a619b0e63 | |
Kevin Matz | d3f1edc33a | |
Kevin Matz | d5f05f6fbe | |
Kevin Matz | 2fa42b1c53 | |
Kevin Matz | 69cb85ef9f | |
Kevin Matz | 4440cb6f37 | |
Kevin Matz | a6620f1def | |
Kevin Matz | abb5bf1466 | |
Kevin Matz | db6a22f4a5 | |
Kevin Matz | 0391910de5 | |
Kevin Matz | 3e0cfe4868 | |
Kevin Matz | 53b6345038 | |
Kevin Matz | 53406333e3 | |
Kevin Matz | 83e7a8d711 | |
Kevin Matz | 95853bd25b | |
Kevin Matz | e6507c738d | |
Kevin Matz | c033be5b6e | |
Kevin Matz | 52d57c282c | |
Kevin Matz | 518f9826de | |
Kevin Matz | 7981acc57b | |
Kevin Matz | 41d7553e1e | |
Kevin Matz | d883f061a4 | |
Kevin Matz | 13cba79652 | |
Kevin Matz | 0c96f9a69c | |
Kevin Matz | 587f5a8d33 | |
Kevin Matz | fa13e03ffa | |
Kevin Matz | 2c304859b9 | |
Kevin Matz | 13651c7cf2 | |
Kevin Matz | edd3056341 | |
Kevin Matz | e54bd78929 | |
Kevin Matz | 2ebc026af7 | |
Kevin Matz | 16217879c1 | |
Kevin Matz | ee6bed286e | |
Kevin Matz | 966e3f3909 | |
Kevin Matz | 0f00adbc55 | |
Kevin Matz | ea9b7f909d | |
Kevin Matz | 9605766ddb | |
Kevin Matz | f93091a1ba | |
Kevin Matz | fc26e5e833 | |
Kevin Matz | 33476e0ceb | |
Kevin Matz | a18501f607 | |
Kevin Matz | 0124123a71 | |
Kevin Matz | 57c3247eaf | |
Kevin Matz | ec80a809db | |
Kevin Matz | 32c5a37f57 | |
Kevin Matz | e273f2ffa6 | |
Kevin Matz | 6684a90b4e | |
Kevin Matz | ef0f38f8ad | |
Kevin Matz | 75b4b13997 | |
Kevin Matz | de435d5021 | |
Kevin Matz | 141786ad67 | |
Kevin Matz | 8e7a96ac82 | |
Kevin Matz | 3a86b10a8b | |
Kevin Matz | 2bb4460444 | |
Kevin Matz | 5bc4c58b0e | |
Kevin Matz | 9127ad45b9 | |
Kevin Matz | 41eba231a5 | |
Kevin Matz | 1bd99631a9 | |
Kevin Matz | 0202dc9422 | |
Kevin Matz | 086cd0520c | |
Kevin Matz | af47a4cfb7 | |
Kevin Matz | e4fb0736fc | |
Kevin Matz | d449d8efe5 | |
Kevin Matz | 1d996f46a4 | |
Kevin Matz | 970617b9cd | |
Kevin Matz | cd2f5cae7f | |
Kevin Matz | d78a5db381 | |
Kevin Matz | 47a5adcc8a | |
Kevin Matz | c8041ca857 | |
Kevin Matz | ac8fe68141 | |
Kevin Matz | cfd0aa647d | |
Kevin Matz | 7ad1962d3b | |
Kevin Matz | d7eb23a120 | |
Kevin Matz | 23a4647342 | |
Kevin Matz | eaf49608e5 | |
Kevin Matz | c6a969bd9a | |
Kevin Matz | af8dcc92aa | |
Kevin Matz | c01cd08b86 | |
Kevin Matz | 4ce0bf73ea | |
Kevin Matz | f9c01e5ad3 | |
Kevin Matz | 31108b7499 | |
Kevin Matz | dd1f4ff4ff | |
Kevin Matz | 92b211056e | |
Kevin Matz | b2facb639a | |
Kevin Matz | 4ce12e250d | |
Kevin Matz | 96fa743548 | |
Kevin Matz | 82f5a61cb2 | |
Kevin Matz | 6c20317de3 | |
Kevin Matz | 51de1b2164 | |
Kevin Matz | 50023e765b | |
Kevin Matz | c67472a8be | |
Kevin Matz | 79502361fd | |
Kevin Matz | 00d769c22e | |
Kevin Matz | aaca7ea461 | |
Kevin Matz | d69566bbc9 | |
Kevin Matz | 7351e3c0df | |
Kevin Matz | 1d4d99c9c2 | |
Kevin Matz | 833b33b751 | |
Kevin Matz | 51fb16455d | |
Kevin Matz | 4702ac333d | |
Kevin Matz | 5851e1d55e | |
Kevin Matz | 8512da0b32 | |
Kevin Matz | 42022baecf | |
Kevin Matz | 0cb3c72226 | |
Kevin Matz | c6aef08e55 | |
Kevin Matz | 7e58ce5562 | |
Kevin Matz | 555122bd3b | |
Kevin Matz | 44f5992340 | |
Kevin Matz | 237943489f | |
Kevin Matz | d1ee4e1dae | |
Kevin Matz | 86576ad4e3 | |
Kevin Matz | e49ba6c53f | |
Kevin Matz | 0c6eb37e38 | |
Kevin Matz | c3c0cc884a | |
Kevin Matz | 32ef49554b | |
Kevin Matz | 84476d33c7 | |
Kevin Matz | ce28e5c645 | |
Kevin Matz | e7ec979b9b | |
Kevin Matz | 9f407da475 | |
Kevin Matz | 619430fe50 | |
Kevin Matz | b2316fe388 | |
Kevin Matz | f223d5fea4 | |
Kevin Matz | 21e61c7ac4 | |
Kevin Matz | c58163fb8d | |
Kevin Matz | 2858d64218 | |
Kevin Matz | 88c7787f41 | |
Kevin Matz | b5e4c05922 | |
Kevin Matz | a761675e89 | |
Kevin Matz | 941f2c74dd | |
Kevin Matz | 41183883f6 | |
Kevin Matz | cee9ce5ed0 | |
Kevin Matz | 474cc28d0e | |
Kevin Matz | 65df669754 | |
Kevin Matz | 2c74470b16 | |
Kevin Matz | 603080b9f3 | |
Kevin Matz | 50f29808ef | |
Kevin Matz | 15b611983a | |
Kevin Matz | 418553fd3c | |
Kevin Matz | 76eca2aebf |
|
@ -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
|
|
@ -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)
|
||||
|
|
2
LICENSE
2
LICENSE
|
@ -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
110
README.md
|
@ -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 © 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.
|
||||
|
|
|
@ -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`]
|
60
acn/acn.h
60
acn/acn.h
|
@ -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
|
|
@ -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
|
|
@ -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
|
119
acn/dmp.cpp
119
acn/dmp.cpp
|
@ -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
121
acn/dmp.h
|
@ -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 property’s value may not be read.
|
||||
NOT_WRITABLE = 4, // The property’s 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 property’s 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
|
177
acn/pdu.cpp
177
acn/pdu.cpp
|
@ -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
171
acn/pdu.h
|
@ -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
|
|
@ -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
|
169
acn/sdt.cpp
169
acn/sdt.cpp
|
@ -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
313
acn/sdt.h
|
@ -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 can’t create one
|
||||
CHANNEL_EXPIRED = 7, // Channel has expired.
|
||||
LOST_SEQUENCE = 8, // Unrecoverable packets missed.
|
||||
SATURATED = 9, // Can’t 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
|
|
@ -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
|
|
@ -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
|
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 518 KiB |
File diff suppressed because it is too large
Load Diff
|
@ -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 © 2020-2023 Kevin Matz -- Generated by doxygen $doxygenversion</li>
|
||||
</ul>
|
||||
</div>
|
||||
<!--END GENERATE_TREEVIEW-->
|
||||
<!--BEGIN !GENERATE_TREEVIEW-->
|
||||
<address class="footer">
|
||||
<small>
|
||||
Copyright © 2020-2023 Kevin Matz -- Generated by doxygen $doxygenversion
|
||||
</small>
|
||||
</address>
|
||||
<!--END !GENERATE_TREEVIEW-->
|
||||
</body>
|
||||
</html>
|
|
@ -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--> <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
|
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 170 KiB |
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 318 KiB |
Binary file not shown.
|
@ -0,0 +1,3 @@
|
|||
add_subdirectory("sACN Explorer")
|
||||
|
||||
add_subdirectory("widgetExplorer")
|
|
@ -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}
|
||||
)
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
};
|
|
@ -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>
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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>
|
|
@ -0,0 +1,7 @@
|
|||
#include "sacnexplorer.h"
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
SacnExplorer a(argc, argv);
|
||||
return a.exec();
|
||||
}
|
|
@ -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 ¤t,
|
||||
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();
|
||||
}
|
|
@ -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 ¤t, 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;
|
||||
};
|
|
@ -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>
|
|
@ -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();
|
||||
}
|
|
@ -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_;
|
||||
};
|
||||
|
|
@ -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}
|
||||
)
|
|
@ -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 ¤t, 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();
|
||||
}
|
|
@ -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 ¤t, const QModelIndex &previous);
|
||||
void openDmxWindow(const QModelIndex &index);
|
||||
|
||||
private:
|
||||
Ui::DeviceWindow *ui;
|
||||
WidgetModel *model;
|
||||
QSortFilterProxyModel *sortProxy;
|
||||
QSet<UniverseWindow*> inspectors_;
|
||||
};
|
||||
|
|
@ -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>
|
|
@ -0,0 +1,7 @@
|
|||
#include "widgetexplorer.h"
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
WidgetExplorer a(argc, argv);
|
||||
return a.exec();
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
|
|
@ -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>
|
|
@ -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();
|
||||
}
|
|
@ -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_;
|
||||
};
|
||||
|
|
@ -0,0 +1 @@
|
|||
add_subdirectory(bufferstream)
|
|
@ -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})
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
};
|
|
@ -0,0 +1 @@
|
|||
add_subdirectory(qt)
|
|
@ -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)
|
|
@ -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})
|
|
@ -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;
|
||||
}
|
|
@ -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)
|
|
@ -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()));
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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_;
|
||||
};
|
||||
|
|
@ -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})
|
|
@ -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_;
|
||||
}
|
||||
|
|
@ -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;
|
||||
};
|
|
@ -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();
|
||||
}
|
|
@ -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);
|
||||
};
|
|
@ -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
|
|
@ -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!";
|
||||
}
|
|
@ -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);
|
||||
};
|
||||
};
|
||||
|
|
@ -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();
|
||||
}
|
|
@ -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*)
|
|
@ -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_;
|
||||
}
|
|
@ -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;
|
||||
};
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
};
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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>
|
|
@ -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)
|
|
@ -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/)
|
||||
|
|
@ -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"
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
# Art-Net 4
|
||||
add_subdirectory(artnet)
|
|
@ -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})
|
|
@ -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.
|
|
@ -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 node’s 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; //!< Node’s 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 SIP’s.
|
||||
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 SIP’s.
|
||||
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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -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
|
|
@ -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
|
@ -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 Controller’s 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 Node’s 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 Node’s 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
|
|
@ -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
|
|
@ -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
|
|
@ -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
Loading…
Reference in New Issue