/* ============================================================================== This file is part of the JUCE library. Copyright (c) 2017 - ROLI Ltd. JUCE is an open source library subject to commercial or open-source licensing. The code included in this file is provided under the terms of the ISC license http://www.isc.org/downloads/software-support-policy/isc-license. Permission To use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted provided that the above copyright notice and this permission notice appear in all copies. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE DISCLAIMED. ============================================================================== */ namespace juce { namespace { static Block::Timestamp deviceTimestampToHost (uint32 timestamp) noexcept { return static_cast (timestamp); } } template struct ConnectedDeviceGroup : private juce::AsyncUpdater, private juce::Timer { //============================================================================== ConnectedDeviceGroup (Detector& d, const juce::String& name, PhysicalTopologySource::DeviceConnection* connection) : detector (d), deviceName (name), deviceConnection (connection) { if (auto midiDeviceConnection = static_cast (deviceConnection.get())) { depreciatedVersionReader = std::make_unique (*midiDeviceConnection); juce::ScopedLock lock (midiDeviceConnection->criticalSecton); setMidiMessageCallback(); } else { setMidiMessageCallback(); } startTimer (200); sendTopologyRequest(); } ~ConnectedDeviceGroup() { for (const auto& device : currentDeviceInfo) detector.handleDeviceRemoved (device); } bool isStillConnected (const juce::StringArray& detectedDevices) const noexcept { return detectedDevices.contains (deviceName) && ! failedToGetTopology(); } bool contains (Block::UID uid) { return getIndexFromDeviceID (uid) >= 0; } void handleBlockRestarting (Block::UID deviceID) { forceApiDisconnected (deviceID); } //============================================================================== // The following methods will be called by the HostPacketDecoder: void beginTopology (int numDevices, int numConnections) { incomingTopologyDevices.clearQuick(); incomingTopologyDevices.ensureStorageAllocated (numDevices); incomingTopologyConnections.clearQuick(); incomingTopologyConnections.ensureStorageAllocated (numConnections); } void extendTopology (int numDevices, int numConnections) { incomingTopologyDevices.ensureStorageAllocated (incomingTopologyDevices.size() + numDevices); incomingTopologyConnections.ensureStorageAllocated (incomingTopologyConnections.size() + numConnections); } void handleTopologyDevice (BlocksProtocol::DeviceStatus status) { incomingTopologyDevices.add (status); } void handleTopologyConnection (BlocksProtocol::DeviceConnection connection) { incomingTopologyConnections.add (connection); } void endTopology() { lastTopologyReceiveTime = juce::Time::getCurrentTime(); if (incomingTopologyDevices.isEmpty() || incomingTopologyConnections.size() < incomingTopologyDevices.size() - 1) { LOG_CONNECTIVITY ("Invalid topology or device list received."); LOG_CONNECTIVITY ("Device size : " << incomingTopologyDevices.size()); LOG_CONNECTIVITY ("Connections size : " << incomingTopologyConnections.size()); scheduleNewTopologyRequest(); return; } LOG_CONNECTIVITY ("Valid topology received"); updateCurrentDeviceList(); updateCurrentDeviceConnections(); } void handleVersion (BlocksProtocol::DeviceVersion version) { setVersion (version.index, version.version); } void handleName (BlocksProtocol::DeviceName name) { if (name.name.length <= 1) return; if (const auto info = getDeviceInfoFromIndex (name.index)) { if (info->name == name.name) return; info->name = name.name; detector.handleDeviceUpdated (*info); } } void handleControlButtonUpDown (BlocksProtocol::TopologyIndex deviceIndex, uint32 timestamp, BlocksProtocol::ControlButtonID buttonID, bool isDown) { if (auto deviceID = getDeviceIDFromIndex (deviceIndex)) detector.handleButtonChange (deviceID, deviceTimestampToHost (timestamp), buttonID.get(), isDown); } void handleCustomMessage (BlocksProtocol::TopologyIndex deviceIndex, uint32 timestamp, const int32* data) { if (auto deviceID = getDeviceIDFromIndex (deviceIndex)) detector.handleCustomMessage (deviceID, deviceTimestampToHost (timestamp), data); } void handleTouchChange (BlocksProtocol::TopologyIndex deviceIndex, uint32 timestamp, BlocksProtocol::TouchIndex touchIndex, BlocksProtocol::TouchPosition position, BlocksProtocol::TouchVelocity velocity, bool isStart, bool isEnd) { if (auto deviceID = getDeviceIDFromIndex (deviceIndex)) { TouchSurface::Touch touch; touch.index = (int) touchIndex.get(); touch.x = position.x.toUnipolarFloat(); touch.y = position.y.toUnipolarFloat(); touch.z = position.z.toUnipolarFloat(); touch.xVelocity = velocity.vx.toBipolarFloat(); touch.yVelocity = velocity.vy.toBipolarFloat(); touch.zVelocity = velocity.vz.toBipolarFloat(); touch.eventTimestamp = deviceTimestampToHost (timestamp); touch.isTouchStart = isStart; touch.isTouchEnd = isEnd; touch.blockUID = deviceID; setTouchStartPosition (touch); detector.handleTouchChange (deviceID, touch); } } void setTouchStartPosition (TouchSurface::Touch& touch) { auto& startPos = touchStartPositions.getValue (touch); if (touch.isTouchStart) startPos = { touch.x, touch.y }; touch.startX = startPos.x; touch.startY = startPos.y; } void handlePacketACK (BlocksProtocol::TopologyIndex deviceIndex, BlocksProtocol::PacketCounter counter) { if (auto deviceID = getDeviceIDFromIndex (deviceIndex)) { detector.handleSharedDataACK (deviceID, counter); updateApiPing (deviceID); } } void handleFirmwareUpdateACK (BlocksProtocol::TopologyIndex deviceIndex, BlocksProtocol::FirmwareUpdateACKCode resultCode, BlocksProtocol::FirmwareUpdateACKDetail resultDetail) { if (auto deviceID = getDeviceIDFromIndex (deviceIndex)) { detector.handleFirmwareUpdateACK (deviceID, (uint8) resultCode.get(), (uint32) resultDetail.get()); updateApiPing (deviceID); } } void handleConfigUpdateMessage (BlocksProtocol::TopologyIndex deviceIndex, int32 item, int32 value, int32 min, int32 max) { if (auto deviceID = getDeviceIDFromIndex (deviceIndex)) detector.handleConfigUpdateMessage (deviceID, item, value, min, max); } void handleConfigSetMessage (BlocksProtocol::TopologyIndex deviceIndex, int32 item, int32 value) { if (auto deviceID = getDeviceIDFromIndex (deviceIndex)) detector.handleConfigSetMessage (deviceID, item, value); } void handleConfigFactorySyncEndMessage (BlocksProtocol::TopologyIndex deviceIndex) { if (auto deviceID = getDeviceIDFromIndex (deviceIndex)) detector.handleConfigFactorySyncEndMessage (deviceID); } void handleConfigFactorySyncResetMessage (BlocksProtocol::TopologyIndex deviceIndex) { if (auto deviceID = getDeviceIDFromIndex (deviceIndex)) detector.handleConfigFactorySyncResetMessage (deviceID); } void handleLogMessage (BlocksProtocol::TopologyIndex deviceIndex, const String& message) { if (auto deviceID = getDeviceIDFromIndex (deviceIndex)) detector.handleLogMessage (deviceID, message); } //============================================================================== template bool sendMessageToDevice (const PacketBuilder& builder) const { if (deviceConnection->sendMessageToDevice (builder.getData(), (size_t) builder.size())) { #if DUMP_BANDWIDTH_STATS registerBytesOut (builder.size()); #endif return true; } return false; } PhysicalTopologySource::DeviceConnection* getDeviceConnection() { return deviceConnection.get(); } juce::Array getCurrentDeviceConnections() { juce::Array connections; for (const auto& connection : currentDeviceConnections) if (isApiConnected (getDeviceIDFromIndex (connection.device1)) && isApiConnected (getDeviceIDFromIndex (connection.device2))) connections.add (getBlockDeviceConnection (connection)); return connections; } Detector& detector; juce::String deviceName; static constexpr double pingTimeoutSeconds = 6.0; private: //============================================================================== juce::Array currentDeviceInfo; juce::Array incomingTopologyDevices; juce::Array incomingTopologyConnections, currentDeviceConnections; std::unique_ptr deviceConnection; juce::CriticalSection incomingPacketLock; juce::Array incomingPackets; std::unique_ptr depreciatedVersionReader; struct TouchStart { float x, y; }; TouchList touchStartPositions; Block::UID masterBlock = 0; //============================================================================== void timerCallback() override { const auto now = juce::Time::getCurrentTime(); if ((now > lastTopologyReceiveTime + juce::RelativeTime::seconds (30.0)) && now > lastTopologyRequestTime + juce::RelativeTime::seconds (1.0) && numTopologyRequestsSent < 4) sendTopologyRequest(); checkApiTimeouts (now); startApiModeOnConnectedBlocks(); requestMasterBlockVersionIfNeeded(); } //============================================================================== void setMidiMessageCallback() { deviceConnection->handleMessageFromDevice = [this] (const void* data, size_t dataSize) { this->handleIncomingMessage (data, dataSize); }; } void handleIncomingMessage (const void* data, size_t dataSize) { juce::MemoryBlock mb (data, dataSize); { const juce::ScopedLock sl (incomingPacketLock); incomingPackets.add (std::move (mb)); } triggerAsyncUpdate(); #if DUMP_BANDWIDTH_STATS registerBytesIn ((int) dataSize); #endif } void handleAsyncUpdate() override { juce::Array packets; packets.ensureStorageAllocated (32); { const juce::ScopedLock sl (incomingPacketLock); incomingPackets.swapWith (packets); } for (auto& packet : packets) { auto data = static_cast (packet.getData()); BlocksProtocol::HostPacketDecoder ::processNextPacket (*this, *data, data + 1, (int) packet.getSize() - 1); } } bool sendCommandMessage (BlocksProtocol::TopologyIndex deviceIndex, uint32 commandID) const { BlocksProtocol::HostPacketBuilder<64> p; p.writePacketSysexHeaderBytes (deviceIndex); p.deviceControlMessage (commandID); p.writePacketSysexFooter(); return sendMessageToDevice (p); } //============================================================================== juce::Time lastTopologyRequestTime, lastTopologyReceiveTime; int numTopologyRequestsSent = 0; void scheduleNewTopologyRequest() { LOG_CONNECTIVITY ("Topology Request Scheduled"); numTopologyRequestsSent = 0; lastTopologyReceiveTime = juce::Time(); lastTopologyRequestTime = juce::Time::getCurrentTime(); } void sendTopologyRequest() { ++numTopologyRequestsSent; lastTopologyRequestTime = juce::Time::getCurrentTime(); sendCommandMessage (0, BlocksProtocol::requestTopologyMessage); } bool failedToGetTopology() const noexcept { return numTopologyRequestsSent >= 4 && lastTopologyReceiveTime == juce::Time(); } //============================================================================== void requestMasterBlockVersionIfNeeded() { if (depreciatedVersionReader == nullptr) return; const auto masterVersion = depreciatedVersionReader->getVersionNumber(); if (masterVersion.isNotEmpty()) { const auto masterIndex = getIndexFromDeviceID (masterBlock); if (masterIndex >= 0) setVersion (BlocksProtocol::TopologyIndex (masterIndex), masterVersion); else jassertfalse; } } void setVersion (const BlocksProtocol::TopologyIndex index, const BlocksProtocol::VersionNumber versionNumber) { if (versionNumber.length <= 1) return; if (const auto info = getDeviceInfoFromIndex (index)) { if (info->version == versionNumber) return; if (info->uid == masterBlock) depreciatedVersionReader.reset(); info->version = versionNumber; detector.handleDeviceUpdated (*info); } } //============================================================================== struct BlockPingTime { Block::UID blockUID; juce::Time lastPing; juce::Time connected; }; juce::Array blockPings; BlockPingTime* getPing (Block::UID uid) { for (auto& ping : blockPings) if (uid == ping.blockUID) return &ping; return nullptr; } void removePing (Block::UID uid) { const auto remove = [uid] (const BlockPingTime& ping) { if (uid == ping.blockUID) { LOG_CONNECTIVITY ("API Disconnected by topology update " << ping.blockUID); return true; } return false; }; blockPings.removeIf (remove); } void updateApiPing (Block::UID uid) { const auto now = juce::Time::getCurrentTime(); if (auto* ping = getPing (uid)) { LOG_PING ("Ping: " << uid << " " << now.formatted ("%Mm %Ss")); ping->lastPing = now; } else { LOG_CONNECTIVITY ("API Connected " << uid); blockPings.add ({ uid, now, now }); if (const auto info = getDeviceInfoFromUID (uid)) detector.handleDeviceAdded (*info); } } bool isApiConnected (Block::UID uid) { return getPing (uid) != nullptr; } void forceApiDisconnected (Block::UID /*uid*/) { Array toRemove; for (const auto& ping : blockPings) toRemove.add (ping.blockUID); for (const auto& uid : toRemove) removeDevice (uid); scheduleNewTopologyRequest(); } void checkApiTimeouts (juce::Time now) { Array toRemove; for (const auto& ping : blockPings) { if (ping.lastPing < now - juce::RelativeTime::seconds (pingTimeoutSeconds)) { LOG_CONNECTIVITY ("Ping timeout: " << ping.blockUID); toRemove.add (ping.blockUID); scheduleNewTopologyRequest(); } } for (const auto& uid : toRemove) removeDevice (uid); } void startApiModeOnConnectedBlocks() { for (auto& info : currentDeviceInfo) { if (! isApiConnected (info.uid)) { LOG_CONNECTIVITY ("API Try " << info.uid); sendCommandMessage (info.index, BlocksProtocol::endAPIMode); sendCommandMessage (info.index, BlocksProtocol::beginAPIMode); } } } //============================================================================== Block::UID getDeviceIDFromIndex (BlocksProtocol::TopologyIndex index) noexcept { for (const auto& device : currentDeviceInfo) if (device.index == index) return device.uid; scheduleNewTopologyRequest(); return {}; } int getIndexFromDeviceID (Block::UID uid) const noexcept { for (auto& d : currentDeviceInfo) if (d.uid == uid) return d.index; return -1; } DeviceInfo* getDeviceInfoFromUID (Block::UID uid) const noexcept { for (auto& d : currentDeviceInfo) if (d.uid == uid) return &d; return nullptr; } DeviceInfo* getDeviceInfoFromIndex (BlocksProtocol::TopologyIndex index) const noexcept { for (auto& d : currentDeviceInfo) if (d.index == index) return &d; return nullptr; } void removeDeviceInfo (Block::UID uid) { currentDeviceInfo.removeIf ([uid] (const DeviceInfo& info) { return info.uid == uid; }); } const DeviceStatus* getIncomingDeviceStatus (BlockSerialNumber serialNumber) const { for (auto& device : incomingTopologyDevices) if (device.serialNumber == serialNumber) return &device; return nullptr; } //============================================================================== void removeDevice (Block::UID uid) { if (const auto info = getDeviceInfoFromUID (uid)) detector.handleDeviceRemoved (*info); removeDeviceInfo (uid); removePing (uid); } void updateCurrentDeviceList() { Array toRemove; //Update known devices for (int i = currentDeviceInfo.size(); --i >= 0; ) { auto& currentDevice = currentDeviceInfo.getReference (i); if (const auto newStatus = getIncomingDeviceStatus (currentDevice.serial)) { if (currentDevice.index != newStatus->index) { currentDevice.index = newStatus->index; detector.handleIndexChanged (currentDevice.uid, currentDevice.index); } if (currentDevice.batteryCharging != newStatus->batteryCharging) { currentDevice.batteryCharging = newStatus->batteryCharging; detector.handleBatteryChargingChanged (currentDevice.uid, currentDevice.batteryCharging); } if (currentDevice.batteryLevel != newStatus->batteryLevel) { currentDevice.batteryLevel = newStatus->batteryLevel; detector.handleBatteryLevelChanged (currentDevice.uid, currentDevice.batteryLevel); } } else { toRemove.add (currentDevice.uid); } } for (const auto& uid : toRemove) removeDevice (uid); //Add new devices for (const auto& device : incomingTopologyDevices) { const auto uid = getBlockUIDFromSerialNumber (device.serialNumber); if (! getDeviceInfoFromUID (uid)) { // For backwards compatibility we assume the first device we see in a group is the master and won't change if (masterBlock == 0) masterBlock = uid; currentDeviceInfo.add ({ uid, device.index, device.serialNumber, BlocksProtocol::VersionNumber(), BlocksProtocol::BlockName(), device.batteryLevel, device.batteryCharging, masterBlock }); } } } //============================================================================== Block::ConnectionPort convertConnectionPort (Block::UID uid, BlocksProtocol::ConnectorPort p) noexcept { if (auto* info = getDeviceInfoFromUID (uid)) return BlocksProtocol::BlockDataSheet (info->serial).convertPortIndexToConnectorPort (p); jassertfalse; return { Block::ConnectionPort::DeviceEdge::north, 0 }; } BlockDeviceConnection getBlockDeviceConnection (const BlocksProtocol::DeviceConnection& connection) { BlockDeviceConnection dc; dc.device1 = getDeviceIDFromIndex (connection.device1); dc.device2 = getDeviceIDFromIndex (connection.device2); if (dc.device1 <= 0 || dc.device2 <= 0) jassertfalse; dc.connectionPortOnDevice1 = convertConnectionPort (dc.device1, connection.port1); dc.connectionPortOnDevice2 = convertConnectionPort (dc.device2, connection.port2); return dc; } void updateCurrentDeviceConnections() { currentDeviceConnections.clearQuick(); currentDeviceConnections.swapWith (incomingTopologyConnections); detector.handleConnectionsChanged(); } JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ConnectedDeviceGroup) }; } // namespace juce