-
Notifications
You must be signed in to change notification settings - Fork 221
Initial FTDI RDM support #1541
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Initial FTDI RDM support #1541
Changes from 24 commits
bfad65f
78cc4b9
e1869e3
2b5a5fa
3b6cb83
c51928e
10fc5a0
1bdf897
71bd693
8553598
81d120b
8914f56
9e95824
ff09297
851401b
1d09fe1
b02363b
c87ebf6
0141ad0
2373f60
2198e3d
65e11d8
d719f6b
5645a01
a0e6cb4
0ba5dfd
29b32d0
f451f19
abdf4d5
b080a84
9874f6d
ab0e961
4b72f70
17a40b1
532c8c3
d28d3df
92729c4
5f6187e
1946005
a4cfe30
faa98ca
5b150c1
fb51664
5564a1e
d533789
6309300
4553fe3
591234c
3f60c66
7d11c58
f7489b6
c4d0ee3
c8e010f
7bf7ea5
6f84568
f144552
6b08435
b6d41af
0a4a51e
b084dcf
b489f96
8d9691d
1e004ac
c222943
8410da0
6252026
3c5a2e6
4e285cb
c912c7f
a90c1ba
c640e4e
8b9ff70
c2083af
717462d
7b0ecf8
ed1eb6b
5392ab0
c666d71
249d826
bfc14db
48d1dbe
a198e63
368a2f1
e80f64d
011f591
b1dc145
a0d202a
4566ff8
c85f065
e48cebd
14a4513
117f831
1fb4bda
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -246,3 +246,4 @@ javascript/new-src/node_modules | |
| .cproject | ||
| .settings | ||
| .vscode/ | ||
| OLA.* | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -27,10 +27,19 @@ | |
| #include <unistd.h> | ||
|
|
||
| #include <string> | ||
| #include <queue> | ||
| #include <utility> | ||
|
|
||
| #include "ola/Clock.h" | ||
| #include "ola/Logging.h" | ||
| #include "ola/StringUtils.h" | ||
|
|
||
| #include "ola/rdm/RDMCommand.h" | ||
| #include "ola/rdm/RDMControllerInterface.h" | ||
| #include "ola/rdm/RDMCommandSerializer.h" | ||
| #include "ola/rdm/RDMResponseCodes.h" | ||
| #include "ola/rdm/DiscoveryAgent.h" | ||
|
|
||
| #include "plugins/ftdidmx/FtdiWidget.h" | ||
| #include "plugins/ftdidmx/FtdiDmxThread.h" | ||
|
|
||
|
|
@@ -42,7 +51,15 @@ FtdiDmxThread::FtdiDmxThread(FtdiInterface *interface, unsigned int frequency) | |
| : m_granularity(UNKNOWN), | ||
| m_interface(interface), | ||
| m_term(false), | ||
| m_frequency(frequency) { | ||
| m_frequency(frequency), | ||
| m_transaction_number(0), | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this is now managed centrally due to what I did here: https://github.com/OpenLightingProject/ola/pull/1343/files
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this mean I can remove all code and references to this? At the moment for this pull I tend to think leave it and then I can remove and test again, but whatever you prefer. |
||
| m_discovery_agent(this), | ||
| m_uid(0x7a70, 0x12345678), | ||
peternewman marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| m_pending_request(nullptr), | ||
| m_rdm_callback(nullptr), | ||
| m_mute_complete(nullptr), | ||
| m_unmute_complete(nullptr), | ||
| m_branch_callback(nullptr) { | ||
| } | ||
|
|
||
| FtdiDmxThread::~FtdiDmxThread() { | ||
|
|
@@ -54,10 +71,17 @@ FtdiDmxThread::~FtdiDmxThread() { | |
| * @brief Stop this thread | ||
| */ | ||
| bool FtdiDmxThread::Stop() { | ||
|
|
||
| { | ||
| ola::thread::MutexLocker locker(&m_term_mutex); | ||
| m_term = true; | ||
| } | ||
|
|
||
| if(m_pending_request != nullptr) { | ||
| m_pending_request = nullptr; | ||
| destroyPendindingCallback(ola::rdm::RDM_FAILED_TO_SEND); | ||
| } | ||
| m_discovery_agent.Abort(); | ||
| return Join(); | ||
| } | ||
|
|
||
|
|
@@ -73,15 +97,133 @@ bool FtdiDmxThread::WriteDMX(const DmxBuffer &buffer) { | |
| } | ||
| } | ||
|
|
||
| void FtdiDmxThread::SendRDMRequest(ola::rdm::RDMRequest *request, | ||
| ola::rdm::RDMCallback *callback) { | ||
peternewman marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| ola::thread::MutexLocker locker(&m_rdm_mutex); | ||
| if(m_pending_request == nullptr) { | ||
| m_pending_request = request; | ||
| m_rdm_callback = callback; | ||
| } else { | ||
| OLA_WARN << "Unable to queue RDM request, RDM operation already pending"; | ||
| } | ||
| OLA_WARN << "Function not properly implemented yet, callback may not get called."; | ||
| } | ||
|
|
||
| void FtdiDmxThread::RunFullDiscovery(ola::rdm::RDMDiscoveryCallback *callback) { | ||
| m_discovery_agent.StartFullDiscovery(ola::NewSingleCallback(this, &FtdiDmxThread::DiscoveryComplete, callback)); | ||
| } | ||
|
|
||
| void FtdiDmxThread::RunIncrementalDiscovery(ola::rdm::RDMDiscoveryCallback *callback) { | ||
| m_discovery_agent.StartIncrementalDiscovery(ola::NewSingleCallback(this, &FtdiDmxThread::DiscoveryComplete, callback)); | ||
| } | ||
|
|
||
| /** | ||
| * Called when the discovery process finally completes | ||
| * @param callback the callback passed to StartFullDiscovery or | ||
| * StartIncrementalDiscovery that we should execute. | ||
| * @param status true if discovery worked, false otherwise | ||
| * @param uids the UIDSet of UIDs that were found. | ||
| */ | ||
| void FtdiDmxThread::DiscoveryComplete(ola::rdm::RDMDiscoveryCallback *callback, | ||
| bool, | ||
Keeper-of-the-Keys marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| const ola::rdm::UIDSet &uids) { | ||
| OLA_DEBUG << "FTDI discovery complete: " << uids; | ||
| if (callback) { | ||
| callback->Run(uids); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * @brief Method called to cleanup any outstanding callbacks | ||
| * @param state | ||
|
||
| * | ||
| * All callbacks except the RDMCallback lack a way of reporting an error state to the caller. | ||
Keeper-of-the-Keys marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| */ | ||
| void FtdiDmxThread::destroyPendindingCallback(ola::rdm::RDMStatusCode state) { | ||
| MuteDeviceCallback *thread_mute_callback = nullptr; | ||
| UnMuteDeviceCallback *thread_unmute_callback = nullptr; | ||
| BranchCallback *thread_branch_callback = nullptr; | ||
| ola::rdm::RDMCallback *thread_rdm_callback = nullptr; | ||
|
|
||
| if(m_mute_complete != nullptr) { | ||
| thread_mute_callback = m_mute_complete; | ||
| m_mute_complete = nullptr; | ||
| thread_mute_callback->Run(false); | ||
| } else if(m_unmute_complete != nullptr) { | ||
| thread_unmute_callback = m_unmute_complete; | ||
| m_unmute_complete = nullptr; | ||
| thread_unmute_callback->Run(); | ||
| } else if(m_branch_callback != nullptr) { | ||
| thread_branch_callback = m_branch_callback; | ||
| m_branch_callback = nullptr; | ||
| thread_branch_callback->Run(nullptr, 0); | ||
| } else if(m_rdm_callback != nullptr) { | ||
| thread_rdm_callback = m_rdm_callback; | ||
| m_rdm_callback = nullptr; | ||
| ola::rdm::RunRDMCallback(thread_rdm_callback, state); | ||
| } | ||
| } | ||
|
|
||
| void FtdiDmxThread::MuteDevice(const ola::rdm::UID &target, | ||
| MuteDeviceCallback *mute_complete) { | ||
| ola::thread::MutexLocker locker(&m_rdm_mutex); | ||
| if(m_pending_request == nullptr) { | ||
| OLA_INFO << "Muting device"; | ||
| m_mute_complete = mute_complete; | ||
| m_pending_request = ola::rdm::NewMuteRequest(m_uid, target, m_transaction_number += 1); | ||
Keeper-of-the-Keys marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } else { | ||
| // Already pending request | ||
Keeper-of-the-Keys marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| OLA_WARN << "Unable to queue Mute request, RDM operation already pending"; | ||
| } | ||
| } | ||
|
|
||
| void FtdiDmxThread::UnMuteAll(UnMuteDeviceCallback *unmute_complete) { | ||
| ola::thread::MutexLocker locker(&m_rdm_mutex); | ||
| if(m_pending_request == nullptr) { | ||
| OLA_INFO << "Sending UnMuteAll"; | ||
| m_unmute_complete = unmute_complete; | ||
| m_pending_request = ola::rdm::NewUnMuteRequest(m_uid, ola::rdm::UID::AllDevices(), m_transaction_number += 1); | ||
| } else { | ||
| // Already pending request | ||
| OLA_WARN << "Unable to queue UnMuteAll request, RDM operation already pending"; | ||
| } | ||
| } | ||
|
|
||
| void FtdiDmxThread::Branch(const ola::rdm::UID &lower, | ||
| const ola::rdm::UID &upper, | ||
| BranchCallback *callback) { | ||
| ola::thread::MutexLocker locker(&m_rdm_mutex); | ||
| if(m_pending_request == nullptr) { | ||
| OLA_INFO << "Sending branch"; | ||
| m_branch_callback = callback; | ||
| m_pending_request = ola::rdm::NewDiscoveryUniqueBranchRequest(m_uid, lower, upper, m_transaction_number += 1); | ||
| } else { | ||
| // Already pending request | ||
| OLA_WARN << "Unable to queue Branch request, RDM operation already pending"; | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * @brief The method called by the thread | ||
| */ | ||
| void *FtdiDmxThread::Run() { | ||
| TimeStamp ts1, ts2, ts3; | ||
| OLA_INFO << "Starting FtdiDmxThread"; | ||
| TimeStamp ts1, ts2, ts3, lastDMX; | ||
| Clock clock; | ||
| CheckTimeGranularity(); | ||
| DmxBuffer buffer; | ||
| bool sendRDM = false; | ||
| TimeInterval elapsed, interval; | ||
| int readBytes; | ||
| unsigned char readBuffer[258]; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Again where has this magic number come from? I'm guessing it's max RDM packet size + a bit?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yup
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's actually - FTDI will always add 1 null byte at the beginning (not 100% sure but I think that is the break being treated as data) of DMX data (except for the DUB reply), the max packet length is 256 bytes and 1 more byte just in case. |
||
| ola::io::ByteString packetBuffer; | ||
|
|
||
| MuteDeviceCallback *thread_mute_callback = nullptr; | ||
| UnMuteDeviceCallback *thread_unmute_callback = nullptr; | ||
| BranchCallback *thread_branch_callback = nullptr; | ||
| ola::rdm::RDMCallback *thread_rdm_callback = nullptr; | ||
| ola::rdm::RDMRequest *thread_pending_request = nullptr; | ||
|
|
||
|
|
||
| int frameTime = static_cast<int>(floor( | ||
| (static_cast<double>(1000) / m_frequency) + static_cast<double>(0.5))); | ||
|
|
@@ -105,6 +247,32 @@ void *FtdiDmxThread::Run() { | |
| } | ||
|
|
||
| clock.CurrentTime(&ts1); | ||
| if(m_pending_request != nullptr) { | ||
|
|
||
| elapsed = ts1 - lastDMX; | ||
|
|
||
| if(elapsed.InMilliSeconds() < 500) { | ||
Keeper-of-the-Keys marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| if(!packetBuffer.empty()) { | ||
| packetBuffer.clear(); | ||
| } | ||
|
|
||
| if(!ola::rdm::RDMCommandSerializer::PackWithStartCode(*m_pending_request, &packetBuffer)) { | ||
| OLA_WARN << "RDMCommandSerializer failed. Dropping packet."; | ||
| m_pending_request = nullptr; | ||
|
|
||
| destroyPendindingCallback(ola::rdm::RDM_FAILED_TO_SEND); | ||
| sendRDM = false; | ||
| } else { | ||
| OLA_INFO << "OK To send RDM"; | ||
| sendRDM = true; | ||
| } | ||
| } else { | ||
| OLA_INFO << "NOK to send RDM (DMX interval)"; | ||
| sendRDM = false; | ||
| } | ||
| } else { | ||
| sendRDM = false; | ||
| } | ||
|
|
||
| if (!m_interface->SetBreak(true)) { | ||
| goto framesleep; | ||
|
|
@@ -122,14 +290,79 @@ void *FtdiDmxThread::Run() { | |
| usleep(DMX_MAB); | ||
| } | ||
|
|
||
| if (!m_interface->Write(buffer)) { | ||
| if(!sendRDM) { | ||
| if (!m_interface->Write(buffer)) { | ||
| goto framesleep; | ||
| } else { | ||
| clock.CurrentTime(&lastDMX); | ||
| } | ||
| } else { | ||
| if(m_interface->Write(&packetBuffer)) { | ||
| OLA_INFO << "RDM packet written to line"; | ||
| if(m_pending_request->IsDUB()) { | ||
| usleep(58000); //min time before next packet broadcast allowed | ||
Keeper-of-the-Keys marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| readBytes = m_interface->Read(readBuffer, 258); | ||
Keeper-of-the-Keys marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| OLA_INFO << "DUB Read: " << readBytes; | ||
| if(m_branch_callback != nullptr) { | ||
| thread_branch_callback = m_branch_callback; | ||
| m_branch_callback = nullptr; | ||
| m_pending_request = nullptr; | ||
| thread_branch_callback->Run(readBuffer, (readBytes >= 0 ? readBytes : 0)); | ||
| } | ||
| } | ||
| else if(!m_pending_request->DestinationUID().IsBroadcast()) { | ||
|
|
||
| usleep(30000); //min time before next packet allowed | ||
| readBytes = m_interface->Read(readBuffer, 258); | ||
|
|
||
| if(readBytes >=0) { | ||
| if(m_mute_complete != nullptr) { | ||
| thread_mute_callback = m_mute_complete; | ||
| m_mute_complete = nullptr; | ||
|
|
||
| if(rdm::RDMReply::FromFrame(rdm::RDMFrame(readBuffer+1, readBytes-1))->Response()->SourceUID() == m_pending_request->DestinationUID()) { | ||
| m_pending_request = nullptr; | ||
| thread_mute_callback->Run(true); | ||
| } else { | ||
| m_pending_request = nullptr; | ||
| thread_mute_callback->Run(false); | ||
| } | ||
| } else if(m_rdm_callback != nullptr) { | ||
| thread_rdm_callback = m_rdm_callback; | ||
| m_rdm_callback = nullptr; | ||
|
|
||
| if(readBytes > 0) { | ||
| thread_pending_request = m_pending_request; | ||
| m_pending_request = nullptr; | ||
| thread_rdm_callback->Run(rdm::RDMReply::FromFrame(rdm::RDMFrame(readBuffer+1, readBytes-1), thread_pending_request)); | ||
| } else { | ||
| m_pending_request = nullptr; | ||
| RunRDMCallback(thread_rdm_callback, rdm::RDM_TIMEOUT); | ||
| } | ||
| } | ||
| } | ||
| } else { | ||
| if(m_unmute_complete != nullptr) { | ||
| thread_unmute_callback = m_unmute_complete; | ||
| m_unmute_complete = nullptr; | ||
| OLA_INFO << "UnMuteAllCallback"; | ||
| m_pending_request = nullptr; | ||
| thread_unmute_callback->Run(); | ||
| } | ||
| } | ||
| } else { | ||
| // Something went wrong, already reported at hw level but we'll need to handle the callbacks | ||
| // Strictly speaking we failed to receive OR send, I have proposed another code: RDM_HW_ERROR | ||
| destroyPendindingCallback(ola::rdm::RDM_FAILED_TO_SEND); | ||
| } // End of Write loop */ | ||
|
|
||
| goto framesleep; | ||
| } | ||
|
|
||
| framesleep: | ||
| // Sleep for the remainder of the DMX frame time | ||
| clock.CurrentTime(&ts2); | ||
| TimeInterval elapsed = ts2 - ts1; | ||
| elapsed = ts2 - ts1; | ||
|
|
||
| if (m_granularity == GOOD) { | ||
| while (elapsed.InMilliSeconds() < frameTime) { | ||
|
|
@@ -141,7 +374,7 @@ void *FtdiDmxThread::Run() { | |
| // See if we can drop out of bad mode. | ||
| usleep(1000); | ||
| clock.CurrentTime(&ts3); | ||
| TimeInterval interval = ts3 - ts2; | ||
| interval = ts3 - ts2; | ||
| if (interval.InMilliSeconds() < BAD_GRANULARITY_LIMIT) { | ||
| m_granularity = GOOD; | ||
| OLA_INFO << "Switching from BAD to GOOD granularity for ftdi thread"; | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.