NanoOcp
Minimal AES70 / OCP.1 TCP client/server library for d&b Soundscape devices
Loading...
Searching...
No Matches
MainComponent.cpp
Go to the documentation of this file.
1/* Copyright (c) 2020-2023, Christian Ahrens
2 *
3 * This file is part of NanoOcp <https://github.com/ChristianAhrens/NanoOcp>
4 *
5 * This library is free software; you can redistribute it and/or modify it under
6 * the terms of the GNU Lesser General Public License version 3.0 as published
7 * by the Free Software Foundation.
8 *
9 * This library is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
12 * details.
13 *
14 * You should have received a copy of the GNU Lesser General Public License
15 * along with this library; if not, write to the Free Software Foundation, Inc.,
16 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17 */
18
19#include "MainComponent.h"
20
21#include "../../Source/NanoOcp1.h"
22#include "../../Source/Ocp1DataTypes.h"
23#include "../../Source/Ocp1DS100ObjectDefinitions.h"
24
25
26namespace NanoOcp1Demo
27{
28
29//==============================================================================
31{
32 auto address = juce::String("127.0.0.1");
33 auto port = 50014;
34
35 // Create definitions to act as event handlers for the supported objects.
36 m_pwrOnObjDef = std::make_unique<NanoOcp1::AmpDxDy::dbOcaObjectDef_Settings_PwrOn>();
37 m_potiLevelObjDef = std::make_unique<NanoOcp1::AmpGeneric::dbOcaObjectDef_Config_PotiLevel>(1);
38 m_soundobjectEnableObjDef = std::make_unique<NanoOcp1::DS100::dbOcaObjectDef_Positioning_Source_Enable>(1);
39 m_speakerGroupObjDef = std::make_unique<NanoOcp1::DS100::dbOcaObjectDef_Positioning_Speaker_Group>(1);
40 m_guidObjDef = std::make_unique<NanoOcp1::DS100::dbOcaObjectDef_Fixed_GUID>();
41
42 // Editor to allow user input for ip address and port to use to connect
43 m_ipAndPortEditor = std::make_unique<TextEditor>();
44 m_ipAndPortEditor->setTextToShowWhenEmpty(address + ";" + juce::String(port), getLookAndFeel().findColour(juce::TextEditor::ColourIds::textColourId).darker().darker());
45 m_ipAndPortEditor->addListener(this);
46 addAndMakeVisible(m_ipAndPortEditor.get());
47
48 // connected status visu
49 m_connectedLED = std::make_unique<TextButton>("con");
50 m_connectedLED->setColour(juce::TextButton::ColourIds::buttonOnColourId, juce::Colours::forestgreen);
51 m_connectedLED->setColour(juce::TextButton::ColourIds::buttonColourId, juce::Colours::dimgrey);
52 m_connectedLED->setToggleState(false, dontSendNotification);
53 m_connectedLED->setEnabled(false);
54 addAndMakeVisible(m_connectedLED.get());
55
56 // Button for AddSubscription / RemoveSubscription
57 m_subscribeButton = std::make_unique<TextButton>("Subscribe");
58 m_subscribeButton->setColour(juce::TextButton::ColourIds::buttonOnColourId, juce::Colours::blue);
59 m_subscribeButton->setClickingTogglesState(true);
60 m_subscribeButton->onClick = [=]()
61 {
62 if (m_subscribeButton->getToggleState())
63 {
64 // Send AddSubscription requests
65 std::uint32_t handle;
66 m_nanoOcp1Client->sendData(NanoOcp1::Ocp1CommandResponseRequired(
68 m_ocaHandleMap.emplace(handle, m_potiLevelObjDef.get());
69 DBG("Sent an OCA AddSubscription command with handle " << NanoOcp1::HandleToString(handle));
70
71 m_nanoOcp1Client->sendData(NanoOcp1::Ocp1CommandResponseRequired(
72 NanoOcp1::AmpDxDy::dbOcaObjectDef_Settings_PwrOn().AddSubscriptionCommand(), handle).GetSerializedData());
73 m_ocaHandleMap.emplace(handle, m_pwrOnObjDef.get());
74 DBG("Sent an OCA AddSubscription command with handle " << NanoOcp1::HandleToString(handle));
75
76 m_nanoOcp1Client->sendData(NanoOcp1::Ocp1CommandResponseRequired(
77 NanoOcp1::DS100::dbOcaObjectDef_Positioning_Source_Enable(1).AddSubscriptionCommand(), handle).GetSerializedData());
78 m_ocaHandleMap.emplace(handle, m_soundobjectEnableObjDef.get());
79 DBG("Sent an OCA AddSubscription command with handle " << NanoOcp1::HandleToString(handle));
80
81 m_nanoOcp1Client->sendData(NanoOcp1::Ocp1CommandResponseRequired(
82 NanoOcp1::DS100::dbOcaObjectDef_Positioning_Speaker_Group(1).AddSubscriptionCommand(), handle).GetSerializedData());
83 m_ocaHandleMap.emplace(handle, m_speakerGroupObjDef.get());
84 DBG("Sent an OCA AddSubscription command with handle " << NanoOcp1::HandleToString(handle));
85
86 // Get initial values
87 m_nanoOcp1Client->sendData(NanoOcp1::Ocp1CommandResponseRequired(*m_pwrOnObjDef.get(), handle).GetSerializedData());
88 m_ocaHandleMap.emplace(handle, m_pwrOnObjDef.get());
89 DBG("Sent an OCA Get command with handle " << NanoOcp1::HandleToString(handle));
90
91 m_nanoOcp1Client->sendData(NanoOcp1::Ocp1CommandResponseRequired(*m_potiLevelObjDef.get(), handle).GetSerializedData());
92 m_ocaHandleMap.emplace(handle, m_potiLevelObjDef.get());
93 DBG("Sent an OCA Get command with handle " << NanoOcp1::HandleToString(handle));
94
95 m_nanoOcp1Client->sendData(NanoOcp1::Ocp1CommandResponseRequired(*m_soundobjectEnableObjDef.get(), handle).GetSerializedData());
96 m_ocaHandleMap.emplace(handle, m_soundobjectEnableObjDef.get());
97 DBG("Sent an OCA Get command with handle " << NanoOcp1::HandleToString(handle));
98
99 m_nanoOcp1Client->sendData(NanoOcp1::Ocp1CommandResponseRequired(*m_speakerGroupObjDef.get(), handle).GetSerializedData());
100 m_ocaHandleMap.emplace(handle, m_speakerGroupObjDef.get());
101 DBG("Sent an OCA Get command with handle " << NanoOcp1::HandleToString(handle));
102
103 m_nanoOcp1Client->sendData(NanoOcp1::Ocp1CommandResponseRequired(*m_guidObjDef.get(), handle).GetSerializedData());
104 m_ocaHandleMap.emplace(handle, m_guidObjDef.get());
105 DBG("Sent an OCA Get command with handle " << NanoOcp1::HandleToString(handle));
106 }
107 else
108 {
109 // Send RemoveSubscription requests
110 std::uint32_t handle;
111 m_nanoOcp1Client->sendData(NanoOcp1::Ocp1CommandResponseRequired(
112 NanoOcp1::AmpGeneric::dbOcaObjectDef_Config_PotiLevel(1).RemoveSubscriptionCommand(), handle).GetSerializedData());
113 m_ocaHandleMap.emplace(handle, m_potiLevelObjDef.get());
114 DBG("Sent an OCA RemoveSubscription command with handle " << NanoOcp1::HandleToString(handle));
115
116 m_nanoOcp1Client->sendData(NanoOcp1::Ocp1CommandResponseRequired(
117 NanoOcp1::AmpDxDy::dbOcaObjectDef_Settings_PwrOn().RemoveSubscriptionCommand(), handle).GetSerializedData());
118 m_ocaHandleMap.emplace(handle, m_pwrOnObjDef.get());
119 DBG("Sent an OCA RemoveSubscription command with handle " << NanoOcp1::HandleToString(handle));
120
121 m_nanoOcp1Client->sendData(NanoOcp1::Ocp1CommandResponseRequired(
122 NanoOcp1::DS100::dbOcaObjectDef_Positioning_Source_Enable(1).RemoveSubscriptionCommand(), handle).GetSerializedData());
123 m_ocaHandleMap.emplace(handle, m_soundobjectEnableObjDef.get());
124 DBG("Sent an OCA RemoveSubscription command with handle " << NanoOcp1::HandleToString(handle));
125
126 m_nanoOcp1Client->sendData(NanoOcp1::Ocp1CommandResponseRequired(
127 NanoOcp1::DS100::dbOcaObjectDef_Positioning_Speaker_Group(1).RemoveSubscriptionCommand(), handle).GetSerializedData());
128 m_ocaHandleMap.emplace(handle, m_speakerGroupObjDef.get());
129 DBG("Sent an OCA RemoveSubscription command with handle " << NanoOcp1::HandleToString(handle));
130 }
131 };
132 addAndMakeVisible(m_subscribeButton.get());
133
134 // Button to act as Power display LED
135 m_powerD40LED = std::make_unique<TextButton>("Power LED");
136 m_powerD40LED->setColour(juce::TextButton::ColourIds::buttonOnColourId, juce::Colours::forestgreen);
137 m_powerD40LED->setColour(juce::TextButton::ColourIds::buttonColourId, juce::Colours::dimgrey);
138 m_powerD40LED->setToggleState(false, dontSendNotification);
139 m_powerD40LED->setEnabled(false);
140 addAndMakeVisible(m_powerD40LED.get());
141
142 // Button to power OFF the D40
143 m_powerOffD40Button = std::make_unique<TextButton>("Pwr Off");
144 m_powerOffD40Button->onClick = [=]()
145 {
146 std::uint32_t handle;
147 auto cmdDef(NanoOcp1::AmpDxDy::dbOcaObjectDef_Settings_PwrOn().SetValueCommand(0)); // 0 == OFF
148 m_nanoOcp1Client->sendData(NanoOcp1::Ocp1CommandResponseRequired(cmdDef, handle).GetSerializedData());
149 };
150 addAndMakeVisible(m_powerOffD40Button.get());
151
152 // Button to power ON the D40
153 m_powerOnD40Button = std::make_unique<TextButton>("Pwr On");
154 m_powerOnD40Button->onClick = [=]()
155 {
156 std::uint32_t handle;
157 auto cmdDef(NanoOcp1::AmpDxDy::dbOcaObjectDef_Settings_PwrOn().SetValueCommand(1)); // 1 == ON
158 m_nanoOcp1Client->sendData(NanoOcp1::Ocp1CommandResponseRequired(cmdDef, handle).GetSerializedData());
159 };
160 addAndMakeVisible(m_powerOnD40Button.get());
161
162 // Gain slider for Channel A
163 m_gainSlider = std::make_unique<Slider>(Slider::LinearVertical, Slider::TextBoxBelow);
164 m_gainSlider->setRange(-57.5, 6, 0.5);
165 m_gainSlider->setTextValueSuffix("dB");
166 m_gainSlider->onValueChange = [=]()
167 {
168 std::uint32_t handle;
169 auto cmdDef(NanoOcp1::AmpGeneric::dbOcaObjectDef_Config_PotiLevel(1).SetValueCommand(m_gainSlider->getValue()));
170 m_nanoOcp1Client->sendData(NanoOcp1::Ocp1CommandResponseRequired(cmdDef, handle).GetSerializedData());
171 };
172 addAndMakeVisible(m_gainSlider.get());
173
174 setSize(300, 200);
175
176 // create the nano ocp1 client and fire it up
177 m_nanoOcp1Client = std::make_unique<NanoOcp1::NanoOcp1Client>(address, port, true /* synch callbacks */);
178 m_nanoOcp1Client->onDataReceived = [=](const NanoOcp1::ByteVector& message)
179 {
180 return OnOcp1MessageReceived(message);
181 };
182 m_nanoOcp1Client->onConnectionEstablished = [=]()
183 {
184 if (m_connectedLED)
185 m_connectedLED->setToggleState(true, juce::dontSendNotification);
186 };
187 m_nanoOcp1Client->onConnectionLost = [=]()
188 {
189 if (m_connectedLED)
190 m_connectedLED->setToggleState(false, juce::dontSendNotification);
191 };
192 m_nanoOcp1Client->start();
193}
194
196{
197 std::unique_ptr<NanoOcp1::Ocp1Message> msgObj = NanoOcp1::Ocp1Message::UnmarshalOcp1Message(message);
198 if (msgObj)
199 {
200 switch (msgObj->GetMessageType())
201 {
203 {
204 NanoOcp1::Ocp1Notification* notifObj = static_cast<NanoOcp1::Ocp1Notification*>(msgObj.get());
205
206 DBG("Got an OCA notification from ONo 0x" << juce::String::toHexString(notifObj->GetEmitterOno()));
207
208 // Update the right GUI element according to the definition of the object
209 // which triggered the notification.
210 if (notifObj->MatchesObject(m_pwrOnObjDef.get()))
211 {
212 std::uint16_t switchSetting = NanoOcp1::DataToUint16(notifObj->GetParameterData());
213 m_powerD40LED->setToggleState(switchSetting > 0, dontSendNotification);
214 }
215 else if (notifObj->MatchesObject(m_potiLevelObjDef.get()))
216 {
217 std::float_t newGain = NanoOcp1::DataToFloat(notifObj->GetParameterData());
218 m_gainSlider->setValue(newGain, dontSendNotification);
219 }
220 else if (notifObj->MatchesObject(m_soundobjectEnableObjDef.get()))
221 {
222 std::uint16_t switchSetting = NanoOcp1::DataToUint16(notifObj->GetParameterData());
223 DBG(juce::String(__FUNCTION__) + juce::String(" Notification for Positioning_Source_Enable: ") + juce::String(switchSetting));
224 }
225 else if (notifObj->MatchesObject(m_speakerGroupObjDef.get()))
226 {
227 std::uint32_t newGroup = NanoOcp1::DataToUint32(notifObj->GetParameterData());
228 DBG(juce::String(__FUNCTION__) + juce::String(" Notification for Positioning_Speaker_Group: ") + juce::String(newGroup));
229 }
230 else
231 {
232 DBG("Got an OCA notification from UNKNOWN object ONo 0x" << juce::String::toHexString(notifObj->GetEmitterOno()));
233 }
234
235 return true;
236 }
238 {
239 NanoOcp1::Ocp1Response* responseObj = static_cast<NanoOcp1::Ocp1Response*>(msgObj.get());
240
241 // Get the objDef matching the obtained response handle.
242 const auto iter = m_ocaHandleMap.find(responseObj->GetResponseHandle());
243 if (iter != m_ocaHandleMap.end())
244 {
245 if (responseObj->GetResponseStatus() != 0)
246 {
247 DBG("Got an OCA response for handle " << NanoOcp1::HandleToString(responseObj->GetResponseHandle()) <<
248 " with status " << NanoOcp1::StatusToString(responseObj->GetResponseStatus()));
249 }
250 else if (responseObj->GetParamCount() == 0)
251 {
252 DBG("Got an empty \"OK\" OCA response for handle " << NanoOcp1::HandleToString(responseObj->GetResponseHandle()));
253 }
254 else
255 {
256 DBG("Got an OCA response for handle " << NanoOcp1::HandleToString(responseObj->GetResponseHandle()) <<
257 " and paramCount " << juce::String(responseObj->GetParamCount()));
258
259 // Update the right GUI element according to the definition of the object
260 // which triggered the response.
261 NanoOcp1::Ocp1CommandDefinition* objDef = iter->second;
262 if (objDef == m_pwrOnObjDef.get())
263 {
264 std::uint16_t switchSetting = NanoOcp1::DataToUint16(responseObj->GetParameterData());
265 m_powerD40LED->setToggleState(switchSetting > 0, dontSendNotification);
266 }
267 else if (objDef == m_potiLevelObjDef.get())
268 {
269 std::float_t newGain = NanoOcp1::DataToFloat(responseObj->GetParameterData());
270 m_gainSlider->setValue(newGain, dontSendNotification);
271 }
272 else if (objDef == m_guidObjDef.get())
273 {
274 auto newValue = NanoOcp1::DataToString(responseObj->GetParameterData());
275 DBG(juce::String(__FUNCTION__) + juce::String(" Response for Fixed_GUID: ") + juce::String(newValue));
276 }
277 else
278 {
279 DBG("Got an OCA response for handle " << NanoOcp1::HandleToString(responseObj->GetResponseHandle()) <<
280 " which could not be processed (unknown object)!");
281 }
282 }
283
284 // Finally remove handle from the list, as it has been processed.
285 m_ocaHandleMap.erase(iter);
286 }
287 else
288 {
289 DBG("Got an OCA response for UNKNOWN handle " << NanoOcp1::HandleToString(responseObj->GetResponseHandle()) <<
290 "; status " << NanoOcp1::StatusToString(responseObj->GetResponseStatus()) <<
291 "; paramCount " << juce::String(responseObj->GetParamCount()));
292 }
293
294 return true;
295 }
297 {
298 // Reset online timer
299
300 return true;
301 }
302 default:
303 break;
304 }
305 }
306
307 return false;
308}
309
311{
312 m_nanoOcp1Client->onDataReceived = std::function<bool(const NanoOcp1::ByteVector&)>();
313 m_nanoOcp1Client->onConnectionEstablished = std::function<void()>();
314 m_nanoOcp1Client->onConnectionLost = std::function<void()>();
315 m_nanoOcp1Client->stop();
316}
317
319{
320 if (&editor == m_ipAndPortEditor.get())
321 {
322 auto ip = editor.getText().upToFirstOccurrenceOf(";", false, true);
323 auto port = editor.getText().fromLastOccurrenceOf(";", false, true).getIntValue();
324
325 m_nanoOcp1Client->stop();
326 m_nanoOcp1Client->setAddress(ip);
327 m_nanoOcp1Client->setPort(port);
328 m_nanoOcp1Client->start();
329 }
330}
331
333{
334 auto bounds = getLocalBounds();
335
336 auto connectionParamsHeight = 35;
337 auto connectionLedWidth = 60;
338
339 auto textEditorBounds = bounds.removeFromTop(connectionParamsHeight);
340 auto connectedLedBounds = textEditorBounds.removeFromRight(connectionLedWidth);
341 m_connectedLED->setBounds(connectedLedBounds.reduced(5));
342 m_ipAndPortEditor->setBounds(textEditorBounds.reduced(5));
343
344 auto sliderBounds = bounds.removeFromRight(connectionLedWidth);
345 m_gainSlider->setBounds(sliderBounds.reduced(5));
346
347 auto button1Bounds = bounds;
348 auto button2Bounds = button1Bounds.removeFromRight(button1Bounds.getWidth() / 2);
349 auto button3Bounds = button2Bounds.removeFromBottom(button2Bounds.getHeight() / 2);
350 auto button4Bounds = button1Bounds.removeFromBottom(button1Bounds.getHeight() / 2);
351
352 button1Bounds.reduce(5, 5);
353 button2Bounds.reduce(5, 5);
354 button3Bounds.reduce(5, 5);
355 button4Bounds.reduce(5, 5);
356
357 m_subscribeButton->setBounds(button1Bounds);
358 m_powerD40LED->setBounds(button2Bounds);
359 m_powerOffD40Button->setBounds(button3Bounds);
360 m_powerOnD40Button->setBounds(button4Bounds);
361}
362
363}
void textEditorReturnKeyPressed(TextEditor &editor) override
bool OnOcp1MessageReceived(const NanoOcp1::ByteVector &message)
ByteVector GetSerializedData() override
ByteVector GetParameterData() const
@ Response
Device reply to a CommandResponseRequired.
@ KeepAlive
Heartbeat for connection supervision.
@ Notification
Unsolicited property change from device to client.
static std::unique_ptr< Ocp1Message > UnmarshalOcp1Message(const ByteVector &receivedData)
bool MatchesObject(const Ocp1CommandDefinition *def) const
std::uint32_t GetEmitterOno() const
std::uint8_t GetParamCount() const
std::uint32_t GetResponseHandle() const
std::uint8_t GetResponseStatus() const
std::string HandleToString(std::uint32_t handle)
std::string DataToString(const ByteVector &parameterData, bool *pOk)
std::vector< std::uint8_t > ByteVector
Binary buffer type used throughout NanoOcp for all serialized OCP.1 data.
std::float_t DataToFloat(const ByteVector &parameterData, bool *pOk)
std::uint32_t DataToUint32(const ByteVector &parameterData, bool *pOk)
std::uint16_t DataToUint16(const ByteVector &parameterData, bool *pOk)
std::string StatusToString(std::uint8_t status)
Parameter bundle that fully describes one OCA controllable property.
Definition Ocp1Message.h:72