Experimental firmware update features
|
|
@ -97,6 +97,10 @@
|
|||
},
|
||||
"shouldTranslate" : false
|
||||
},
|
||||
" Send Reboot into DFU" : {
|
||||
"comment" : "A button that initiates",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
": %@" : {
|
||||
"localizations" : {
|
||||
"it" : {
|
||||
|
|
@ -520,6 +524,16 @@
|
|||
},
|
||||
"shouldTranslate" : false
|
||||
},
|
||||
"%@ %lf" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "new",
|
||||
"value" : "%1$@ %2$lf"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"%@ %lld" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
|
|
@ -1452,6 +1466,26 @@
|
|||
"• %@" : {
|
||||
"shouldTranslate" : false
|
||||
},
|
||||
"• Navigate all the way back to **Locations** in the file picker." : {
|
||||
"comment" : "A step in the firmware export process, describing the user's action to navigate back to the \"Locations\" folder in the file picker.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"• Select your USB device and tap **Save**." : {
|
||||
"comment" : "A step in the firmware export process, instructing the user to save the file to their USB.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"• Tap the **Save Firmware to USB** button below." : {
|
||||
"comment" : "A step in the firmware update process, describing how to save the firmware to a USB device.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"• The filename will be a random string ending in `.uf2` to prevent iOS caching." : {
|
||||
"comment" : "A note explaining that the firmware filename will be a random string with a `.uf2` extension to prevent iOS caching.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"• You may see an error saying the file could not be saved. This is normal, as the device disconnects immediately after updating." : {
|
||||
"comment" : "A note explaining that a file could not be saved, which is normal as the device disconnects after updating.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"< 1%" : {
|
||||
"localizations" : {
|
||||
"it" : {
|
||||
|
|
@ -3440,6 +3474,9 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Alpha" : {
|
||||
|
||||
},
|
||||
"Alt" : {
|
||||
"localizations" : {
|
||||
|
|
@ -4210,6 +4247,9 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Architecture" : {
|
||||
|
||||
},
|
||||
"Are you sure you want to delete this message?" : {
|
||||
"localizations" : {
|
||||
|
|
@ -4349,6 +4389,10 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"Attributes" : {
|
||||
"comment" : "A section header that lists the attributes of a managed object.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Australia / New Zealand" : {
|
||||
"localizations" : {
|
||||
"it" : {
|
||||
|
|
@ -5136,6 +5180,10 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"Begin Update" : {
|
||||
"comment" : "A button that initiates the process of flashing new firmware to a device.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Biking" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
|
|
@ -5176,6 +5224,14 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"BIN" : {
|
||||
"comment" : "The file format for the Nordic Semiconductor nRF52832 series chips.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Binary Data (%lld bytes)" : {
|
||||
"comment" : "A text label displaying the size of a binary data field. The argument is the size of the binary data in bytes.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"BLE" : {
|
||||
"localizations" : {
|
||||
"it" : {
|
||||
|
|
@ -7414,6 +7470,10 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"Check For Updates" : {
|
||||
"comment" : "A button label that triggers a refresh of the firmware API data.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"CHG" : {
|
||||
"localizations" : {
|
||||
"it" : {
|
||||
|
|
@ -9261,8 +9321,12 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Current Firmware Version" : {
|
||||
|
||||
},
|
||||
"Current Firmware Version: %@" : {
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
|
|
@ -9303,6 +9367,7 @@
|
|||
}
|
||||
},
|
||||
"Current Firmware Version: %@, Latest Firmware Version: %@" : {
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
|
|
@ -9410,6 +9475,10 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"Database Browser" : {
|
||||
"comment" : "The title of the main view, which lists all entities in the database.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Date" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
|
|
@ -10120,6 +10189,10 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"Details" : {
|
||||
"comment" : "The title of the view that lists detailed information about a single database entity.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Details..." : {
|
||||
"localizations" : {
|
||||
"sr" : {
|
||||
|
|
@ -11140,6 +11213,10 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"DFU Firmware Update" : {
|
||||
"comment" : "The title of the screen that appears when flashing new firmware to a device.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Dilution of precision (DOP) PDOP used by default" : {
|
||||
"localizations" : {
|
||||
"it" : {
|
||||
|
|
@ -12189,8 +12266,16 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Download" : {
|
||||
"comment" : "A button label that downloads firmware.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Downloaded" : {
|
||||
|
||||
},
|
||||
"Drag & Drop Firmware Update" : {
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"it" : {
|
||||
"stringUnit" : {
|
||||
|
|
@ -12225,6 +12310,7 @@
|
|||
}
|
||||
},
|
||||
"Drag & Drop Firmware Update Documentation" : {
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"it" : {
|
||||
"stringUnit" : {
|
||||
|
|
@ -12259,6 +12345,7 @@
|
|||
}
|
||||
},
|
||||
"Drag & Drop is the recommended way to update firmware for NRF devices. If your iPhone or iPad is USB-C it will work with your regular USB-C charging cable, for lightning devices you need the Apple Lightning to USB camera adaptor." : {
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"it" : {
|
||||
"stringUnit" : {
|
||||
|
|
@ -13165,6 +13252,7 @@
|
|||
}
|
||||
},
|
||||
"Enter DFU Mode" : {
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
|
|
@ -13216,6 +13304,14 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"Entities" : {
|
||||
"comment" : "The header text for the \"Entities\" section in the Core Data Browser.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Entity Name" : {
|
||||
"comment" : "A label describing the name of the entity in the database.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"environment" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
|
|
@ -14268,6 +14364,10 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"False" : {
|
||||
"comment" : "A label for a boolean value of false.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Favorite" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
|
|
@ -14768,6 +14868,14 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"Firmware installation is not supported for this device architecture." : {
|
||||
"comment" : "An alert message displayed when attempting to install firmware on a device that is not supported.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Firmware Releases" : {
|
||||
"comment" : "A section header that lists available firmware releases.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Firmware update docs" : {
|
||||
"localizations" : {
|
||||
"it" : {
|
||||
|
|
@ -15474,6 +15582,10 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"For security reasons, iOS cannot write directly to external USB devices. You must save the file manually." : {
|
||||
"comment" : "An explanatory text about the need to manually save the firmware file.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Forty Eight Hours" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
|
|
@ -16075,6 +16187,7 @@
|
|||
}
|
||||
},
|
||||
"Get NRF DFU from the App Store" : {
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"it" : {
|
||||
"stringUnit" : {
|
||||
|
|
@ -16125,6 +16238,7 @@
|
|||
}
|
||||
},
|
||||
"Get the latest stable firmware" : {
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"it" : {
|
||||
"stringUnit" : {
|
||||
|
|
@ -17507,6 +17621,7 @@
|
|||
}
|
||||
},
|
||||
"How to update Firmware" : {
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
|
|
@ -17746,6 +17861,10 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"I Know What I'm Doing" : {
|
||||
"comment" : "A button that dismisses the update warning alert when pressed.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"IAQ" : {
|
||||
"localizations" : {
|
||||
"it" : {
|
||||
|
|
@ -17898,6 +18017,10 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"If connected, use the button below to reboot into DFU. Otherwise, press your device's reset button twice rapidly." : {
|
||||
"comment" : "A description of what to do if the device is not connected to the computer.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"If DOP is set, use HDOP / VDOP values instead of PDOP" : {
|
||||
"localizations" : {
|
||||
"it" : {
|
||||
|
|
@ -17967,6 +18090,7 @@
|
|||
}
|
||||
},
|
||||
"If it is hard to access your device's reset button enter DFU mode here." : {
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"it" : {
|
||||
"stringUnit" : {
|
||||
|
|
@ -18254,6 +18378,10 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"Important Notes" : {
|
||||
"comment" : "A section header that explains important notes about the view.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"In addition to Config, Keys and BLE bonds will be wiped" : {
|
||||
"localizations" : {
|
||||
"sr" : {
|
||||
|
|
@ -18572,6 +18700,10 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"Install" : {
|
||||
"comment" : "A button label that says \"Install\".",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Invalid file content. Please check the file format." : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
|
|
@ -18932,6 +19064,14 @@
|
|||
},
|
||||
"Last seen device: %@" : {
|
||||
|
||||
},
|
||||
"Last Updated: %@" : {
|
||||
"comment" : "A footer displaying the last time the firmware list was updated.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Last Updated: Never" : {
|
||||
"comment" : "A label displayed below a list of firmware releases, indicating that the list has never been updated.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Latitude" : {
|
||||
"localizations" : {
|
||||
|
|
@ -19303,6 +19443,10 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"Loading" : {
|
||||
"comment" : "A label displayed while waiting for data to load.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Loading Logs. . ." : {
|
||||
"localizations" : {
|
||||
"it" : {
|
||||
|
|
@ -21370,6 +21514,10 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"Metadata" : {
|
||||
"comment" : "A section title for the metadata section of the entity detail view.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Metric" : {
|
||||
"localizations" : {
|
||||
"it" : {
|
||||
|
|
@ -22749,6 +22897,7 @@
|
|||
}
|
||||
},
|
||||
"Newer firmware is available" : {
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
|
|
@ -22822,6 +22971,10 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"nil" : {
|
||||
"comment" : "A placeholder text that indicates a relationship value is \"nil\".",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"NMEA Positions" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
|
|
@ -23117,6 +23270,10 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"No Entities Found" : {
|
||||
"comment" : "A message displayed when there are no entities in the Core Data store.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"No Environment Metrics" : {
|
||||
"localizations" : {
|
||||
"it" : {
|
||||
|
|
@ -23166,6 +23323,9 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"No firmware has been downloaded for this device." : {
|
||||
|
||||
},
|
||||
"No Interface" : {
|
||||
"localizations" : {
|
||||
|
|
@ -23402,6 +23562,10 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"No records found for %@" : {
|
||||
"comment" : "A view to show when a list is empty, with an appropriate message and icon. The argument is a title for the view, the second is a system image name for the icon, and the third is a",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"No Response" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
|
|
@ -23954,6 +24118,10 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"Nordic DFU Update" : {
|
||||
"comment" : "The title of the view.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Not a valid route file" : {
|
||||
"localizations" : {
|
||||
"it" : {
|
||||
|
|
@ -24046,6 +24214,10 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"Not Now" : {
|
||||
"comment" : "The label of a button that dismisses a view without taking any action.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Not Present" : {
|
||||
"localizations" : {
|
||||
"fr" : {
|
||||
|
|
@ -24282,6 +24454,10 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"Object ID" : {
|
||||
"comment" : "A label for the \"Object ID\" field in the entity details view.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Ok" : {
|
||||
"localizations" : {
|
||||
"sr" : {
|
||||
|
|
@ -25055,6 +25231,7 @@
|
|||
}
|
||||
},
|
||||
"OTA Updates are not supported on this NRF Device." : {
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"it" : {
|
||||
"stringUnit" : {
|
||||
|
|
@ -25089,6 +25266,7 @@
|
|||
}
|
||||
},
|
||||
"OTA Updates are not supported on your platform." : {
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"it" : {
|
||||
"stringUnit" : {
|
||||
|
|
@ -26109,6 +26287,13 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Place your device in DFU mode and connect it via USB." : {
|
||||
"comment" : "A step in the firmware update process, instructing the user to connect their device in DFU mode.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Platform IO" : {
|
||||
|
||||
},
|
||||
"Please be advised that because the map report is not encrypted, your data may be stored and displayed permanently by third parties. Meshtastic does not assume responsibility for any such storage, display or disclosure of this data." : {
|
||||
"localizations" : {
|
||||
|
|
@ -26131,6 +26316,9 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Please connect to a device to see firmware updates." : {
|
||||
|
||||
},
|
||||
"Please connect to a radio to configure settings." : {
|
||||
"localizations" : {
|
||||
|
|
@ -26172,6 +26360,10 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"Please do not leave this screen until this process is complete." : {
|
||||
"comment" : "A message displayed in the bottom-right corner of the screen, instructing the user to wait until the DFU process is complete.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Please set a region" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
|
|
@ -28800,6 +28992,10 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"Relationships" : {
|
||||
"comment" : "A section header that lists relationships for an entity.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Relayed by %d %@" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
|
|
@ -30986,6 +31182,10 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"Save Firmware to USB" : {
|
||||
"comment" : "A button that allows the user to save the U2F firmware to a USB drive.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Save User Config to %@?" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
|
|
@ -34834,6 +35034,9 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Stable" : {
|
||||
|
||||
},
|
||||
"Standard" : {
|
||||
"localizations" : {
|
||||
|
|
@ -35047,6 +35250,14 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"Step 1: Connect Device" : {
|
||||
"comment" : "A step label for connecting a device to flash a firmware update.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Step 2: Save the File" : {
|
||||
"comment" : "A step in the firmware update process, describing how to save the firmware file.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Store & Forward" : {
|
||||
"localizations" : {
|
||||
"it" : {
|
||||
|
|
@ -35381,6 +35592,10 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"Table Empty" : {
|
||||
"comment" : "A message indicating that a table is empty.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Taiwan" : {
|
||||
"localizations" : {
|
||||
"ja" : {
|
||||
|
|
@ -36028,6 +36243,14 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"Test Devices API Refresh" : {
|
||||
"comment" : "A button that refreshes the list of devices from the API.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Test Firmware API Refresh" : {
|
||||
"comment" : "A button label that refreshes the list of available firmware versions from the API.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Text Message" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
|
|
@ -38389,6 +38612,10 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"True" : {
|
||||
"comment" : "A label indicating a boolean value of true.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Try Again" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
|
|
@ -38771,6 +38998,14 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"UF2" : {
|
||||
"comment" : "A label indicating that the firmware is in UF2 format.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"UF2 Firmware Update" : {
|
||||
"comment" : "The title of the view.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Ukraine 433MHz" : {
|
||||
"localizations" : {
|
||||
"it" : {
|
||||
|
|
@ -39247,6 +39482,10 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"Unsupported Installation" : {
|
||||
"comment" : "The title of an alert that appears when attempting to install firmware on a device that is not supported.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Up" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
|
|
@ -39373,6 +39612,10 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"Update Warning" : {
|
||||
"comment" : "The title of an alert that warns the user about flashing new firmware.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Update Your Firmware" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
|
|
@ -39499,6 +39742,10 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"Updating now..." : {
|
||||
"comment" : "A message displayed while the firmware list is being updated.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Uplink Enabled" : {
|
||||
"localizations" : {
|
||||
"it" : {
|
||||
|
|
@ -41928,6 +42175,7 @@
|
|||
}
|
||||
},
|
||||
"You can also update your Meshtastic device over bluetooth using the Nordic DFU app." : {
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"it" : {
|
||||
"stringUnit" : {
|
||||
|
|
@ -42060,6 +42308,7 @@
|
|||
}
|
||||
},
|
||||
"Your Firmware is up to date" : {
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
|
|
@ -42303,6 +42552,10 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"ZIP" : {
|
||||
"comment" : "A label indicating that a firmware file is in ZIP format.",
|
||||
"isCommentAutoGenerated" : true
|
||||
}
|
||||
},
|
||||
"version" : "1.1"
|
||||
|
|
|
|||
|
|
@ -17,6 +17,12 @@
|
|||
10D109F42E2047D600536CE6 /* DatadogTrace in Frameworks */ = {isa = PBXBuildFile; productRef = 10D109F32E2047D600536CE6 /* DatadogTrace */; };
|
||||
230BC3972E31071E0046BF2A /* AccessoryManager+Discovery.swift in Sources */ = {isa = PBXBuildFile; fileRef = 230BC3962E31071E0046BF2A /* AccessoryManager+Discovery.swift */; };
|
||||
231251382E3BC96400E6ED07 /* BLEAuthorizationHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 231251372E3BC96400E6ED07 /* BLEAuthorizationHelper.swift */; };
|
||||
23148E302EE1CCE500F0DB2C /* MeshtasticAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23148E2F2EE1CCE500F0DB2C /* MeshtasticAPI.swift */; };
|
||||
2315D19D2EECB3DA00E0FAE7 /* UTI+UF2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2315D19C2EECB3D400E0FAE7 /* UTI+UF2.swift */; };
|
||||
2315D1A02EECB44800E0FAE7 /* UF2MassStorageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2315D19F2EECB44800E0FAE7 /* UF2MassStorageView.swift */; };
|
||||
2315D1A22EECD2CB00E0FAE7 /* APIStructs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2315D1A12EECD2BE00E0FAE7 /* APIStructs.swift */; };
|
||||
2315D1A52EED94E800E0FAE7 /* FirmwareFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2315D1A42EED94E800E0FAE7 /* FirmwareFile.swift */; };
|
||||
2315D1A82EEF2ED400E0FAE7 /* SwiftDraw in Frameworks */ = {isa = PBXBuildFile; productRef = 2315D1A72EEF2ED400E0FAE7 /* SwiftDraw */; };
|
||||
231A53782E69ADB900216B99 /* NodeFilterParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 231A53772E69ADB900216B99 /* NodeFilterParameters.swift */; };
|
||||
231B3F212D087A4C0069A07D /* MetricTableColumn.swift in Sources */ = {isa = PBXBuildFile; fileRef = 231B3F202D087A4C0069A07D /* MetricTableColumn.swift */; };
|
||||
231B3F222D087A4C0069A07D /* MetricsColumnList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 231B3F1F2D087A4C0069A07D /* MetricsColumnList.swift */; };
|
||||
|
|
@ -53,12 +59,23 @@
|
|||
237AEB972E1FE627003B7CE3 /* BLETransport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 237AEB962E1FE627003B7CE3 /* BLETransport.swift */; };
|
||||
237AEB992E20098B003B7CE3 /* BLEConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 237AEB982E20098B003B7CE3 /* BLEConnection.swift */; };
|
||||
237B46962DC8F1C100B22D99 /* RateLimitedButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 237B46952DC8F1C100B22D99 /* RateLimitedButton.swift */; };
|
||||
2388EC382EDF88E900F6F982 /* NordicDFU in Frameworks */ = {isa = PBXBuildFile; productRef = 2388EC372EDF88E900F6F982 /* NordicDFU */; };
|
||||
2388EC3A2EDF8A1400F6F982 /* DFUModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2388EC392EDF8A1400F6F982 /* DFUModel.swift */; };
|
||||
23A1AFB72E42BD2500E46C96 /* RXTXIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23A1AFB62E42BD2500E46C96 /* RXTXIndicatorView.swift */; };
|
||||
23AB0E662EE35E0200AFA09D /* Image.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23AB0E652EE35E0200AFA09D /* Image.swift */; };
|
||||
23AD54692E2A6EAA0046E9AB /* AccessoryManager+FromRadio.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23AD54682E2A6EAA0046E9AB /* AccessoryManager+FromRadio.swift */; };
|
||||
23AD546B2E2AA5A80046E9AB /* AccessoryManager+ToRadio.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23AD546A2E2AA5A80046E9AB /* AccessoryManager+ToRadio.swift */; };
|
||||
23AD546D2E2AE9630046E9AB /* AccessoryManager+MQTT.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23AD546C2E2AE9630046E9AB /* AccessoryManager+MQTT.swift */; };
|
||||
23C2BD292EE87D0300F6A997 /* CoreDataBrowser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23C2BD282EE87D0300F6A997 /* CoreDataBrowser.swift */; };
|
||||
23C2BD2B2EE8993800F6A997 /* DeviceHardwareImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23C2BD2A2EE8993800F6A997 /* DeviceHardwareImage.swift */; };
|
||||
23C2BE252EE9A8E100F6A997 /* SupportedHardwareBadge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23C2BE242EE9A8E100F6A997 /* SupportedHardwareBadge.swift */; };
|
||||
23C2BE272EE9F4BD00F6A997 /* DeviceHardwareEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23C2BE262EE9F4BD00F6A997 /* DeviceHardwareEntity.swift */; };
|
||||
23C2BE2A2EEAF96A00F6A997 /* FirmwareViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23C2BE292EEAF96A00F6A997 /* FirmwareViewModel.swift */; };
|
||||
23C2BE312EEB823900F6A997 /* NRFDFUSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23C2BE302EEB823900F6A997 /* NRFDFUSheet.swift */; };
|
||||
23C2BE342EEC3F9600F6A997 /* ESP32DFUSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23C2BE332EEC3F9600F6A997 /* ESP32DFUSheet.swift */; };
|
||||
23D316932E5618D2002FA4FB /* AsyncGate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23D316922E5618D2002FA4FB /* AsyncGate.swift */; };
|
||||
23D9D9392E50DA97005D1C18 /* ResettableTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23D9D9382E50DA97005D1C18 /* ResettableTimer.swift */; };
|
||||
23DC50BB2EE76D9C0023838A /* URL+fetch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23DC50BA2EE76D9C0023838A /* URL+fetch.swift */; };
|
||||
23E23F922E392C2B00919073 /* LogRecord+StringRepresentation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23E23F912E392C2B00919073 /* LogRecord+StringRepresentation.swift */; };
|
||||
23F061B32E7B056600A1E2EA /* Logger+DataDog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23F061B22E7B056600A1E2EA /* Logger+DataDog.swift */; };
|
||||
23F488122E32980B002C776F /* AccessoryManager+Position.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23F488112E32980B002C776F /* AccessoryManager+Position.swift */; };
|
||||
|
|
@ -147,7 +164,6 @@
|
|||
DD2553572855B02500E55709 /* LoRaConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD2553562855B02500E55709 /* LoRaConfig.swift */; };
|
||||
DD2553592855B52700E55709 /* PositionConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD2553582855B52700E55709 /* PositionConfig.swift */; };
|
||||
DD268D8E2BCC90E2008073AE /* RouteEnums.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD268D8D2BCC90E2008073AE /* RouteEnums.swift */; };
|
||||
DD33DB622B3D27C7003E1EA0 /* FirmwareApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD33DB612B3D27C7003E1EA0 /* FirmwareApi.swift */; };
|
||||
DD3501892852FC3B000FC853 /* Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD3501882852FC3B000FC853 /* Settings.swift */; };
|
||||
DD354FD92BD96A0B0061A25F /* IAQScale.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD354FD82BD96A0B0061A25F /* IAQScale.swift */; };
|
||||
DD3619152B1EF9F900C41C8C /* LocationsHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD3619142B1EF9F900C41C8C /* LocationsHandler.swift */; };
|
||||
|
|
@ -315,6 +331,15 @@
|
|||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
236CA24D2EE3073100A47F96 /* CopyFiles */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 7;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
DDDE5A0829AF163F00490C6C /* Embed Foundation Extensions */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
|
|
@ -333,6 +358,11 @@
|
|||
108FFECC2DD4005600BFAA81 /* NodeInfoEntityToNodeInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeInfoEntityToNodeInfo.swift; sourceTree = "<group>"; };
|
||||
230BC3962E31071E0046BF2A /* AccessoryManager+Discovery.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AccessoryManager+Discovery.swift"; sourceTree = "<group>"; };
|
||||
231251372E3BC96400E6ED07 /* BLEAuthorizationHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BLEAuthorizationHelper.swift; sourceTree = "<group>"; };
|
||||
23148E2F2EE1CCE500F0DB2C /* MeshtasticAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshtasticAPI.swift; sourceTree = "<group>"; };
|
||||
2315D19C2EECB3D400E0FAE7 /* UTI+UF2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UTI+UF2.swift"; sourceTree = "<group>"; };
|
||||
2315D19F2EECB44800E0FAE7 /* UF2MassStorageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UF2MassStorageView.swift; sourceTree = "<group>"; };
|
||||
2315D1A12EECD2BE00E0FAE7 /* APIStructs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIStructs.swift; sourceTree = "<group>"; };
|
||||
2315D1A42EED94E800E0FAE7 /* FirmwareFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirmwareFile.swift; sourceTree = "<group>"; };
|
||||
231A53772E69ADB900216B99 /* NodeFilterParameters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeFilterParameters.swift; sourceTree = "<group>"; };
|
||||
231B3F1F2D087A4C0069A07D /* MetricsColumnList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetricsColumnList.swift; sourceTree = "<group>"; };
|
||||
231B3F202D087A4C0069A07D /* MetricTableColumn.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetricTableColumn.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -369,12 +399,22 @@
|
|||
237AEB962E1FE627003B7CE3 /* BLETransport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BLETransport.swift; sourceTree = "<group>"; };
|
||||
237AEB982E20098B003B7CE3 /* BLEConnection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BLEConnection.swift; sourceTree = "<group>"; };
|
||||
237B46952DC8F1C100B22D99 /* RateLimitedButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RateLimitedButton.swift; sourceTree = "<group>"; };
|
||||
2388EC392EDF8A1400F6F982 /* DFUModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DFUModel.swift; sourceTree = "<group>"; };
|
||||
23A1AFB62E42BD2500E46C96 /* RXTXIndicatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RXTXIndicatorView.swift; sourceTree = "<group>"; };
|
||||
23AB0E652EE35E0200AFA09D /* Image.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Image.swift; sourceTree = "<group>"; };
|
||||
23AD54682E2A6EAA0046E9AB /* AccessoryManager+FromRadio.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AccessoryManager+FromRadio.swift"; sourceTree = "<group>"; };
|
||||
23AD546A2E2AA5A80046E9AB /* AccessoryManager+ToRadio.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AccessoryManager+ToRadio.swift"; sourceTree = "<group>"; };
|
||||
23AD546C2E2AE9630046E9AB /* AccessoryManager+MQTT.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AccessoryManager+MQTT.swift"; sourceTree = "<group>"; };
|
||||
23C2BD282EE87D0300F6A997 /* CoreDataBrowser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataBrowser.swift; sourceTree = "<group>"; };
|
||||
23C2BD2A2EE8993800F6A997 /* DeviceHardwareImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceHardwareImage.swift; sourceTree = "<group>"; };
|
||||
23C2BE242EE9A8E100F6A997 /* SupportedHardwareBadge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SupportedHardwareBadge.swift; sourceTree = "<group>"; };
|
||||
23C2BE262EE9F4BD00F6A997 /* DeviceHardwareEntity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceHardwareEntity.swift; sourceTree = "<group>"; };
|
||||
23C2BE292EEAF96A00F6A997 /* FirmwareViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirmwareViewModel.swift; sourceTree = "<group>"; };
|
||||
23C2BE302EEB823900F6A997 /* NRFDFUSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRFDFUSheet.swift; sourceTree = "<group>"; };
|
||||
23C2BE332EEC3F9600F6A997 /* ESP32DFUSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ESP32DFUSheet.swift; sourceTree = "<group>"; };
|
||||
23D316922E5618D2002FA4FB /* AsyncGate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncGate.swift; sourceTree = "<group>"; };
|
||||
23D9D9382E50DA97005D1C18 /* ResettableTimer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResettableTimer.swift; sourceTree = "<group>"; };
|
||||
23DC50BA2EE76D9C0023838A /* URL+fetch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URL+fetch.swift"; sourceTree = "<group>"; };
|
||||
23E23F912E392C2B00919073 /* LogRecord+StringRepresentation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LogRecord+StringRepresentation.swift"; sourceTree = "<group>"; };
|
||||
23F061B22E7B056600A1E2EA /* Logger+DataDog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Logger+DataDog.swift"; sourceTree = "<group>"; };
|
||||
23F488112E32980B002C776F /* AccessoryManager+Position.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AccessoryManager+Position.swift"; sourceTree = "<group>"; };
|
||||
|
|
@ -474,7 +514,6 @@
|
|||
DD2CC2E52ABFE04E00EDFDA7 /* MeshtasticDataModelV19.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV19.xcdatamodel; sourceTree = "<group>"; };
|
||||
DD31B04D2BDC6FD30024FA63 /* MeshtasticDataModelV 36.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 36.xcdatamodel"; sourceTree = "<group>"; };
|
||||
DD33DB602B3D1ECC003E1EA0 /* MeshtasticDataModelV 23.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 23.xcdatamodel"; sourceTree = "<group>"; };
|
||||
DD33DB612B3D27C7003E1EA0 /* FirmwareApi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirmwareApi.swift; sourceTree = "<group>"; };
|
||||
DD3501882852FC3B000FC853 /* Settings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Settings.swift; sourceTree = "<group>"; };
|
||||
DD354FD82BD96A0B0061A25F /* IAQScale.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IAQScale.swift; sourceTree = "<group>"; };
|
||||
DD3619132B1EE20700C41C8C /* MeshtasticDataModelV21.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV21.xcdatamodel; sourceTree = "<group>"; };
|
||||
|
|
@ -667,7 +706,76 @@
|
|||
DDFFA7462B3A7F3C004730DB /* Bundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bundle.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
||||
236CA24C2EE3071A00A47F96 /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = {
|
||||
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
|
||||
membershipExceptions = (
|
||||
crowpanel_2_8.svg,
|
||||
crowpanel_3_5.svg,
|
||||
crowpanel_5_0.svg,
|
||||
crowpanel_7_0.svg,
|
||||
diy.svg,
|
||||
heltec_mesh_pocket.svg,
|
||||
heltec_v4.svg,
|
||||
"heltec-ht62-esp32c3-sx1262.svg",
|
||||
"heltec-mesh-node-t114-case.svg",
|
||||
"heltec-mesh-node-t114.svg",
|
||||
"heltec-mesh-solar.svg",
|
||||
"heltec-v3-case.svg",
|
||||
"heltec-v3.svg",
|
||||
"heltec-vision-master-e213.svg",
|
||||
"heltec-vision-master-e290.svg",
|
||||
"heltec-vision-master-t190.svg",
|
||||
"heltec-wireless-paper.svg",
|
||||
"heltec-wireless-tracker.svg",
|
||||
"heltec-wsl-v3.svg",
|
||||
image_manifest.json,
|
||||
"lilygo-tlora-pager.svg",
|
||||
m5_c6l.svg,
|
||||
meteor_pro.svg,
|
||||
muzi_r1_neo.svg,
|
||||
"nano-g2-ultra.svg",
|
||||
pico.svg,
|
||||
promicro.svg,
|
||||
rak_3312.svg,
|
||||
rak_wismesh_tag.svg,
|
||||
"rak-wismesh-tap-v2.svg",
|
||||
"rak-wismeshtap.svg",
|
||||
rak2560.svg,
|
||||
rak4631_case.svg,
|
||||
rak4631.svg,
|
||||
rak11200.svg,
|
||||
rak11310.svg,
|
||||
rpipicow.svg,
|
||||
seeed_solar.svg,
|
||||
seeed_xiao_nrf52_kit.svg,
|
||||
"seeed-sensecap-indicator.svg",
|
||||
"seeed-xiao-s3.svg",
|
||||
"station-g2.svg",
|
||||
"t-deck.svg",
|
||||
"t-echo.svg",
|
||||
"t-watch-s3.svg",
|
||||
"tbeam-s3-core.svg",
|
||||
tbeam.svg,
|
||||
tdeck_pro.svg,
|
||||
techo_lite.svg,
|
||||
thinknode_m1.svg,
|
||||
thinknode_m2.svg,
|
||||
"tlora-t3s3-epaper.svg",
|
||||
"tlora-t3s3-v1.svg",
|
||||
"tlora-v2-1-1_6.svg",
|
||||
"tlora-v2-1-1_8.svg",
|
||||
"tracker-t1000-e.svg",
|
||||
wio_tracker_l1_case.svg,
|
||||
wio_tracker_l1_eink.svg,
|
||||
"wio-tracker-wm1110.svg",
|
||||
);
|
||||
target = DDC2E15326CE248E0042C5E4 /* Meshtastic */;
|
||||
};
|
||||
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
||||
|
||||
/* Begin PBXFileSystemSynchronizedRootGroup section */
|
||||
236CA20D2EE3036A00A47F96 /* images */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (236CA24C2EE3071A00A47F96 /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = images; sourceTree = "<group>"; };
|
||||
DD4C11E02E8099C3003F2F2E /* PreferenceKeys */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = PreferenceKeys; sourceTree = "<group>"; };
|
||||
/* End PBXFileSystemSynchronizedRootGroup section */
|
||||
|
||||
|
|
@ -689,7 +797,9 @@
|
|||
10D109F22E2047D600536CE6 /* DatadogSessionReplay in Frameworks */,
|
||||
102B5EAF2E172F41003D191E /* DatadogLogs in Frameworks */,
|
||||
102B5EB12E172F41003D191E /* DatadogRUM in Frameworks */,
|
||||
2315D1A82EEF2ED400E0FAE7 /* SwiftDraw in Frameworks */,
|
||||
DD0D3D222A55CEB10066DB71 /* CocoaMQTT in Frameworks */,
|
||||
2388EC382EDF88E900F6F982 /* NordicDFU in Frameworks */,
|
||||
10D109F42E2047D600536CE6 /* DatadogTrace in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
|
@ -707,6 +817,33 @@
|
|||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
23148E2E2EE1CCB100F0DB2C /* API */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
2315D1A12EECD2BE00E0FAE7 /* APIStructs.swift */,
|
||||
23DC51382EE76DA20023838A /* Helpers */,
|
||||
23148E2F2EE1CCE500F0DB2C /* MeshtasticAPI.swift */,
|
||||
);
|
||||
path = API;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
2315D19E2EECB42D00E0FAE7 /* U2F Mass Storage */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
2315D19F2EECB44800E0FAE7 /* UF2MassStorageView.swift */,
|
||||
);
|
||||
path = "U2F Mass Storage";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
2315D1A32EED94D700E0FAE7 /* Firmware */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
23C2BE292EEAF96A00F6A997 /* FirmwareViewModel.swift */,
|
||||
2315D1A42EED94E800E0FAE7 /* FirmwareFile.swift */,
|
||||
);
|
||||
path = Firmware;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
231B3F1E2D0879BC0069A07D /* Metrics Visualization */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
|
@ -766,6 +903,42 @@
|
|||
path = Accessory;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
23C2BD272EE87CFD00F6A997 /* Debugging */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
23C2BD282EE87D0300F6A997 /* CoreDataBrowser.swift */,
|
||||
);
|
||||
path = Debugging;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
23C2BE282EEAF91900F6A997 /* Firmware */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DDD6EEAE29BC024700383354 /* Firmware.swift */,
|
||||
2315D19E2EECB42D00E0FAE7 /* U2F Mass Storage */,
|
||||
23C2BE322EEC3F7800F6A997 /* ESP32 DFU */,
|
||||
23C2BE2F2EEB821400F6A997 /* NRF DFU */,
|
||||
);
|
||||
path = Firmware;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
23C2BE2F2EEB821400F6A997 /* NRF DFU */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
2388EC392EDF8A1400F6F982 /* DFUModel.swift */,
|
||||
23C2BE302EEB823900F6A997 /* NRFDFUSheet.swift */,
|
||||
);
|
||||
path = "NRF DFU";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
23C2BE322EEC3F7800F6A997 /* ESP32 DFU */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
23C2BE332EEC3F9600F6A997 /* ESP32DFUSheet.swift */,
|
||||
);
|
||||
path = "ESP32 DFU";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
23D9D9312E50DA0E005D1C18 /* Protocols */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
|
@ -839,6 +1012,15 @@
|
|||
path = Helpers;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
23DC51382EE76DA20023838A /* Helpers */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
2315D19C2EECB3D400E0FAE7 /* UTI+UF2.swift */,
|
||||
23DC50BA2EE76D9C0023838A /* URL+fetch.swift */,
|
||||
);
|
||||
path = Helpers;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
251926882C3BAF2E00249DF5 /* Actions */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
|
@ -915,6 +1097,7 @@
|
|||
DD964FC1297272AE007C176F /* WaypointEntityExtension.swift */,
|
||||
DDAB580E2B0DAFBC00147258 /* LocationEntityExtension.swift */,
|
||||
DDE5B4052B227E3200FCDD05 /* TraceRouteEntityExtension.swift */,
|
||||
23C2BE262EE9F4BD00F6A997 /* DeviceHardwareEntity.swift */,
|
||||
);
|
||||
path = CoreData;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -960,14 +1143,13 @@
|
|||
DDD5BB0E2C285F92007E03CA /* Logs */,
|
||||
DD93800C2BA74CE3008BEC06 /* Channels */,
|
||||
DD61937A2863876A00E59241 /* Config */,
|
||||
23C2BE282EEAF91900F6A997 /* Firmware */,
|
||||
DD97E96728EFE9A00056DDA4 /* About.swift */,
|
||||
DDD5BB152C28B1E4007E03CA /* AppData.swift */,
|
||||
DDD5BB082C285DDC007E03CA /* AppLog.swift */,
|
||||
DD4A911D2708C65400501B7E /* AppSettings.swift */,
|
||||
ABB99DEA2E2EA1C500CFBD05 /* AppIconPicker.swift */,
|
||||
DDA0B6B1294CDC55001356EC /* Channels.swift */,
|
||||
DDD6EEAE29BC024700383354 /* Firmware.swift */,
|
||||
DD33DB612B3D27C7003E1EA0 /* FirmwareApi.swift */,
|
||||
DD1B8F3F2B35E2F10022AABC /* GPSStatus.swift */,
|
||||
DDAB580C2B0DAA9E00147258 /* Routes.swift */,
|
||||
DDE9659B2B1C3B6A00531070 /* RouteRecorder.swift */,
|
||||
|
|
@ -1163,6 +1345,7 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
237AEB8D2E1FE120003B7CE3 /* Accessory */,
|
||||
23148E2E2EE1CCB100F0DB2C /* API */,
|
||||
BCB6137F2C6728E700485544 /* AppIntents */,
|
||||
DD1BD0EC2C603C5B008C0C70 /* Measurement */,
|
||||
25F5D5BC2C3F6D7B008036E3 /* Router */,
|
||||
|
|
@ -1196,6 +1379,7 @@
|
|||
DDC2E18726CE24E40042C5E4 /* Views */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
23C2BD272EE87CFD00F6A997 /* Debugging */,
|
||||
DD47E3D726F2F21A00029299 /* Connect */,
|
||||
DDC2E18D26CE25CB0042C5E4 /* Helpers */,
|
||||
DD6D5A312CA1176A00ED3032 /* Layouts */,
|
||||
|
|
@ -1211,6 +1395,7 @@
|
|||
DDC2E18826CE24EE0042C5E4 /* Model */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
2315D1A32EED94D700E0FAE7 /* Firmware */,
|
||||
2344A2AC2D66978000170A77 /* CoreData */,
|
||||
231B3F1E2D0879BC0069A07D /* Metrics Visualization */,
|
||||
DD23A50E26FD1B4400D9B90C /* PeripheralModel.swift */,
|
||||
|
|
@ -1221,6 +1406,7 @@
|
|||
DDC2E18926CE24F70042C5E4 /* Resources */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
236CA20D2EE3036A00A47F96 /* images */,
|
||||
2339EA972E6C65570032C234 /* AppIcon.icon */,
|
||||
2339EA992E6C65DC0032C234 /* AppIconDebug.icon */,
|
||||
DD98EB282E7A42CC0016320A /* AppIcon_Chirpy.icon */,
|
||||
|
|
@ -1274,6 +1460,8 @@
|
|||
8D3F8A3E2D44BB02009EAAA4 /* PowerMetrics.swift */,
|
||||
237B46952DC8F1C100B22D99 /* RateLimitedButton.swift */,
|
||||
23A1AFB62E42BD2500E46C96 /* RXTXIndicatorView.swift */,
|
||||
23C2BD2A2EE8993800F6A997 /* DeviceHardwareImage.swift */,
|
||||
23C2BE242EE9A8E100F6A997 /* SupportedHardwareBadge.swift */,
|
||||
);
|
||||
path = Helpers;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -1377,6 +1565,7 @@
|
|||
DDD5BB0C2C285F00007E03CA /* Logger.swift */,
|
||||
23F061B22E7B056600A1E2EA /* Logger+DataDog.swift */,
|
||||
DD6F65732C6CB80A0053C113 /* View.swift */,
|
||||
23AB0E652EE35E0200AFA09D /* Image.swift */,
|
||||
);
|
||||
path = Extensions;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -1430,6 +1619,8 @@
|
|||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = DDC2E17E26CE248F0042C5E4 /* Build configuration list for PBXNativeTarget "Meshtastic" */;
|
||||
buildPhases = (
|
||||
23148E312EE1F43B00F0DB2C /* Download Node SVG Images */,
|
||||
236CA24D2EE3073100A47F96 /* CopyFiles */,
|
||||
BB450974275599CE00509624 /* ShellScript */,
|
||||
DDC2E15026CE248E0042C5E4 /* Sources */,
|
||||
DDC2E15126CE248E0042C5E4 /* Frameworks */,
|
||||
|
|
@ -1442,6 +1633,7 @@
|
|||
DDDE5A0229AF163E00490C6C /* PBXTargetDependency */,
|
||||
);
|
||||
fileSystemSynchronizedGroups = (
|
||||
236CA20D2EE3036A00A47F96 /* images */,
|
||||
DD4C11E02E8099C3003F2F2E /* PreferenceKeys */,
|
||||
);
|
||||
name = Meshtastic;
|
||||
|
|
@ -1454,6 +1646,8 @@
|
|||
102B5EB02E172F41003D191E /* DatadogRUM */,
|
||||
10D109F12E2047D600536CE6 /* DatadogSessionReplay */,
|
||||
10D109F32E2047D600536CE6 /* DatadogTrace */,
|
||||
2388EC372EDF88E900F6F982 /* NordicDFU */,
|
||||
2315D1A72EEF2ED400E0FAE7 /* SwiftDraw */,
|
||||
);
|
||||
productName = MeshtasticClient;
|
||||
productReference = DDC2E15426CE248E0042C5E4 /* Meshtastic.app */;
|
||||
|
|
@ -1525,6 +1719,8 @@
|
|||
25A978B82C13F8ED0003AAE7 /* XCLocalSwiftPackageReference "MeshtasticProtobufs" */,
|
||||
259792242C2F10B600AD1659 /* XCRemoteSwiftPackageReference "swift-protobuf" */,
|
||||
102B5EA92E172F41003D191E /* XCRemoteSwiftPackageReference "dd-sdk-ios" */,
|
||||
2388EC362EDF450200F6F982 /* XCRemoteSwiftPackageReference "IOS-DFU-Library" */,
|
||||
2315D1A62EEF2ED400E0FAE7 /* XCRemoteSwiftPackageReference "SwiftDraw" */,
|
||||
);
|
||||
productRefGroup = DDC2E15526CE248E0042C5E4 /* Products */;
|
||||
projectDirPath = "";
|
||||
|
|
@ -1575,6 +1771,26 @@
|
|||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
23148E312EE1F43B00F0DB2C /* Download Node SVG Images */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
alwaysOutOfDate = 1;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "Download Node SVG Images";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "MARKER=\"${TARGET_TEMP_DIR}/first-build.marker\"\n\nif [ ! -f \"$MARKER\" ]; then\n # Treating as CLEAN build (or first build in this DerivedData)\n # Force update all the note board images\n \"$SRCROOT/scripts/download_images.py\" --output-dir \"$SRCROOT/Meshtastic/Resources/images\" --output-json \"$SRCROOT/Meshtastic/Resources/DeviceHardware.json\" --force\n touch \"$MARKER\"\nelse\n # Incremental build\n # update only if there was a change in the devices json\n \"$SRCROOT/scripts/download_images.py\" --output-dir \"$SRCROOT/Meshtastic/Resources/images\" --output-json \"$SRCROOT/Meshtastic/Resources/DeviceHardware.json\"\nfi\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
BB450974275599CE00509624 /* ShellScript */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
alwaysOutOfDate = 1;
|
||||
|
|
@ -1610,6 +1826,7 @@
|
|||
files = (
|
||||
230BC3972E31071E0046BF2A /* AccessoryManager+Discovery.swift in Sources */,
|
||||
25F26B1F2C2F611300C9CD9D /* AppData.swift in Sources */,
|
||||
23C2BE252EE9A8E100F6A997 /* SupportedHardwareBadge.swift in Sources */,
|
||||
25F26B1E2C2F610D00C9CD9D /* Logger.swift in Sources */,
|
||||
259792252C2F114500AD1659 /* ChannelEntityExtension.swift in Sources */,
|
||||
BCE2D3C52C7AE369008E6199 /* RestartNodeIntent.swift in Sources */,
|
||||
|
|
@ -1625,9 +1842,11 @@
|
|||
DD836AE726F6B38600ABCC23 /* Connect.swift in Sources */,
|
||||
D93069082B81DF040066FBC8 /* SaveConfigButton.swift in Sources */,
|
||||
DD5E523F298F5A9E00D21B61 /* AirQualityIndex.swift in Sources */,
|
||||
2315D19D2EECB3DA00E0FAE7 /* UTI+UF2.swift in Sources */,
|
||||
DDD5BB182C2F9C36007E03CA /* OSLogEntryLog.swift in Sources */,
|
||||
DD3501892852FC3B000FC853 /* Settings.swift in Sources */,
|
||||
DDDC22382BA92344002C44F1 /* MeshMapContent.swift in Sources */,
|
||||
23C2BE2A2EEAF96A00F6A997 /* FirmwareViewModel.swift in Sources */,
|
||||
DD5D0A9C2931B9F200F7EA61 /* EthernetModes.swift in Sources */,
|
||||
6DEDA55A2A957B8E00321D2E /* DetectionSensorLog.swift in Sources */,
|
||||
DD798B072915928D005217CD /* ChannelMessageList.swift in Sources */,
|
||||
|
|
@ -1660,9 +1879,11 @@
|
|||
DD15E4F32B8BA56E00654F61 /* PaxCounterConfig.swift in Sources */,
|
||||
DDDB445229F8ACF900EE2349 /* Date.swift in Sources */,
|
||||
2373AE132D0A216C0086C749 /* MetricsChartSeries.swift in Sources */,
|
||||
23C2BD2B2EE8993800F6A997 /* DeviceHardwareImage.swift in Sources */,
|
||||
DDC4D568275499A500A4208E /* Persistence.swift in Sources */,
|
||||
DDD6EEAF29BC024700383354 /* Firmware.swift in Sources */,
|
||||
DD77093B2AA1ABB8007A8BF0 /* BluetoothTips.swift in Sources */,
|
||||
23C2BE272EE9F4BD00F6A997 /* DeviceHardwareEntity.swift in Sources */,
|
||||
D9C9839D2B79CFD700BDBE6A /* TextMessageSize.swift in Sources */,
|
||||
DDC94FCE29CF55310082EA6E /* RtttlConfig.swift in Sources */,
|
||||
DD964FBD296E6B01007C176F /* EmojiOnlyTextField.swift in Sources */,
|
||||
|
|
@ -1693,6 +1914,7 @@
|
|||
DDB8F4102A9EE5B400230ECE /* Messages.swift in Sources */,
|
||||
233E99C32D849D7A00CC3A77 /* WeightCompactWidget.swift in Sources */,
|
||||
DD4A911E2708C65400501B7E /* AppSettings.swift in Sources */,
|
||||
2315D1A52EED94E800E0FAE7 /* FirmwareFile.swift in Sources */,
|
||||
DD1BD0F32C63C65E008C0C70 /* SecurityConfig.swift in Sources */,
|
||||
DD1BEF4C2E030D310090CE24 /* KeyBackupStatus.swift in Sources */,
|
||||
23F061B32E7B056600A1E2EA /* Logger+DataDog.swift in Sources */,
|
||||
|
|
@ -1701,6 +1923,7 @@
|
|||
DD13AA492AB73BF400BA0C98 /* PositionPopover.swift in Sources */,
|
||||
6DEDA55C2A9592F900321D2E /* MessageEntityExtension.swift in Sources */,
|
||||
DD9C700D2E8D5A9500106227 /* ChannelMessageRow.swift in Sources */,
|
||||
2315D1A02EECB44800E0FAE7 /* UF2MassStorageView.swift in Sources */,
|
||||
DDDB444229F8A88700EE2349 /* Double.swift in Sources */,
|
||||
DDF45C342BC1A48E005ED5F2 /* MQTTIcon.swift in Sources */,
|
||||
DDA9515A2BC6624100CEA535 /* TelemetryWeather.swift in Sources */,
|
||||
|
|
@ -1712,6 +1935,8 @@
|
|||
DD6193772862F90F00E59241 /* CannedMessagesConfig.swift in Sources */,
|
||||
DD3619152B1EF9F900C41C8C /* LocationsHandler.swift in Sources */,
|
||||
DD6F65792C6EADE60053C113 /* DirectMessagesHelp.swift in Sources */,
|
||||
23148E302EE1CCE500F0DB2C /* MeshtasticAPI.swift in Sources */,
|
||||
23C2BD292EE87D0300F6A997 /* CoreDataBrowser.swift in Sources */,
|
||||
233E99B62D849C3D00CC3A77 /* WeatherConditionsCompactWidget.swift in Sources */,
|
||||
25F5D5C02C3F6DA6008036E3 /* Router.swift in Sources */,
|
||||
2346A7192E2FB9A300CB9239 /* SerialConnection.swift in Sources */,
|
||||
|
|
@ -1721,7 +1946,6 @@
|
|||
DD41582628582E9B009B0E59 /* DeviceConfig.swift in Sources */,
|
||||
DDF45C372BC46A5A005ED5F2 /* TimeZone.swift in Sources */,
|
||||
DD007BAE2AA4E91200F5FA12 /* MyInfoEntityExtension.swift in Sources */,
|
||||
DD33DB622B3D27C7003E1EA0 /* FirmwareApi.swift in Sources */,
|
||||
DD3CC6B528E33FD100FA9159 /* ShareChannels.swift in Sources */,
|
||||
23D9D9392E50DA97005D1C18 /* ResettableTimer.swift in Sources */,
|
||||
DD1BF2F92776FE2E008C8D2F /* UserMessageList.swift in Sources */,
|
||||
|
|
@ -1756,7 +1980,9 @@
|
|||
DDB6ABD928B0A4BA00384BA1 /* BluetoothModes.swift in Sources */,
|
||||
DD1BD0EE2C603C91008C0C70 /* CustomFormatters.swift in Sources */,
|
||||
DD0BE3102CB9FDC4000BA445 /* DetectionSensorEnums.swift in Sources */,
|
||||
23AB0E662EE35E0200AFA09D /* Image.swift in Sources */,
|
||||
DDD9E4E4284B208E003777C5 /* UserEntityExtension.swift in Sources */,
|
||||
23DC50BB2EE76D9C0023838A /* URL+fetch.swift in Sources */,
|
||||
233E99CB2D85AAA900CC3A77 /* RainfallCompactWidget.swift in Sources */,
|
||||
DD2553592855B52700E55709 /* PositionConfig.swift in Sources */,
|
||||
DD9C70112E916EBD00106227 /* UpdateIntervalPicker.swift in Sources */,
|
||||
|
|
@ -1803,6 +2029,7 @@
|
|||
DD268D8E2BCC90E2008073AE /* RouteEnums.swift in Sources */,
|
||||
251926922C3CB52300249DF5 /* DeleteNodeButton.swift in Sources */,
|
||||
DDB6ABE428B13FFF00384BA1 /* DisplayEnums.swift in Sources */,
|
||||
2388EC3A2EDF8A1400F6F982 /* DFUModel.swift in Sources */,
|
||||
DD4975A52B147BA90026544E /* AmbientLightingConfig.swift in Sources */,
|
||||
3D3417D22E2DC260006A988B /* MapDataManager.swift in Sources */,
|
||||
D93068D92B81509C0066FBC8 /* TapbackResponses.swift in Sources */,
|
||||
|
|
@ -1813,6 +2040,7 @@
|
|||
DD86D40A287F04F100BAEB7A /* InvalidVersion.swift in Sources */,
|
||||
233E99BE2D849D3200CC3A77 /* RadiationCompactWidget.swift in Sources */,
|
||||
DDD94A502845C8F5004A87A0 /* DateTimeText.swift in Sources */,
|
||||
23C2BE312EEB823900F6A997 /* NRFDFUSheet.swift in Sources */,
|
||||
DDB6ABE228B13FB500384BA1 /* PositionConfigEnums.swift in Sources */,
|
||||
DD994B69295F88B60013760A /* IntervalEnums.swift in Sources */,
|
||||
23D316932E5618D2002FA4FB /* AsyncGate.swift in Sources */,
|
||||
|
|
@ -1857,12 +2085,14 @@
|
|||
DD1BEF4A2E0292320090CE24 /* KeychainHelper.swift in Sources */,
|
||||
DD41582A28585C32009B0E59 /* RangeTestConfig.swift in Sources */,
|
||||
DD1925B728CDA5A400720036 /* CannedMessagesConfigEnums.swift in Sources */,
|
||||
23C2BE342EEC3F9600F6A997 /* ESP32DFUSheet.swift in Sources */,
|
||||
DDDB444429F8A8DD00EE2349 /* Float.swift in Sources */,
|
||||
DDAB580F2B0DAFBC00147258 /* LocationEntityExtension.swift in Sources */,
|
||||
233E99BC2D849C8C00CC3A77 /* WindCompactWidget.swift in Sources */,
|
||||
B3E905B12B71F7F300654D07 /* TextMessageField.swift in Sources */,
|
||||
232ED4C32E2C5E89009DA392 /* TCPTransport.swift in Sources */,
|
||||
BC6B45FF2CB2F98900723CEB /* SaveChannelSettingsIntent.swift in Sources */,
|
||||
2315D1A22EECD2CB00E0FAE7 /* APIStructs.swift in Sources */,
|
||||
D93068D72B8146690066FBC8 /* MessageText.swift in Sources */,
|
||||
DDC2E15826CE248E0042C5E4 /* MeshtasticApp.swift in Sources */,
|
||||
);
|
||||
|
|
@ -2268,6 +2498,22 @@
|
|||
minimumVersion = 3.3.0;
|
||||
};
|
||||
};
|
||||
2315D1A62EEF2ED400E0FAE7 /* XCRemoteSwiftPackageReference "SwiftDraw" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/swhitty/SwiftDraw";
|
||||
requirement = {
|
||||
kind = upToNextMajorVersion;
|
||||
minimumVersion = 0.25.3;
|
||||
};
|
||||
};
|
||||
2388EC362EDF450200F6F982 /* XCRemoteSwiftPackageReference "IOS-DFU-Library" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/NordicSemiconductor/IOS-DFU-Library";
|
||||
requirement = {
|
||||
kind = upToNextMajorVersion;
|
||||
minimumVersion = 4.16.0;
|
||||
};
|
||||
};
|
||||
259792242C2F10B600AD1659 /* XCRemoteSwiftPackageReference "swift-protobuf" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/apple/swift-protobuf.git";
|
||||
|
|
@ -2317,6 +2563,16 @@
|
|||
package = 102B5EA92E172F41003D191E /* XCRemoteSwiftPackageReference "dd-sdk-ios" */;
|
||||
productName = DatadogTrace;
|
||||
};
|
||||
2315D1A72EEF2ED400E0FAE7 /* SwiftDraw */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 2315D1A62EEF2ED400E0FAE7 /* XCRemoteSwiftPackageReference "SwiftDraw" */;
|
||||
productName = SwiftDraw;
|
||||
};
|
||||
2388EC372EDF88E900F6F982 /* NordicDFU */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 2388EC362EDF450200F6F982 /* XCRemoteSwiftPackageReference "IOS-DFU-Library" */;
|
||||
productName = NordicDFU;
|
||||
};
|
||||
25A978B92C13F8ED0003AAE7 /* MeshtasticProtobufs */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = MeshtasticProtobufs;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"originHash" : "2569905853aec088d5bac6b540eac77f78963f88b406e8dd95a88c40623cc8b4",
|
||||
"originHash" : "943f1047f8d99b0600f1c91f14d7bf4808ab1caf172ae4d7f3ebea325c27437f",
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "cocoamqtt",
|
||||
|
|
@ -15,8 +15,17 @@
|
|||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/DataDog/dd-sdk-ios.git",
|
||||
"state" : {
|
||||
"revision" : "d0a42d8067665cb6ee86af51251ccc071f62bd54",
|
||||
"version" : "2.29.0"
|
||||
"revision" : "8d67e973ff4a958cb536263cb816646ee904c508",
|
||||
"version" : "3.3.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "ios-dfu-library",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/NordicSemiconductor/IOS-DFU-Library",
|
||||
"state" : {
|
||||
"revision" : "4773d7eed944684dfdd177a4a91af8a89ebbaac8",
|
||||
"version" : "4.16.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -63,6 +72,24 @@
|
|||
"revision" : "102a647b573f60f73afdce5613a51d71349fe507",
|
||||
"version" : "1.30.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swiftdraw",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/swhitty/SwiftDraw",
|
||||
"state" : {
|
||||
"revision" : "17d55c17540f3eb10685058e803d7ae73d9bf9d3",
|
||||
"version" : "0.25.3"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "zipfoundation",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/weichsel/ZIPFoundation",
|
||||
"state" : {
|
||||
"revision" : "02b6abe5f6eef7e3cbd5f247c5cc24e246efcfe0",
|
||||
"version" : "0.9.19"
|
||||
}
|
||||
}
|
||||
],
|
||||
"version" : 3
|
||||
|
|
|
|||
|
|
@ -1,6 +1,15 @@
|
|||
{
|
||||
"originHash" : "fd71b247ba909b0eb360db5530e1068363839c5e169dea6f6a9974b2d98276f4",
|
||||
"originHash" : "33a490b0eed23f6be325b80c313e6b146614a761edd63ec6dc35cb21f5df06b9",
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "cocoalumberjack",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/CocoaLumberjack/CocoaLumberjack.git",
|
||||
"state" : {
|
||||
"revision" : "a9ed4b6f9bdedce7d77046f43adfb8ce1fd54114",
|
||||
"version" : "3.9.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "cocoamqtt",
|
||||
"kind" : "remoteSourceControl",
|
||||
|
|
@ -19,6 +28,15 @@
|
|||
"version" : "3.3.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "ios-dfu-library",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/NordicSemiconductor/IOS-DFU-Library",
|
||||
"state" : {
|
||||
"revision" : "4773d7eed944684dfdd177a4a91af8a89ebbaac8",
|
||||
"version" : "4.16.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "mqttcocoaasyncsocket",
|
||||
"kind" : "remoteSourceControl",
|
||||
|
|
@ -55,6 +73,24 @@
|
|||
"version" : "4.0.8"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "svgkit",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/SVGKit/SVGKit",
|
||||
"state" : {
|
||||
"revision" : "58152b9f7c85eab239160b36ffdfd364aa43d666",
|
||||
"version" : "3.0.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-log",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-log",
|
||||
"state" : {
|
||||
"revision" : "b1fa4ef41fe21b13120c034854042d12c43f66b2",
|
||||
"version" : "1.7.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-protobuf",
|
||||
"kind" : "remoteSourceControl",
|
||||
|
|
@ -63,6 +99,15 @@
|
|||
"revision" : "d72aed98f8253ec1aa9ea1141e28150f408cf17f",
|
||||
"version" : "1.29.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "zipfoundation",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/weichsel/ZIPFoundation",
|
||||
"state" : {
|
||||
"revision" : "02b6abe5f6eef7e3cbd5f247c5cc24e246efcfe0",
|
||||
"version" : "0.9.19"
|
||||
}
|
||||
}
|
||||
],
|
||||
"version" : 3
|
||||
|
|
|
|||
61
Meshtastic/API/APIStructs.swift
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
//
|
||||
// APIStructs.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Created by jake on 12/12/25.
|
||||
//
|
||||
|
||||
/// Device Hardware API
|
||||
struct DeviceHardware: Codable {
|
||||
let hwModel: Int
|
||||
let hwModelSlug: String
|
||||
let platformioTarget: String
|
||||
let architecture: Architecture
|
||||
let activelySupported: Bool
|
||||
let displayName: String
|
||||
let supportLevel: Int?
|
||||
let tags: [String]?
|
||||
let images: [String]?
|
||||
let requiresDfu: Bool?
|
||||
let hasInkHud: Bool?
|
||||
let partitionScheme: String?
|
||||
let hasMui: Bool?
|
||||
}
|
||||
enum Architecture: String, Codable, Identifiable {
|
||||
case esp32 = "esp32"
|
||||
case esp32C3 = "esp32-c3"
|
||||
case esp32S3 = "esp32-s3"
|
||||
case nrf52840 = "nrf52840"
|
||||
case rp2040 = "rp2040"
|
||||
case esp32C6 = "esp32-c6"
|
||||
|
||||
var id: String { rawValue }
|
||||
}
|
||||
|
||||
/// Firmware Release Lists
|
||||
struct FirmwareReleases: Codable {
|
||||
let releases: Releases
|
||||
let pullRequests: [FirmwareRelease]
|
||||
}
|
||||
struct Releases: Codable {
|
||||
let stable, alpha: [FirmwareRelease]
|
||||
}
|
||||
struct FirmwareRelease: Codable {
|
||||
let id, title: String
|
||||
let pageURL: String
|
||||
let zipURL: String
|
||||
let releaseNotes: String
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case id, title
|
||||
case pageURL = "page_url"
|
||||
case zipURL = "zip_url"
|
||||
case releaseNotes = "release_notes"
|
||||
}
|
||||
|
||||
enum ReleaseType: String {
|
||||
case stable = "Stable"
|
||||
case alpha = "Alpha"
|
||||
case unlisted = "Unlisted"
|
||||
}
|
||||
}
|
||||
0
Meshtastic/API/FirmwareReleaseEntity.swift
Normal file
99
Meshtastic/API/Helpers/URL+fetch.swift
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
//
|
||||
// URL+fetch.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Created by jake on 12/6/25.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension URL {
|
||||
|
||||
/// Custom error type for the URL extension
|
||||
enum TimeoutError: Error, LocalizedError {
|
||||
case timedOut(TimeInterval)
|
||||
|
||||
var errorDescription: String? {
|
||||
switch self {
|
||||
case .timedOut(let seconds):
|
||||
return "The operation timed out after \(seconds) seconds."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Fetches data from the URL (local or remote) with a strict timeout.
|
||||
/// - Parameter timeout: The duration in seconds to wait before throwing an error.
|
||||
/// - Returns: The `Data` retrieved.
|
||||
func data(timeout: TimeInterval) async throws -> Data {
|
||||
|
||||
return try await withThrowingTaskGroup(of: Data.self) { group in
|
||||
|
||||
// Task 1: The Fetch Operation
|
||||
group.addTask {
|
||||
if self.isFileURL {
|
||||
// Handle Local Files
|
||||
// Note: Data(contentsOf:) is synchronous (blocking).
|
||||
// Running it inside a Task allows it to be raced, though
|
||||
// the underlying thread may remain blocked until OS IO completes
|
||||
// if cancellation occurs.
|
||||
return try Data(contentsOf: self)
|
||||
} else {
|
||||
// Handle Remote Network Requests
|
||||
let (data, _) = try await URLSession.shared.data(from: self)
|
||||
return data
|
||||
}
|
||||
}
|
||||
|
||||
// Task 2: The Timer
|
||||
group.addTask {
|
||||
// Convert seconds to nanoseconds
|
||||
let nanoseconds = UInt64(timeout * 1_000_000_000)
|
||||
try await Task.sleep(nanoseconds: nanoseconds)
|
||||
|
||||
// If we wake up, it means the fetch hasn't finished yet
|
||||
throw TimeoutError.timedOut(timeout)
|
||||
}
|
||||
|
||||
// Race Handling
|
||||
|
||||
// Wait for the first task to finish (either success or error)
|
||||
guard let result = try await group.next() else {
|
||||
// Should not be reachable, but required by compiler
|
||||
throw URLError(.unknown)
|
||||
}
|
||||
|
||||
// If we are here, one task finished successfully.
|
||||
// Cancel the other task immediately.
|
||||
group.cancelAll()
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
/// Performs a HEAD request to fetch the ETag header for the URL.
|
||||
/// - Parameter session: The URLSession to use (defaults to .shared).
|
||||
/// - Returns: The ETag string if found and the request is successful, otherwise nil.
|
||||
func eTag(using session: URLSession = .shared) async throws -> String? {
|
||||
var request = URLRequest(url: self)
|
||||
request.httpMethod = "HEAD"
|
||||
|
||||
// Ensure we don't use the local cache so we get the real ETag from the server
|
||||
request.cachePolicy = .reloadIgnoringLocalCacheData
|
||||
|
||||
let (_, response) = try await session.data(for: request)
|
||||
|
||||
guard let httpResponse = response as? HTTPURLResponse else {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Optional: Check for success status codes (200-299)
|
||||
guard (200...299).contains(httpResponse.statusCode) else {
|
||||
// You might want to return nil or throw a specific error here
|
||||
// depending on your requirements (e.g. 404 Not Found)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Header lookup is case-insensitive
|
||||
return httpResponse.value(forHTTPHeaderField: "ETag")
|
||||
}
|
||||
}
|
||||
40
Meshtastic/API/Helpers/UTI+UF2.swift
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
//
|
||||
// UTI+UF2.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Created by jake on 12/12/25.
|
||||
//
|
||||
|
||||
import UniformTypeIdentifiers
|
||||
import SwiftUI
|
||||
|
||||
extension UTType {
|
||||
// Define a custom type for your firmware
|
||||
// Identifier: Use your bundle ID prefix (e.g., com.yourcompany.firmware)
|
||||
static let UF2Firmware = UTType(exportedAs: "com.meshtastic.uf2-firmware")
|
||||
}
|
||||
|
||||
|
||||
struct FirmwareDocument: FileDocument {
|
||||
// 1. Tell the system this document supports your custom UTType
|
||||
static var readableContentTypes: [UTType] { [.UF2Firmware] }
|
||||
|
||||
var firmwareData: Data
|
||||
|
||||
init(data: Data) {
|
||||
self.firmwareData = data
|
||||
}
|
||||
|
||||
// Initialize from an existing file (Read)
|
||||
init(configuration: ReadConfiguration) throws {
|
||||
guard let data = configuration.file.regularFileContents else {
|
||||
throw CocoaError(.fileReadCorruptFile)
|
||||
}
|
||||
self.firmwareData = data
|
||||
}
|
||||
|
||||
// Prepare data for saving (Write)
|
||||
func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper {
|
||||
return FileWrapper(regularFileWithContents: firmwareData)
|
||||
}
|
||||
}
|
||||
395
Meshtastic/API/MeshtasticAPI.swift
Normal file
|
|
@ -0,0 +1,395 @@
|
|||
//
|
||||
// MeshtasticAPI.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Created by Jake Bordens on 12/4/25.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import OSLog
|
||||
import SwiftUI
|
||||
import CoreData
|
||||
|
||||
extension MeshtasticAPI {
|
||||
enum MeshtasticAPIError: Error, LocalizedError {
|
||||
case timedOut(TimeInterval)
|
||||
case unableToRetreviveJSON
|
||||
case unableToFindOrCreateEntity
|
||||
case unknownArchitecture
|
||||
case unknownPlatformIOTarget
|
||||
var errorDescription: String? {
|
||||
switch self {
|
||||
case .timedOut(let seconds):
|
||||
return "The operation timed out after \(seconds) seconds."
|
||||
case .unableToRetreviveJSON:
|
||||
return "Unable to retreive device hardware information."
|
||||
case .unableToFindOrCreateEntity:
|
||||
return "Unable to find or create Core Data entity."
|
||||
case .unknownArchitecture:
|
||||
return "Unknown architecture."
|
||||
case .unknownPlatformIOTarget:
|
||||
return "Unknown platformio target."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class MeshtasticAPI: ObservableObject, @unchecked Sendable {
|
||||
// Singleton Access
|
||||
static let shared = {
|
||||
MeshtasticAPI(container: PersistenceController.shared.container)
|
||||
}()
|
||||
|
||||
// MARK: - Constants
|
||||
static let deviceURLEndpoint = URL(string: "https://api.meshtastic.org/resource/deviceHardware")!
|
||||
static let imageURLPrefix = URL(string: "https://flasher.meshtastic.org/img/devices/")!
|
||||
static let firmwareURLEndpoint = URL(string: "https://api.meshtastic.org/github/firmware/list")!
|
||||
|
||||
// MARK: - Private properties
|
||||
private let fileManager = FileManager.default
|
||||
private let decoder = JSONDecoder()
|
||||
private let container: NSPersistentContainer
|
||||
|
||||
@Published var isLoadingDeviceList: Bool = false
|
||||
@Published var isLoadingFirmwareList: Bool = false
|
||||
|
||||
private init(container: NSPersistentContainer) {
|
||||
self.container = container
|
||||
Task.detached {
|
||||
try? await self.refreshDevicesAPIData()
|
||||
try? await self.refreshFirmwareAPIData()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Main Logic
|
||||
|
||||
func refreshFirmwareAPIData() async throws {
|
||||
await MainActor.run {
|
||||
self.isLoadingFirmwareList = true
|
||||
}
|
||||
|
||||
let apiData = try await Self.firmwareURLEndpoint.data(timeout: 5.0)
|
||||
|
||||
let decodedFirmware = try decoder.decode(FirmwareReleases.self, from: apiData)
|
||||
|
||||
let stableVersions = Set(decodedFirmware.releases.stable.map { $0.id })
|
||||
let alphaVersions = Set(decodedFirmware.releases.alpha.map { $0.id })
|
||||
|
||||
await withTaskGroup(of: Void.self) { group in
|
||||
|
||||
for stableRelease in decodedFirmware.releases.stable {
|
||||
group.addTask {
|
||||
await self.processFirmware(release: stableRelease, releaseType: .stable)
|
||||
}
|
||||
}
|
||||
|
||||
for alphaRelease in decodedFirmware.releases.alpha {
|
||||
group.addTask {
|
||||
await self.processFirmware(release: alphaRelease, releaseType: .alpha)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Anything that's left in stableVersions and alphaVersions is no longer present in the API and should be deleted.
|
||||
let context = container.newBackgroundContext()
|
||||
context.performAndWait {
|
||||
let deleteRequest = FirmwareReleaseEntity.fetchRequest()
|
||||
deleteRequest.predicate = Self.firmwareCompoundPredicate(stableVersions: stableVersions, alphaVersions: alphaVersions)
|
||||
if let objectsToDelete = try? context.fetch(deleteRequest) {
|
||||
for object in objectsToDelete {
|
||||
Logger.services.info("Deleting orphaned firmware release: \(object.versionId ?? "unknown")")
|
||||
context.delete(object)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Save the deletions if any
|
||||
if context.hasChanges {
|
||||
try? context.save()
|
||||
}
|
||||
|
||||
// Save the last update date for the firmware
|
||||
UserDefaults.lastFirmwareAPIUpdate = Date()
|
||||
|
||||
await MainActor.run {
|
||||
self.isLoadingFirmwareList = false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func refreshDevicesAPIData() async throws {
|
||||
await MainActor.run {
|
||||
self.isLoadingDeviceList = true
|
||||
}
|
||||
|
||||
// PHASE 1: Network (Async) - Get the JSON first
|
||||
var apiData: Data?
|
||||
do {
|
||||
apiData = try await Self.deviceURLEndpoint.data(timeout: 5.0)
|
||||
} catch {
|
||||
Logger.services.error("Unable to fetch device hardware from network: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
|
||||
// Fallback to local bundle
|
||||
if apiData == nil {
|
||||
if let bundledJsonURL = Bundle.main.url(forResource: "DeviceHardware.json", withExtension: nil) {
|
||||
apiData = try? Data(contentsOf: bundledJsonURL)
|
||||
}
|
||||
}
|
||||
|
||||
guard let finalData = apiData else {
|
||||
throw MeshtasticAPIError.unableToRetreviveJSON
|
||||
}
|
||||
|
||||
// Decode Swift Structs (Safe to do off the DB thread)
|
||||
let decodedDevices = try decoder.decode([DeviceHardware].self, from: finalData)
|
||||
|
||||
// PHASE 2: Database (Sync) - Update Devices & Tags
|
||||
let context = container.newBackgroundContext()
|
||||
context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
|
||||
|
||||
// We will perform the bulk update and return a simple list of images we need to check next.
|
||||
// We DO NOT do network calls for images inside this block.
|
||||
try await context.perform {
|
||||
|
||||
// 1. Update Devices and Tags
|
||||
for device in decodedDevices {
|
||||
let fetchRequest = DeviceHardwareEntity.fetchRequest()
|
||||
fetchRequest.predicate = NSPredicate(format: "platformioTarget == %@", device.platformioTarget)
|
||||
fetchRequest.fetchLimit = 1
|
||||
|
||||
let existing = try? context.fetch(fetchRequest).first
|
||||
let deviceEntity = existing ?? DeviceHardwareEntity(context: context)
|
||||
|
||||
// Update Properties
|
||||
deviceEntity.hwModel = Int64(device.hwModel)
|
||||
deviceEntity.hwModelSlug = device.hwModelSlug
|
||||
deviceEntity.platformioTarget = device.platformioTarget
|
||||
deviceEntity.architecture = device.architecture.rawValue
|
||||
deviceEntity.activelySupported = device.activelySupported
|
||||
deviceEntity.displayName = device.displayName
|
||||
|
||||
// Handle Tags (Helper function is now synchronous)
|
||||
var tags = Set<DeviceHardwareTagEntity>()
|
||||
if let tagList = device.tags {
|
||||
for tagString in tagList {
|
||||
// Safe because findOrCreateTag is synchronous and uses `context`
|
||||
if let tagEntity = try? Self.findOrCreateTag(tag: tagString, context: context) {
|
||||
tags.insert(tagEntity)
|
||||
}
|
||||
}
|
||||
}
|
||||
deviceEntity.tags = tags as NSSet
|
||||
}
|
||||
|
||||
// 2. Cleanup Orphans
|
||||
Self.deleteOrphanedTags(context: context)
|
||||
|
||||
// 3. Save Device Metadata
|
||||
if context.hasChanges {
|
||||
try context.save()
|
||||
}
|
||||
}
|
||||
|
||||
// PHASE 3: Images (Async Mixed)
|
||||
// Now that the devices exist in DB, we process images one by one.
|
||||
// We loop through the *Decoded Structs* (not DB objects) to get URLs.
|
||||
await withTaskGroup(of: Void.self) { group in
|
||||
for device in decodedDevices {
|
||||
group.addTask {
|
||||
guard let imagesList = device.images else { return }
|
||||
for imageName in imagesList {
|
||||
await self.processImage(imageName: imageName, platform: device.platformioTarget)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Final cleanup of images (Sync)
|
||||
try await context.perform {
|
||||
Self.deleteOrphanedImages(context: context)
|
||||
if context.hasChanges { try context.save() }
|
||||
}
|
||||
|
||||
await MainActor.run {
|
||||
self.isLoadingDeviceList = false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private func processFirmware(release: FirmwareRelease, releaseType: FirmwareRelease.ReleaseType) async {
|
||||
let context = container.newBackgroundContext()
|
||||
|
||||
await context.perform {
|
||||
let fetchRequest = FirmwareReleaseEntity.fetchRequest()
|
||||
fetchRequest.predicate = NSPredicate(format: "versionId == %@", release.id)
|
||||
fetchRequest.fetchLimit = 1
|
||||
|
||||
let existingRelease = (try? context.fetch(fetchRequest).first) ?? FirmwareReleaseEntity(context: context)
|
||||
existingRelease.versionId = release.id
|
||||
existingRelease.title = release.title
|
||||
existingRelease.releaseNotes = release.releaseNotes
|
||||
existingRelease.pageUrl = release.pageURL
|
||||
existingRelease.releaseType = releaseType.rawValue
|
||||
|
||||
let cleanString = release.id.hasPrefix("v") ? release.id.dropFirst() : Substring(release.id)
|
||||
let parts = cleanString.split(separator: ".")
|
||||
if parts.count >= 3 {
|
||||
existingRelease.versionMajor = Int32(parts[0]) ?? 0
|
||||
existingRelease.versionMinor = Int32(parts[1]) ?? 0
|
||||
existingRelease.versionPatch = Int32(parts[2]) ?? 0
|
||||
}
|
||||
|
||||
try? context.save()
|
||||
Logger.services.info("Saving firmware release \(release.id) in database.")
|
||||
}
|
||||
}
|
||||
|
||||
/// Handles the logic of checking ETag -> Checking DB -> Downloading -> Bundle Fallback -> Saving
|
||||
private func processImage(imageName: String, platform: String ) async {
|
||||
let url = Self.imageURLPrefix.appendingPathComponent(imageName)
|
||||
|
||||
// 1. Network: Try to get ETag (Optional - might fail if offline or timeout)
|
||||
let remoteETag = try? await url.eTag()
|
||||
|
||||
let context = container.newBackgroundContext()
|
||||
context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
|
||||
|
||||
// 2. DB: Check if we already have this version or a usable cached version
|
||||
let dbStatus: (isUpToDate: Bool, hasData: Bool) = await context.perform {
|
||||
let request = DeviceHardwareImageEntity.fetchRequest()
|
||||
request.predicate = NSPredicate(format: "fileName == %@", imageName)
|
||||
request.fetchLimit = 1
|
||||
|
||||
if let existing = try? context.fetch(request).first,
|
||||
let data = existing.svgData, !data.isEmpty {
|
||||
|
||||
// A: If we have a remote tag, does it match?
|
||||
if let rTag = remoteETag {
|
||||
return (existing.eTag == rTag, true)
|
||||
}
|
||||
|
||||
// B: We are offline (no remote ETag), but we have data. Keep it.
|
||||
return (true, true)
|
||||
}
|
||||
|
||||
// No data in DB
|
||||
return (false, false)
|
||||
}
|
||||
|
||||
if dbStatus.isUpToDate {
|
||||
Logger.services.debug("Image \(imageName) is up to date (or cached offline).")
|
||||
return
|
||||
}
|
||||
|
||||
// 3. Acquire Data (Network Primary -> Bundle Secondary)
|
||||
var dataToSave: Data?
|
||||
var eTagToSave: String?
|
||||
|
||||
// A: Attempt Network Download (only if we successfully got an ETag previously)
|
||||
if let rTag = remoteETag {
|
||||
if let networkData = try? await url.data(timeout: 5.0) {
|
||||
dataToSave = networkData
|
||||
eTagToSave = rTag
|
||||
}
|
||||
}
|
||||
|
||||
// B: Fallback to Bundle if Network failed or returned no data
|
||||
if dataToSave == nil {
|
||||
Logger.services.debug("Network unavailable or failed for \(imageName). Checking local bundle.")
|
||||
|
||||
// Look in the 'images' subdirectory
|
||||
if let bundleURL = Bundle.main.url(forResource: imageName, withExtension: nil, subdirectory: "images"),
|
||||
let bundleData = try? Data(contentsOf: bundleURL) {
|
||||
|
||||
dataToSave = bundleData
|
||||
// We use "bundled" as a placeholder ETag.
|
||||
// Next time the app runs with internet, "bundled" != "real_etag", forcing an update.
|
||||
eTagToSave = "bundled"
|
||||
}
|
||||
}
|
||||
|
||||
// 4. DB: Save Image and Link to Device
|
||||
guard let finalData = dataToSave, let finalETag = eTagToSave else {
|
||||
Logger.services.error("Could not find image \(imageName) in Network or Bundle.")
|
||||
return
|
||||
}
|
||||
|
||||
await context.perform {
|
||||
// Find the Device (we must fetch it in THIS context)
|
||||
let deviceReq = DeviceHardwareEntity.fetchRequest()
|
||||
deviceReq.predicate = NSPredicate(format: "platformioTarget == %@", platform)
|
||||
|
||||
guard let deviceEntity = try? context.fetch(deviceReq).first else { return }
|
||||
|
||||
// Find or Create Image Entity
|
||||
let imageReq = DeviceHardwareImageEntity.fetchRequest()
|
||||
imageReq.predicate = NSPredicate(format: "fileName == %@", imageName)
|
||||
|
||||
let existingImg = try? context.fetch(imageReq).first
|
||||
let imageEntity = existingImg ?? DeviceHardwareImageEntity(context: context)
|
||||
|
||||
imageEntity.fileName = imageName
|
||||
imageEntity.eTag = finalETag
|
||||
imageEntity.svgData = finalData
|
||||
|
||||
// Create Relationship
|
||||
deviceEntity.addToImages(imageEntity)
|
||||
|
||||
try? context.save()
|
||||
Logger.services.info("Saving \(imageName) in database. eTag=\(finalETag)")
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Helpers
|
||||
|
||||
// Removed @MainActor - this must run on the background context passed in
|
||||
private static func findOrCreateTag(tag: String, context: NSManagedObjectContext) throws -> DeviceHardwareTagEntity {
|
||||
let fetchRequest = DeviceHardwareTagEntity.fetchRequest()
|
||||
fetchRequest.predicate = NSPredicate(format: "tag == %@", tag)
|
||||
fetchRequest.fetchLimit = 1
|
||||
|
||||
if let existingTag = try context.fetch(fetchRequest).first {
|
||||
return existingTag
|
||||
}
|
||||
|
||||
let newTag = DeviceHardwareTagEntity(context: context)
|
||||
newTag.tag = tag
|
||||
return newTag
|
||||
}
|
||||
|
||||
private static func deleteOrphanedTags(context: NSManagedObjectContext) {
|
||||
let req = DeviceHardwareTagEntity.fetchRequest()
|
||||
req.predicate = NSPredicate(format: "devices.@count == 0")
|
||||
if let tags = try? context.fetch(req) {
|
||||
tags.forEach { context.delete($0) }
|
||||
}
|
||||
}
|
||||
|
||||
private static func deleteOrphanedImages(context: NSManagedObjectContext) {
|
||||
let req = DeviceHardwareImageEntity.fetchRequest()
|
||||
req.predicate = NSPredicate(format: "device == nil")
|
||||
if let images = try? context.fetch(req) {
|
||||
images.forEach { context.delete($0) }
|
||||
}
|
||||
}
|
||||
|
||||
// Helper to build compound predicate for firmware deletion (selects orphans)
|
||||
static func firmwareCompoundPredicate(stableVersions: Set<String>, alphaVersions: Set<String>) -> NSPredicate {
|
||||
let stablePredicate = NSCompoundPredicate(andPredicateWithSubpredicates: [
|
||||
NSPredicate(format: "releaseType == %@", FirmwareRelease.ReleaseType.stable.rawValue),
|
||||
NSPredicate(format: "NOT (versionId IN %@)", stableVersions)
|
||||
])
|
||||
let alphaPredicate = NSCompoundPredicate(andPredicateWithSubpredicates: [
|
||||
NSPredicate(format: "releaseType == %@", FirmwareRelease.ReleaseType.alpha.rawValue),
|
||||
NSPredicate(format: "NOT (versionId IN %@)", alphaVersions)
|
||||
])
|
||||
return NSCompoundPredicate(orPredicateWithSubpredicates: [stablePredicate, alphaPredicate])
|
||||
}
|
||||
}
|
||||
|
||||
// Image Manifest Decoding
|
||||
private struct ImageManifest: Codable {
|
||||
let files: [String: [String: String]]
|
||||
let api_hash: String
|
||||
}
|
||||
|
|
@ -826,7 +826,7 @@ extension AccessoryManager {
|
|||
throw AccessoryError.ioFailed("removeNode: Unable to serialize admin packet")
|
||||
}
|
||||
|
||||
let messageDescription = "🛎️ [Device Metadata] Requested for node \(toUser?.longName ?? "#\(toUserNum)") by \(fromUser?.longName ?? "#\(fromUser)")"
|
||||
let messageDescription = "🛎️ [Device Metadata] Requested for node \(toUser?.longName ?? "#\(toUserNum)") by \(fromUser?.longName ?? "#\(fromUserNum)")"
|
||||
try await sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription)
|
||||
return Int64(meshPacket.id)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -353,6 +353,9 @@ class BLETransport: Transport {
|
|||
if let shortName = fetchedMyInfo[0].user?.shortName {
|
||||
device.shortName = shortName
|
||||
}
|
||||
if let version = fetchedMyInfo[0].user?.userNode?.metadata?.firmwareVersion {
|
||||
device.firmwareVersion = version
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// No-op
|
||||
|
|
|
|||
|
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "play_store_icon_114px-4.png",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 12 KiB |
|
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "heltec-ht62-esp32c3-sx1262.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "heltec-mesh-node-t114-case.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "heltec_mesh_pocket.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "heltec-v3-case.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "heltec-vision-master-e213.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "heltec-vision-master-e290.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "heltec-wireless-paper.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "heltec-wireless-tracker.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "heltec-wsl-v3.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "tbeam-s3-core.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 348 KiB |
|
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "2022-04-01T18-01-04.120Z-meshtastic_mesh_device_nano_edition_G1_P1 1.png",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "nano-g2-ultra.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "promicro.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 58 KiB |
|
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "rak4631_case.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "pico.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "seeed_solar.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "wio_tracker_l1_case.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "seeed-xiao-s3.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "seeed-sensecap-indicator.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "solar_node.png",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 1.8 MiB |
|
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "meshtastic_mesh_device_station_edition_overview 1.png",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 57 KiB |
|
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "station-g2.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "tbeam.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "t-deck.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "t-echo.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "thinknode_m1.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "thinknode_m2.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "tlora-c6.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 30 KiB |
|
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "tlora-t3s3-epaper.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "tlora-t3s3-v1.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "tlora-v2-1-1_6.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "tlora-v2-1-1_8.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "tracker-t1000-e.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "t-watch-s3.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "UNPHONE.png",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 756 KiB |
|
|
@ -1,129 +1,160 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
class="svg-icon"
|
||||
style="overflow:hidden;fill:currentColor"
|
||||
viewBox="0 0 909.87988 546.85529"
|
||||
version="1.1"
|
||||
id="svg3"
|
||||
xml:space="preserve"
|
||||
width="909.87988"
|
||||
height="546.85529"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||
id="defs3"><style
|
||||
id="style1">.cls-1{fill:#383838;}.cls-2{fill:#9f9f9e;}.cls-3{fill:#cbcccb;}.cls-4{fill:#b7b7b7;}.cls-5{fill:#353535;}.cls-6{fill:#b1a368;}.cls-7{fill:#2c2d2d;}.cls-10,.cls-11,.cls-8,.cls-9{fill:none;stroke:#050606;}.cls-10,.cls-11,.cls-8{stroke-miterlimit:10;}.cls-8,.cls-9{stroke-width:2px;}.cls-9{stroke-linecap:round;stroke-linejoin:round;}.cls-10{stroke-width:2.04px;}.cls-11{stroke-width:1.99px;}.cls-12{fill:#c08c2d;}.cls-13{fill:#af7a2b;}</style></defs><g
|
||||
id="Layer_7"
|
||||
data-name="Layer 7"
|
||||
transform="translate(-646.6554,-758.05941)"><path
|
||||
class="cls-2"
|
||||
d="m 1545.1753,893.49468 h 4.69 a 5.67,5.67 0 0 1 5.67,5.67 v 84.64998 a 5.67,5.67 0 0 1 -5.67,5.67 h -4.69"
|
||||
id="path1-4" /><rect
|
||||
class="cls-3"
|
||||
x="647.6554"
|
||||
y="862.80469"
|
||||
width="897.52002"
|
||||
height="441.10999"
|
||||
rx="11.7"
|
||||
id="rect2" /><path
|
||||
class="cls-2"
|
||||
d="m 681.12532,862.80468 v 113.47998 a 3.67,3.67 0 0 0 3.67,3.67 h 41 a 2.35,2.35 0 0 1 2.35,2.35 V 1303.9147 H 1517.6053 V 862.80468 Z M 1492.6453,1278.9147 H 753.18532 V 972.01466 a 17.06,17.06 0 0 0 -17.06,-17.06 h -27.5 a 2.5,2.5 0 0 1 -2.5,-2.5 v -62.14998 a 2.5,2.5 0 0 1 2.5,-2.5 h 783.99998 z"
|
||||
id="path2-7" /><path
|
||||
class="cls-3"
|
||||
d="M 1492.6453,887.80468 V 1278.9147 H 753.18532 V 972.01466 a 17,17 0 0 0 -7.2,-13.92 v -70.28998 z"
|
||||
id="path3-7" /><path
|
||||
class="cls-4"
|
||||
d="m 745.98532,887.80468 v 70.28998 a 17,17 0 0 0 -9.86,-3.14 h -27.5 a 2.5,2.5 0 0 1 -2.5,-2.5 v -62.14998 a 2.5,2.5 0 0 1 2.5,-2.5 z"
|
||||
id="path4" /><rect
|
||||
class="cls-2"
|
||||
x="672.10535"
|
||||
y="1011.4448"
|
||||
width="13.53"
|
||||
height="148.39999"
|
||||
id="rect4" /><path
|
||||
class="cls-6"
|
||||
d="m 1077.2923,853.76468 h 71.71 a 2.55,2.55 0 0 1 2.55,2.55 v 6.48 h -76.8 v -6.48 a 2.55,2.55 0 0 1 2.54,-2.55 z"
|
||||
id="path7" /><path
|
||||
class="cls-8"
|
||||
d="m 1082.9205,761.22647 h 60.8838 a 6.1958134,4.8451518 0 0 1 6.1958,4.84516 v 77.32638 h -73.2754 v -77.32638 a 6.1958134,4.8451518 0 0 1 6.1958,-4.84516 z"
|
||||
id="path39"
|
||||
style="fill:#b1a368" /><rect
|
||||
class="cls-8"
|
||||
x="1066.9833"
|
||||
y="778.19855"
|
||||
width="91.504646"
|
||||
height="55.957298"
|
||||
rx="5.5511622"
|
||||
id="rect39"
|
||||
style="fill:#b1a368" /><path
|
||||
class="cls-2"
|
||||
d="m 1158.4522,782.53954 v 47.24724 a 5.5153484,4.3130254 0 0 1 -5.5512,4.34102 h -80.3665 a 5.5511623,4.341032 0 0 1 -5.587,-4.34102 v -47.24724 a 5.5511623,4.341032 0 0 1 5.587,-4.34103 h 80.5098 a 5.5153484,4.3130254 0 0 1 5.4079,4.34103 z"
|
||||
id="path41-4"
|
||||
style="fill:none;stroke:#050606;stroke-width:3.16706;stroke-miterlimit:10" /><rect
|
||||
class="cls-6"
|
||||
x="1079.9424"
|
||||
y="843.73468"
|
||||
width="65.989998"
|
||||
height="10.03"
|
||||
id="rect8" /><path
|
||||
class="cls-8"
|
||||
d="M 1492.6453,887.80468 V 1278.9147 H 753.18532 V 972.01466 a 17.06,17.06 0 0 0 -17.06,-17.06 h -27.5 a 2.5,2.5 0 0 1 -2.5,-2.5 v -62.14998 a 2.5,2.5 0 0 1 2.5,-2.5 h 783.99998 m 25,-25 H 681.12532 v 113.47998 a 3.68,3.68 0 0 0 3.67,3.67 h 41 a 2.35,2.35 0 0 1 2.35,2.35 V 1303.9147 H 1517.6053 V 862.80468 Z"
|
||||
id="path10" /><line
|
||||
class="cls-8"
|
||||
x1="745.99536"
|
||||
y1="958.09467"
|
||||
x2="745.99536"
|
||||
y2="887.80469"
|
||||
id="line10" /><rect
|
||||
class="cls-8"
|
||||
x="672.10535"
|
||||
y="1011.4448"
|
||||
width="13.53"
|
||||
height="148.39999"
|
||||
id="rect11" /><path
|
||||
class="cls-8"
|
||||
d="m 1545.1753,893.49468 h 4.69 a 5.67,5.67 0 0 1 5.67,5.67 v 84.64998 a 5.67,5.67 0 0 1 -5.67,5.67 h -4.69"
|
||||
id="path14" /><path
|
||||
class="cls-10"
|
||||
d="m 1077.2923,853.76468 h 71.71 a 2.55,2.55 0 0 1 2.55,2.55 v 6.48 h -76.8 v -6.48 a 2.55,2.55 0 0 1 2.54,-2.55 z"
|
||||
id="path16" /><rect
|
||||
class="cls-11"
|
||||
x="1079.9424"
|
||||
y="843.73468"
|
||||
width="65.989998"
|
||||
height="10.03"
|
||||
id="rect17" /><path
|
||||
class="cls-2"
|
||||
d="m 725.27532,910.38466 a 14,14 0 1 0 14,14 13.95,13.95 0 0 0 -14,-14 z m 0,21.5 a 7.55,7.55 0 1 1 7.54,-7.55 7.55,7.55 0 0 1 -7.54,7.55 z"
|
||||
id="path19" /><circle
|
||||
class="cls-8"
|
||||
cx="725.27539"
|
||||
cy="924.33466"
|
||||
r="7.5500002"
|
||||
id="circle19" /><circle
|
||||
class="cls-8"
|
||||
cx="725.27539"
|
||||
cy="924.33466"
|
||||
r="13.95"
|
||||
id="circle20" /><path
|
||||
d="m 445.36309,440.05365 c 0,11.52004 10.38375,20.85861 23.19309,20.85861 12.80937,0 23.19311,-9.33857 23.19311,-20.85861 0,-11.52005 -10.38374,-20.85861 -23.19311,-20.85861 -12.80934,0 -23.19309,9.33856 -23.19309,20.85861 z"
|
||||
fill="#ccc"
|
||||
id="path1"
|
||||
style="overflow:hidden;fill:#4d4d4d;stroke-width:0.458227"
|
||||
transform="translate(646.6554,758.05941)" /><path
|
||||
d="m 469.40305,538.40107 c -119.83415,0 -217.31582,-93.40624 -217.31582,-208.23067 0,-114.82425 97.48167,-208.23058 217.31582,-208.23058 119.83417,0 217.31585,93.40633 217.31585,208.23058 0,114.82443 -97.48168,208.23067 -217.31585,208.23067 z m 0,-386.58065 c -102.63515,0 -186.13149,80.00572 -186.13149,178.34998 0,98.32948 83.49634,178.34997 186.13149,178.34997 102.61966,0 186.13151,-80.01997 186.13151,-178.34997 0,-98.34426 -83.51185,-178.34998 -186.13151,-178.34998 z"
|
||||
fill="#ccc"
|
||||
id="path2"
|
||||
style="overflow:hidden;fill:#4d4d4d;stroke-width:0.474832"
|
||||
transform="translate(646.6554,758.05941)" /><path
|
||||
d="m 468.55618,391.96713 c -8.53552,0 -15.46205,-6.22977 -15.46205,-13.90533 v -23.51468 c 0,-22.75028 19.32709,-40.13201 36.39722,-55.47009 12.50833,-11.26363 25.45056,-22.88885 25.45056,-32.16398 0,-23.18095 -20.81195,-42.03718 -46.38573,-42.03718 -26.0067,0 -46.38619,18.0497 -46.38619,41.09158 0,7.67594 -6.92654,13.90533 -15.46208,13.90533 -8.53554,0 -15.46207,-6.22977 -15.46207,-13.9058 0,-37.99002 34.68046,-68.90262 77.31034,-68.90262 42.62989,0 77.31034,31.32967 77.31034,69.84869 0,20.81694 -17.54944,36.5856 -34.51132,51.84064 -13.452,12.07016 -27.33645,24.55758 -27.33645,35.77907 v 23.51468 c 0,7.6764 -6.92702,13.91969 -15.46257,13.91969 z"
|
||||
fill="#ccc"
|
||||
id="path3"
|
||||
style="overflow:hidden;fill:#4d4d4d;stroke-width:0.458227;stroke:#000000;stroke-opacity:1"
|
||||
transform="translate(646.6554,758.05941)" /><rect
|
||||
class="cls-8"
|
||||
x="647.6554"
|
||||
y="862.80469"
|
||||
width="897.52002"
|
||||
height="441.10999"
|
||||
rx="11.7"
|
||||
id="rect28" /></g></svg>
|
||||
class="svg-icon"
|
||||
style="overflow:hidden;fill:currentColor"
|
||||
viewBox="0 0 909.87988 546.85529"
|
||||
version="1.1"
|
||||
id="svg3"
|
||||
xml:space="preserve"
|
||||
width="909.87988"
|
||||
height="546.85529"
|
||||
sodipodi:docname="unknown.svg"
|
||||
inkscape:version="1.4 (e7c3feb1, 2024-10-09)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="0.57169944"
|
||||
inkscape:cx="291.23695"
|
||||
inkscape:cy="107.57401"
|
||||
inkscape:window-width="1472"
|
||||
inkscape:window-height="890"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="38"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="Layer_7" /><defs
|
||||
id="defs3"><style
|
||||
id="style1">.cls-1{fill:#383838;}.cls-2{fill:#9f9f9e;}.cls-3{fill:#cbcccb;}.cls-4{fill:#b7b7b7;}.cls-5{fill:#353535;}.cls-6{fill:#b1a368;}.cls-7{fill:#2c2d2d;}.cls-10,.cls-11,.cls-8,.cls-9{fill:none;stroke:#050606;}.cls-10,.cls-11,.cls-8{stroke-miterlimit:10;}.cls-8,.cls-9{stroke-width:2px;}.cls-9{stroke-linecap:round;stroke-linejoin:round;}.cls-10{stroke-width:2.04px;}.cls-11{stroke-width:1.99px;}.cls-12{fill:#c08c2d;}.cls-13{fill:#af7a2b;}</style></defs><g
|
||||
id="Layer_7"
|
||||
data-name="Layer 7"
|
||||
transform="translate(-646.6554,-758.05941)"><path
|
||||
class="cls-2"
|
||||
d="m 1545.1753,893.49468 h 4.69 a 5.67,5.67 0 0 1 5.67,5.67 v 84.64998 a 5.67,5.67 0 0 1 -5.67,5.67 h -4.69"
|
||||
id="path1-4" /><rect
|
||||
class="cls-3"
|
||||
x="647.6554"
|
||||
y="862.80469"
|
||||
width="897.52002"
|
||||
height="441.10999"
|
||||
rx="11.7"
|
||||
id="rect2" /><path
|
||||
class="cls-2"
|
||||
d="m 681.12532,862.80468 v 113.47998 a 3.67,3.67 0 0 0 3.67,3.67 h 41 a 2.35,2.35 0 0 1 2.35,2.35 V 1303.9147 H 1517.6053 V 862.80468 Z M 1492.6453,1278.9147 H 753.18532 V 972.01466 a 17.06,17.06 0 0 0 -17.06,-17.06 h -27.5 a 2.5,2.5 0 0 1 -2.5,-2.5 v -62.14998 a 2.5,2.5 0 0 1 2.5,-2.5 h 783.99998 z"
|
||||
id="path2-7" /><path
|
||||
class="cls-3"
|
||||
d="M 1492.6453,887.80468 V 1278.9147 H 753.18532 V 972.01466 a 17,17 0 0 0 -7.2,-13.92 v -70.28998 z"
|
||||
id="path3-7"
|
||||
style="fill:#ffffff" /><path
|
||||
class="cls-4"
|
||||
d="m 745.98532,887.80468 v 70.28998 a 17,17 0 0 0 -9.86,-3.14 h -27.5 a 2.5,2.5 0 0 1 -2.5,-2.5 v -62.14998 a 2.5,2.5 0 0 1 2.5,-2.5 z"
|
||||
id="path4" /><rect
|
||||
class="cls-2"
|
||||
x="672.10535"
|
||||
y="1011.4448"
|
||||
width="13.53"
|
||||
height="148.39999"
|
||||
id="rect4" /><path
|
||||
class="cls-6"
|
||||
d="m 1077.2923,853.76468 h 71.71 a 2.55,2.55 0 0 1 2.55,2.55 v 6.48 h -76.8 v -6.48 a 2.55,2.55 0 0 1 2.54,-2.55 z"
|
||||
id="path7" /><path
|
||||
class="cls-8"
|
||||
d="m 1082.9205,761.22647 h 60.8838 a 6.1958134,4.8451518 0 0 1 6.1958,4.84516 v 77.32638 h -73.2754 v -77.32638 a 6.1958134,4.8451518 0 0 1 6.1958,-4.84516 z"
|
||||
id="path39"
|
||||
style="fill:#b1a368" /><rect
|
||||
class="cls-8"
|
||||
x="1066.9833"
|
||||
y="778.19855"
|
||||
width="91.504646"
|
||||
height="55.957298"
|
||||
rx="5.5511622"
|
||||
id="rect39"
|
||||
style="fill:#b1a368" /><path
|
||||
class="cls-2"
|
||||
d="m 1158.4522,782.53954 v 47.24724 a 5.5153484,4.3130254 0 0 1 -5.5512,4.34102 h -80.3665 a 5.5511623,4.341032 0 0 1 -5.587,-4.34102 v -47.24724 a 5.5511623,4.341032 0 0 1 5.587,-4.34103 h 80.5098 a 5.5153484,4.3130254 0 0 1 5.4079,4.34103 z"
|
||||
id="path41-4"
|
||||
style="fill:none;stroke:#050606;stroke-width:3.16706;stroke-miterlimit:10" /><rect
|
||||
class="cls-6"
|
||||
x="1079.9424"
|
||||
y="843.73468"
|
||||
width="65.989998"
|
||||
height="10.03"
|
||||
id="rect8" /><path
|
||||
class="cls-8"
|
||||
d="M 1492.6453,887.80468 V 1278.9147 H 753.18532 V 972.01466 a 17.06,17.06 0 0 0 -17.06,-17.06 h -27.5 a 2.5,2.5 0 0 1 -2.5,-2.5 v -62.14998 a 2.5,2.5 0 0 1 2.5,-2.5 h 783.99998 m 25,-25 H 681.12532 v 113.47998 a 3.68,3.68 0 0 0 3.67,3.67 h 41 a 2.35,2.35 0 0 1 2.35,2.35 V 1303.9147 H 1517.6053 V 862.80468 Z"
|
||||
id="path10" /><line
|
||||
class="cls-8"
|
||||
x1="745.99536"
|
||||
y1="958.09467"
|
||||
x2="745.99536"
|
||||
y2="887.80469"
|
||||
id="line10" /><rect
|
||||
class="cls-8"
|
||||
x="672.10535"
|
||||
y="1011.4448"
|
||||
width="13.53"
|
||||
height="148.39999"
|
||||
id="rect11" /><path
|
||||
class="cls-8"
|
||||
d="m 1545.1753,893.49468 h 4.69 a 5.67,5.67 0 0 1 5.67,5.67 v 84.64998 a 5.67,5.67 0 0 1 -5.67,5.67 h -4.69"
|
||||
id="path14" /><path
|
||||
class="cls-10"
|
||||
d="m 1077.2923,853.76468 h 71.71 a 2.55,2.55 0 0 1 2.55,2.55 v 6.48 h -76.8 v -6.48 a 2.55,2.55 0 0 1 2.54,-2.55 z"
|
||||
id="path16" /><rect
|
||||
class="cls-11"
|
||||
x="1079.9424"
|
||||
y="843.73468"
|
||||
width="65.989998"
|
||||
height="10.03"
|
||||
id="rect17" /><path
|
||||
class="cls-2"
|
||||
d="m 725.27532,910.38466 a 14,14 0 1 0 14,14 13.95,13.95 0 0 0 -14,-14 z m 0,21.5 a 7.55,7.55 0 1 1 7.54,-7.55 7.55,7.55 0 0 1 -7.54,7.55 z"
|
||||
id="path19" /><circle
|
||||
class="cls-8"
|
||||
cx="725.27539"
|
||||
cy="924.33466"
|
||||
r="7.5500002"
|
||||
id="circle19" /><circle
|
||||
class="cls-8"
|
||||
cx="725.27539"
|
||||
cy="924.33466"
|
||||
r="13.95"
|
||||
id="circle20" /><path
|
||||
d="m 445.36309,440.05365 c 0,11.52004 10.38375,20.85861 23.19309,20.85861 12.80937,0 23.19311,-9.33857 23.19311,-20.85861 0,-11.52005 -10.38374,-20.85861 -23.19311,-20.85861 -12.80934,0 -23.19309,9.33856 -23.19309,20.85861 z"
|
||||
fill="#ccc"
|
||||
id="path1"
|
||||
style="overflow:hidden;fill:#4d4d4d;stroke-width:0.458227"
|
||||
transform="translate(646.6554,758.05941)" /><path
|
||||
d="m 469.40305,538.40107 c -119.83415,0 -217.31582,-93.40624 -217.31582,-208.23067 0,-114.82425 97.48167,-208.23058 217.31582,-208.23058 119.83417,0 217.31585,93.40633 217.31585,208.23058 0,114.82443 -97.48168,208.23067 -217.31585,208.23067 z m 0,-386.58065 c -102.63515,0 -186.13149,80.00572 -186.13149,178.34998 0,98.32948 83.49634,178.34997 186.13149,178.34997 102.61966,0 186.13151,-80.01997 186.13151,-178.34997 0,-98.34426 -83.51185,-178.34998 -186.13151,-178.34998 z"
|
||||
fill="#ccc"
|
||||
id="path2"
|
||||
style="overflow:hidden;fill:#4d4d4d;stroke-width:0.474832"
|
||||
transform="translate(646.6554,758.05941)" /><path
|
||||
d="m 468.55618,391.96713 c -8.53552,0 -15.46205,-6.22977 -15.46205,-13.90533 v -23.51468 c 0,-22.75028 19.32709,-40.13201 36.39722,-55.47009 12.50833,-11.26363 25.45056,-22.88885 25.45056,-32.16398 0,-23.18095 -20.81195,-42.03718 -46.38573,-42.03718 -26.0067,0 -46.38619,18.0497 -46.38619,41.09158 0,7.67594 -6.92654,13.90533 -15.46208,13.90533 -8.53554,0 -15.46207,-6.22977 -15.46207,-13.9058 0,-37.99002 34.68046,-68.90262 77.31034,-68.90262 42.62989,0 77.31034,31.32967 77.31034,69.84869 0,20.81694 -17.54944,36.5856 -34.51132,51.84064 -13.452,12.07016 -27.33645,24.55758 -27.33645,35.77907 v 23.51468 c 0,7.6764 -6.92702,13.91969 -15.46257,13.91969 z"
|
||||
fill="#ccc"
|
||||
id="path3"
|
||||
style="overflow:hidden;fill:#4d4d4d;stroke-width:0.458227;stroke:#000000;stroke-opacity:1"
|
||||
transform="translate(646.6554,758.05941)" /><rect
|
||||
class="cls-8"
|
||||
x="647.6554"
|
||||
y="862.80469"
|
||||
width="897.52002"
|
||||
height="441.10999"
|
||||
rx="11.7"
|
||||
id="rect28" /></g><path
|
||||
style="fill:#ffffff;fill-opacity:0;stroke-width:0.92"
|
||||
d="m 107.41785,363.03448 -0.23786,-156.30546 -2.99,-3.72057 -2.99,-3.72058 v -34.09394 -34.09394 l 150.64998,0.048 150.64999,0.048 -8.28,3.06943 c -19.31509,7.16019 -34.46167,14.82453 -50.21721,25.41044 -50.57644,33.98158 -84.35747,88.86991 -91.06203,147.96009 -1.43336,12.63279 -0.63536,44.7022 1.3876,55.76392 7.76201,42.44321 25.98398,77.92651 55.67763,108.41989 17.37837,17.84644 33.98994,30.29944 55.42867,41.55255 l 11.30534,5.93414 -134.54212,0.0167 -134.54213,0.0167 z"
|
||||
id="path11" /><path
|
||||
style="fill:#ffffff;fill-opacity:0;stroke-width:0.92"
|
||||
d="m 107.41785,363.03448 -0.23786,-156.30546 -2.99,-3.72057 -2.99,-3.72058 v -34.09394 -34.09394 l 150.64998,0.048 150.64999,0.048 -8.28,3.06943 c -19.31509,7.16019 -34.46167,14.82453 -50.21721,25.41044 -50.57644,33.98158 -84.35747,88.86991 -91.06203,147.96009 -1.43336,12.63279 -0.63536,44.7022 1.3876,55.76392 7.76201,42.44321 25.98398,77.92651 55.67763,108.41989 17.37837,17.84644 33.98994,30.29944 55.42867,41.55255 l 11.30534,5.93414 -134.54212,0.0167 -134.54213,0.0167 z"
|
||||
id="path12" /><path
|
||||
style="fill:#ffffff;fill-opacity:0;stroke-width:0.92"
|
||||
d="m 107.41785,363.03448 -0.23786,-156.30546 -2.99,-3.72057 -2.99,-3.72058 v -34.09394 -34.09394 l 150.64998,0.048 150.64999,0.048 -8.28,3.06943 c -19.31509,7.16019 -34.46167,14.82453 -50.21721,25.41044 -50.57644,33.98158 -84.35747,88.86991 -91.06203,147.96009 -1.43336,12.63279 -0.63536,44.7022 1.3876,55.76392 7.76201,42.44321 25.98398,77.92651 55.67763,108.41989 17.37837,17.84644 33.98994,30.29944 55.42867,41.55255 l 11.30534,5.93414 -134.54212,0.0167 -134.54213,0.0167 z"
|
||||
id="path13" /></svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 9.1 KiB |
|
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "wio-tracker-wm1110.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "rak-wismeshtap.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "seeed_xiao_nrf52_kit.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "rak11310.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
},
|
||||
"symbols" : [
|
||||
{
|
||||
"filename" : "usb-symbol.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg height="600" width="800" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="Notes" font-family="'LucidaGrande', 'Lucida Grande', sans-serif" font-size="13">
|
||||
<rect fill="white" height="600.0" width="800.0" x="0.0" y="0.0" />
|
||||
<g font-size="13">
|
||||
<text x="18.0" y="176.0">Small</text>
|
||||
<text x="18.0" y="376.0">Medium</text>
|
||||
<text x="18.0" y="576.0">Large</text>
|
||||
</g>
|
||||
<g font-size="9">
|
||||
<text x="250.0" y="30.0">Ultralight</text>
|
||||
<text x="450.0" y="30.0">Regular</text>
|
||||
<text x="650.0" y="30.0">Black</text>
|
||||
<text id="template-version" fill="#505050" text-anchor="end" x="785.0" y="575.0">Template v.3.0</text>
|
||||
<a href="https://github.com/swhitty/SwiftDraw">
|
||||
<text fill="#505050" text-anchor="end" x="785.0" y="590.0">https://github.com/swhitty/SwiftDraw</text>
|
||||
</a>
|
||||
</g>
|
||||
</g>
|
||||
<g id="Guides" stroke="rgb(39, 170, 225)" stroke-width="0.5">
|
||||
<path id="Capline-S" d="M18,76 l800,0" />
|
||||
<path id="H-reference" d="M85,145.755 L87.685,145.755 L113.369,79.287 L114.052,79.287 L114.052,76 L112.148,76 L85,145.755 Z M95.693,121.536 L130.996,121.536 L130.263,119.313 L96.474,119.313 L95.693,121.536 Z M139.15,145.755 L141.787,145.755 L114.638,76 L113.466,76 L113.466,79.287 L139.15,145.755 Z" stroke="none" transform="translate(0,0)" />
|
||||
<path id="Baseline-S" d="M18,146 l800,0" />
|
||||
<path id="left-margin-Ultralight-S" d="M221,56 l0,110" />
|
||||
<path id="right-margin-Ultralight-S" d="M309,56 l0,110" />
|
||||
<path id="left-margin-Regular-S" d="M421,56 l0,110" />
|
||||
<path id="right-margin-Regular-S" d="M509,56 l0,110" />
|
||||
<path id="left-margin-Black-S" d="M621,56 l0,110" />
|
||||
<path id="right-margin-Black-S" d="M709,56 l0,110" />
|
||||
<path id="Capline-M" d="M18,276 l800,0" />
|
||||
<path id="Baseline-M" d="M18,346 l800,0" />
|
||||
<path id="Capline-L" d="M18,476 l800,0" />
|
||||
<path id="Baseline-L" d="M18,546 l800,0" />
|
||||
</g>
|
||||
<g id="Symbols">
|
||||
<g id="Ultralight-S" transform="translate(0,0)">
|
||||
<path d="M265.002,76.625 L260.238,84.262 L263.636,84.262 L263.636,123.299 L254.963,115.698 C254.403,115.051 254.011,114.205 253.989,113.334 C253.989,109.812 253.988,107.721 253.986,106.951 C255.592,106.429 256.751,105.027 256.751,103.361 C256.751,101.252 254.903,99.542 252.626,99.542 C250.347,99.542 248.5,101.252 248.5,103.361 C248.5,105.027 249.658,106.429 251.263,106.951 L251.262,113.259 C251.262,114.969 252.275,116.761 253.463,117.901 C253.427,117.87 253.39,117.838 253.463,117.903 C253.493,117.927 262.664,125.967 262.664,125.967 C263.223,126.612 263.613,127.458 263.636,128.328 L263.636,132.744 C260.485,133.329 258.111,135.906 258.111,138.996 C258.111,142.519 261.196,145.375 265,145.375 C268.805,145.375 271.889,142.519 271.889,138.996 C271.889,135.905 269.514,133.329 266.36,132.744 L266.36,128.405 C266.36,128.393 266.361,128.383 266.36,128.371 L266.36,118.775 C266.384,117.907 266.775,117.062 267.335,116.417 C267.335,116.417 276.506,108.379 276.535,108.354 C276.609,108.29 276.571,108.322 276.536,108.354 C277.723,107.213 278.736,105.421 278.736,103.711 L278.734,97.631 L281.5,97.631 L281.5,89.992 L273.25,89.992 L273.25,97.631 L276.012,97.631 C276.012,97.631 276.009,99.232 276.009,103.785 C275.987,104.656 275.595,105.503 275.035,106.15 L266.361,113.752 L266.361,84.262 L269.764,84.262 Z" />
|
||||
</g>
|
||||
<g id="Regular-S" transform="translate(0,0)">
|
||||
<path d="M465.002,76.625 L460.238,84.262 L463.636,84.262 L463.636,123.299 L454.963,115.698 C454.403,115.051 454.011,114.205 453.989,113.334 C453.989,109.812 453.988,107.721 453.986,106.951 C455.592,106.429 456.751,105.027 456.751,103.361 C456.751,101.252 454.903,99.542 452.626,99.542 C450.347,99.542 448.5,101.252 448.5,103.361 C448.5,105.027 449.658,106.429 451.263,106.951 L451.262,113.259 C451.262,114.969 452.275,116.761 453.463,117.901 C453.427,117.87 453.39,117.838 453.463,117.903 C453.493,117.927 462.664,125.967 462.664,125.967 C463.223,126.612 463.613,127.458 463.636,128.328 L463.636,132.744 C460.485,133.329 458.111,135.906 458.111,138.996 C458.111,142.519 461.196,145.375 465,145.375 C468.805,145.375 471.889,142.519 471.889,138.996 C471.889,135.905 469.514,133.329 466.36,132.744 L466.36,128.405 C466.36,128.393 466.361,128.383 466.36,128.371 L466.36,118.775 C466.384,117.907 466.775,117.062 467.335,116.417 C467.335,116.417 476.506,108.379 476.535,108.354 C476.609,108.29 476.571,108.322 476.536,108.354 C477.723,107.213 478.736,105.421 478.736,103.711 L478.734,97.631 L481.5,97.631 L481.5,89.992 L473.25,89.992 L473.25,97.631 L476.012,97.631 C476.012,97.631 476.009,99.232 476.009,103.785 C475.987,104.656 475.595,105.503 475.035,106.15 L466.361,113.752 L466.361,84.262 L469.764,84.262 Z" />
|
||||
</g>
|
||||
<g id="Black-S" transform="translate(0,0)">
|
||||
<path d="M665.002,76.625 L660.238,84.262 L663.636,84.262 L663.636,123.299 L654.963,115.698 C654.403,115.051 654.011,114.205 653.989,113.334 C653.989,109.812 653.988,107.721 653.986,106.951 C655.592,106.429 656.751,105.027 656.751,103.361 C656.751,101.252 654.903,99.542 652.625,99.542 C650.347,99.542 648.5,101.252 648.5,103.361 C648.5,105.027 649.658,106.429 651.263,106.951 L651.262,113.259 C651.262,114.969 652.275,116.761 653.463,117.901 C653.427,117.87 653.39,117.838 653.463,117.903 C653.493,117.927 662.664,125.967 662.664,125.967 C663.223,126.612 663.613,127.458 663.636,128.328 L663.636,132.744 C660.485,133.329 658.111,135.906 658.111,138.996 C658.111,142.519 661.196,145.375 665,145.375 C668.805,145.375 671.889,142.519 671.889,138.996 C671.889,135.905 669.514,133.329 666.36,132.744 L666.36,128.405 C666.36,128.393 666.361,128.383 666.36,128.371 L666.36,118.775 C666.384,117.907 666.775,117.062 667.335,116.417 C667.335,116.417 676.506,108.379 676.535,108.354 C676.609,108.29 676.571,108.322 676.536,108.354 C677.724,107.213 678.736,105.421 678.736,103.711 L678.734,97.631 L681.5,97.631 L681.5,89.992 L673.25,89.992 L673.25,97.631 L676.012,97.631 C676.012,97.631 676.009,99.232 676.009,103.785 C675.987,104.656 675.595,105.503 675.035,106.15 L666.361,113.752 L666.361,84.262 L669.764,84.262 Z" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.2 KiB |
12
Meshtastic/Extensions/CoreData/DeviceHardwareEntity.swift
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
//
|
||||
// DeviceHardwareEntity.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Created by Jake Bordens on 12/10/25.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
extension DeviceHardwareEntity {
|
||||
|
||||
}
|
||||
60
Meshtastic/Extensions/Image.swift
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
//
|
||||
// Image.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Created by jake on 12/5/25.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
extension Image {
|
||||
// Initializer taking a URL
|
||||
// init?(svgURL: URL, maxSize: CGSize? = nil) {
|
||||
// guard let svg = SVGKImage(contentsOf: svgURL) else { return nil }
|
||||
// self.init(svgkImage: svg, maxSize: maxSize)
|
||||
// }
|
||||
//
|
||||
// // Initializer taking Data
|
||||
// init?(svgData: Data, maxSize: CGSize? = nil) {
|
||||
// guard let svg = SVGKImage(data: svgData) else { return nil }
|
||||
// self.init(svgkImage: svg, maxSize: maxSize)
|
||||
// }
|
||||
//
|
||||
// // MARK: - Private Shared Logic
|
||||
//
|
||||
//// private init?(svgkImage svg: SVGKImage, maxSize: CGSize?) {
|
||||
//// guard let root = svg.domDocument?.rootElement as? SVGSVGElement else { return nil }
|
||||
////
|
||||
//// // Calculate the intrinsic size, handling missing width/height attributes
|
||||
//// // by falling back to the viewBox if necessary.
|
||||
//// let intrinsicSize: CGSize = {
|
||||
//// if let w = root.width, w.valueInSpecifiedUnits > 0,
|
||||
//// let h = root.height, h.valueInSpecifiedUnits > 0 {
|
||||
//// return CGSize(width: CGFloat(root.width.valueInSpecifiedUnits),
|
||||
//// height: CGFloat(root.height.valueInSpecifiedUnits))
|
||||
//// } else if root.hasAttribute("viewBox") {
|
||||
//// let viewBox = root.viewBox
|
||||
//// if viewBox.width > 0, viewBox.height > 0 {
|
||||
//// return CGSize(width: CGFloat(viewBox.width),
|
||||
//// height: CGFloat(viewBox.height))
|
||||
//// }
|
||||
//// }
|
||||
//// return svg.size // Fallback
|
||||
//// }()
|
||||
////
|
||||
//// guard intrinsicSize.width > 0, intrinsicSize.height > 0 else { return nil }
|
||||
////
|
||||
//// // Apply scaling if maxSize is provided
|
||||
//// if let maxSize {
|
||||
//// let scale = min(maxSize.width / intrinsicSize.width,
|
||||
//// maxSize.height / intrinsicSize.height)
|
||||
//// svg.size = CGSize(width: intrinsicSize.width * scale,
|
||||
//// height: intrinsicSize.height * scale)
|
||||
//// } else {
|
||||
//// svg.size = intrinsicSize
|
||||
//// }
|
||||
////
|
||||
//// guard let uiImage = svg.uiImage else { return nil }
|
||||
//// self.init(uiImage: uiImage)
|
||||
//// }
|
||||
}
|
||||
|
|
@ -63,4 +63,49 @@ extension URL {
|
|||
var creationDate: Date? {
|
||||
return attributes?[.creationDate] as? Date
|
||||
}
|
||||
|
||||
/// Checks if the URL points to a valid file without downloading the body.
|
||||
/// - Parameter timeout: How long to wait before failing (default: 5 seconds).
|
||||
/// - Returns: True if the server returns a 200 OK status.
|
||||
func isValidDownload(timeout: TimeInterval = 5.0) async -> Bool {
|
||||
var request = URLRequest(url: self)
|
||||
request.httpMethod = "HEAD"
|
||||
request.timeoutInterval = timeout
|
||||
|
||||
do {
|
||||
let (_, response) = try await URLSession.shared.data(for: request)
|
||||
|
||||
guard let httpResponse = response as? HTTPURLResponse else {
|
||||
return false
|
||||
}
|
||||
|
||||
// Accept 200 (OK).
|
||||
// Depending on your needs, you might also accept 200...299
|
||||
return httpResponse.statusCode == 200
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if the URL points to a valid file (Closure based for older iOS).
|
||||
func isValidDownload(timeout: TimeInterval = 5.0, completion: @escaping (Bool) -> Void) {
|
||||
var request = URLRequest(url: self)
|
||||
request.httpMethod = "HEAD"
|
||||
request.timeoutInterval = timeout
|
||||
|
||||
let task = URLSession.shared.dataTask(with: request) { _, response, error in
|
||||
if let _ = error {
|
||||
completion(false)
|
||||
return
|
||||
}
|
||||
|
||||
if let httpResponse = response as? HTTPURLResponse,
|
||||
httpResponse.statusCode == 200 {
|
||||
completion(true)
|
||||
} else {
|
||||
completion(false)
|
||||
}
|
||||
}
|
||||
task.resume()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -81,6 +81,8 @@ extension UserDefaults {
|
|||
case autoconnectOnDiscovery
|
||||
case manualConnections
|
||||
case testIntEnum
|
||||
case lastDeviceAPIUpdate
|
||||
case lastFirmwareAPIUpdate
|
||||
}
|
||||
|
||||
func reset() {
|
||||
|
|
@ -209,6 +211,11 @@ extension UserDefaults {
|
|||
}
|
||||
}
|
||||
}
|
||||
@UserDefault(.lastDeviceAPIUpdate, defaultValue: .distantPast)
|
||||
static var lastDeviceAPIUpdate: Date
|
||||
|
||||
@UserDefault(.lastFirmwareAPIUpdate, defaultValue: .distantPast)
|
||||
static var lastFirmwareAPIUpdate: Date
|
||||
}
|
||||
|
||||
enum TestIntEnum: Int, Decodable {
|
||||
|
|
|
|||
|
|
@ -127,6 +127,8 @@ func myInfoPacket (myInfo: MyNodeInfo, peripheralId: String, context: NSManagedO
|
|||
myInfoEntity.myNodeNum = Int64(myInfo.myNodeNum)
|
||||
myInfoEntity.rebootCount = Int32(myInfo.rebootCount)
|
||||
myInfoEntity.deviceId = myInfo.deviceID
|
||||
myInfoEntity.pioEnv = myInfo.pioEnv
|
||||
|
||||
do {
|
||||
try context.save()
|
||||
Logger.data.info("💾 Saved a new myInfo for node: \(myInfo.myNodeNum.toHex(), privacy: .public)")
|
||||
|
|
@ -141,6 +143,7 @@ func myInfoPacket (myInfo: MyNodeInfo, peripheralId: String, context: NSManagedO
|
|||
fetchedMyInfo[0].peripheralId = peripheralId
|
||||
fetchedMyInfo[0].myNodeNum = Int64(myInfo.myNodeNum)
|
||||
fetchedMyInfo[0].rebootCount = Int32(myInfo.rebootCount)
|
||||
fetchedMyInfo[0].pioEnv = myInfo.pioEnv
|
||||
|
||||
do {
|
||||
try context.save()
|
||||
|
|
@ -314,12 +317,14 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje
|
|||
newUser.shortName = nodeInfo.user.shortName
|
||||
newUser.hwModel = String(describing: nodeInfo.user.hwModel).uppercased()
|
||||
newUser.hwModelId = Int32(nodeInfo.user.hwModel.rawValue)
|
||||
Task {
|
||||
Api().loadDeviceHardwareData { (hw) in
|
||||
let dh = hw.first(where: { $0.hwModel == newUser.hwModelId })
|
||||
newUser.hwDisplayName = dh?.displayName
|
||||
}
|
||||
|
||||
let fetchRequest = DeviceHardwareEntity.fetchRequest()
|
||||
fetchRequest.predicate = NSPredicate(format: "hwModel == %d", newUser.hwModelId)
|
||||
let fetchedHardware = try context.fetch(fetchRequest)
|
||||
if let hardwareEntity = fetchedHardware.first {
|
||||
newUser.hwDisplayName = hardwareEntity.displayName
|
||||
}
|
||||
|
||||
newUser.isLicensed = nodeInfo.user.isLicensed
|
||||
newUser.role = Int32(nodeInfo.user.role.rawValue)
|
||||
if !nodeInfo.user.publicKey.isEmpty {
|
||||
|
|
@ -429,22 +434,13 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje
|
|||
fetchedNode[0].user?.unmessagable = false
|
||||
}
|
||||
}
|
||||
Task {
|
||||
Api().loadDeviceHardwareData { (hw: [DeviceHardware]) in
|
||||
guard !hw.isEmpty,
|
||||
let firstNode = fetchedNode.first,
|
||||
let user = firstNode.user else {
|
||||
Logger.data.error("Error: Required DeviceHardware data is missing or array is empty.")
|
||||
return
|
||||
}
|
||||
|
||||
let dh = hw.first(where: { $0.hwModel == user.hwModelId })
|
||||
|
||||
if let deviceHardware = dh {
|
||||
firstNode.user?.hwDisplayName = deviceHardware.displayName
|
||||
} else {
|
||||
Logger.data.error("No matching hardware model found for ID: \(user.hwModelId, privacy: .public)")
|
||||
}
|
||||
|
||||
if let user = fetchedNode.first?.user {
|
||||
let fetchRequest = DeviceHardwareEntity.fetchRequest()
|
||||
fetchRequest.predicate = NSPredicate(format: "hwModel == %d", user.hwModelId)
|
||||
let fetchedHardware = try context.fetch(fetchRequest)
|
||||
if let hardwareEntity = fetchedHardware.first {
|
||||
user.hwDisplayName = hardwareEntity.displayName
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -133,6 +133,8 @@
|
|||
<string>bluetooth-central</string>
|
||||
<string>location</string>
|
||||
</array>
|
||||
<key>UIFileSharingEnabled</key>
|
||||
<true/>
|
||||
<key>UILaunchScreen</key>
|
||||
<dict/>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
|
|
@ -158,6 +160,28 @@
|
|||
</array>
|
||||
<key>UISupportsDocumentBrowser</key>
|
||||
<true/>
|
||||
<key>UTExportedTypeDeclarations</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>UTTypeConformsTo</key>
|
||||
<array>
|
||||
<string>public.data</string>
|
||||
</array>
|
||||
<key>UTTypeDescription</key>
|
||||
<string>UF2 Firmware</string>
|
||||
<key>UTTypeIconFiles</key>
|
||||
<array/>
|
||||
<key>UTTypeIdentifier</key>
|
||||
<string>com.meshtastic.uf2-firmware</string>
|
||||
<key>UTTypeTagSpecification</key>
|
||||
<dict>
|
||||
<key>public.filename-extension</key>
|
||||
<array>
|
||||
<string>uf2</string>
|
||||
</array>
|
||||
</dict>
|
||||
</dict>
|
||||
</array>
|
||||
<key>UTImportedTypeDeclarations</key>
|
||||
<array>
|
||||
<dict>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="24299" systemVersion="25A362" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="24299" systemVersion="25B78" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
|
||||
<entity name="AmbientLightingConfigEntity" representedClassName="AmbientLightingConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="blue" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="current" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
|
|
@ -73,6 +73,26 @@
|
|||
<attribute name="tzdef" optional="YES" attributeType="String"/>
|
||||
<relationship name="deviceConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="deviceConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="DeviceHardwareEntity" representedClassName="DeviceHardwareEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="activelySupported" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="architecture" optional="YES" attributeType="String"/>
|
||||
<attribute name="displayName" optional="YES" attributeType="String"/>
|
||||
<attribute name="hwModel" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="hwModelSlug" optional="YES" attributeType="String"/>
|
||||
<attribute name="platformioTarget" optional="YES" attributeType="String"/>
|
||||
<relationship name="images" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="DeviceHardwareImageEntity" inverseName="device" inverseEntity="DeviceHardwareImageEntity"/>
|
||||
<relationship name="tags" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="DeviceHardwareTagEntity" inverseName="devices" inverseEntity="DeviceHardwareTagEntity"/>
|
||||
</entity>
|
||||
<entity name="DeviceHardwareImageEntity" representedClassName="DeviceHardwareImageEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="eTag" optional="YES" attributeType="String"/>
|
||||
<attribute name="fileName" optional="YES" attributeType="String"/>
|
||||
<attribute name="svgData" optional="YES" attributeType="Binary"/>
|
||||
<relationship name="device" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="DeviceHardwareEntity" inverseName="images" inverseEntity="DeviceHardwareEntity"/>
|
||||
</entity>
|
||||
<entity name="DeviceHardwareTagEntity" representedClassName="DeviceHardwareTagEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="tag" attributeType="String"/>
|
||||
<relationship name="devices" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="DeviceHardwareEntity" inverseName="tags" inverseEntity="DeviceHardwareEntity"/>
|
||||
</entity>
|
||||
<entity name="DeviceMetadataEntity" representedClassName="DeviceMetadataEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="canShutdown" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="deviceStateVersion" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
|
|
@ -118,6 +138,16 @@
|
|||
<attribute name="usePWM" optional="YES" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<relationship name="externalNotificationConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="externalNotificationConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="FirmwareReleaseEntity" representedClassName="FirmwareReleaseEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="pageUrl" optional="YES" attributeType="String"/>
|
||||
<attribute name="releaseNotes" optional="YES" attributeType="String"/>
|
||||
<attribute name="releaseType" optional="YES" attributeType="String"/>
|
||||
<attribute name="title" optional="YES" attributeType="String"/>
|
||||
<attribute name="versionId" attributeType="String"/>
|
||||
<attribute name="versionMajor" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="versionMinor" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="versionPatch" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
</entity>
|
||||
<entity name="LocationEntity" representedClassName="LocationEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="altitude" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="heading" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
|
|
@ -199,6 +229,7 @@
|
|||
<attribute name="minAppVersion" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="myNodeNum" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="peripheralId" optional="YES" attributeType="String"/>
|
||||
<attribute name="pioEnv" optional="YES" attributeType="String"/>
|
||||
<attribute name="rebootCount" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="registered" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<relationship name="channels" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="ChannelEntity" inverseName="myInfoChannel" inverseEntity="ChannelEntity"/>
|
||||
|
|
|
|||
|
|
@ -217,5 +217,7 @@ struct MeshtasticAppleApp: App {
|
|||
.environment(\.managedObjectContext, persistenceController.container.viewContext)
|
||||
.environmentObject(appState)
|
||||
.environmentObject(accessoryManager)
|
||||
.environmentObject(appState.router)
|
||||
.environmentObject(MeshtasticAPI.shared)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,18 @@ class MeshtasticAppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificat
|
|||
if locationsHandler.backgroundActivity {
|
||||
locationsHandler.backgroundActivity = true
|
||||
}
|
||||
|
||||
if Calendar.current.date(byAdding: .day, value: 1, to: UserDefaults.lastDeviceAPIUpdate)! < Date() {
|
||||
// lastUpdate is older than 1 day
|
||||
Task {
|
||||
Logger.services.info("📋 Device list API data is older than one day, updating...")
|
||||
try await MeshtasticAPI.shared.refreshDevicesAPIData()
|
||||
UserDefaults.lastDeviceAPIUpdate = Date()
|
||||
}
|
||||
} else {
|
||||
Logger.services.info("📋 Device list API data update is not needed...")
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
// Lets us show the notification in the app in the foreground
|
||||
|
|
|
|||
317
Meshtastic/Model/Firmware/FirmwareFile.swift
Normal file
|
|
@ -0,0 +1,317 @@
|
|||
//
|
||||
// FirmwareFile.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Created by jake on 12/13/25.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
import CoreData
|
||||
|
||||
extension FirmwareFile {
|
||||
enum FirmwareFileError: Error, LocalizedError {
|
||||
case invalidFilenamePrefix
|
||||
case parseError
|
||||
case unknownFileType
|
||||
case unknownTarget
|
||||
case unknownArchitecture
|
||||
case unknownVersion
|
||||
case unknownReleaseType
|
||||
case unknownRemoteURL
|
||||
|
||||
var errorDescription: String? {
|
||||
switch self {
|
||||
case .invalidFilenamePrefix:
|
||||
return "Filename must start with `firmware-`."
|
||||
case .parseError:
|
||||
return "Unable to parse the components of the filename (target and version)."
|
||||
case .unknownFileType:
|
||||
return "Unknown file type. May not be a firmware file."
|
||||
case .unknownTarget:
|
||||
return "Unknown platformio target."
|
||||
case .unknownArchitecture:
|
||||
return "Unknown architecture."
|
||||
case .unknownVersion:
|
||||
return "Unknown version."
|
||||
case .unknownReleaseType:
|
||||
return "Unknown release type (stable/alpha)."
|
||||
case .unknownRemoteURL:
|
||||
return "Unknown remote URL."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Various Enums and constants
|
||||
extension FirmwareFile {
|
||||
enum DownloadStatus: Equatable {
|
||||
case notDownloaded
|
||||
case downloading
|
||||
case downloaded
|
||||
case error(String)
|
||||
}
|
||||
|
||||
enum FirmwareType: String, Identifiable, CustomStringConvertible {
|
||||
var id: String { rawValue }
|
||||
var description: String { return rawValue }
|
||||
|
||||
case uf2 = ".uf2"
|
||||
case bin = ".bin"
|
||||
case otaZip = "-ota.zip"
|
||||
}
|
||||
|
||||
static let localFirmwareStorageURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
|
||||
static let remoteFirmwareURLPrefix = URL(string: "https://raw.githubusercontent.com/meshtastic/meshtastic.github.io/master/")!
|
||||
}
|
||||
|
||||
class FirmwareFile: ObservableObject, Hashable, Equatable {
|
||||
let localUrl: URL
|
||||
let remoteUrl: URL?
|
||||
let versionId: String
|
||||
let platformioTarget: String
|
||||
let releaseType: FirmwareRelease.ReleaseType
|
||||
@Published var status: DownloadStatus
|
||||
let firmwareType: FirmwareType
|
||||
let architecure: Architecture
|
||||
let releaseNotes: String?
|
||||
|
||||
let versionMajor, versionMinor, versionPatch: Int
|
||||
|
||||
init(firmware: FirmwareReleaseEntity, hardware: DeviceHardwareEntity, type: FirmwareType? = nil) throws {
|
||||
var target: String?
|
||||
var architecture: Architecture?
|
||||
|
||||
// Thread safe operationt to get the target and architecture
|
||||
// from the given DeviceHardwareEntity
|
||||
if let context = hardware.managedObjectContext {
|
||||
context.performAndWait {
|
||||
target = hardware.platformioTarget
|
||||
architecture = hardware.architecture.flatMap { Architecture(rawValue: $0) }
|
||||
}
|
||||
} else {
|
||||
// Detached, not yet inserted NSManagedObject
|
||||
target = hardware.platformioTarget
|
||||
architecture = hardware.architecture.flatMap { Architecture(rawValue: $0) }
|
||||
}
|
||||
|
||||
guard let target else { throw FirmwareFileError.unknownTarget }
|
||||
self.platformioTarget = target
|
||||
|
||||
guard let architecture else { throw FirmwareFileError.unknownArchitecture }
|
||||
self.architecure = architecture
|
||||
|
||||
// Thread safe operation to get the versionf`rom the given FirmwareReleaseEntity
|
||||
var version: String?
|
||||
var releaseType: FirmwareRelease.ReleaseType?
|
||||
var releaseNotes: String?
|
||||
if let context = firmware.managedObjectContext {
|
||||
context.performAndWait {
|
||||
version = firmware.versionId
|
||||
releaseType = firmware.releaseType.flatMap { FirmwareRelease.ReleaseType(rawValue: $0) }
|
||||
releaseNotes = firmware.releaseNotes
|
||||
}
|
||||
} else {
|
||||
version = firmware.versionId
|
||||
releaseType = firmware.releaseType.flatMap { FirmwareRelease.ReleaseType(rawValue: $0) }
|
||||
releaseNotes = firmware.releaseNotes
|
||||
}
|
||||
|
||||
self.releaseNotes = releaseNotes
|
||||
|
||||
guard let version else { throw FirmwareFileError.unknownVersion }
|
||||
self.versionId = version
|
||||
|
||||
let cleanString = version.hasPrefix("v") ? version.dropFirst() : Substring(version)
|
||||
let parts = cleanString.split(separator: ".")
|
||||
if parts.count >= 3 {
|
||||
self.versionMajor = Int(parts[0]) ?? 0
|
||||
self.versionMinor = Int(parts[1]) ?? 0
|
||||
self.versionPatch = Int(parts[2]) ?? 0
|
||||
} else {
|
||||
throw FirmwareFileError.parseError
|
||||
}
|
||||
|
||||
guard let releaseType else { throw FirmwareFileError.unknownReleaseType }
|
||||
self.releaseType = releaseType
|
||||
|
||||
// Calculate the filename
|
||||
// Regarding the force unwrap: validFilenameSuffixes should always return at least one type
|
||||
let defaultFileType = FirmwareFile.validFilenameSuffixes(forArchitecture: architecture).first!
|
||||
self.firmwareType = type ?? defaultFileType
|
||||
let fileNameVersion = versionId.hasPrefix("v") ? String(versionId.dropFirst()) : versionId
|
||||
let fileName = "firmware-\(target)-\(fileNameVersion)\(firmwareType)"
|
||||
self.localUrl = FirmwareFile.localFirmwareStorageURL.appendingPathComponent(fileName)
|
||||
self.remoteUrl = FirmwareFile.remoteFirmwareURLPrefix
|
||||
.appendingPathComponent("firmware-\(fileNameVersion)")
|
||||
.appendingPathComponent(fileName)
|
||||
|
||||
if FileManager.default.fileExists(atPath: localUrl.path) {
|
||||
self.status = .downloaded
|
||||
} else {
|
||||
self.status = .notDownloaded
|
||||
}
|
||||
}
|
||||
|
||||
init(localFile url: URL) throws {
|
||||
self.localUrl = url
|
||||
|
||||
let fileName = url.lastPathComponent
|
||||
|
||||
// Check Prefix
|
||||
guard fileName.hasPrefix("firmware-") else {
|
||||
throw FirmwareFileError.invalidFilenamePrefix
|
||||
}
|
||||
|
||||
// Check and Strip Suffix (Extension)
|
||||
// We strip the prefix and suffix first to isolate "<target>-<version>"
|
||||
var coreName = String(fileName.dropFirst("firmware-".count))
|
||||
|
||||
if fileName.hasSuffix("-ota.zip") {
|
||||
coreName = String(coreName.dropLast("-ota.zip".count))
|
||||
self.firmwareType = .otaZip
|
||||
} else if fileName.hasSuffix(".uf2") {
|
||||
coreName = String(coreName.dropLast(".uf2".count))
|
||||
self.firmwareType = .uf2
|
||||
} else if fileName.hasSuffix(".bin") {
|
||||
coreName = String(coreName.dropLast(".bin".count))
|
||||
self.firmwareType = .bin
|
||||
} else {
|
||||
// File does not match supported extensions
|
||||
throw FirmwareFileError.unknownFileType
|
||||
}
|
||||
|
||||
// Extract Target and Version
|
||||
// Strategy: We assume the format is Target-Version.
|
||||
// Since Targets can have hyphens (e.g. "esp32-s3"), but Versions usually don't contain
|
||||
// the separating hyphen in this specific naming convention, we split by the *last* hyphen.
|
||||
guard let lastHyphenIndex = coreName.lastIndex(of: "-") else {
|
||||
throw FirmwareFileError.parseError
|
||||
}
|
||||
|
||||
let target = String(coreName[..<lastHyphenIndex])
|
||||
var version = String(coreName[coreName.index(after: lastHyphenIndex)...])
|
||||
|
||||
let cleanString = version.hasPrefix("v") ? version.dropFirst() : Substring(version)
|
||||
let parts = cleanString.split(separator: ".")
|
||||
if parts.count >= 3 {
|
||||
self.versionMajor = Int(parts[0]) ?? 0
|
||||
self.versionMinor = Int(parts[1]) ?? 0
|
||||
self.versionPatch = Int(parts[2]) ?? 0
|
||||
} else {
|
||||
throw FirmwareFileError.parseError
|
||||
}
|
||||
|
||||
if !version.hasPrefix("v") {
|
||||
version = "v" + version
|
||||
}
|
||||
|
||||
// Validation to ensure we didn't end up with empty strings
|
||||
guard !target.isEmpty, !version.isEmpty else {
|
||||
throw FirmwareFileError.parseError
|
||||
}
|
||||
|
||||
self.versionId = version
|
||||
self.platformioTarget = target
|
||||
|
||||
if FileManager.default.fileExists(atPath: url.path) {
|
||||
self.status = .downloaded
|
||||
} else {
|
||||
self.status = .notDownloaded
|
||||
}
|
||||
|
||||
// Look up the architecture for this file
|
||||
let context = PersistenceController.shared.container.newBackgroundContext()
|
||||
var architecture: Architecture?
|
||||
context.performAndWait {
|
||||
let hardwareFetchRequest = DeviceHardwareEntity.fetchRequest()
|
||||
hardwareFetchRequest.predicate = NSPredicate(format: "platformioTarget == %@", target)
|
||||
hardwareFetchRequest.fetchLimit = 1
|
||||
let hardware = try? context.fetch(hardwareFetchRequest).first
|
||||
architecture = hardware?.architecture.flatMap { Architecture(rawValue: $0) }
|
||||
}
|
||||
|
||||
guard let architecture else { throw FirmwareFileError.unknownArchitecture }
|
||||
self.architecure = architecture
|
||||
|
||||
// Determine release type
|
||||
var releaseType: FirmwareRelease.ReleaseType = .unlisted
|
||||
var releaseNotes: String?
|
||||
context.performAndWait {
|
||||
let firmwareFetchRequest = FirmwareReleaseEntity.fetchRequest()
|
||||
firmwareFetchRequest.predicate = NSPredicate(format: "versionId == %@", version)
|
||||
firmwareFetchRequest.fetchLimit = 1
|
||||
if let firmware = try? context.fetch(firmwareFetchRequest).first {
|
||||
releaseType = firmware.releaseType.flatMap { FirmwareRelease.ReleaseType(rawValue: $0) } ?? .unlisted
|
||||
releaseNotes = firmware.releaseNotes
|
||||
}
|
||||
}
|
||||
self.releaseType = releaseType
|
||||
self.releaseNotes = releaseNotes
|
||||
|
||||
let fileNameVersion = versionId.hasPrefix("v") ? String(versionId.dropFirst()) : versionId
|
||||
self.remoteUrl = FirmwareFile.remoteFirmwareURLPrefix
|
||||
.appendingPathComponent("firmware-\(fileNameVersion)")
|
||||
.appendingPathComponent(fileName)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func download() async throws {
|
||||
guard let remoteUrl else {
|
||||
throw FirmwareFileError.unknownRemoteURL
|
||||
}
|
||||
Task {
|
||||
do {
|
||||
let (tempLocalUrl, response) = try await URLSession.shared.download(from: remoteUrl)
|
||||
|
||||
if let httpResponse = response as? HTTPURLResponse, !(200...299).contains(httpResponse.statusCode) {
|
||||
throw URLError(.badServerResponse)
|
||||
}
|
||||
|
||||
if FileManager.default.fileExists(atPath: localUrl.path) {
|
||||
try FileManager.default.removeItem(at: localUrl)
|
||||
}
|
||||
|
||||
try FileManager.default.moveItem(at: tempLocalUrl, to: localUrl)
|
||||
|
||||
self.status = .downloaded
|
||||
|
||||
} catch {
|
||||
try? FileManager.default.removeItem(at: localUrl)
|
||||
self.status = .error(error.localizedDescription)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static func validFilenameSuffixes(forArchitecture: Architecture) -> [FirmwareType] {
|
||||
switch forArchitecture {
|
||||
case .esp32, .esp32C3, .esp32S3, .esp32C6:
|
||||
return [.bin]
|
||||
case .nrf52840:
|
||||
return [.uf2, .otaZip]
|
||||
case .rp2040:
|
||||
return [.uf2]
|
||||
}
|
||||
}
|
||||
|
||||
static func == (lhs: FirmwareFile, rhs: FirmwareFile) -> Bool {
|
||||
return lhs.localUrl == rhs.localUrl &&
|
||||
lhs.remoteUrl == rhs.remoteUrl &&
|
||||
lhs.versionId == rhs.versionId &&
|
||||
lhs.platformioTarget == rhs.platformioTarget &&
|
||||
lhs.releaseType == rhs.releaseType &&
|
||||
lhs.firmwareType == rhs.firmwareType &&
|
||||
lhs.architecure == rhs.architecure
|
||||
}
|
||||
|
||||
func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(localUrl)
|
||||
hasher.combine(remoteUrl)
|
||||
hasher.combine(versionId)
|
||||
hasher.combine(platformioTarget)
|
||||
hasher.combine(releaseType)
|
||||
hasher.combine(firmwareType)
|
||||
hasher.combine(architecure)
|
||||
}
|
||||
}
|
||||
|
||||
191
Meshtastic/Model/Firmware/FirmwareViewModel.swift
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
//
|
||||
// FirmwareViewModel.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Created by Jake Bordens on 12/11/25.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
import CoreData
|
||||
import OSLog
|
||||
|
||||
extension FirmwareViewModel {
|
||||
enum FirmwareViewModelError: Error, LocalizedError {
|
||||
case timedOut(TimeInterval)
|
||||
case unknownFirmwareVersion
|
||||
case unableToFindOrCreateEntity
|
||||
case unknownArchitecture
|
||||
case unknownPlatformIOTarget
|
||||
var errorDescription: String? {
|
||||
switch self {
|
||||
case .timedOut(let seconds):
|
||||
return "The operation timed out after \(seconds) seconds."
|
||||
case .unknownFirmwareVersion:
|
||||
return "Unknown firmware version."
|
||||
case .unableToFindOrCreateEntity:
|
||||
return "Unable to find or create Core Data entity."
|
||||
case .unknownArchitecture:
|
||||
return "Unknown architecture."
|
||||
case .unknownPlatformIOTarget:
|
||||
return "Unknown platformio target."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class FirmwareViewModel: ObservableObject {
|
||||
@Published var firmwareFiles: [FirmwareFile] = []
|
||||
let hardware: DeviceHardwareEntity
|
||||
|
||||
init(forHardware: DeviceHardwareEntity) {
|
||||
self.hardware = forHardware
|
||||
Task {
|
||||
try? await MeshtasticAPI.shared.refreshFirmwareAPIData()
|
||||
refresh()
|
||||
}
|
||||
}
|
||||
|
||||
func refresh() {
|
||||
var newFirmwareList = [String: FirmwareFile]()
|
||||
|
||||
// First, loop through all firmware entities and create an entry for those
|
||||
let context = PersistenceController.shared.container.newBackgroundContext()
|
||||
context.performAndWait {
|
||||
let fetchRequest = FirmwareReleaseEntity.fetchRequest()
|
||||
do {
|
||||
let firmwareReleases = try context.fetch(fetchRequest)
|
||||
for release in firmwareReleases {
|
||||
if let architecture = hardware.architecture.flatMap({ Architecture(rawValue: $0) }) {
|
||||
for firmwareType in FirmwareFile.validFilenameSuffixes(forArchitecture: architecture) {
|
||||
let firmwareFile = try FirmwareFile(firmware: release, hardware: hardware, type: firmwareType)
|
||||
newFirmwareList[firmwareFile.localUrl.lastPathComponent] = firmwareFile
|
||||
}
|
||||
} else {
|
||||
// Just the default
|
||||
let firmwareFile = try FirmwareFile(firmware: release, hardware: hardware)
|
||||
newFirmwareList[firmwareFile.localUrl.lastPathComponent] = firmwareFile
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
Logger.services.error("Error fetching firmware releases: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
// Now look for unlisted files on the filesystem
|
||||
let fileManager = FileManager.default
|
||||
var isDirectory: ObjCBool = false
|
||||
|
||||
// 1. Check if directory exists
|
||||
if !fileManager.fileExists(atPath: FirmwareFile.localFirmwareStorageURL.path, isDirectory: &isDirectory) {
|
||||
return
|
||||
}
|
||||
|
||||
// 2. Iterate the files in the folder
|
||||
do {
|
||||
let fileURLs = try fileManager.contentsOfDirectory(at: FirmwareFile.localFirmwareStorageURL, includingPropertiesForKeys: nil)
|
||||
|
||||
for url in fileURLs {
|
||||
do {
|
||||
let firmwareFile = try FirmwareFile(localFile: url)
|
||||
if firmwareFile.platformioTarget != hardware.platformioTarget {
|
||||
// Skip if this is not for the current hardware we are dealing with
|
||||
continue
|
||||
}
|
||||
|
||||
if newFirmwareList[firmwareFile.localUrl.lastPathComponent] != nil {
|
||||
// Already have this one from the Core Data entries
|
||||
continue
|
||||
}
|
||||
newFirmwareList[firmwareFile.localUrl.lastPathComponent] = firmwareFile
|
||||
} catch {
|
||||
Logger.services.error("Error parsing local firmware file at \(url.path): \(error)")
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
Logger.services.error("Error loading firmware files: \(error)")
|
||||
}
|
||||
|
||||
// Keep the list sorted by version, with deterministic ordering of the firmware type
|
||||
self.firmwareFiles = newFirmwareList.values.sorted {
|
||||
if ($0.versionMajor, $0.versionMinor, $0.versionPatch) == ($1.versionMajor, $1.versionMinor, $1.versionPatch) {
|
||||
// If versions are equal, sort by firmwareType (assuming it's String or Comparable)
|
||||
return String(describing: $0.firmwareType) < String(describing: $1.firmwareType)
|
||||
}
|
||||
return ($0.versionMajor, $0.versionMinor, $0.versionPatch) > ($1.versionMajor, $1.versionMinor, $1.versionPatch)
|
||||
}
|
||||
}
|
||||
|
||||
func mostRecentFirmwareVersion(forReleaseType releaseType: FirmwareRelease.ReleaseType) -> String? {
|
||||
let context = PersistenceController.shared.container.newBackgroundContext()
|
||||
var versionId: String?
|
||||
|
||||
try? context.performAndWait {
|
||||
let fetchRequest = FirmwareReleaseEntity.fetchRequest()
|
||||
fetchRequest.predicate = NSPredicate(format: "releaseType == %@", releaseType.rawValue)
|
||||
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "versionMajor", ascending: false),
|
||||
NSSortDescriptor(key: "versionMinor", ascending: false),
|
||||
NSSortDescriptor(key: "versionPatch", ascending: false)]
|
||||
fetchRequest.fetchLimit = 1
|
||||
do {
|
||||
if let firmwareRelease = try context.fetch(fetchRequest).first {
|
||||
versionId = firmwareRelease.versionId
|
||||
}
|
||||
}
|
||||
}
|
||||
return versionId
|
||||
}
|
||||
|
||||
func firmwareFiles(forVersionId versionId: String) -> [FirmwareFile] {
|
||||
return firmwareFiles.filter({ $0.versionId == versionId })
|
||||
}
|
||||
|
||||
func mostRecentFirmware(forReleaseType releaseType: FirmwareRelease.ReleaseType) -> [FirmwareFile] {
|
||||
if let versionId = mostRecentFirmwareVersion(forReleaseType: releaseType) {
|
||||
return firmwareFiles.filter { $0.releaseType == releaseType && $0.versionId == versionId }
|
||||
} else {
|
||||
// Worst case, rely on sorting and only return the first one
|
||||
let firmwareOfType = firmwareFiles.filter { $0.releaseType == releaseType }
|
||||
if let singleVersionToReturn = firmwareOfType.first {
|
||||
return [singleVersionToReturn]
|
||||
}
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
func downloadedFirmware(includeInProgressDownloads: Bool = true) -> [FirmwareFile] {
|
||||
if includeInProgressDownloads {
|
||||
return firmwareFiles.filter( { $0.status == .downloading || $0.status == .downloaded })
|
||||
} else {
|
||||
return firmwareFiles.filter( { $0.status == .downloaded })
|
||||
}
|
||||
}
|
||||
|
||||
var hasDownloadedFirmware: Bool {
|
||||
return !downloadedFirmware(includeInProgressDownloads: false).isEmpty
|
||||
}
|
||||
|
||||
func delete(_ filesToDelete:[FirmwareFile]) {
|
||||
// 1. Create a bucket for files that were actually deleted
|
||||
var deletedFiles = Set<FirmwareFile>()
|
||||
|
||||
// 2. Perform Disk I/O
|
||||
for file in filesToDelete {
|
||||
do {
|
||||
try FileManager.default.removeItem(at: file.localUrl)
|
||||
deletedFiles.insert(file)
|
||||
} catch {
|
||||
// Optional: Handle "File not found" as a success so it clears from UI
|
||||
if (error as NSError).code == NSFileNoSuchFileError {
|
||||
deletedFiles.insert(file)
|
||||
} else {
|
||||
Logger.services.error("Failed to delete \(file.localUrl.path): \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Update State ONCE (Efficient O(n))
|
||||
// This triggers the UI update/Publisher only one time
|
||||
firmwareFiles.removeAll { deletedFiles.contains($0) }
|
||||
}
|
||||
}
|
||||
|
|
@ -140,7 +140,7 @@ public func deleteUserMessages(user: UserEntity, context: NSManagedObjectContext
|
|||
}
|
||||
}
|
||||
|
||||
public func clearCoreDataDatabase(context: NSManagedObjectContext, includeRoutes: Bool) {
|
||||
public func clearCoreDataDatabase(context: NSManagedObjectContext, includeRoutes: Bool, includeAppLevelData: Bool = false) {
|
||||
|
||||
let persistenceController = PersistenceController.shared.container
|
||||
for i in 0...persistenceController.managedObjectModel.entities.count-1 {
|
||||
|
|
@ -150,13 +150,17 @@ public func clearCoreDataDatabase(context: NSManagedObjectContext, includeRoutes
|
|||
var deleteRequest = NSBatchDeleteRequest(fetchRequest: query)
|
||||
let entityName = entity.name ?? "UNK"
|
||||
|
||||
if includeRoutes {
|
||||
deleteRequest = NSBatchDeleteRequest(fetchRequest: query)
|
||||
} else if !includeRoutes {
|
||||
if !(entityName.contains("RouteEntity") || entityName.contains("LocationEntity")) {
|
||||
deleteRequest = NSBatchDeleteRequest(fetchRequest: query)
|
||||
}
|
||||
if !includeRoutes, ["RouteEntity", "LocationEntity"].contains(entityName) {
|
||||
continue
|
||||
}
|
||||
|
||||
if !includeAppLevelData, ["DeviceHardwareEntity","DeviceHardwareImageEntity", "DeviceHardwareTagEntity"].contains(entityName) {
|
||||
// These are non-node-specific "app level" data, keep them even when switching nodes
|
||||
continue
|
||||
}
|
||||
|
||||
// Execute the delete for this entry
|
||||
deleteRequest = NSBatchDeleteRequest(fetchRequest: query)
|
||||
do {
|
||||
try context.executeAndMergeChanges(using: deleteRequest)
|
||||
} catch {
|
||||
|
|
@ -299,11 +303,11 @@ func upsertNodeInfoPacket (packet: MeshPacket, favorite: Bool = false, context:
|
|||
newUser.publicKey = newUserMessage.publicKey
|
||||
}
|
||||
|
||||
Task {
|
||||
Api().loadDeviceHardwareData { (hw) in
|
||||
let dh = hw.first(where: { $0.hwModel == newUser.hwModelId })
|
||||
newUser.hwDisplayName = dh?.displayName
|
||||
}
|
||||
let fetchRequest = DeviceHardwareEntity.fetchRequest()
|
||||
fetchRequest.predicate = NSPredicate(format: "hwModel == %d", newUser.hwModelId)
|
||||
let fetchedHardware = try context.fetch(fetchRequest)
|
||||
if let hardwareEntity = fetchedHardware.first {
|
||||
newUser.hwDisplayName = hardwareEntity.displayName
|
||||
}
|
||||
newNode.user = newUser
|
||||
|
||||
|
|
@ -412,10 +416,13 @@ func upsertNodeInfoPacket (packet: MeshPacket, favorite: Bool = false, context:
|
|||
fetchedNode[0].user?.pkiEncrypted = true
|
||||
fetchedNode[0].user?.publicKey = nodeInfoMessage.user.publicKey
|
||||
}
|
||||
Task {
|
||||
Api().loadDeviceHardwareData { (hw) in
|
||||
let dh = hw.first(where: { $0.hwModel == fetchedNode[0].user?.hwModelId ?? 0 })
|
||||
fetchedNode[0].user?.hwDisplayName = dh?.displayName
|
||||
|
||||
if let user = fetchedNode.first?.user {
|
||||
let fetchRequest = DeviceHardwareEntity.fetchRequest()
|
||||
fetchRequest.predicate = NSPredicate(format: "hwModel == %d", user.hwModelId)
|
||||
let fetchedHardware = try context.fetch(fetchRequest)
|
||||
if let hardwareEntity = fetchedHardware.first {
|
||||
user.hwDisplayName = hardwareEntity.displayName
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
1
Meshtastic/Resources/Devices.xcassets/crowpanel_2_4.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 470.13 470.13"><defs><style>.cls-1{fill:#3d3d3d;}.cls-2{fill:#2c2c2c;}.cls-3{fill:#1e1f1e;}.cls-4{fill:#d2d2cd;}.cls-5{fill:#a3a2a2;}.cls-6{fill:#97621d;}.cls-7{fill:#5b1f0f;}.cls-8{fill:#fdfefb;}.cls-9{fill:none;stroke:#1d1d1b;stroke-miterlimit:10;stroke-width:0.59px;}.cls-10{fill:#1d1d1b;}.cls-11{fill:#feffff;}</style></defs><g id="Layer_2" data-name="Layer 2"><path class="cls-1" d="M350,141.34H113a11.15,11.15,0,0,0-11.15,11.15V297.83A11.15,11.15,0,0,0,113,309H350a11.15,11.15,0,0,0,11.15-11.15V152.49A11.15,11.15,0,0,0,350,141.34Zm-9.16,160.44H125.25v-153H340.79Z"/><path class="cls-2" d="M313.64,147.88a1.93,1.93,0,0,1-.23.92H310a1.92,1.92,0,1,1,3.61-.92Z"/><circle class="cls-3" cx="311.72" cy="147.88" r="0.9"/><path class="cls-3" d="M125.25,148.8v153H340.79v-153Zm209,142.12H152.12V155.74H334.23Z"/><rect class="cls-2" x="152.12" y="155.74" width="182.11" height="135.18"/><rect class="cls-4" x="107.87" y="200.37" width="8.03" height="4.33" rx="1.99"/><rect class="cls-5" x="109.47" y="201.12" width="4.76" height="2.82" rx="0.82"/><rect class="cls-4" x="107.87" y="170.52" width="8.03" height="4.33" rx="1.99"/><rect class="cls-5" x="109.47" y="171.27" width="4.76" height="2.82" rx="0.82"/><circle class="cls-4" cx="111.68" cy="191.27" r="2.35"/><circle class="cls-4" cx="111.68" cy="183.64" r="2.35"/><circle class="cls-4" cx="118.25" cy="191.27" r="2.35"/><circle class="cls-4" cx="118.25" cy="183.64" r="2.35"/><rect class="cls-4" x="107.87" y="273.84" width="8.03" height="4.33" rx="1.99"/><rect class="cls-5" x="109.47" y="274.59" width="4.76" height="2.82" rx="0.82"/><rect class="cls-4" x="107.87" y="243.99" width="8.03" height="4.33" rx="1.99"/><rect class="cls-5" x="109.47" y="244.74" width="4.76" height="2.82" rx="0.82"/><circle class="cls-4" cx="111.68" cy="264.74" r="2.35"/><circle class="cls-4" cx="111.68" cy="257.11" r="2.35"/><circle class="cls-4" cx="118.25" cy="264.74" r="2.35"/><circle class="cls-4" cx="118.25" cy="257.11" r="2.35"/><path class="cls-6" d="M125.25,168.35l-2.9,3.46a3.14,3.14,0,0,0-.73,2V176a3.14,3.14,0,0,1-.92,2.22l0,0a3.12,3.12,0,0,0-.92,2.21v31.16a5.19,5.19,0,0,0,.29,1.7l1.57,4.46a5.13,5.13,0,0,1,.29,1.69V300a1.73,1.73,0,0,0,1.74,1.74h1.63"/><rect class="cls-7" x="119.72" y="195.21" width="5.77" height="2.23"/><rect class="cls-7" x="119.72" y="201.12" width="5.77" height="1.12"/><rect class="cls-7" x="119.72" y="204.7" width="5.77" height="1.12"/><circle class="cls-4" cx="351.41" cy="150.61" r="5.13"/><circle class="cls-4" cx="351.41" cy="150.61" r="8.82"/><circle class="cls-8" cx="351.41" cy="150.61" r="5.13"/><circle class="cls-9" cx="351.41" cy="150.61" r="8.82"/><circle class="cls-4" cx="351.41" cy="299.74" r="5.13"/><circle class="cls-4" cx="351.41" cy="299.74" r="8.82"/><circle class="cls-8" cx="351.41" cy="299.74" r="5.13"/><circle class="cls-9" cx="351.41" cy="299.74" r="8.82"/><circle class="cls-4" cx="110.71" cy="150.61" r="5.13"/><circle class="cls-4" cx="110.71" cy="150.61" r="8.82"/><circle class="cls-8" cx="110.71" cy="150.61" r="5.13"/><circle class="cls-9" cx="110.71" cy="150.61" r="8.82"/><circle class="cls-4" cx="110.71" cy="299.74" r="5.13"/><circle class="cls-4" cx="110.71" cy="299.74" r="8.82"/><circle class="cls-8" cx="110.71" cy="299.74" r="5.13"/><circle class="cls-9" cx="110.71" cy="299.74" r="8.82"/><path class="cls-10" d="M350,141.34a11.15,11.15,0,0,1,11.15,11.15V297.83A11.15,11.15,0,0,1,350,309H113a11.15,11.15,0,0,1-11.15-11.15V152.49A11.15,11.15,0,0,1,113,141.34H350m0-.76H113a11.92,11.92,0,0,0-11.9,11.91V297.83A11.92,11.92,0,0,0,113,309.74H350a11.92,11.92,0,0,0,11.9-11.91V152.49A11.92,11.92,0,0,0,350,140.58Z"/><rect class="cls-9" x="125.25" y="148.8" width="215.54" height="152.98"/><rect class="cls-9" x="152.13" y="155.74" width="182.11" height="135.18"/><path class="cls-9" d="M125.25,168.35l-2.9,3.46a3.14,3.14,0,0,0-.73,2V176a3.14,3.14,0,0,1-.92,2.22l0,0a3.12,3.12,0,0,0-.92,2.21v31.16a5.19,5.19,0,0,0,.29,1.7l1.57,4.46a5.13,5.13,0,0,1,.29,1.69V300a1.73,1.73,0,0,0,1.74,1.74h1.63"/><path class="cls-11" d="M164.77,195.92V192.6l3-2.74c5.12-4.58,7.61-7.22,7.68-10,0-1.91-1.15-3.43-3.86-3.43a8.46,8.46,0,0,0-5,1.95l-1.55-3.93a13,13,0,0,1,7.68-2.42c5.3,0,8.22,3.11,8.22,7.36,0,3.93-2.85,7.07-6.24,10.1l-2.16,1.81v.07h8.84v4.51Z"/><path class="cls-11" d="M185.08,193a3.18,3.18,0,0,1,3.25-3.35,3.34,3.34,0,1,1-3.25,3.35Z"/><path class="cls-11" d="M203.88,195.92v-5.6H193.49v-3.57l8.87-14.28h6.71v13.74h2.81v4.11h-2.81v5.6Zm0-9.71V181c0-1.41.07-2.85.18-4.37h-.15c-.76,1.52-1.37,2.89-2.16,4.37l-3.14,5.12v.07Z"/><path class="cls-11" d="M221.23,171.21A48.05,48.05,0,0,1,216.9,181l-3.32.29a58.37,58.37,0,0,0,2.53-9.74Zm7,0a46,46,0,0,1-4.33,9.77l-3.32.29a56.49,56.49,0,0,0,2.53-9.74Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 4.6 KiB |
1
Meshtastic/Resources/Devices.xcassets/crowpanel_2_8.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 470.13 470.13"><defs><style>.cls-1{fill:#3d3d3d;}.cls-2{fill:#1e1f1e;}.cls-3{fill:#2c2c2c;}.cls-4{fill:#d2d2cd;}.cls-5{fill:#a3a2a2;}.cls-6{fill:#97621d;}.cls-7{fill:#c69355;}.cls-8{fill:#323031;}.cls-9{fill:none;stroke:#1d1d1b;stroke-miterlimit:10;stroke-width:0.57px;}.cls-10{fill:#feffff;}.cls-11{fill:#fdfefb;}.cls-12{fill:#1d1d1b;}</style></defs><g id="Layer_2" data-name="Layer 2"><path class="cls-1" d="M378.36,143.68H118.27a8.5,8.5,0,0,0-8.5,8.5V317.94a8.51,8.51,0,0,0,8.5,8.51H378.36a8.51,8.51,0,0,0,8.5-8.51V152.18A8.5,8.5,0,0,0,378.36,143.68Zm-11,177.14H128.67V147.26H367.34Z"/><path class="cls-2" d="M128.67,147.26V320.82H367.34V147.26Zm230,163.57H155.13V157.23H358.7Z"/><rect class="cls-3" x="155.13" y="157.23" width="203.57" height="153.6"/><path class="cls-3" d="M350.35,146.34a1.93,1.93,0,0,1-.23.92h-3.38a1.92,1.92,0,1,1,3.61-.92Z"/><circle class="cls-2" cx="348.43" cy="146.35" r="0.9"/><rect class="cls-4" x="126.91" y="147.26" width="1.76" height="22.62"/><rect class="cls-4" x="126.91" y="308.63" width="1.76" height="12.2"/><rect class="cls-4" x="113.35" y="212.05" width="8.03" height="4.33" rx="1.99"/><rect class="cls-5" x="114.96" y="212.8" width="4.76" height="2.82" rx="0.82"/><path class="cls-6" d="M128.67,200.27H124a2.63,2.63,0,0,0-2.63,2.63v98.59a2.63,2.63,0,0,0,2.63,2.63h4.66V268.46h-7.29V251.07h7.29"/><rect class="cls-7" x="121.38" y="287.43" width="7.29" height="4.72"/><rect class="cls-8" x="121.38" y="251.07" width="7.29" height="17.39"/><path class="cls-9" d="M128.67,304.12H124a2.63,2.63,0,0,1-2.63-2.63V202.9a2.63,2.63,0,0,1,2.63-2.63h4.66"/><path class="cls-10" d="M174,200.27v-3.49l3.18-2.89c5.4-4.82,8-7.59,8.09-10.47,0-2-1.21-3.61-4.06-3.61a8.92,8.92,0,0,0-5.28,2.05l-1.63-4.14a13.68,13.68,0,0,1,8.09-2.54c5.58,0,8.65,3.26,8.65,7.74,0,4.14-3,7.44-6.57,10.63l-2.27,1.9v.08h9.3v4.74Z"/><path class="cls-10" d="M195.34,197.2a3.34,3.34,0,0,1,3.41-3.53,3.51,3.51,0,1,1-3.41,3.53Z"/><path class="cls-10" d="M204.52,193.82c0-3,1.71-5,4.41-6.19v-.11a6,6,0,0,1-3.57-5.39c0-4.18,3.76-6.95,8.69-6.95,5.77,0,8.16,3.34,8.16,6.34a6,6,0,0,1-3.57,5.43v.11a6.52,6.52,0,0,1,4.52,6.15c0,4.48-3.76,7.48-9.49,7.48C207.41,200.69,204.52,197.16,204.52,193.82Zm12.76-.19c0-2.17-1.6-3.46-3.87-4.06a3.73,3.73,0,0,0-3,3.68,3.38,3.38,0,0,0,3.49,3.45A3.1,3.1,0,0,0,217.28,193.63Zm-6.49-11.69c0,1.67,1.51,2.73,3.49,3.34a3.38,3.38,0,0,0,2.5-3.15,2.83,2.83,0,0,0-3-3A2.75,2.75,0,0,0,210.79,181.94Z"/><path class="cls-10" d="M233.37,174.27a50.18,50.18,0,0,1-4.55,10.28l-3.5.31A60.59,60.59,0,0,0,228,174.61Zm7.41,0a48.12,48.12,0,0,1-4.56,10.28l-3.49.31a60.56,60.56,0,0,0,2.65-10.25Z"/><circle class="cls-4" cx="118.34" cy="152.25" r="4.99"/><circle class="cls-4" cx="118.34" cy="152.25" r="8.57"/><circle class="cls-11" cx="118.34" cy="152.25" r="4.99"/><circle class="cls-9" cx="118.34" cy="152.25" r="8.57"/><circle class="cls-4" cx="118.34" cy="317.88" r="4.99"/><circle class="cls-4" cx="118.34" cy="317.88" r="8.57"/><circle class="cls-11" cx="118.34" cy="317.88" r="4.99"/><circle class="cls-9" cx="118.34" cy="317.88" r="8.57"/><circle class="cls-4" cx="378.29" cy="152.25" r="4.99"/><circle class="cls-4" cx="378.29" cy="152.25" r="8.57"/><circle class="cls-11" cx="378.29" cy="152.25" r="4.99"/><circle class="cls-9" cx="378.29" cy="152.25" r="8.57"/><circle class="cls-4" cx="378.29" cy="317.88" r="4.99"/><circle class="cls-4" cx="378.29" cy="317.88" r="8.57"/><circle class="cls-11" cx="378.29" cy="317.88" r="4.99"/><circle class="cls-9" cx="378.29" cy="317.88" r="8.57"/><path class="cls-12" d="M378.36,143.68a8.51,8.51,0,0,1,8.5,8.5V318a8.51,8.51,0,0,1-8.5,8.5H118.27a8.51,8.51,0,0,1-8.5-8.5V152.18a8.51,8.51,0,0,1,8.5-8.5H378.36m0-.75H118.27a9.27,9.27,0,0,0-9.26,9.25V318a9.27,9.27,0,0,0,9.26,9.25H378.36a9.27,9.27,0,0,0,9.26-9.25V152.18a9.27,9.27,0,0,0-9.26-9.25Z"/><rect class="cls-9" x="128.67" y="147.26" width="238.67" height="173.56"/><rect class="cls-9" x="155.13" y="157.24" width="203.57" height="153.6"/><rect class="cls-4" x="113.35" y="182.2" width="8.03" height="4.33" rx="1.99"/><rect class="cls-5" x="114.96" y="182.95" width="4.76" height="2.82" rx="0.82"/><circle class="cls-4" cx="115.7" cy="205.3" r="2.35"/><circle class="cls-4" cx="115.7" cy="196.61" r="2.35"/><circle class="cls-4" cx="124.46" cy="196.61" r="2.35"/><rect class="cls-4" x="113.35" y="253.25" width="8.03" height="4.33" rx="1.99"/><rect class="cls-5" x="114.96" y="254" width="4.76" height="2.82" rx="0.82"/><rect class="cls-4" x="113.35" y="283.1" width="8.03" height="4.33" rx="1.99"/><rect class="cls-5" x="114.96" y="283.85" width="4.76" height="2.82" rx="0.82"/><circle class="cls-4" cx="115.7" cy="276.34" r="2.35"/><circle class="cls-4" cx="115.7" cy="267.66" r="2.35"/><line class="cls-9" x1="121.38" y1="251.07" x2="128.67" y2="251.07"/><line class="cls-9" x1="121.38" y1="268.46" x2="128.67" y2="268.46"/></g></svg>
|
||||
|
After Width: | Height: | Size: 4.8 KiB |
1
Meshtastic/Resources/Devices.xcassets/crowpanel_3_5.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 470.13 470.13"><defs><style>.cls-1{fill:#3d3d3d;}.cls-2{fill:#1e1f1e;}.cls-3{fill:#2c2c2c;}.cls-4{fill:#d2d2cd;}.cls-5{fill:#a3a2a2;}.cls-6{fill:#97621d;}.cls-7,.cls-9{fill:none;stroke:#1d1d1b;stroke-miterlimit:10;}.cls-7{stroke-width:0.59px;}.cls-8{fill:#fdfefb;}.cls-9{stroke-width:0.59px;}.cls-10{fill:#1d1d1b;}.cls-11{fill:#feffff;}</style></defs><g id="Layer_2" data-name="Layer 2"><rect class="cls-1" x="68.96" y="128.35" width="332.07" height="199.87" rx="8.7"/><path class="cls-2" d="M91.43,132.15V322.71h289V132.15ZM373.77,313.6h-259V139.82h259Z"/><rect class="cls-3" x="114.72" y="139.82" width="259.05" height="173.78"/><rect class="cls-4" x="72.34" y="236.77" width="8.03" height="4.33" rx="1.99"/><rect class="cls-5" x="73.94" y="237.52" width="4.76" height="2.82" rx="0.82"/><rect class="cls-4" x="72.34" y="247.19" width="8.03" height="4.33" rx="1.99"/><rect class="cls-5" x="73.94" y="247.94" width="4.76" height="2.82" rx="0.82"/><path class="cls-4" d="M91.43,236.77v4.33h-2a2,2,0,0,1-2-2v-.35a2,2,0,0,1,2-2Z"/><path class="cls-5" d="M91.43,237.52v2.83h-1.6a.82.82,0,0,1-.82-.82v-1.19a.82.82,0,0,1,.82-.82Z"/><path class="cls-4" d="M91.43,247.19v4.33h-2a2,2,0,0,1-2-2v-.35a2,2,0,0,1,2-2Z"/><path class="cls-5" d="M91.43,247.94v2.83h-1.6A.82.82,0,0,1,89,250v-1.19a.82.82,0,0,1,.82-.82Z"/><rect class="cls-4" x="72.34" y="277.47" width="8.03" height="4.33" rx="1.99"/><rect class="cls-5" x="73.94" y="278.22" width="4.76" height="2.82" rx="0.82"/><path class="cls-4" d="M91.43,277.47v4.33h-2a2,2,0,0,1-2-2v-.35a2,2,0,0,1,2-2Z"/><path class="cls-5" d="M91.43,278.22v2.83h-1.6a.82.82,0,0,1-.82-.82V279a.82.82,0,0,1,.82-.82Z"/><path class="cls-6" d="M91.42,149.56A9,9,0,0,1,82.86,154v59.41s6.17,1.45,8.56,6.79"/><path class="cls-7" d="M91.42,149.56A9,9,0,0,1,82.86,154v59.41s6.17,1.45,8.56,6.79"/><circle class="cls-4" cx="77.75" cy="137.14" r="5.11"/><circle class="cls-4" cx="77.75" cy="137.14" r="8.79"/><circle class="cls-8" cx="77.75" cy="137.14" r="5.11"/><circle class="cls-9" cx="77.75" cy="137.14" r="8.79"/><circle class="cls-4" cx="77.75" cy="319.43" r="5.11"/><circle class="cls-4" cx="77.75" cy="319.43" r="8.79"/><circle class="cls-8" cx="77.75" cy="319.43" r="5.11"/><circle class="cls-9" cx="77.75" cy="319.43" r="8.79"/><circle class="cls-4" cx="392.39" cy="137.14" r="5.11"/><circle class="cls-4" cx="392.39" cy="137.14" r="8.79"/><circle class="cls-8" cx="392.39" cy="137.14" r="5.11"/><circle class="cls-9" cx="392.39" cy="137.14" r="8.79"/><circle class="cls-4" cx="392.39" cy="319.43" r="5.11"/><circle class="cls-4" cx="392.39" cy="319.43" r="8.79"/><circle class="cls-8" cx="392.39" cy="319.43" r="5.11"/><circle class="cls-9" cx="392.39" cy="319.43" r="8.79"/><path class="cls-10" d="M392.33,128.35a8.7,8.7,0,0,1,8.69,8.7V319.52a8.7,8.7,0,0,1-8.69,8.7H77.66a8.7,8.7,0,0,1-8.7-8.7V137.05a8.7,8.7,0,0,1,8.7-8.7H392.33m0-.75H77.66a9.46,9.46,0,0,0-9.45,9.45V319.52A9.46,9.46,0,0,0,77.66,329H392.33a9.45,9.45,0,0,0,9.44-9.45V137.05a9.45,9.45,0,0,0-9.44-9.45Z"/><circle class="cls-3" cx="362.26" cy="137.42" r="1.14"/><circle class="cls-2" cx="362.26" cy="137.42" r="0.53"/><path class="cls-9" d="M91.43,132.15V322.71h289V132.15ZM373.77,313.6h-259V139.82h259Z"/><rect class="cls-7" x="114.72" y="139.82" width="259.05" height="173.78"/><rect class="cls-4" x="72.34" y="207.38" width="8.03" height="4.33" rx="1.99"/><rect class="cls-5" x="73.94" y="208.13" width="4.76" height="2.82" rx="0.82"/><circle class="cls-4" cx="76.35" cy="261.36" r="2.35"/><circle class="cls-4" cx="76.35" cy="268.62" r="2.35"/><circle class="cls-4" cx="83.48" cy="261.36" r="2.35"/><circle class="cls-4" cx="83.48" cy="268.62" r="2.35"/><circle class="cls-4" cx="76.35" cy="220.61" r="2.35"/><circle class="cls-4" cx="76.35" cy="227.87" r="2.35"/><circle class="cls-4" cx="83.48" cy="220.61" r="2.35"/><circle class="cls-4" cx="83.48" cy="227.87" r="2.35"/><path class="cls-11" d="M135.78,178.25a16,16,0,0,0,6.78,1.8c3.55,0,5.34-1.71,5.34-3.91,0-2.88-2.87-4.18-5.88-4.18h-2.79v-4.9h2.65c2.3,0,5.22-.89,5.22-3.37,0-1.75-1.44-3.05-4.32-3.05a13.35,13.35,0,0,0-6.11,1.75l-1.39-4.94a18.34,18.34,0,0,1,9-2.2c6.24,0,9.7,3.28,9.7,7.28,0,3.1-1.75,5.52-5.34,6.78v.09c3.5.63,6.33,3.28,6.33,7.1,0,5.16-4.54,8.94-11.95,8.94a17.25,17.25,0,0,1-8.67-2.07Z"/><path class="cls-11" d="M159.86,181.3a4,4,0,0,1,4-4.17,4.16,4.16,0,0,1-.05,8.31A3.93,3.93,0,0,1,159.86,181.3Z"/><path class="cls-11" d="M190.67,161.36H179.35l-.63,4.49a12.67,12.67,0,0,1,1.89-.09,13.34,13.34,0,0,1,7.68,2.11,8.31,8.31,0,0,1,3.55,7.32c0,5.44-4.67,10.25-12.53,10.25a18.42,18.42,0,0,1-8.13-1.67l1.21-5.12a16.72,16.72,0,0,0,6.6,1.44c2.83,0,5.84-1.35,5.84-4.45s-2.38-4.85-8.22-4.85a25.16,25.16,0,0,0-4,.27l1.93-15.32h16.08Z"/><path class="cls-11" d="M204.87,154.17a59.32,59.32,0,0,1-5.39,12.17l-4.13.36a71.51,71.51,0,0,0,3.14-12.12Zm8.76,0a56.74,56.74,0,0,1-5.39,12.17l-4.13.36a71.51,71.51,0,0,0,3.14-12.12Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 4.8 KiB |
1
Meshtastic/Resources/Devices.xcassets/crowpanel_5_0.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 470.13 470.13"><defs><style>.cls-1{fill:#3d3d3d;}.cls-2{fill:#1d1d1b;}.cls-3{fill:#d2d2cd;}.cls-4{fill:#fdfefb;}.cls-5{fill:none;stroke:#1d1d1b;stroke-miterlimit:10;stroke-width:0.5px;}.cls-6{fill:#1e1f1e;}.cls-7{fill:#2c2c2c;}.cls-8{fill:#a3a2a2;}.cls-9{fill:#656458;}.cls-10{fill:#97621d;}.cls-11{fill:#feffff;}</style></defs><g id="Layer_2" data-name="Layer 2"><rect class="cls-1" x="54.78" y="125.56" width="360.57" height="219.01" rx="7.11"/><path class="cls-2" d="M408.24,125.56a7.11,7.11,0,0,1,7.11,7.11V337.46a7.11,7.11,0,0,1-7.11,7.11H61.89a7.11,7.11,0,0,1-7.11-7.11V132.67a7.11,7.11,0,0,1,7.11-7.11H408.24m0-.75H61.89A7.87,7.87,0,0,0,54,132.67V337.46a7.87,7.87,0,0,0,7.86,7.86H408.24a7.86,7.86,0,0,0,7.86-7.86V132.67a7.87,7.87,0,0,0-7.86-7.86Z"/><circle class="cls-3" cx="62.25" cy="133.03" r="4.34"/><circle class="cls-3" cx="62.25" cy="133.03" r="7.47"/><circle class="cls-4" cx="62.25" cy="133.03" r="4.34"/><circle class="cls-5" cx="62.25" cy="133.03" r="7.47"/><circle class="cls-3" cx="62.25" cy="337.11" r="4.34"/><circle class="cls-3" cx="62.25" cy="337.11" r="7.47"/><circle class="cls-4" cx="62.25" cy="337.11" r="4.34"/><circle class="cls-5" cx="62.25" cy="337.11" r="7.47"/><circle class="cls-3" cx="407.89" cy="133.03" r="4.34"/><circle class="cls-3" cx="407.89" cy="133.03" r="7.47"/><circle class="cls-4" cx="407.89" cy="133.03" r="4.34"/><circle class="cls-5" cx="407.89" cy="133.03" r="7.47"/><circle class="cls-3" cx="407.89" cy="337.11" r="4.34"/><circle class="cls-3" cx="407.89" cy="337.11" r="7.47"/><circle class="cls-4" cx="407.89" cy="337.11" r="4.34"/><circle class="cls-5" cx="407.89" cy="337.11" r="7.47"/><circle class="cls-3" cx="68.31" cy="238.85" r="2.24"/><circle class="cls-3" cx="68.31" cy="231.84" r="2.24"/><rect class="cls-6" x="68.31" y="128.68" width="333.02" height="208.42"/><rect class="cls-7" x="84.88" y="137.37" width="301.07" height="180.26"/><rect class="cls-5" x="68.31" y="128.68" width="333.02" height="208.42"/><rect class="cls-5" x="84.88" y="137.37" width="301.07" height="180.26"/><circle class="cls-3" cx="61.5" cy="238.85" r="2.24"/><circle class="cls-3" cx="61.5" cy="231.84" r="2.24"/><circle class="cls-3" cx="61.5" cy="197.44" r="2.24"/><circle class="cls-3" cx="61.5" cy="191.39" r="1.78"/><rect class="cls-3" x="58.49" y="221.53" width="6.22" height="3.35" rx="1.54"/><rect class="cls-8" x="59.73" y="222.11" width="3.69" height="2.19" rx="0.63"/><rect class="cls-3" x="58.49" y="245.57" width="6.22" height="3.35" rx="1.54"/><rect class="cls-8" x="59.73" y="246.16" width="3.69" height="2.19" rx="0.63"/><circle class="cls-6" cx="407.89" cy="311.24" r="1.18"/><rect class="cls-9" x="68.31" y="337.1" width="127.43" height="3.53"/><rect class="cls-9" x="336.48" y="337.1" width="64.84" height="3.53"/><rect class="cls-10" x="195.74" y="337.1" width="140.74" height="3.53"/><polyline class="cls-5" points="68.31 337.11 68.31 340.63 401.33 340.63 401.33 337.11"/><path class="cls-11" d="M118.93,164.21H107.61l-.63,4.5a12.67,12.67,0,0,1,1.89-.09,13.27,13.27,0,0,1,7.68,2.11,8.3,8.3,0,0,1,3.55,7.32c0,5.43-4.67,10.24-12.53,10.24a18.58,18.58,0,0,1-8.13-1.66l1.21-5.12a16.72,16.72,0,0,0,6.6,1.44c2.83,0,5.84-1.35,5.84-4.45s-2.38-4.85-8.22-4.85a25.07,25.07,0,0,0-4,.27l1.93-15.32h16.08Z"/><path class="cls-11" d="M124.73,184.16a4,4,0,0,1,4-4.18,4.16,4.16,0,1,1-4,4.18Z"/><path class="cls-11" d="M157.61,173.06c0,9-3.63,15.23-11.09,15.23s-10.87-6.78-10.92-15c0-8.44,3.6-15.14,11.14-15.14C154.56,158.1,157.61,165.07,157.61,173.06Zm-15.13.18c0,6.7,1.57,9.89,4.22,9.89s4.09-3.33,4.09-10c0-6.47-1.4-9.88-4.14-9.88C144.14,163.27,142.43,166.46,142.48,173.24Z"/><path class="cls-11" d="M169.75,157a59.57,59.57,0,0,1-5.4,12.17l-4.13.36a71.9,71.9,0,0,0,3.15-12.13Zm8.75,0a57,57,0,0,1-5.39,12.17l-4.13.36a71.9,71.9,0,0,0,3.15-12.13Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 3.8 KiB |
1
Meshtastic/Resources/Devices.xcassets/crowpanel_7_0.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 470.13 470.13"><defs><style>.cls-1{fill:#3d3d3d;}.cls-2{fill:#97621d;}.cls-3{fill:#1e1f1e;}.cls-4{fill:#2c2c2c;}.cls-5{fill:#d2d2cd;}.cls-6{fill:#feffff;}.cls-7{fill:#1d1d1b;}.cls-8{fill:#fdfefb;}.cls-9{fill:none;stroke:#1d1d1b;stroke-miterlimit:10;stroke-width:0.38px;}.cls-10{fill:#a3a2a2;}</style></defs><g id="Layer_2" data-name="Layer 2"><rect class="cls-1" x="38.79" y="113.99" width="385.42" height="226.21" rx="5.64"/><path class="cls-2" d="M265.61,337.11v3.09H194.52v-3.09h-9.1a20.34,20.34,0,0,1-8,3.3l-51.52.11a29.53,29.53,0,0,1-10.26-3.41Z"/><path class="cls-3" d="M50.19,116.37V337.11H412.8V116.37ZM401.51,318.79H55.1V123H401.51Z"/><rect class="cls-4" x="55.1" y="123" width="346.41" height="195.79"/><path class="cls-5" d="M50.19,228.24v3.47a1.43,1.43,0,0,1-.58.11,1.85,1.85,0,0,1,0-3.69A1.43,1.43,0,0,1,50.19,228.24Z"/><path class="cls-5" d="M50.19,222.47v3.47a1.43,1.43,0,0,1-.58.11,1.85,1.85,0,0,1,0-3.69A1.43,1.43,0,0,1,50.19,222.47Z"/><path class="cls-6" d="M104.64,142.48v4.31l-12,24.88H85.36l12-23.49v-.09H84v-5.61Z"/><path class="cls-6" d="M109,168a4,4,0,0,1,4-4.18,4.16,4.16,0,1,1-4,4.18Z"/><path class="cls-6" d="M141.88,156.94c0,9-3.64,15.23-11.1,15.23s-10.87-6.78-10.91-15c0-8.45,3.59-15.14,11.14-15.14C138.83,142,141.88,148.94,141.88,156.94Zm-15.14.18c0,6.69,1.57,9.88,4.22,9.88s4.09-3.32,4.09-10c0-6.47-1.39-9.88-4.13-9.88C128.4,147.15,126.7,150.34,126.74,157.12Z"/><path class="cls-6" d="M154,140.9a59.41,59.41,0,0,1-5.39,12.18l-4.13.36a71.86,71.86,0,0,0,3.14-12.13Zm8.76,0a56.83,56.83,0,0,1-5.39,12.18l-4.13.36a71.86,71.86,0,0,0,3.14-12.13Z"/><path class="cls-7" d="M418.57,114a5.64,5.64,0,0,1,5.64,5.64V334.56a5.65,5.65,0,0,1-5.64,5.64H44.43a5.65,5.65,0,0,1-5.64-5.64V119.63A5.64,5.64,0,0,1,44.43,114H418.57m0-.75H44.43A6.4,6.4,0,0,0,38,119.63V334.56A6.4,6.4,0,0,0,44.43,341H418.57a6.4,6.4,0,0,0,6.39-6.39V119.63a6.4,6.4,0,0,0-6.39-6.39Z"/><circle class="cls-5" cx="44.49" cy="119.69" r="3.32"/><circle class="cls-5" cx="44.49" cy="119.69" r="5.7"/><circle class="cls-8" cx="44.49" cy="119.69" r="3.32"/><circle class="cls-9" cx="44.49" cy="119.69" r="5.7"/><circle class="cls-5" cx="418.51" cy="119.69" r="3.32"/><circle class="cls-5" cx="418.51" cy="119.69" r="5.7"/><circle class="cls-8" cx="418.51" cy="119.69" r="3.32"/><circle class="cls-9" cx="418.51" cy="119.69" r="5.7"/><circle class="cls-5" cx="44.49" cy="334.5" r="3.32"/><circle class="cls-5" cx="44.49" cy="334.5" r="5.7"/><circle class="cls-8" cx="44.49" cy="334.5" r="3.32"/><circle class="cls-9" cx="44.49" cy="334.5" r="5.7"/><circle class="cls-5" cx="418.51" cy="334.5" r="3.32"/><circle class="cls-5" cx="418.51" cy="334.5" r="5.7"/><circle class="cls-8" cx="418.51" cy="334.5" r="3.32"/><circle class="cls-9" cx="418.51" cy="334.5" r="5.7"/><rect class="cls-9" x="50.19" y="116.37" width="362.62" height="220.74"/><rect class="cls-9" x="55.11" y="123" width="346.4" height="195.79"/><circle class="cls-3" cx="418.51" cy="280.87" r="1.17"/><path class="cls-9" d="M115.6,337.11a29.53,29.53,0,0,0,10.26,3.41"/><path class="cls-9" d="M177.38,340.41a20.34,20.34,0,0,0,8-3.3"/><line class="cls-9" x1="194.52" y1="337.11" x2="194.52" y2="340.2"/><line class="cls-9" x1="265.61" y1="337.11" x2="265.61" y2="340.2"/><circle class="cls-5" cx="44.25" cy="229.98" r="1.84"/><circle class="cls-5" cx="44.25" cy="224.21" r="1.84"/><rect class="cls-5" x="40.98" y="215.47" width="5.12" height="2.76" rx="1.27"/><rect class="cls-10" x="42" y="215.95" width="3.03" height="1.8" rx="0.52"/><rect class="cls-5" x="40.98" y="235.26" width="5.12" height="2.76" rx="1.27"/><rect class="cls-10" x="42" y="235.74" width="3.03" height="1.8" rx="0.52"/></g></svg>
|
||||
|
After Width: | Height: | Size: 3.6 KiB |
1
Meshtastic/Resources/Devices.xcassets/diy.svg
Normal file
|
After Width: | Height: | Size: 89 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 8.8 KiB |
7218
Meshtastic/Resources/Devices.xcassets/heltec-mesh-solar.svg
Normal file
|
After Width: | Height: | Size: 199 KiB |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB |
1
Meshtastic/Resources/Devices.xcassets/heltec-v3.svg
Normal file
|
After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 6.1 KiB |
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="479.57 786.58 1040.84 433.17"><defs><style>.cls-1{fill:#cccccb;}.cls-2{fill:#2b2b2b;}.cls-3,.cls-6,.cls-7,.cls-8{fill:none;stroke-miterlimit:10;}.cls-3,.cls-6,.cls-7{stroke:#050606;}.cls-3,.cls-8{stroke-width:1.65px;}.cls-4{fill:#40403f;}.cls-5{fill:#ddd;}.cls-6{stroke-width:1.62px;}.cls-7{stroke-width:1.64px;}.cls-8{stroke:#fff;}.cls-9{fill:#353535;}.cls-10{fill:#c08c2d;}</style></defs><g id="Layer_4" data-name="Layer 4"><path class="cls-1" d="M595,923.44H519.6A10.46,10.46,0,0,1,509.14,913V802.7a15.28,15.28,0,0,1,15.28-15.28h979.89a15.29,15.29,0,0,1,15.29,15.29v400.93a15.3,15.3,0,0,1-15.29,15.29H524.42a15.28,15.28,0,0,1-15.28-15.28V1102.1a10.46,10.46,0,0,1,10.46-10.46H595"></path><rect class="cls-2" x="611.37" y="796.48" width="819.83" height="411.47"></rect><line class="cls-3" x1="1441.99" y1="787.41" x2="1441.99" y2="1218.92"></line><path class="cls-4" d="M620.91,851.7v302.78a1.87,1.87,0,0,1-1.87,1.87h-13.8a8.7,8.7,0,0,1-.89,0,10.23,10.23,0,0,1-9.35-10.2v-286a10.24,10.24,0,0,1,9.35-10.2,8.7,8.7,0,0,1,.89,0H619A1.87,1.87,0,0,1,620.91,851.7Z"></path><rect class="cls-5" x="480.4" y="942.42" width="114.6" height="127.58"></rect><rect class="cls-3" x="480.4" y="942.42" width="114.6" height="127.58"></rect><path class="cls-6" d="M595,923.44H519.6A10.46,10.46,0,0,1,509.14,913V802.7a15.28,15.28,0,0,1,15.28-15.28h979.89a15.29,15.29,0,0,1,15.29,15.29v400.93a15.3,15.3,0,0,1-15.29,15.29H524.42a15.28,15.28,0,0,1-15.28-15.28V1102.1a10.46,10.46,0,0,1,10.46-10.46H595"></path><path class="cls-2" d="M584.65,970.14H595a0,0,0,0,1,0,0v24.44a0,0,0,0,1,0,0H584.65a3,3,0,0,1-3-3V973.11A3,3,0,0,1,584.65,970.14Z"></path><rect class="cls-2" x="560.58" y="976.5" width="9.04" height="16.58" rx="2.72"></rect><path class="cls-2" d="M584.65,1026.63H595a0,0,0,0,1,0,0v24.44a0,0,0,0,1,0,0H584.65a3,3,0,0,1-3-3v-18.49A3,3,0,0,1,584.65,1026.63Z"></path><rect class="cls-2" x="560.58" y="1028.91" width="9.04" height="16.58" rx="2.72"></rect><path class="cls-3" d="M584.65,970.14H595a0,0,0,0,1,0,0v24.44a0,0,0,0,1,0,0H584.65a3,3,0,0,1-3-3V973.11A3,3,0,0,1,584.65,970.14Z"></path><rect class="cls-3" x="560.58" y="976.5" width="9.04" height="16.58" rx="2.72"></rect><path class="cls-3" d="M584.65,1026.63H595a0,0,0,0,1,0,0v24.44a0,0,0,0,1,0,0H584.65a3,3,0,0,1-3-3v-18.49A3,3,0,0,1,584.65,1026.63Z"></path><rect class="cls-3" x="560.58" y="1028.91" width="9.04" height="16.58" rx="2.72"></rect><polyline class="cls-7" points="611.37 1156.35 611.37 1207.95 1431.2 1207.95 1431.2 796.48 611.37 796.48 611.37 849.83"></polyline><line class="cls-3" x1="611.37" y1="1207.95" x2="611.37" y2="1218.93"></line><line class="cls-3" x1="611.37" y1="796.48" x2="611.37" y2="787.42"></line><rect class="cls-8" x="560.58" y="1107.17" width="18.37" height="41.67" rx="4.91"></rect><rect class="cls-8" x="528.51" y="1107.17" width="18.37" height="41.67" rx="4.91"></rect><rect class="cls-8" x="560.58" y="1166.28" width="18.37" height="41.67" rx="4.91"></rect><rect class="cls-8" x="528.51" y="1166.28" width="18.37" height="41.67" rx="4.91"></rect><rect class="cls-8" x="560.58" y="804.46" width="18.37" height="41.67" rx="4.91"></rect><rect class="cls-8" x="528.51" y="804.46" width="18.37" height="41.67" rx="4.91"></rect><rect class="cls-8" x="560.58" y="863.57" width="18.37" height="41.67" rx="4.91"></rect><rect class="cls-8" x="528.51" y="863.57" width="18.37" height="41.67" rx="4.91"></rect><circle class="cls-8" cx="1476.63" cy="831.74" r="27.15"></circle><circle class="cls-8" cx="1476.63" cy="1173.49" r="27.15"></circle><rect class="cls-9" x="676.15" y="804.6" width="742.79" height="396.03"></rect><path class="cls-10" d="M604.35,849.87v306.44a10.23,10.23,0,0,1-9.35-10.2v-286A10.24,10.24,0,0,1,604.35,849.87Z"></path><path class="cls-3" d="M605.24,849.83H619a1.87,1.87,0,0,1,1.87,1.87v302.78a1.87,1.87,0,0,1-1.87,1.87h-13.8A10.24,10.24,0,0,1,595,1146.11v-286A10.24,10.24,0,0,1,605.24,849.83Z"></path><rect class="cls-6" x="676.15" y="804.6" width="742.79" height="396.03"></rect></g></svg>
|
||||
|
After Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 9.9 KiB After Width: | Height: | Size: 9.9 KiB |
|
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 83 KiB |
|
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 43 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
1
Meshtastic/Resources/Devices.xcassets/heltec_v4.svg
Normal file
|
After Width: | Height: | Size: 36 KiB |
182
Meshtastic/Resources/Devices.xcassets/image_manifest.json
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
{
|
||||
"files": {
|
||||
"heltec-wireless-paper.svg": {
|
||||
"etag": "\"7a8d8c8e9e712f32ccdb32edcdaebf5e\""
|
||||
},
|
||||
"seeed-xiao-s3.svg": {
|
||||
"etag": "\"9d583ddf39288934736d7ac248987524\""
|
||||
},
|
||||
"heltec-mesh-node-t114.svg": {
|
||||
"etag": "\"ca927ce170fba26438c557af0de47a1e\""
|
||||
},
|
||||
"nano-g2-ultra.svg": {
|
||||
"etag": "\"82575f89ab2f60ffe6c1e009b19b596e\""
|
||||
},
|
||||
"rak2560.svg": {
|
||||
"etag": "\"da3e309e4f746f0539e13b1f089411e3\""
|
||||
},
|
||||
"tlora-v2-1-1_6.svg": {
|
||||
"etag": "\"7a9de7eff40aab166d5ab9f251dedaaa\""
|
||||
},
|
||||
"rak4631_case.svg": {
|
||||
"etag": "\"d141ca68501d83f3ca19ed74cb7ce12e\""
|
||||
},
|
||||
"heltec-wireless-tracker.svg": {
|
||||
"etag": "\"bb7143e1b25d1d18d5727baf69a1caed\""
|
||||
},
|
||||
"heltec-ht62-esp32c3-sx1262.svg": {
|
||||
"etag": "\"1f4d07a164cbc2cb99f26e4a05a763ea\""
|
||||
},
|
||||
"pico.svg": {
|
||||
"etag": "\"9f6b3557953065cce6d56ba6e6d48241\""
|
||||
},
|
||||
"lilygo-tlora-pager.svg": {
|
||||
"etag": "\"deb184deacb8006da18ae4751d2e0591\""
|
||||
},
|
||||
"rak-wismesh-tap-v2.svg": {
|
||||
"etag": "\"4acc893e184de92446357fcb5bba7812\""
|
||||
},
|
||||
"heltec-wsl-v3.svg": {
|
||||
"etag": "\"3ecfe8273cdf0d7dfb04dad6c3fa449a\""
|
||||
},
|
||||
"rak11200.svg": {
|
||||
"etag": "\"1a0bfda4331a9bfd29722382a787c700\""
|
||||
},
|
||||
"seeed_solar.svg": {
|
||||
"etag": "\"3cc4099ae22ed261b88f1a9f7d235275\""
|
||||
},
|
||||
"crowpanel_3_5.svg": {
|
||||
"etag": "\"2d4ee10776f01156dd9570da888be34f\""
|
||||
},
|
||||
"heltec_mesh_pocket.svg": {
|
||||
"etag": "\"933aafb0ce3a7b0e1faa67e951bc98ea\""
|
||||
},
|
||||
"tlora-t3s3-epaper.svg": {
|
||||
"etag": "\"dfe63532b984fd3f34ce26b38e1f0807\""
|
||||
},
|
||||
"rak-wismeshtap.svg": {
|
||||
"etag": "\"8c707dda5c384a10822d3ed785aeb411\""
|
||||
},
|
||||
"m5_c6l.svg": {
|
||||
"etag": "\"f17cb7e59a20ccf41243c666cbe54546\""
|
||||
},
|
||||
"heltec_v4.svg": {
|
||||
"etag": "\"54e84516a04e1276ca385b41c7aa8b8d\""
|
||||
},
|
||||
"wio-tracker-wm1110.svg": {
|
||||
"etag": "\"2dfb221a6a481f957a59b81dfb0dbaf7\""
|
||||
},
|
||||
"meteor_pro.svg": {
|
||||
"etag": "\"47ba8e4bc6e224fbd3b09401573549dd\""
|
||||
},
|
||||
"tlora-t3s3-v1.svg": {
|
||||
"etag": "\"89510451d52482a475e9cc13503f11a6\""
|
||||
},
|
||||
"seeed_xiao_nrf52_kit.svg": {
|
||||
"etag": "\"660b2c3bee85adeccdd5de7ea8d06648\""
|
||||
},
|
||||
"rak_3312.svg": {
|
||||
"etag": "\"a2b5c4fdf127868323c8129f84f8691e\""
|
||||
},
|
||||
"tbeam-s3-core.svg": {
|
||||
"etag": "\"04c0dab7e74a5c1e647567e150136e5b\""
|
||||
},
|
||||
"diy.svg": {
|
||||
"etag": "\"7b670e81e7aace4814887ba681fc9f5b\""
|
||||
},
|
||||
"promicro.svg": {
|
||||
"etag": "\"d100b5d3aacf51191d7c4a7eb28db231\""
|
||||
},
|
||||
"wio_tracker_l1_eink.svg": {
|
||||
"etag": "\"9074596ea8f08acacfa0ce2c9a48152f\""
|
||||
},
|
||||
"heltec-vision-master-e290.svg": {
|
||||
"etag": "\"71b598c2c125b115663ab2d40abcd154\""
|
||||
},
|
||||
"tracker-t1000-e.svg": {
|
||||
"etag": "\"b4194c4bb550f8ccbbf205489f37134c\""
|
||||
},
|
||||
"crowpanel_2_4.svg": {
|
||||
"etag": "\"3aa8b71d6e9d16f82fddde4ba8b472bd\""
|
||||
},
|
||||
"muzi_r1_neo.svg": {
|
||||
"etag": "\"d73a20b71a27e530dc6fbe514f3e9d88\""
|
||||
},
|
||||
"t-deck.svg": {
|
||||
"etag": "\"2187caebf4304bb2308c8ee3ca74dd60\""
|
||||
},
|
||||
"heltec-v3.svg": {
|
||||
"etag": "\"0e22f17d2a0cd67159a222eb0a01bed1\""
|
||||
},
|
||||
"crowpanel_2_8.svg": {
|
||||
"etag": "\"caad57326211a595f18b5f494ae24b59\""
|
||||
},
|
||||
"t-echo.svg": {
|
||||
"etag": "\"bd2db1e3f0764478a9841ff568abc807\""
|
||||
},
|
||||
"heltec-mesh-solar.svg": {
|
||||
"etag": "\"6d3a4f6266a80493f42c0013e30bb31c\""
|
||||
},
|
||||
"heltec-v3-case.svg": {
|
||||
"etag": "\"e935a15ddd7cd116b9c4203f434ff627\""
|
||||
},
|
||||
"station-g2.svg": {
|
||||
"etag": "\"f0a75bb77ddfcd8fa4c080caa018e539\""
|
||||
},
|
||||
"rak4631.svg": {
|
||||
"etag": "\"3f19ff501b98598546fb6d6e5db1151c\""
|
||||
},
|
||||
"thinknode_m2.svg": {
|
||||
"etag": "\"97441ac3a41d23e5e0f4702f5788643d\""
|
||||
},
|
||||
"tlora-v2-1-1_8.svg": {
|
||||
"etag": "\"7a9de7eff40aab166d5ab9f251dedaaa\""
|
||||
},
|
||||
"rak_wismesh_tag.svg": {
|
||||
"etag": "\"257d649982a6689ec7e7c326c0b4dd2f\""
|
||||
},
|
||||
"wio_tracker_l1_case.svg": {
|
||||
"etag": "\"21eccba8adbb33b1df19fe0de79a8734\""
|
||||
},
|
||||
"rak11310.svg": {
|
||||
"etag": "\"0761c4ec6607993e6133aca9634cd42e\""
|
||||
},
|
||||
"heltec-vision-master-t190.svg": {
|
||||
"etag": "\"7f58cc25f93b203c778a3d6d1c6dc53f\""
|
||||
},
|
||||
"crowpanel_7_0.svg": {
|
||||
"etag": "\"c593914e105b75ee978f5ce2e2a27f1c\""
|
||||
},
|
||||
"crowpanel_5_0.svg": {
|
||||
"etag": "\"a2920df06d5335284db85a2016c0c6c6\""
|
||||
},
|
||||
"thinknode_m1.svg": {
|
||||
"etag": "\"e525d5710fddf72e1626cf35346a6b25\""
|
||||
},
|
||||
"t-watch-s3.svg": {
|
||||
"etag": "\"2e474b5742ec392304c939b4ec63d466\""
|
||||
},
|
||||
"techo_lite.svg": {
|
||||
"etag": "\"42fdf86393b02396e828149f29295239\""
|
||||
},
|
||||
"heltec-vision-master-e213.svg": {
|
||||
"etag": "\"a56c7707865246300bd9e89b1f7155c5\""
|
||||
},
|
||||
"tbeam.svg": {
|
||||
"etag": "\"ad1781f30226fbe36bae1cbad7e85bac\""
|
||||
},
|
||||
"tdeck_pro.svg": {
|
||||
"etag": "\"6fca0ce5392b390bb7aa690c57ab0fee\""
|
||||
},
|
||||
"heltec-mesh-node-t114-case.svg": {
|
||||
"etag": "\"ac7c2abd66e7980db365006332d2b6e7\""
|
||||
},
|
||||
"seeed-sensecap-indicator.svg": {
|
||||
"etag": "\"7a0fc63602d8c978b75799032dfda252\""
|
||||
},
|
||||
"rpipicow.svg": {
|
||||
"etag": "\"04fd9771add804a62fbfe45b3d360f22\""
|
||||
}
|
||||
},
|
||||
"api_hash": "0a8536dc1b62574588e40b0aa1838d19e9d8123c5d2e6872f41c66cacb4add4d"
|
||||
}
|
||||
1213
Meshtastic/Resources/Devices.xcassets/lilygo-tlora-pager.svg
Normal file
|
After Width: | Height: | Size: 48 KiB |
1
Meshtastic/Resources/Devices.xcassets/m5_c6l.svg
Normal file
|
After Width: | Height: | Size: 14 KiB |
1
Meshtastic/Resources/Devices.xcassets/meteor_pro.svg
Normal file
|
After Width: | Height: | Size: 24 KiB |
141
Meshtastic/Resources/Devices.xcassets/muzi_r1_neo.svg
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 217.61 761.78589"
|
||||
version="1.1"
|
||||
id="svg15"
|
||||
sodipodi:docname="buyer benmeshtastic-01 (6).svg"
|
||||
width="217.61"
|
||||
height="761.78589"
|
||||
inkscape:version="1.4 (e7c3feb1, 2024-10-09)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview15"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="1.0249904"
|
||||
inkscape:cx="-4.3902849"
|
||||
inkscape:cy="683.42102"
|
||||
inkscape:window-width="1472"
|
||||
inkscape:window-height="890"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="38"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg15" />
|
||||
<defs
|
||||
id="defs1">
|
||||
<style
|
||||
id="style1">.cls-1{fill:#9a4451;}.cls-2{fill:#5b232b;}.cls-3{fill:#eadadb;}.cls-4{fill:#191919;}.cls-5{fill:#1f2020;}.cls-6{fill:#353535;}.cls-7{fill:#8b3944;}.cls-8{fill:#2a2a2a;}.cls-9{fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:0.95px;}</style>
|
||||
</defs>
|
||||
<g
|
||||
id="Layer_3"
|
||||
data-name="Layer 3"
|
||||
transform="translate(-407.715,-82.379135)">
|
||||
<path
|
||||
class="cls-1"
|
||||
d="m 617.2,633.34 v 21.18 a 5.74,5.74 0 0 1 -11.48,0 v -14.38 l -50,26.45 -1,2.9 1,1.54 0.08,137.28 -0.05,2.08 69.19,-13 V 628.81 Z"
|
||||
id="path1" />
|
||||
<path
|
||||
class="cls-2"
|
||||
d="m 624.85,628.49 v 0.32 l -7.65,4.53 v -1.79 a 5.74,5.74 0 0 0 -11.48,0 v 8.59 l -47.72,28.13 -90.13,48.82 -13.82,92.52 -17.26,0.38 -28.6,-8.44 v -85.47 a 9.33,9.33 0 0 1 1,-4.12 l 19.64,-40 a 8.71,8.71 0 0 1 0.85,-1.4 l 5.13,-7 0.11,-0.82 11,-14.53 v 0 l 5.36,-7.05 h -0.11 l 3.37,-4.6 20.46,-28.15 a 9.35,9.35 0 0 1 5,-3.51 l 5.46,7.23 a 4.65,4.65 0 0 0 3.69,1.83 h 32.07 a 4.62,4.62 0 0 0 3.6,-1.72 l 6.19,-7.68 h 69.87 a 23.93,23.93 0 0 1 23.97,23.93 z"
|
||||
id="path2" />
|
||||
<path
|
||||
class="cls-2"
|
||||
d="m 451.29,641.14 -5.36,7.05 v 0 c -0.13,-0.13 0.33,-0.73 0.33,-0.73 l 5,-6.32 z"
|
||||
id="path3" />
|
||||
<path
|
||||
class="cls-2"
|
||||
d="m 531.05,604.56 -6.19,7.68 a 4.62,4.62 0 0 1 -3.6,1.72 h -32.07 a 4.65,4.65 0 0 1 -3.69,-1.83 L 480,604.9 a 9.41,9.41 0 0 1 2.49,-0.34 z"
|
||||
id="path4" />
|
||||
<path
|
||||
class="cls-3"
|
||||
d="m 531.05,604.56 -6.19,7.68 a 4.62,4.62 0 0 1 -3.6,1.72 h -32.07 a 4.65,4.65 0 0 1 -3.69,-1.83 L 480,604.9 a 9.41,9.41 0 0 1 2.49,-0.34 z"
|
||||
id="path5" />
|
||||
<path
|
||||
class="cls-4"
|
||||
d="m 462.31,618.89 v 7 l -12.15,16.71 -11.08,15.2 -10.29,14.2 v -48.63 l 5.51,-37.49 a 2.21,2.21 0 0 1 2.18,-1.88 h 17.73 a 2.21,2.21 0 0 1 2.17,1.81 z"
|
||||
id="path6" />
|
||||
<polygon
|
||||
class="cls-5"
|
||||
points="436.48,584 436.48,125.69 454.02,125.58 454.02,584 "
|
||||
id="polygon6" />
|
||||
<path
|
||||
class="cls-4"
|
||||
d="m 456.2,92.91 c 0,0.12 0,0.23 0,0.35 L 455.05,125 a 0.82,0.82 0 0 1 -0.82,0.78 h -18.14 a 0.81,0.81 0 0 1 -0.81,-0.79 l -1,-31.68 a 9.78,9.78 0 0 1 5.05,-8.88 h 0.08 a 12.08,12.08 0 0 1 11.77,0 9.78,9.78 0 0 1 5.02,8.48 z"
|
||||
id="path7" />
|
||||
<polygon
|
||||
class="cls-6"
|
||||
points="439.08,657.8 439.08,584 450.16,584 450.16,642.56 "
|
||||
id="polygon7" />
|
||||
<path
|
||||
class="cls-6"
|
||||
d="m 448.65,93 v 32.7 h -6.37 V 93 a 3.19,3.19 0 0 1 3.19,-3.19 3.15,3.15 0 0 1 2.25,0.94 3.18,3.18 0 0 1 0.93,2.25 z"
|
||||
id="path8" />
|
||||
<path
|
||||
class="cls-7"
|
||||
d="M 555.75,675.9 V 843.69 H 439.42 V 677 a 27.34,27.34 0 0 1 27.34,-27.34 h 62.75 a 26.24,26.24 0 0 1 26.24,26.24 z"
|
||||
id="path9" />
|
||||
<path
|
||||
class="cls-8"
|
||||
d="m 624.85,797.42 v 22.34 a 23.93,23.93 0 0 1 -23.93,23.93 h -168.8 a 23.93,23.93 0 0 1 -23.93,-23.93 v -18.21 l 31.25,8.45 h 118.62 z"
|
||||
id="path10" />
|
||||
</g>
|
||||
<g
|
||||
id="Layer_2"
|
||||
data-name="Layer 2"
|
||||
transform="translate(-407.715,-82.379135)">
|
||||
<path
|
||||
class="cls-9"
|
||||
d="m 624.85,628.49 v 191.27 a 23.94,23.94 0 0 1 -23.93,23.93 H 432.12 A 23.93,23.93 0 0 1 408.19,819.76 V 716.08 a 9.46,9.46 0 0 1 0.95,-4.12 l 19.65,-40 a 8.38,8.38 0 0 1 0.85,-1.41 l 24.91,-34 20.45,-28.14 a 9.35,9.35 0 0 1 7.56,-3.85 h 118.36 a 23.93,23.93 0 0 1 23.93,23.93 z"
|
||||
id="path11" />
|
||||
<rect
|
||||
class="cls-9"
|
||||
x="605.71997"
|
||||
y="625.81"
|
||||
width="11.49"
|
||||
height="34.459999"
|
||||
rx="5.7399998"
|
||||
id="rect11" />
|
||||
<polyline
|
||||
class="cls-9"
|
||||
points="408.19 801.55 439.44 809.97 439.44 809.97 555.79 810.09 555.79 810.09 624.85 797.42"
|
||||
id="polyline11" />
|
||||
<path
|
||||
class="cls-9"
|
||||
d="m 480,604.9 5.46,7.23 a 4.65,4.65 0 0 0 3.69,1.83 h 32.07 a 4.59,4.59 0 0 0 3.59,-1.72 l 6.2,-7.68"
|
||||
id="path12" />
|
||||
<path
|
||||
class="cls-9"
|
||||
d="m 428.79,672 v -48.63 l 5.51,-37.49 a 2.21,2.21 0 0 1 2.18,-1.88 h 17.73 a 2.19,2.19 0 0 1 2.16,1.81 l 5.94,33.08 v 7"
|
||||
id="path13" />
|
||||
<line
|
||||
class="cls-9"
|
||||
x1="436.48001"
|
||||
y1="584"
|
||||
x2="436.48001"
|
||||
y2="125.69"
|
||||
id="line13" />
|
||||
<line
|
||||
class="cls-9"
|
||||
x1="454.01999"
|
||||
y1="584"
|
||||
x2="454.01999"
|
||||
y2="125.58"
|
||||
id="line14" />
|
||||
<path
|
||||
class="cls-9"
|
||||
d="m 454.23,125.73 h -18.14 a 0.82,0.82 0 0 1 -0.82,-0.79 l -1,-31.69 a 9.78,9.78 0 0 1 5,-8.87 h 0.09 a 12.11,12.11 0 0 1 11.77,0 v 0 a 9.81,9.81 0 0 1 5,8.91 l -1.14,31.68 a 0.82,0.82 0 0 1 -0.76,0.76 z"
|
||||
id="path14" />
|
||||
<path
|
||||
class="cls-9"
|
||||
d="M 555.75,843.69 V 675.9 A 26.24,26.24 0 0 0 529.51,649.66 H 466.76 A 27.34,27.34 0 0 0 439.42,677 v 166.69"
|
||||
id="path15" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 8.3 KiB After Width: | Height: | Size: 8.3 KiB |
|
Before Width: | Height: | Size: 102 KiB After Width: | Height: | Size: 102 KiB |