Compare commits
No commits in common. "master" and "1.0.0" have entirely different histories.
3
.gitmodules
vendored
3
.gitmodules
vendored
|
@ -1,3 +0,0 @@
|
|||
[submodule "docs/doxygen_theme_flat_design"]
|
||||
path = docs/doxygen_theme_flat_design
|
||||
url = https://github.com/kcwongjoe/doxygen_theme_flat_design.git
|
|
@ -1,41 +1,47 @@
|
|||
cmake_minimum_required(VERSION 3.20)
|
||||
set(DEFAULT_BUILD_TYPE "Release")
|
||||
project(lcp VERSION 0.3.1)
|
||||
cmake_minimum_required(VERSION 3.14)
|
||||
|
||||
project(ESTA VERSION 1.0.0 LANGUAGES CXX)
|
||||
|
||||
set(DEFAULT_BUILD_TYPE "Release")
|
||||
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()
|
||||
|
||||
add_subdirectory(example)
|
||||
add_subdirectory(lib)
|
||||
add_subdirectory(platform)
|
||||
add_subdirectory(protocol)
|
||||
add_subdirectory(test)
|
||||
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
|
||||
)
|
||||
|
||||
#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()
|
||||
add_library(${PROJECT_NAME} SHARED ${SOURCE_FILES})
|
||||
|
||||
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-2022 Kevin Matz
|
||||
Copyright (c) 2020 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,50 +1,15 @@
|
|||
# OpenLCP
|
||||
# LibESTA
|
||||
|
||||
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%">
|
||||
|
||||
A collection of C++ libraries for ESTA sponsored ANSI standards.
|
||||
|
||||
## 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 | ✓ |
|
||||
| ACN Session Data Transport Protocol (SDT) | E1.17 | i/o |
|
||||
| ACN Root Layer Protocol (RLP) | E1.17 | Rx |
|
||||
| ACN Session Data Transport Protocol (SDT) | E1.17 | |
|
||||
| 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 | |
|
||||
|
@ -53,68 +18,27 @@ may not be inherited from sibling `PDU` in the data.
|
|||
| 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 | ✓ |
|
||||
| EPI 17 - ACN Root Layer Protocol Operation on UDP | E1.17 | Rx |
|
||||
| EPI 18 - Operation of SDT on UDP Networks | E1.17 | |
|
||||
| EPI 19 - ACN Discovery on IP Networks | E1.17 | Names |
|
||||
| EPI 19 - ACN Discovery on IP Networks | E1.17 | |
|
||||
| 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 | Responder |
|
||||
| RDM Subdevice | E1.20 | ✓ |
|
||||
| RDM Sensors | E1.20 | ✓ |
|
||||
| RDM Discovery | E1.20 | ✓ |
|
||||
| RDM Required PIDs | E1.20 | ✓ |
|
||||
| RDM Status collection PIDs | E1.20 | ✓ |
|
||||
| Remote Device Management (RDM) | 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/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 |
|
||||
| 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 | |
|
||||
|
||||
### 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 |
|
||||
| :- | :-: |:-: | :-: |
|
||||
| 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.
|
||||
|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 | |
|
||||
|
|
35
acn/README.md
Normal file
35
acn/README.md
Normal file
|
@ -0,0 +1,35 @@
|
|||
# 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`]
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
rlp.h
|
||||
acn.h
|
||||
|
||||
Copyright (c) 2020 Kevin Matz (kevin.matz@gmail.com)
|
||||
|
||||
|
@ -23,58 +23,38 @@
|
|||
*/
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include "appliance.h"
|
||||
#include "component.h"
|
||||
#include "dmp.h"
|
||||
#include "pdu.h"
|
||||
#include "uuid.h"
|
||||
#include "rlp.h"
|
||||
#include "rlp-tcp.h"
|
||||
#include "rlp-udp.h"
|
||||
#include "sdt.h"
|
||||
#include "sdt-udp.h"
|
||||
|
||||
/**
|
||||
* @brief @cite ACN 2.3.2 The Root Layer Protocol
|
||||
*
|
||||
* The lowest ACN layer above the underlying transport (e.g. the contents of a UDP packet) is
|
||||
* handled by the ACN Root Layer Protocol. The Root Layer protocol may be tailored to fit higher
|
||||
* ACN protocols onto each individual transport.
|
||||
*/
|
||||
namespace ACN::RLP {
|
||||
// ANSI E1.17- 2015, Architecture for Control Networks
|
||||
namespace ACN {
|
||||
|
||||
/**
|
||||
* @brief 2.6.1.2.2. Header Field in Root Layer PDUs
|
||||
*
|
||||
* The Header field in Root Layer PDUs shall contain the CID of the component
|
||||
* that generated the PDU (the Source CID).
|
||||
*/
|
||||
struct rlp_header : PDU::pdu_header
|
||||
{
|
||||
UUID::uuid cid; //!< Component's UUID
|
||||
// EPI 16 Protocol Identifier Database
|
||||
// from https://tsp.esta.org/tsp/working_groups/CP/epi16ids.php
|
||||
// as of 1/14/21
|
||||
|
||||
size_t streamSize() const override;
|
||||
void iStream(PDU::Stream) override;
|
||||
void oStream(PDU::Stream) const override;
|
||||
};
|
||||
// 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;
|
||||
|
||||
|
||||
/**
|
||||
* @brief The RLP::Pdu class
|
||||
*/
|
||||
class Pdu
|
||||
: public PDU::Pdu
|
||||
{
|
||||
public:
|
||||
Pdu();
|
||||
void iStream(PDU::Stream) override;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief The message_transport struct
|
||||
*/
|
||||
struct message_transport
|
||||
: streamable
|
||||
{
|
||||
PDU::Block<RLP::Pdu> root; //!< root PDU block
|
||||
|
||||
void iStream(PDU::Stream) override;
|
||||
void oStream(PDU::Stream) const override;
|
||||
};
|
||||
|
||||
} // ACN::RLP
|
||||
} // ACN
|
152
acn/appliance.cpp
Normal file
152
acn/appliance.cpp
Normal file
|
@ -0,0 +1,152 @@
|
|||
/*
|
||||
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
|
79
acn/appliance.h
Normal file
79
acn/appliance.h
Normal file
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
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
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
rpt/device.h
|
||||
component.h
|
||||
|
||||
Copyright (c) 2021 Kevin Matz (kevin.matz@gmail.com)
|
||||
|
||||
|
@ -23,18 +23,19 @@
|
|||
*/
|
||||
#pragma once
|
||||
|
||||
namespace RDMnet::RPT {
|
||||
#include "../uuid/uuid.h"
|
||||
|
||||
/**
|
||||
* @brief The Device class
|
||||
*/
|
||||
class Device
|
||||
{
|
||||
namespace ACN {
|
||||
|
||||
// 2.1. ACN Components and Component Identifiers (CIDs)
|
||||
class Component {
|
||||
public:
|
||||
explicit Device();
|
||||
Component(UUID::uuid cid = UUID::uuid()) { cid_ = cid; };
|
||||
|
||||
protected:
|
||||
bool connectedToBroker; ///< True if the device is connected to a broker.
|
||||
const UUID::uuid cid() const { return cid_; };
|
||||
|
||||
private:
|
||||
UUID::uuid cid_;
|
||||
};
|
||||
|
||||
} // namespace RDMnet::RPT
|
||||
}; // ACN
|
119
acn/dmp.cpp
Normal file
119
acn/dmp.cpp
Normal file
|
@ -0,0 +1,119 @@
|
|||
/*
|
||||
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
Normal file
121
acn/dmp.h
Normal file
|
@ -0,0 +1,121 @@
|
|||
/*
|
||||
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
Normal file
177
acn/pdu.cpp
Normal file
|
@ -0,0 +1,177 @@
|
|||
/*
|
||||
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
Normal file
171
acn/pdu.h
Normal file
|
@ -0,0 +1,171 @@
|
|||
/*
|
||||
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,7 +1,7 @@
|
|||
/*
|
||||
test_uuid.cpp
|
||||
rlp-tcp.cpp
|
||||
|
||||
Copyright (c) 2021 Kevin Matz (kevin.matz@gmail.com)
|
||||
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
|
||||
|
@ -22,29 +22,35 @@
|
|||
SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <string>
|
||||
#include "uuid.h"
|
||||
#include "rlp-tcp.h"
|
||||
#include <cstring>
|
||||
|
||||
TEST(UUID, ver4fromString)
|
||||
{
|
||||
// from online generator
|
||||
auto str = std::string("c2ffda84-0743-4d3f-8299-e2ba3a09e16c");
|
||||
auto uuid = UUID::uuid(str);
|
||||
namespace ACN {
|
||||
namespace RLP {
|
||||
namespace TCP {
|
||||
|
||||
ASSERT_EQ(uuid.type(), UUID::RFC4122);
|
||||
ASSERT_EQ(uuid.version(), 4);
|
||||
ASSERT_EQ(uuid.string(), str);
|
||||
preamble_t::preamble_t(PDU::Stream stream) {
|
||||
stream->read(acn_id, sizeof(acn_id));
|
||||
if (stream->gcount() != 12)
|
||||
stream->setstate(stream->rdstate() | std::ios_base::failbit);
|
||||
length = stream->read32();
|
||||
}
|
||||
|
||||
|
||||
TEST(UUID, createV4)
|
||||
{
|
||||
auto uuid = UUID::uuid();
|
||||
uuid.uuid4();
|
||||
/*
|
||||
Validate compliance with ACN EPI 33. ACN Root Layer Protocol Operation on TCP
|
||||
4.2. Reception
|
||||
*/
|
||||
preamble_t::operator bool () {
|
||||
// 2. Preamble Format: The ACN Packet Identifier shall be the text string
|
||||
// “ASC-E1.17\0\0\0” encoded in [ASCII].
|
||||
if (memcmp(acn_id, ACN_PACKET_IDENTIFIER, 12))
|
||||
return false;
|
||||
|
||||
ASSERT_EQ(uuid.type(), UUID::RFC4122);
|
||||
ASSERT_EQ(uuid.version(), 4);
|
||||
ASSERT_EQ(uuid, UUID::uuid(uuid.string()));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
} // TCP
|
||||
} // RLP
|
||||
} // ACN
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
controller.h
|
||||
rlp-tcp.h
|
||||
|
||||
Copyright (c) 2021 Kevin Matz (kevin.matz@gmail.com)
|
||||
|
||||
|
@ -24,32 +24,32 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include "message.h"
|
||||
#include "uid.h"
|
||||
#include "rlp.h"
|
||||
#include "pdu.h"
|
||||
|
||||
namespace RDM {
|
||||
// EPI 33. ACN Root Layer Protocol Operation on TCP
|
||||
|
||||
/**
|
||||
* @brief The Controller class
|
||||
*/
|
||||
class Controller
|
||||
{
|
||||
public:
|
||||
explicit Controller();
|
||||
virtual ~Controller();
|
||||
namespace ACN {
|
||||
namespace RLP {
|
||||
namespace TCP {
|
||||
|
||||
bool findDevices(uint64_t lowerBound = 0,
|
||||
uint64_t upperBound = 0xFFFFFFFFFFFE) const;
|
||||
|
||||
protected:
|
||||
bool quick_find(const UID &uid, uint64_t lowerBound, uint64_t upperBound) const;
|
||||
|
||||
virtual void rxDiscoveryResponse(const MsgPtr message);
|
||||
virtual void rxGetResponse(const MsgPtr message);
|
||||
virtual void rxSetResponse(const MsgPtr message);
|
||||
|
||||
private:
|
||||
uint8_t next_transaction_;
|
||||
// 3. Frame Preamble Format
|
||||
struct preamble_t {
|
||||
uint8_t acn_id[12]; // 3.1 Packet Identifier
|
||||
uint32_t length; // 3.2 PDU Block Size
|
||||
preamble_t(PDU::Stream);
|
||||
operator bool();
|
||||
};
|
||||
|
||||
} // namespace RDM
|
||||
// 3.1 Packet Identifier
|
||||
// The ACN Packet Identifier shall be the text string
|
||||
// “ASC-E1.17\0\0\0” encoded in [ASCII].
|
||||
static constexpr uint8_t ACN_PACKET_IDENTIFIER[] = { 0x41, 0x53, 0x43, 0x2d, 0x45, 0x31, 0x2e, 0x31, 0x37, 0x00, 0x00, 0x00 };
|
||||
|
||||
// 3.2 PDU Block Size
|
||||
static const uint32_t INDEFINITE = 0xffffffff;
|
||||
|
||||
|
||||
} // TCP
|
||||
} // RLP
|
||||
} // ACN
|
|
@ -1,7 +1,7 @@
|
|||
/*
|
||||
personality.cpp
|
||||
rlp-udp.cpp
|
||||
|
||||
Copyright (c) 2021 Kevin Matz (kevin.matz@gmail.com)
|
||||
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
|
||||
|
@ -22,57 +22,41 @@
|
|||
SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "personality.h"
|
||||
#include "rlp-udp.h"
|
||||
#include <cstring>
|
||||
|
||||
namespace DMX {
|
||||
namespace ACN {
|
||||
namespace RLP {
|
||||
namespace UDP {
|
||||
|
||||
/**
|
||||
* @brief Personality::Personality
|
||||
*/
|
||||
Personality::Personality()
|
||||
: name_("Default Name")
|
||||
, description_("Default Description")
|
||||
, footprint_(0)
|
||||
{
|
||||
preamble_t::preamble_t(PDU::Stream stream) {
|
||||
length = stream->read16();
|
||||
postamble_size = stream->read16();
|
||||
stream->read(acn_id, 12);
|
||||
if (stream->gcount() != 12)
|
||||
stream->setstate(stream->rdstate() | std::ios_base::failbit);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Personality::~Personality
|
||||
*/
|
||||
Personality::~Personality()
|
||||
{
|
||||
/*
|
||||
Validate compliance with ACN EPI 17: ACN Root Layer Protocol on UDP
|
||||
4.2. Reception
|
||||
*/
|
||||
preamble_t::operator bool () {
|
||||
// 2. Preamble Format: The preamble size includes both size fields so the
|
||||
// minimum value for preamble size is 16 (octets).
|
||||
if (length < PREAMBLE_MINIMUM_SIZE)
|
||||
return false;
|
||||
|
||||
// 2. Preamble Format: The ACN Packet Identifier shall be the text string
|
||||
// “ASC-E1.17\0\0\0” encoded in [ASCII].
|
||||
if (memcmp(acn_id, ACN_PACKET_IDENTIFIER, 12))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Personality::name
|
||||
* @return
|
||||
*/
|
||||
const std::string Personality::name() const
|
||||
{
|
||||
return name_;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Personality::description
|
||||
* @return
|
||||
*/
|
||||
const std::string Personality::description() const
|
||||
{
|
||||
return description_;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Personality::footprint
|
||||
* @return
|
||||
*/
|
||||
uint16_t Personality::footprint() const
|
||||
{
|
||||
return footprint_;
|
||||
}
|
||||
|
||||
|
||||
} // namespace DMX
|
||||
} // UDP
|
||||
} // RLP
|
||||
} // ACN
|
|
@ -1,7 +1,7 @@
|
|||
/*
|
||||
dmx/patch.h
|
||||
rlp-udp.h
|
||||
|
||||
Copyright (c) 2021 Kevin Matz (kevin.matz@gmail.com)
|
||||
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
|
||||
|
@ -23,39 +23,37 @@
|
|||
*/
|
||||
#pragma once
|
||||
|
||||
#include "personality.h"
|
||||
#include "universe.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include "rlp.h"
|
||||
#include "pdu.h"
|
||||
|
||||
namespace DMX {
|
||||
// ACN EPI 17. ACN Root Layer Protocol Operation on UDP
|
||||
|
||||
/**
|
||||
* @brief The DMX::Patch class
|
||||
*/
|
||||
class Patch
|
||||
{
|
||||
public:
|
||||
explicit Patch();
|
||||
namespace ACN {
|
||||
namespace RLP {
|
||||
namespace UDP {
|
||||
|
||||
void setUniverse(std::shared_ptr<Universe>);
|
||||
using std::uint8_t;
|
||||
using std::uint16_t;
|
||||
|
||||
bool patch(std::shared_ptr<Personality> device, uint16_t address);
|
||||
void unpatch(std::shared_ptr<Personality> device);
|
||||
void unpatch(size_t index);
|
||||
// The ACN Packet Identifier shall be the text string
|
||||
// “ASC-E1.17\0\0\0” encoded in [ASCII].
|
||||
static constexpr uint8_t ACN_PACKET_IDENTIFIER[] = { 0x41, 0x53, 0x43, 0x2d, 0x45, 0x31, 0x2e, 0x31, 0x37, 0x00, 0x00, 0x00 };
|
||||
|
||||
const std::vector<std::pair<std::weak_ptr<Personality>,uint16_t>> * listing();
|
||||
// 2. Preamble Format: The preamble size includes both size fields so the
|
||||
// minimum value for preamble size is 16 (octets).
|
||||
static const uint8_t PREAMBLE_MINIMUM_SIZE = 16;
|
||||
|
||||
protected:
|
||||
void universeDataChanged();
|
||||
|
||||
private:
|
||||
std::vector<std::pair<std::weak_ptr<Personality>,uint16_t>> members_;
|
||||
|
||||
std::weak_ptr<Universe> universe_;
|
||||
std::shared_ptr<void> data_change_token;
|
||||
// 2. Preamble Format
|
||||
struct preamble_t {
|
||||
uint16_t length;
|
||||
uint16_t postamble_size;
|
||||
uint8_t acn_id[12];
|
||||
preamble_t(PDU::Stream);
|
||||
operator bool();
|
||||
};
|
||||
|
||||
} // namespace DMX
|
||||
|
||||
} // UDP
|
||||
} // RLP
|
||||
} // ACN
|
|
@ -1,7 +1,7 @@
|
|||
/*
|
||||
rdm/subdevice.cpp
|
||||
rlp.cpp
|
||||
|
||||
Copyright (c) 2023 Kevin Matz (kevin.matz@gmail.com)
|
||||
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
|
||||
|
@ -22,18 +22,31 @@
|
|||
SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "subdevice.h"
|
||||
#include "rlp.h"
|
||||
|
||||
namespace RDM {
|
||||
namespace ACN {
|
||||
namespace RLP {
|
||||
|
||||
SubDevice::SubDevice()
|
||||
: BasicDevice()
|
||||
rlp_header::rlp_header(PDU::Stream stream)
|
||||
: PDU::pdu_header()
|
||||
, cid()
|
||||
{
|
||||
uint8_t buf[16];
|
||||
stream->read(buf, 16);
|
||||
if (stream->gcount() != 16)
|
||||
stream->setstate(stream->rdstate() | std::ios_base::failbit);
|
||||
cid = UUID::uuid(buf);
|
||||
}
|
||||
|
||||
|
||||
SubDevice::~SubDevice()
|
||||
Pdu::Pdu(PDU::Stream stream)
|
||||
: ACN::PDU::Pdu(stream, 4)
|
||||
{
|
||||
if (stream->fail()) return;
|
||||
if (!buffer_->good()) return;
|
||||
|
||||
if (flags_.hasHeader)
|
||||
setHeader(new rlp_header(buffer_));
|
||||
}
|
||||
|
||||
} // namespace RDM
|
||||
} // RLP
|
||||
} // ACN
|
|
@ -1,7 +1,7 @@
|
|||
/*
|
||||
fptc.h
|
||||
rlp.h
|
||||
|
||||
Copyright (c) 2023 Kevin Matz (kevin.matz@gmail.com)
|
||||
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
|
||||
|
@ -24,15 +24,27 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include "pdu.h"
|
||||
#include "../uuid/uuid.h"
|
||||
|
||||
/**
|
||||
* \cite CITP 6 CITP/FPTC, Fixture patch layer
|
||||
*
|
||||
* The Fixture Patch layer is used to communicate fixture existence and patch information.
|
||||
*/
|
||||
namespace CITP::FPTC {
|
||||
|
||||
} // namespace FPTC
|
||||
namespace ACN {
|
||||
namespace RLP {
|
||||
|
||||
// 2.6.1.2.2. Header Field in Root Layer PDUs
|
||||
// The Header field in Root Layer PDUs shall contain the CID of the component
|
||||
// that generated the PDU (the Source CID).
|
||||
struct rlp_header : PDU::pdu_header {
|
||||
UUID::uuid cid;
|
||||
rlp_header(PDU::Stream);
|
||||
};
|
||||
|
||||
class Pdu
|
||||
: public PDU::Pdu
|
||||
{
|
||||
public:
|
||||
Pdu(PDU::Stream);
|
||||
};
|
||||
|
||||
} // RLP
|
||||
} // ACN
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
pdu-stream.cpp
|
||||
sdt-udp.cpp
|
||||
|
||||
Copyright (c) 2020 Kevin Matz (kevin.matz@gmail.com)
|
||||
|
||||
|
@ -22,35 +22,33 @@
|
|||
SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "pdu-stream.h"
|
||||
#include "sdt-udp.h"
|
||||
|
||||
namespace ACN {
|
||||
namespace SDT {
|
||||
namespace UDP {
|
||||
|
||||
/**
|
||||
* @brief operator >>
|
||||
* @param buffer
|
||||
* @param uuid
|
||||
* @return
|
||||
*/
|
||||
bufferstream &operator>> (bufferstream &buffer, UUID::uuid &uuid)
|
||||
{
|
||||
uint8_t data[UUID_LENGTH];
|
||||
buffer.read(data, UUID_LENGTH);
|
||||
if (buffer.gcount() == UUID_LENGTH)
|
||||
uuid.setBytes(data);
|
||||
else
|
||||
buffer.setstate(std::ios_base::badbit);
|
||||
return buffer;
|
||||
};
|
||||
address_t::address_t(PDU::Stream stream) {
|
||||
type = stream->read8();
|
||||
|
||||
if (type != SDT_ADDR_NULL)
|
||||
port = stream->read16();
|
||||
|
||||
/**
|
||||
* @brief operator <<
|
||||
* @param buffer
|
||||
* @param uuid
|
||||
* @return
|
||||
*/
|
||||
bufferstream &operator<< (bufferstream &buffer, const UUID::uuid &uuid)
|
||||
{
|
||||
buffer.write(uuid.bytes(), UUID_LENGTH);
|
||||
return buffer;
|
||||
switch (type) {
|
||||
case SDT_ADDR_IPV4:
|
||||
for (unsigned int i = 0; i < sizeof(ipv4); i++)
|
||||
ipv4[i] = stream->read8();
|
||||
break;
|
||||
case SDT_ADDR_IPV6:
|
||||
for (unsigned int i = 0; i < sizeof(ipv6); i++)
|
||||
ipv6[i] = stream->read8();
|
||||
break;
|
||||
case SDT_ADDR_NULL:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}; // UDP
|
||||
}; // SDT
|
||||
}; // ACN
|
69
acn/sdt-udp.h
Normal file
69
acn/sdt-udp.h
Normal file
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
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
Normal file
169
acn/sdt.cpp
Normal file
|
@ -0,0 +1,169 @@
|
|||
/*
|
||||
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
Normal file
313
acn/sdt.h
Normal file
|
@ -0,0 +1,313 @@
|
|||
/*
|
||||
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,7 +1,7 @@
|
|||
/*
|
||||
universe.cpp
|
||||
|
||||
Copyright (c) 2022 Kevin Matz (kevin.matz@gmail.com)
|
||||
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
|
||||
|
@ -24,11 +24,41 @@
|
|||
|
||||
#include "universe.h"
|
||||
|
||||
namespace ARTNET {
|
||||
|
||||
universe::universe()
|
||||
{
|
||||
namespace DMX {
|
||||
using std::copy;
|
||||
|
||||
/**
|
||||
*/
|
||||
Universe::Universe() {
|
||||
null_start_data_.fill(0);
|
||||
}
|
||||
|
||||
} // namespace ARTNET
|
||||
/**
|
||||
accept new data from receiver
|
||||
|
||||
@param vector<uint8_t> varaibly sized data, beginning with a start code.
|
||||
*/
|
||||
void Universe::set(vector<uint8_t> vect) {
|
||||
switch (vect.front()) { // start code
|
||||
case E111_NULL_START:
|
||||
vect.resize(null_start_data_.size(), 0); // pad shorter, truncate larger
|
||||
copy(vect.begin(), vect.end(), null_start_data_.begin());
|
||||
for (const auto &cb : callbacks_)
|
||||
cb(this);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
register a data consumer callback function
|
||||
|
||||
@param const DataHandlerFunction something that knows what to do with DMX
|
||||
*/
|
||||
void Universe::onData(const DataHandler callback)
|
||||
{
|
||||
callbacks_.push_back(callback);
|
||||
}
|
||||
|
||||
} // DMX
|
72
dmx/universe.h
Normal file
72
dmx/universe.h
Normal file
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
universe.h
|
||||
|
||||
Copyright (c) 2020 Kevin Matz (kevin.matz@gmail.com)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
namespace DMX {
|
||||
|
||||
using std::uint8_t;
|
||||
using std::uint16_t;
|
||||
using std::array;
|
||||
using std::function;
|
||||
using std::vector;
|
||||
|
||||
// Table D1 - Reserved START Codes
|
||||
static const uint8_t E111_NULL_START = 0;
|
||||
static const uint8_t E111_ASC_TEXT_ASCII = 23;
|
||||
static const uint8_t E111_ASC_TEST = 85;
|
||||
static const uint8_t E111_ASC_TEXT_UTF8 = 144;
|
||||
static const uint8_t E111_ASC_MANUFACTURER = 145;
|
||||
static const uint8_t E111_ASC_SIP = 207;
|
||||
|
||||
|
||||
class Universe; // forward declare the Univserse class
|
||||
using DataHandler = function<void(Universe *)>;
|
||||
using DimmerData = array<uint8_t, 513>;
|
||||
|
||||
/*
|
||||
The Universe class
|
||||
|
||||
ANSI E1.11 describes may things about moving data over
|
||||
serial EIA-485-A links. This class encapselates that data,
|
||||
regardless of how it was transmitted.
|
||||
*/
|
||||
class Universe {
|
||||
public:
|
||||
Universe ();
|
||||
|
||||
DimmerData * data() { return &null_start_data_; }
|
||||
void onData (const DataHandler callback);
|
||||
void set (vector<uint8_t>);
|
||||
uint8_t slot (uint16_t address) { return null_start_data_[address]; }
|
||||
|
||||
private:
|
||||
DimmerData null_start_data_;
|
||||
vector<DataHandler> callbacks_;
|
||||
};
|
||||
|
||||
} // DMX
|
304
docs/Doxyfile.in
304
docs/Doxyfile.in
|
@ -1,304 +0,0 @@
|
|||
# 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.
Before Width: | Height: | Size: 518 KiB |
File diff suppressed because it is too large
Load Diff
|
@ -1,17 +0,0 @@
|
|||
<!--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>
|
|
@ -1,56 +0,0 @@
|
|||
<!-- 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 -->
|
|
@ -1 +0,0 @@
|
|||
Subproject commit 7be08cbe5531f60607dffb2f8f76f6c070a9c81b
|
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 170 KiB |
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 318 KiB |
Binary file not shown.
|
@ -1,3 +0,0 @@
|
|||
add_subdirectory("sACN Explorer")
|
||||
|
||||
add_subdirectory("widgetExplorer")
|
|
@ -1,38 +0,0 @@
|
|||
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}
|
||||
)
|
|
@ -1,17 +0,0 @@
|
|||
#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;
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
#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;
|
||||
};
|
|
@ -1,77 +0,0 @@
|
|||
<?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>
|
|
@ -1,23 +0,0 @@
|
|||
#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;
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
#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
|
|
@ -1,81 +0,0 @@
|
|||
<?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>
|
|
@ -1,7 +0,0 @@
|
|||
#include "sacnexplorer.h"
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
SacnExplorer a(argc, argv);
|
||||
return a.exec();
|
||||
}
|
|
@ -1,327 +0,0 @@
|
|||
#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();
|
||||
}
|
|
@ -1,68 +0,0 @@
|
|||
/*
|
||||
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;
|
||||
};
|
|
@ -1,304 +0,0 @@
|
|||
<?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>
|
|
@ -1,67 +0,0 @@
|
|||
#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();
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
#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_;
|
||||
};
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
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}
|
||||
)
|
|
@ -1,178 +0,0 @@
|
|||
#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();
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
#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_;
|
||||
};
|
||||
|
|
@ -1,323 +0,0 @@
|
|||
<?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>
|
|
@ -1,7 +0,0 @@
|
|||
#include "widgetexplorer.h"
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
WidgetExplorer a(argc, argv);
|
||||
return a.exec();
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
#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;
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
#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;
|
||||
};
|
||||
|
|
@ -1,130 +0,0 @@
|
|||
<?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>
|
|
@ -1,37 +0,0 @@
|
|||
#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();
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "devicewindow.h"
|
||||
#include <QApplication>
|
||||
#include <QSettings>
|
||||
|
||||
class WidgetExplorer
|
||||
: public QApplication
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit WidgetExplorer(int argc, char *argv[]);
|
||||
virtual ~WidgetExplorer();
|
||||
|
||||
private:
|
||||
void saveSettings();
|
||||
|
||||
QSettings *settings_;
|
||||
DeviceWindow *window_;
|
||||
};
|
||||
|
|
@ -1 +0,0 @@
|
|||
add_subdirectory(bufferstream)
|
|
@ -1,14 +0,0 @@
|
|||
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})
|
|
@ -1,443 +0,0 @@
|
|||
/*
|
||||
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);
|
||||
}
|
|
@ -1,184 +0,0 @@
|
|||
/*
|
||||
bufferstream.h
|
||||
|
||||
Copyright (c) 2023 Kevin Matz (kevin.matz@gmail.com)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
struct streamable;
|
||||
|
||||
/**
|
||||
* @brief Input/Output stream for unsigned 8-bit buffers
|
||||
*
|
||||
* By default, wide types will be handled as big-endian, the network byte order.
|
||||
*/
|
||||
class bufferstream
|
||||
: public std::enable_shared_from_this<bufferstream>
|
||||
, private std::basic_streambuf<uint8_t>
|
||||
, public std::basic_iostream<uint8_t>
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief The endian enum is not available until C++20.
|
||||
*
|
||||
* \note This implimentation may be compiler dependant.
|
||||
*
|
||||
* \note After upgrading the project to C++20 or better, drop this if favor of
|
||||
* the standard implimentation in <bit.h>
|
||||
*/
|
||||
enum class endian
|
||||
{
|
||||
little = __ORDER_LITTLE_ENDIAN__,
|
||||
big = __ORDER_BIG_ENDIAN__,
|
||||
native = __BYTE_ORDER__,
|
||||
};
|
||||
|
||||
bufferstream(uint8_t *p, std::streamsize l, endian o = endian::big); // default network byte-order
|
||||
|
||||
// input sequence
|
||||
size_t available();
|
||||
uint8_t *data();
|
||||
|
||||
// output sequence
|
||||
size_t size();
|
||||
uint8_t *base();
|
||||
|
||||
// unsigned integer
|
||||
bufferstream &operator>> (uint8_t &val);
|
||||
bufferstream &operator>> (uint16_t &val);
|
||||
bufferstream &operator>> (uint32_t &val);
|
||||
bufferstream &operator>> (uint64_t &val);
|
||||
bufferstream &operator<< (const uint8_t &val);
|
||||
bufferstream &operator<< (const uint16_t &val);
|
||||
bufferstream &operator<< (const uint32_t &val);
|
||||
bufferstream &operator<< (const uint64_t &val);
|
||||
|
||||
// signed integer
|
||||
bufferstream &operator>> (int8_t &val);
|
||||
bufferstream &operator>> (int16_t &val);
|
||||
bufferstream &operator>> (int32_t &val);
|
||||
bufferstream &operator>> (int64_t &val);
|
||||
bufferstream &operator<< (const int8_t &val);
|
||||
bufferstream &operator<< (const int16_t &val);
|
||||
bufferstream &operator<< (const int32_t &val);
|
||||
bufferstream &operator<< (const int64_t &val);
|
||||
|
||||
// floating point
|
||||
bufferstream &operator>> (float &val);
|
||||
bufferstream &operator>> (double &val);
|
||||
bufferstream &operator<< (const float &val);
|
||||
bufferstream &operator<< (const double &val);
|
||||
|
||||
// stream objects
|
||||
bufferstream &operator>> (streamable &obj);
|
||||
bufferstream &operator<< (const streamable &obj);
|
||||
|
||||
// null-terminated strings
|
||||
bufferstream &operator>> (std::string &str);
|
||||
bufferstream &operator<< (const std::string &str);
|
||||
|
||||
// strings
|
||||
void readString(std::string& str, const int fixed_length = 0, const bool terminated = true);
|
||||
void writeString(const std::string& str, const size_t fixed_length = 0,
|
||||
const bool terminated = true);
|
||||
|
||||
// reinterpreted i/o
|
||||
/**
|
||||
* @brief readType
|
||||
* @return T
|
||||
*/
|
||||
template<typename T>
|
||||
T readType()
|
||||
{
|
||||
T ret = 0;
|
||||
uint_fast8_t width = sizeof(T);
|
||||
if (in_avail() < width)
|
||||
setstate(std::ios_base::failbit);
|
||||
if (!good())
|
||||
return ret;
|
||||
if (width == 1)
|
||||
{
|
||||
ret = static_cast<T>(get());
|
||||
}
|
||||
else
|
||||
{
|
||||
auto bytes = reinterpret_cast<uint8_t*>(&ret);
|
||||
if (order_ == endian::native)
|
||||
for (int_fast8_t i = 0; i < width; i++)
|
||||
bytes[i] = get();
|
||||
else
|
||||
for (int_fast8_t i = width; --i >= 0;)
|
||||
bytes[i] = get();
|
||||
}
|
||||
if (!in_avail())
|
||||
setstate(std::ios_base::eofbit);
|
||||
return ret;
|
||||
}
|
||||
/**
|
||||
* @brief writeType
|
||||
* @param val
|
||||
*/
|
||||
template<typename T>
|
||||
void writeType (const T& val)
|
||||
{
|
||||
uint_fast8_t width = sizeof(T);
|
||||
if (width == 1) {
|
||||
put(static_cast<uint8_t>(val));
|
||||
return;
|
||||
}
|
||||
auto bytes = reinterpret_cast<const uint8_t*>(&val);
|
||||
if (order_ == endian::native)
|
||||
for (int_fast8_t i = 0; i < width; i++)
|
||||
put(bytes[i]);
|
||||
else
|
||||
for (int_fast8_t i = width; --i >= 0;)
|
||||
put(bytes[i]);
|
||||
}
|
||||
|
||||
private:
|
||||
endian order_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief The base for data objects that can be read from and written to a bufferstream.
|
||||
*/
|
||||
struct streamable
|
||||
{
|
||||
virtual ~streamable() {};
|
||||
/**
|
||||
* @brief streamSize
|
||||
* @return length (count of octets) on the wire
|
||||
*/
|
||||
virtual size_t streamSize() const = 0;
|
||||
/**
|
||||
* @brief fill structure data from input stream
|
||||
*/
|
||||
virtual void iStream(std::shared_ptr<bufferstream>) = 0;
|
||||
/**
|
||||
* @brief write structure data to output stream
|
||||
*/
|
||||
virtual void oStream(std::shared_ptr<bufferstream>) const = 0;
|
||||
};
|
|
@ -1 +0,0 @@
|
|||
add_subdirectory(qt)
|
|
@ -1,11 +0,0 @@
|
|||
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)
|
|
@ -1,25 +0,0 @@
|
|||
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})
|
|
@ -1,197 +0,0 @@
|
|||
/*
|
||||
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;
|
||||
}
|
|
@ -1,86 +0,0 @@
|
|||
/*
|
||||
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)
|
|
@ -1,162 +0,0 @@
|
|||
#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()));
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
#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_;
|
||||
};
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
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})
|
|
@ -1,335 +0,0 @@
|
|||
#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_;
|
||||
}
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
#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;
|
||||
};
|
|
@ -1,321 +0,0 @@
|
|||
#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();
|
||||
}
|
|
@ -1,68 +0,0 @@
|
|||
#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);
|
||||
};
|
|
@ -1,9 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <QtCore/qglobal.h>
|
||||
|
||||
#if defined(QsACN_LIBRARY)
|
||||
# define QT_EXPORT Q_DECL_EXPORT
|
||||
#else
|
||||
# define QT_EXPORT Q_DECL_IMPORT
|
||||
#endif
|
|
@ -1,302 +0,0 @@
|
|||
#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!";
|
||||
}
|
|
@ -1,69 +0,0 @@
|
|||
#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);
|
||||
};
|
||||
};
|
||||
|
|
@ -1,312 +0,0 @@
|
|||
#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();
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
#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*)
|
|
@ -1,213 +0,0 @@
|
|||
#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_;
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
#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;
|
||||
};
|
|
@ -1,155 +0,0 @@
|
|||
/*
|
||||
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
|
||||
}
|
||||
}
|
|
@ -1,51 +0,0 @@
|
|||
/*
|
||||
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;
|
||||
};
|
|
@ -1,140 +0,0 @@
|
|||
/*
|
||||
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,57 +0,0 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <universe.h>
|
||||
#include <QMainWindow>
|
||||
#include <QTimer>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
namespace Ui {
|
||||
class UniverseWindow;
|
||||
}
|
||||
QT_END_NAMESPACE
|
||||
|
||||
class UniverseWindow
|
||||
: public QMainWindow
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit UniverseWindow(QWidget *parent = nullptr,
|
||||
std::shared_ptr<DMX::Universe> universe = nullptr);
|
||||
virtual ~UniverseWindow();
|
||||
|
||||
/**
|
||||
* @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_;
|
||||
};
|
|
@ -1,169 +0,0 @@
|
|||
<?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>
|
|
@ -1,32 +0,0 @@
|
|||
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)
|
|
@ -1,52 +0,0 @@
|
|||
# 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/)
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
@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"
|
||||
}
|
|
@ -1,2 +0,0 @@
|
|||
# Art-Net 4
|
||||
add_subdirectory(artnet)
|
|
@ -1,36 +0,0 @@
|
|||
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})
|
|
@ -1,214 +0,0 @@
|
|||
# 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.
|
|
@ -1,541 +0,0 @@
|
|||
/*
|
||||
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
|
|
@ -1,123 +0,0 @@
|
|||
/*
|
||||
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
|
|
@ -1,70 +0,0 @@
|
|||
/*
|
||||
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
|
|
@ -1,589 +0,0 @@
|
|||
/*
|
||||
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
|
|
@ -1,108 +0,0 @@
|
|||
/*
|
||||
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
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user