From 4f7137cc08438d739a22eeef8b7c12195d34ecab Mon Sep 17 00:00:00 2001 From: dimitri Date: Tue, 18 Dec 2018 11:47:39 +0000 Subject: [PATCH] BLOCKS: Request version number from old firmware --- .../blocks/juce_BlocksVersion.cpp | 230 +++++++++++++ .../blocks/juce_BlocksVersion.h | 82 +++++ .../juce_blocks_basics/juce_blocks_basics.cpp | 3 + .../juce_blocks_basics/juce_blocks_basics.h | 1 + .../protocol/juce_BlocksProtocolDefinitions.h | 43 +-- .../protocol/juce_HostPacketDecoder.h | 4 +- .../internal/juce_BlockImplementation.cpp | 8 +- .../internal/juce_ConnectedDeviceGroup.cpp | 308 +++++++++++++----- .../juce_DepreciatedVersionReader.cpp | 128 ++++++++ .../topology/internal/juce_Detector.cpp | 9 +- .../internal/juce_MidiDeviceConnection.cpp | 2 +- .../topology/juce_PhysicalTopologySource.cpp | 13 + 12 files changed, 716 insertions(+), 115 deletions(-) create mode 100644 modules/juce_blocks_basics/blocks/juce_BlocksVersion.cpp create mode 100644 modules/juce_blocks_basics/blocks/juce_BlocksVersion.h create mode 100644 modules/juce_blocks_basics/topology/internal/juce_DepreciatedVersionReader.cpp diff --git a/modules/juce_blocks_basics/blocks/juce_BlocksVersion.cpp b/modules/juce_blocks_basics/blocks/juce_BlocksVersion.cpp new file mode 100644 index 0000000000..e1968f3e80 --- /dev/null +++ b/modules/juce_blocks_basics/blocks/juce_BlocksVersion.cpp @@ -0,0 +1,230 @@ +/* + ============================================================================== + + 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 +{ + +BlocksVersion::BlocksVersion (const String& versionString) +{ + evaluate (versionString); +} + +String BlocksVersion::toString (bool extended) const +{ + String output = String (major) + "." + String (minor) + "." + String (patch); + + if (extended) + { + if (releaseType.isNotEmpty()) + output += "-" + releaseType + "-" + String (releaseCount); + + if (commit.isNotEmpty()) + output += "-" + commit; + + if (forced) + output += "-f"; + } + + return output; +} + +static std::regex getRegEx() +{ + static const std::string majorMinorPatchRegex { "([0-9]+)\\.([0-9]+)\\.([0-9]+)" }; + static const std::string releaseAndCommitDetailsRegex { "(?:-(alpha|beta|rc))?(?:-([0-9]+))?(?:-g([A-Za-z0-9]+))?" }; + static const std::string forcedUpdateRegex { "(-f)?" }; + + static const std::regex regEx ("(?:.+)?" + majorMinorPatchRegex + releaseAndCommitDetailsRegex + forcedUpdateRegex + "(?:.+)?"); + + return regEx; +} + +bool BlocksVersion::isValidVersion (const String& versionString) +{ + return std::regex_match (versionString.toRawUTF8(), getRegEx()); +} + +bool BlocksVersion::evaluate (const String& versionString) +{ + std::cmatch groups; + const bool result = std::regex_match (versionString.toRawUTF8(), groups, getRegEx()); + + jassert (result); + + auto toInt = [](const std::sub_match match) + { + return std::atoi (match.str().c_str()); + }; + + enum tags { FULL, MAJOR, MINOR, PATCH, RELEASE, COUNT, COMMIT, FORCED}; + + major = toInt (groups[MAJOR]); + minor = toInt (groups[MINOR]); + patch = toInt (groups[PATCH]); + releaseType = String (groups[RELEASE]); + releaseCount = toInt (groups[COUNT]); + commit = String (groups[COMMIT]); + forced = groups[FORCED].matched; + + return result; +} + +bool BlocksVersion::isEqualTo (const BlocksVersion& other) const +{ + return major == other.major && + minor == other.minor && + patch == other.patch && + releaseType == other.releaseType && + releaseCount == other.releaseCount; +} + +bool BlocksVersion::isGreaterThan (const BlocksVersion& other) const +{ + if (major != other.major) return (major > other.major); + if (minor != other.minor) return (minor > other.minor); + if (patch != other.patch) return (patch > other.patch); + + return releaseTypeGreaterThan (other); +} + +bool BlocksVersion::releaseTypeGreaterThan (const BlocksVersion& other) const +{ + auto getReleaseTypePriority = [](const juce::BlocksVersion& version) + { + String releaseTypes[4] = { "alpha", "beta", "rc", {} }; + + for (int i = 0; i < 4; ++i) + if (version.releaseType == releaseTypes[i]) + return i; + + return -1; + }; + + if (releaseType != other.releaseType) + return getReleaseTypePriority (*this) > getReleaseTypePriority (other); + + return releaseCount > other.releaseCount; +} + +bool BlocksVersion::operator== (const BlocksVersion& other) const +{ + return isEqualTo (other); +} + +bool BlocksVersion::operator!=(const BlocksVersion& other) const +{ + return ! (*this == other); +} + +bool BlocksVersion::operator> (const BlocksVersion& other) const +{ + return isGreaterThan (other); +} + +bool BlocksVersion::operator< (const BlocksVersion& other) const +{ + return ! (*this > other) && (*this != other); +} + +bool BlocksVersion::operator<= (const BlocksVersion& other) const +{ + return (*this < other) || (*this == other); +} + +bool BlocksVersion::operator>= (const BlocksVersion& other) const +{ + return (*this > other) || (*this == other); +} + +#if JUCE_UNIT_TESTS +class BlocksVersionUnitTests : public UnitTest +{ +public: + BlocksVersionUnitTests() : UnitTest ("BlocksVersionUnitTests", "Blocks") {} + + void runTest() override + { + beginTest ("Compare patch number"); + expect (BlocksVersion ("4.6.7") < BlocksVersion ("4.6.11")); + expect (BlocksVersion ("4.6.6") > BlocksVersion ("4.6.2")); + expect (BlocksVersion ("4.6.5") <= BlocksVersion ("4.6.8")); + expect (BlocksVersion ("4.6.4") >= BlocksVersion ("4.6.3")); + + beginTest ("Compare minor number"); + expect (BlocksVersion ("4.5.9") < BlocksVersion ("4.6.7")); + expect (BlocksVersion ("4.15.2") > BlocksVersion ("4.6.6")); + expect (BlocksVersion ("4.4.8") <= BlocksVersion ("4.6.5")); + expect (BlocksVersion ("4.7.4") >= BlocksVersion ("4.6.3")); + + beginTest ("Compare major number"); + expect (BlocksVersion ("4.6.9") < BlocksVersion ("8.5.7")); + expect (BlocksVersion ("15.6.2") > BlocksVersion ("4.9.6")); + expect (BlocksVersion ("4.6.8") <= BlocksVersion ("7.4.5")); + expect (BlocksVersion ("5.6.4") >= BlocksVersion ("4.7.3")); + + beginTest ("Compare build number"); + expect (BlocksVersion ("0.3.2-alpha-3-gjduh") < BlocksVersion ("0.3.2-alpha-12-gjduh")); + expect (BlocksVersion ("0.3.2-alpha-4-gjduh") > BlocksVersion ("0.3.2-alpha-1-gjduh")); + expect (BlocksVersion ("0.3.2-beta-5-gjduh") <= BlocksVersion ("0.3.2-beta-6-gjduh")); + expect (BlocksVersion ("0.3.2-beta-6-gjduh") >= BlocksVersion ("0.3.2-beta-3-gjduh")); + + beginTest ("Compare build type"); + expect (BlocksVersion ("0.3.2-alpha-3-gjduhenf") < BlocksVersion ("0.3.2-beta-1-gjduhenf")); + expect (BlocksVersion ("0.3.2-beta-3-gjduhenf") < BlocksVersion ("0.3.2")); + expect (BlocksVersion ("0.3.2") > BlocksVersion ("0.3.2-alpha-3-gjduhenf")); + + beginTest ("Compare equal numbers"); + expect (BlocksVersion ("4.6.7") == BlocksVersion ("4.6.7")); + expect (BlocksVersion ("4.6.7-alpha-3-gsdfsf") == BlocksVersion ("4.6.7-alpha-3-gsdfsf")); + + beginTest ("Identify forced version"); + expect (BlocksVersion("0.2.2-2-g25eaec8a-f").forced); + expect (BlocksVersion("0.2.2-2-f").forced); + expect (! BlocksVersion("0.2.2-2-g25eaec8-d7").forced); + + beginTest ("Valid Strings"); + expect (BlocksVersion::isValidVersion ("Rainbow 0.4.5-beta-1-g4c36e")); + expect (! BlocksVersion::isValidVersion ("0.4-beta-1-g4c36e")); + expect (! BlocksVersion::isValidVersion ("a.0.4-beta-1-g4c36e")); + expect (BlocksVersion::isValidVersion ("BLOCKS control 0.2.2-2-g25eaec8a-f.syx")); + expect (BlocksVersion("BLOCKS control 0.2.2-2-g25eaec8a-f.syx") == BlocksVersion("0.2.2-2-g25eaec8a-f")); + + beginTest ("Default constructors"); + { + BlocksVersion v1 ("4.5.9"); + BlocksVersion v2 (v1); + BlocksVersion v3; + v3 = v1; + + expect (v2 == v1); + expect (v3 == v1); + + BlocksVersion emptyVersion; + expect (emptyVersion == BlocksVersion ("0.0.0")); + } + } +}; + +static BlocksVersionUnitTests BlocksVersionUnitTests; +#endif + +} // namespace juce diff --git a/modules/juce_blocks_basics/blocks/juce_BlocksVersion.h b/modules/juce_blocks_basics/blocks/juce_BlocksVersion.h new file mode 100644 index 0000000000..c621c7a42d --- /dev/null +++ b/modules/juce_blocks_basics/blocks/juce_BlocksVersion.h @@ -0,0 +1,82 @@ +/* + ============================================================================== + + 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. + + ============================================================================== +*/ + +#pragma once + +namespace juce +{ + +struct BlocksVersion +{ +public: + /** The main value in a version number x.0.0 */ + int major = 0; + + /** The secondary value in a version number 1.x.0 */ + int minor = 0; + + /** The tertiary value in a version number 1.0.x */ + int patch = 0; + + /** The release tag for this version, such as "beta", "alpha", "rc", etc */ + juce::String releaseType; + + /** A numberical value assosiated with the release tag, such as "beta 4" */ + int releaseCount = 0; + + /** The assosiated git commit that generated this firmware version */ + juce::String commit; + + /** Identify "forced" firmware builds **/ + bool forced = false; + + juce::String toString (bool extended = false) const; + + /** Constructs a version number from an formatted juce::String */ + BlocksVersion (const juce::String&); + + /** Constructs a version number from another BlocksVersion */ + BlocksVersion (const BlocksVersion& other) = default; + + /** Creates an empty version number **/ + BlocksVersion() = default; + + /** Returns true if string format is valid */ + static bool isValidVersion (const juce::String& versionString); + + bool operator == (const BlocksVersion&) const; + bool operator != (const BlocksVersion&) const; + bool operator < (const BlocksVersion&) const; + bool operator > (const BlocksVersion&) const; + bool operator <= (const BlocksVersion&) const; + bool operator >= (const BlocksVersion&) const; + +private: + /** @internal */ + bool evaluate (const juce::String& versionString); + bool releaseTypeGreaterThan (const BlocksVersion& otherReleaseType) const; + + bool isGreaterThan (const BlocksVersion& other) const; + bool isEqualTo (const BlocksVersion& other) const; +}; + +} // namespace juce diff --git a/modules/juce_blocks_basics/juce_blocks_basics.cpp b/modules/juce_blocks_basics/juce_blocks_basics.cpp index 034dc53aee..e8681d4a84 100644 --- a/modules/juce_blocks_basics/juce_blocks_basics.cpp +++ b/modules/juce_blocks_basics/juce_blocks_basics.cpp @@ -28,6 +28,8 @@ #endif #else +#include + namespace juce { #include "littlefoot/juce_LittleFootRemoteHeap.h" @@ -41,6 +43,7 @@ namespace juce #include "blocks/juce_BlockConfigManager.h" #include "blocks/juce_Block.cpp" +#include "blocks/juce_BlocksVersion.cpp" #include "topology/juce_PhysicalTopologySource.cpp" #include "topology/juce_RuleBasedTopologySource.cpp" #include "visualisers/juce_DrumPadLEDProgram.cpp" diff --git a/modules/juce_blocks_basics/juce_blocks_basics.h b/modules/juce_blocks_basics/juce_blocks_basics.h index 64886173e7..02aebdbbbc 100644 --- a/modules/juce_blocks_basics/juce_blocks_basics.h +++ b/modules/juce_blocks_basics/juce_blocks_basics.h @@ -73,6 +73,7 @@ namespace juce #include "blocks/juce_ControlButton.h" #include "blocks/juce_TouchList.h" #include "blocks/juce_StatusLight.h" +#include "blocks/juce_BlocksVersion.h" #include "topology/juce_Topology.h" #include "topology/juce_TopologySource.h" #include "topology/juce_PhysicalTopologySource.h" diff --git a/modules/juce_blocks_basics/protocol/juce_BlocksProtocolDefinitions.h b/modules/juce_blocks_basics/protocol/juce_BlocksProtocolDefinitions.h index 04f4aa5e81..25a05e1e54 100644 --- a/modules/juce_blocks_basics/protocol/juce_BlocksProtocolDefinitions.h +++ b/modules/juce_blocks_basics/protocol/juce_BlocksProtocolDefinitions.h @@ -154,41 +154,44 @@ struct BlockSerialNumber }; //============================================================================== -/** Structure for the version number +/** Structure for generic block data @tags{Blocks} -*/ -struct VersionNumber + */ +template +struct BlockStringData { - uint8 version[21] = {}; + uint8 data[MaxSize] = {}; uint8 length = 0; - juce::String asString() const + static const size_t maxLength { MaxSize }; + + bool isNotEmpty() const { - return juce::String (reinterpret_cast (version), - std::min (sizeof (version), static_cast (length))); + return length > 0; } -}; -//============================================================================== -/** Structure for the block name + bool operator== (const BlockStringData& other) const + { + if (length != other.length) + return false; - @tags{Blocks} -*/ -struct BlockName -{ - uint8 name[33] = {}; - uint8 length = 0; + for (int i = 0; i < length; ++i) + if (data[i] != other.data[i]) + return false; - bool isValid() const { return length > 0; } + return true; + } - juce::String asString() const + bool operator!= (const BlockStringData& other) const { - return juce::String (reinterpret_cast (name), - std::min (sizeof (name), static_cast (length))); + return ! ( *this == other ); } }; +using VersionNumber = BlockStringData<21>; +using BlockName = BlockStringData<33>; + //============================================================================== /** Structure for the device status diff --git a/modules/juce_blocks_basics/protocol/juce_HostPacketDecoder.h b/modules/juce_blocks_basics/protocol/juce_HostPacketDecoder.h index 517d6ea620..97cc2ab667 100644 --- a/modules/juce_blocks_basics/protocol/juce_HostPacketDecoder.h +++ b/modules/juce_blocks_basics/protocol/juce_HostPacketDecoder.h @@ -194,7 +194,7 @@ struct HostPacketDecoder version.version.length = (uint8) reader.readBits (7); for (uint32 i = 0; i < version.version.length; ++i) - version.version.version[i] = (uint8) reader.readBits (7); + version.version.data[i] = (uint8) reader.readBits (7); handler.handleVersion (version); return true; @@ -208,7 +208,7 @@ struct HostPacketDecoder name.name.length = (uint8) reader.readBits (7); for (uint32 i = 0; i < name.name.length; ++i) - name.name.name[i] = (uint8) reader.readBits (7); + name.name.data[i] = (uint8) reader.readBits (7); handler.handleName (name); return true; diff --git a/modules/juce_blocks_basics/topology/internal/juce_BlockImplementation.cpp b/modules/juce_blocks_basics/topology/internal/juce_BlockImplementation.cpp index d19026e47d..48ac8916a1 100644 --- a/modules/juce_blocks_basics/topology/internal/juce_BlockImplementation.cpp +++ b/modules/juce_blocks_basics/topology/internal/juce_BlockImplementation.cpp @@ -40,8 +40,8 @@ public: BlocksProtocol::BlockName blockName, bool isMasterBlock) : Block (juce::String ((const char*) serial.serial, sizeof (serial.serial)), - juce::String ((const char*) version.version, version.length), - juce::String ((const char*) blockName.name, blockName.length)), + juce::String ((const char*) version.data, version.length), + juce::String ((const char*) blockName.data, blockName.length)), modelData (serial), remoteHeap (modelData.programAndHeapSize), detector (&detectorToUse), @@ -81,8 +81,8 @@ public: void markReconnected (const DeviceInfo& deviceInfo) { - versionNumber = deviceInfo.version.asString(); - name = deviceInfo.name.asString(); + versionNumber = asString (deviceInfo.version); + name = asString (deviceInfo.name); isMaster = deviceInfo.isMaster; setProgram (nullptr); diff --git a/modules/juce_blocks_basics/topology/internal/juce_ConnectedDeviceGroup.cpp b/modules/juce_blocks_basics/topology/internal/juce_ConnectedDeviceGroup.cpp index 6ec226bd82..29261dd330 100644 --- a/modules/juce_blocks_basics/topology/internal/juce_ConnectedDeviceGroup.cpp +++ b/modules/juce_blocks_basics/topology/internal/juce_ConnectedDeviceGroup.cpp @@ -29,6 +29,55 @@ namespace { return static_cast (timestamp); } + + template + static int removeUnusedBlocksFromMap (std::map& mapToClean, const juce::Array& currentDevices) + { + int numRemoved = 0; + + for (auto iter = mapToClean.begin(); iter != mapToClean.end();) + { + bool found = false; + + for (auto& info : currentDevices) + { + if (info.uid == iter->first) + { + found = true; + break; + } + } + + if (found) + { + ++iter; + } + else + { + mapToClean.erase (iter++); + ++numRemoved; + } + } + + return numRemoved; + } + + /* Returns true new element added to map or value of existing key changed */ + template + static bool insertOrAssign (std::map& map, const Block::UID& key, const V& value) + { + const auto result = map.emplace (std::make_pair (key, value)); + + if (! result.second) + { + if (result.first->second == value) + return false; + + result.first->second = value; + } + + return true; + } } template @@ -44,14 +93,16 @@ struct ConnectedDeviceGroup : private juce::AsyncUpdater, this->handleIncomingMessage (data, dataSize); }; + if (auto midiDeviceConnection = static_cast (deviceConnection.get())) + depreciatedVersionReader = std::make_unique (*midiDeviceConnection); + startTimer (200); sendTopologyRequest(); } bool isStillConnected (const juce::StringArray& detectedDevices) const noexcept { - return detectedDevices.contains (deviceName) - && ! failedToGetTopology(); + return detectedDevices.contains (deviceName) && ! failedToGetTopology(); } int getIndexFromDeviceID (Block::UID uid) const noexcept @@ -63,15 +114,6 @@ struct ConnectedDeviceGroup : private juce::AsyncUpdater, return -1; } - const DeviceInfo* getDeviceInfoFromUID (Block::UID uid) const noexcept - { - for (auto& d : currentDeviceInfo) - if (d.uid == uid) - return &d; - - return nullptr; - } - const BlocksProtocol::DeviceStatus* getLastStatus (Block::UID deviceID) const noexcept { for (auto&& status : currentTopologyDevices) @@ -114,37 +156,53 @@ struct ConnectedDeviceGroup : private juce::AsyncUpdater, void endTopology() { - currentDeviceInfo = getArrayOfDeviceInfo (incomingTopologyDevices); - currentDeviceConnections = getArrayOfConnections (incomingTopologyConnections); - currentTopologyDevices = incomingTopologyDevices; lastTopologyReceiveTime = juce::Time::getCurrentTime(); - const int numRemoved = blockPings.removeIf ([this] (auto& ping) - { - for (auto& info : currentDeviceInfo) - if (info.uid == ping.blockUID) - return false; + if (incomingTopologyDevices.isEmpty()) + { + jassertfalse; + return; + } - LOG_CONNECTIVITY ("API Disconnected by topology update " << ping.blockUID); - return true; - }); + currentTopologyDevices.swapWith (incomingTopologyDevices); + currentTopologyConnections.swapWith (incomingTopologyConnections); - if (numRemoved > 0) - detector.handleTopologyChange(); + incomingTopologyDevices.clearQuick(); + incomingTopologyConnections.clearQuick(); + + refreshCurrentDeviceInfo(); + refreshCurrentDeviceConnections(); + + removeUnusedBlocksFromMap (versionNumbers, currentDeviceInfo); + removeUnusedBlocksFromMap (blockNames, currentDeviceInfo); + + removePingForDisconnectedBlocks(); + + detector.handleTopologyChange(); } void handleVersion (BlocksProtocol::DeviceVersion version) { - for (auto& d : currentDeviceInfo) - if (d.index == version.index && version.version.length > 1) - d.version = version.version; + const auto uid = getDeviceIDFromIndex (version.index); + + if (uid == Block::UID() || version.version.length <= 1) + return; + + setVersion (uid, version.version); } void handleName (BlocksProtocol::DeviceName name) { - for (auto& d : currentDeviceInfo) - if (d.index == name.index && name.name.length > 1) - d.name = name.name; + const auto uid = getDeviceIDFromIndex (name.index); + + if (uid == Block::UID() || name.name.length <= 1) + return; + + if (insertOrAssign (blockNames, uid, name.name)) + { + refreshCurrentDeviceInfo(); + detector.handleTopologyChange(); + } } void handleControlButtonUpDown (BlocksProtocol::TopologyIndex deviceIndex, uint32 timestamp, @@ -299,14 +357,19 @@ private: std::unique_ptr deviceConnection; juce::Array incomingTopologyDevices, currentTopologyDevices; - juce::Array incomingTopologyConnections; + juce::Array incomingTopologyConnections, currentTopologyConnections; juce::CriticalSection incomingPacketLock; juce::Array incomingPackets; + std::map versionNumbers; + std::map blockNames; + + std::unique_ptr depreciatedVersionReader; + struct TouchStart { float x, y; }; - TouchList touchStartPositions; - + TouchList touchStartPositions; + Block::UID masterBlock = 0; //============================================================================== @@ -338,11 +401,12 @@ private: checkApiTimeouts (now); startApiModeOnConnectedBlocks(); + requestMasterBlockVersionIfNeeded(); } bool failedToGetTopology() const noexcept { - return numTopologyRequestsSent > 4 && lastTopologyReceiveTime == juce::Time(); + return numTopologyRequestsSent >= 4 && lastTopologyReceiveTime == juce::Time(); } bool sendCommandMessage (BlocksProtocol::TopologyIndex deviceIndex, uint32 commandID) const @@ -354,11 +418,36 @@ private: return sendMessageToDevice (p); } + //============================================================================== + void requestMasterBlockVersionIfNeeded() + { + if (depreciatedVersionReader == nullptr) + return; + + const auto masterVersion = depreciatedVersionReader->getVersionNumber(); + + if (masterVersion.isNotEmpty()) + setVersion (masterBlock, masterVersion); + } + + void setVersion (const Block::UID uid, const BlocksProtocol::VersionNumber versionNumber) + { + if (uid == masterBlock) + depreciatedVersionReader.reset(); + + if (insertOrAssign (versionNumbers, uid, versionNumber)) + { + refreshCurrentDeviceInfo(); + detector.handleTopologyChange(); + } + } + //============================================================================== struct BlockPingTime { Block::UID blockUID; juce::Time lastPing; + juce::Time connected; }; juce::Array blockPings; @@ -375,7 +464,7 @@ private: else { LOG_CONNECTIVITY ("API Connected " << uid); - blockPings.add ({ uid, now }); + blockPings.add ({ uid, now, now }); detector.handleTopologyChange(); } } @@ -408,6 +497,8 @@ private: LOG_CONNECTIVITY ("API Disconnected " << uid << ", re-probing topology"); currentDeviceInfo.clearQuick(); blockPings.clearQuick(); + blockNames.clear(); + versionNumbers.clear(); detector.handleTopologyChange(); scheduleNewTopologyRequest(); } @@ -432,6 +523,22 @@ private: } } + /* Returns true is ping was removed */ + void removePingForDisconnectedBlocks() + { + const auto removed = [this] (auto& ping) + { + for (auto& info : currentDeviceInfo) + if (info.uid == ping.blockUID) + return false; + + LOG_CONNECTIVITY ("API Disconnected by topology update " << ping.blockUID); + return true; + }; + + blockPings.removeIf (removed); + } + void startApiModeOnConnectedBlocks() { for (auto& info : currentDeviceInfo) @@ -445,6 +552,21 @@ private: } } + //============================================================================== + void checkVersionNumberTimeouts() + { + for (const auto& device : currentDeviceInfo) + { + const auto version = versionNumbers.find (device.uid); + + if (version == versionNumbers.end()) + { + auto* ping = getPing (device.uid); + + } + } + } + //============================================================================== Block::UID getDeviceIDFromIndex (BlocksProtocol::TopologyIndex index) const noexcept { @@ -463,38 +585,7 @@ private: if (uid == Block::UID()) scheduleNewTopologyRequest(); - return uid; - } - - juce::Array getArrayOfConnections (const juce::Array& connections) - { - juce::Array result; - - for (auto&& c : connections) - { - BlockDeviceConnection dc; - dc.device1 = getDeviceIDFromIndex (c.device1); - dc.device2 = getDeviceIDFromIndex (c.device2); - - if (dc.device1 <= 0 || dc.device2 <= 0) - continue; - - dc.connectionPortOnDevice1 = convertConnectionPort (dc.device1, c.port1); - dc.connectionPortOnDevice2 = convertConnectionPort (dc.device2, c.port2); - - result.add (dc); - } - - return result; - } - - 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 }; + return uid; } //============================================================================== @@ -534,30 +625,79 @@ private: } //============================================================================== - juce::Array getArrayOfDeviceInfo (const juce::Array& devices) + BlocksProtocol::VersionNumber getVersionNumber (Block::UID uid) { - juce::Array result; + const auto version = versionNumbers.find (uid); + return version == versionNumbers.end() ? BlocksProtocol::VersionNumber() : version->second; + } + + BlocksProtocol::BlockName getName (Block::UID uid) + { + const auto name = blockNames.find (uid); + return name == blockNames.end() ? BlocksProtocol::BlockName() : name->second; + } - for (auto& device : devices) + //============================================================================== + const DeviceInfo* getDeviceInfoFromUID (Block::UID uid) const noexcept + { + for (auto& d : currentDeviceInfo) + if (d.uid == uid) + return &d; + + return nullptr; + } + + 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 }; + } + + //============================================================================== + void refreshCurrentDeviceInfo() + { + currentDeviceInfo.clearQuick(); + + for (auto& device : currentTopologyDevices) { - BlocksProtocol::VersionNumber version; - BlocksProtocol::BlockName name; - - const auto uid = getBlockUIDFromSerialNumber (device.serialNumber); - - // For backwards compatibility we assume the first device we see in a group is the master and won't change - if (masterBlock == 0) + const auto uid = getBlockUIDFromSerialNumber (device.serialNumber); + const auto version = getVersionNumber (uid); + const auto name = getName (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; - result.add ({ uid, - device.index, - device.serialNumber, - version, - name, - masterBlock == uid }); + currentDeviceInfo.add ({ uid, + device.index, + device.serialNumber, + version, + name, + masterBlock == uid }); } + } - return result; + void refreshCurrentDeviceConnections() + { + currentDeviceConnections.clearQuick(); + + for (auto&& c : currentTopologyConnections) + { + BlockDeviceConnection dc; + dc.device1 = getDeviceIDFromIndex (c.device1); + dc.device2 = getDeviceIDFromIndex (c.device2); + + if (dc.device1 <= 0 || dc.device2 <= 0) + continue; + + dc.connectionPortOnDevice1 = convertConnectionPort (dc.device1, c.port1); + dc.connectionPortOnDevice2 = convertConnectionPort (dc.device2, c.port2); + + currentDeviceConnections.add (dc); + } } JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ConnectedDeviceGroup) diff --git a/modules/juce_blocks_basics/topology/internal/juce_DepreciatedVersionReader.cpp b/modules/juce_blocks_basics/topology/internal/juce_DepreciatedVersionReader.cpp new file mode 100644 index 0000000000..37b1a272bd --- /dev/null +++ b/modules/juce_blocks_basics/topology/internal/juce_DepreciatedVersionReader.cpp @@ -0,0 +1,128 @@ +/* + ============================================================================== + + 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 +{ + +/** + Firmware below 0.2.5 does not report its version over the Blocks API. + This class can make requests and process responses to retreive the master Block version. +*/ +class DepreciatedVersionReader : private MIDIDeviceConnection::Listener, + private Timer +{ +public: + //============================================================================== + DepreciatedVersionReader (MIDIDeviceConnection& deviceConnectionToUse) + : deviceConnection (deviceConnectionToUse) + { + deviceConnection.addListener (this); + startTimer (10); + } + + //============================================================================== + ~DepreciatedVersionReader() + { + deviceConnection.removeListener (this); + } + + //============================================================================== + BlocksProtocol::VersionNumber getVersionNumber() + { + if (! allRequestsComplete()) + return {}; + + auto highestVersion = result[0]; + + for (size_t i = 1; i < numFirmwareApps; ++i) + { + const BlocksVersion highest { asString (highestVersion) }; + const BlocksVersion test { asString ( result[i]) }; + + if (highest < test) + highestVersion = result[i]; + } + + return highestVersion; + } + +private: + //============================================================================== + static constexpr size_t numFirmwareApps = 3; + BlocksProtocol::VersionNumber result[numFirmwareApps]; + MIDIDeviceConnection& deviceConnection; + size_t currentRequest = 0; + + //============================================================================== + bool allRequestsComplete() const { return currentRequest >= numFirmwareApps; } + + //============================================================================== + void makeNextRequest() + { + static constexpr size_t requestSize { 8 }; + static constexpr uint8 requests[][requestSize] = {{ 0xf0, 0x00, 0x21, 0x10, 0x47, 0x03, 0x00, 0xf7 }, // Main App + { 0xf0, 0x00, 0x21, 0x10, 0x47, 0x03, 0x01, 0xf7 }, // Stm32 + { 0xf0, 0x00, 0x21, 0x10, 0x47, 0x03, 0x03, 0xf7 }}; // Bootloader + + deviceConnection.sendMessageToDevice (&requests[currentRequest][0], requestSize); + } + + //============================================================================== + void processVersionMessage (const uint8* data, const size_t size) + { + if (currentRequest >= numFirmwareApps || size < 1 || size - 1 > VersionNumber::maxLength) + return; + + result[currentRequest].length = uint8 (size - 1); + memcpy (result[currentRequest].data, data, result[currentRequest].length); + + ++currentRequest; + + allRequestsComplete() ? stopTimer() : startTimer (10); + } + + //============================================================================== + void handleIncomingMidiMessage (const MidiMessage& message) override + { + const uint8 roliVersionHeader[] = { 0xf0, 0x00, 0x21, 0x10, 0x47, 0x03 }; + const auto headerSize = sizeof (roliVersionHeader); + const auto data = message.getRawData(); + const auto size = size_t (message.getRawDataSize()); + + if (memcmp (data, roliVersionHeader, headerSize) == 0) + processVersionMessage (data + headerSize, size - headerSize); + } + + void connectionBeingDeleted (const MIDIDeviceConnection&) override {} + + //============================================================================== + void timerCallback() override + { + startTimer (200); + makeNextRequest(); + } + + //============================================================================== + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DepreciatedVersionReader) +}; + +} // namespace juce diff --git a/modules/juce_blocks_basics/topology/internal/juce_Detector.cpp b/modules/juce_blocks_basics/topology/internal/juce_Detector.cpp index 699fba2ce0..1187ce32a0 100644 --- a/modules/juce_blocks_basics/topology/internal/juce_Detector.cpp +++ b/modules/juce_blocks_basics/topology/internal/juce_Detector.cpp @@ -36,20 +36,20 @@ namespace static bool versionNumberChanged (const DeviceInfo& device, juce::String version) noexcept { - auto deviceVersion = device.version.asString(); + auto deviceVersion = asString (device.version); return deviceVersion != version && deviceVersion.isNotEmpty(); } static void setVersionNumberForBlock (const DeviceInfo& deviceInfo, Block& block) noexcept { jassert (deviceInfo.uid == block.uid); - block.versionNumber = deviceInfo.version.asString(); + block.versionNumber = asString (deviceInfo.version); } static void setNameForBlock (const DeviceInfo& deviceInfo, Block& block) { jassert (deviceInfo.uid == block.uid); - block.name = deviceInfo.name.asString(); + block.name = asString (deviceInfo.name); } } @@ -465,7 +465,7 @@ private: if (versionNumberChanged (updatedInfo, blockToUpdate->versionNumber)) setVersionNumberForBlock (updatedInfo, *blockToUpdate); - if (updatedInfo.name.isValid()) + if (updatedInfo.name.isNotEmpty()) setNameForBlock (updatedInfo, *blockToUpdate); if (updatedInfo.isMaster != blockToUpdate->isMasterBlock()) @@ -692,6 +692,7 @@ private: } } + //============================================================================== JUCE_DECLARE_WEAK_REFERENCEABLE (Detector) JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Detector) }; diff --git a/modules/juce_blocks_basics/topology/internal/juce_MidiDeviceConnection.cpp b/modules/juce_blocks_basics/topology/internal/juce_MidiDeviceConnection.cpp index b27b3474ac..30beb56aa9 100644 --- a/modules/juce_blocks_basics/topology/internal/juce_MidiDeviceConnection.cpp +++ b/modules/juce_blocks_basics/topology/internal/juce_MidiDeviceConnection.cpp @@ -76,7 +76,7 @@ struct MIDIDeviceConnection : public PhysicalTopologySource::DeviceConnection, JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED // This method must only be called from the message thread! jassert (dataSize > sizeof (BlocksProtocol::roliSysexHeader) + 2); - jassert (memcmp (data, BlocksProtocol::roliSysexHeader, sizeof (BlocksProtocol::roliSysexHeader)) == 0); + jassert (memcmp (data, BlocksProtocol::roliSysexHeader, sizeof (BlocksProtocol::roliSysexHeader) - 1) == 0); jassert (static_cast (data)[dataSize - 1] == 0xf7); if (midiOutput != nullptr) diff --git a/modules/juce_blocks_basics/topology/juce_PhysicalTopologySource.cpp b/modules/juce_blocks_basics/topology/juce_PhysicalTopologySource.cpp index 6184f2d4a9..bc73ce3da0 100644 --- a/modules/juce_blocks_basics/topology/juce_PhysicalTopologySource.cpp +++ b/modules/juce_blocks_basics/topology/juce_PhysicalTopologySource.cpp @@ -25,6 +25,7 @@ #define LOG_BLOCKS_CONNECTIVITY 0 #define LOG_BLOCKS_PINGS 0 #define DUMP_BANDWIDTH_STATS 0 +#define DUMP_TOPOLOGY 0 #define TOPOLOGY_LOG(text) \ JUCE_BLOCK_WITH_FORCED_SEMICOLON (juce::String buf ("Topology Src: "); \ @@ -46,9 +47,21 @@ #include "internal/juce_BandwidthStatsLogger.cpp" #endif +namespace +{ + /** Helper function to create juce::String from BlockStringData */ + template + juce::String asString (juce::BlocksProtocol::BlockStringData blockString) + { + return { reinterpret_cast (blockString.data), + juce::jmin (sizeof (blockString.data), static_cast (blockString.length))}; + } +} + #include "internal/juce_MidiDeviceConnection.cpp" #include "internal/juce_MIDIDeviceDetector.cpp" #include "internal/juce_DeviceInfo.cpp" +#include "internal/juce_DepreciatedVersionReader.cpp" #include "internal/juce_ConnectedDeviceGroup.cpp" #include "internal/juce_BlockImplementation.cpp" #include "internal/juce_Detector.cpp"