├── Meshtastic
├── ShowTime.swift
├── Resources
│ ├── alpha.png
│ ├── Assets.xcassets
│ │ ├── Assets.xcassets
│ │ │ ├── Contents.json
│ │ │ ├── tbeam.imageset
│ │ │ │ ├── tbeam.jpg
│ │ │ │ ├── tbeam-1.jpg
│ │ │ │ ├── tbeam-2.jpg
│ │ │ │ └── Contents.json
│ │ │ ├── techo.imageset
│ │ │ │ ├── techo.jpg
│ │ │ │ ├── techo-1.jpg
│ │ │ │ ├── techo-2.jpg
│ │ │ │ └── Contents.json
│ │ │ ├── AppIcon.appiconset
│ │ │ │ ├── 1024.png
│ │ │ │ ├── 120.png
│ │ │ │ ├── 152.png
│ │ │ │ ├── 167.png
│ │ │ │ ├── 180.png
│ │ │ │ ├── 20.png
│ │ │ │ ├── 29.png
│ │ │ │ ├── 40-1.png
│ │ │ │ ├── 40-2.png
│ │ │ │ ├── 40.png
│ │ │ │ ├── 58-1.png
│ │ │ │ ├── 58.png
│ │ │ │ ├── 60.png
│ │ │ │ ├── 76.png
│ │ │ │ ├── 80-1.png
│ │ │ │ ├── 80.png
│ │ │ │ ├── 87.png
│ │ │ │ └── 120-1.png
│ │ │ ├── tlorav1.imageset
│ │ │ │ ├── tlora.jpeg
│ │ │ │ ├── tlora-1.jpeg
│ │ │ │ ├── tlora-2.jpeg
│ │ │ │ └── Contents.json
│ │ │ ├── HELTECV20.imageset
│ │ │ │ ├── 655DCEC0-309D-430A-AF50-2453B6ADB1F6.png
│ │ │ │ ├── 655DCEC0-309D-430A-AF50-2453B6ADB1F6-1.png
│ │ │ │ ├── 655DCEC0-309D-430A-AF50-2453B6ADB1F6-2.png
│ │ │ │ └── Contents.json
│ │ │ ├── rak4631.imageset
│ │ │ │ ├── RAK7205_Enclosure-With-Solar-Panel_Top-View_01_9ed42002-fb51-4c49-a69e-43fcef692ef6_739x@2x.progressive.png
│ │ │ │ ├── RAK7205_Enclosure-With-Solar-Panel_Top-View_01_9ed42002-fb51-4c49-a69e-43fcef692ef6_739x@2x.progressive-1.png
│ │ │ │ └── Contents.json
│ │ │ ├── UNSET.imageset
│ │ │ │ └── Contents.json
│ │ │ ├── TLORAV2.imageset
│ │ │ │ └── Contents.json
│ │ │ ├── TLORAV211p6.imageset
│ │ │ │ └── Contents.json
│ │ │ ├── AccentColor.colorset
│ │ │ │ └── Contents.json
│ │ │ └── Color.colorset
│ │ │ │ └── Contents.json
│ │ └── TBEAM.imageset
│ │ │ ├── tbeam.jpg
│ │ │ └── tbeam-1.jpg
│ ├── AppIcon_Ham.icon
│ │ ├── Assets
│ │ │ └── ham.png
│ │ └── icon.json
│ ├── AppIcon.icon
│ │ └── Assets
│ │ │ ├── Icon Grid Inner Circle.svg
│ │ │ ├── Icon Grid Middle Stroke 3.svg
│ │ │ ├── Icon Grid Outer Stroke 2.svg
│ │ │ ├── Icon Grid Middle Circle.svg
│ │ │ ├── Icon Grid Outer Circle.svg
│ │ │ ├── Meshtastic Logo.svg
│ │ │ └── Icon Grid Inner Stroke.svg
│ ├── AppIconDebug.icon
│ │ └── Assets
│ │ │ ├── Icon Grid Inner Circle.svg
│ │ │ ├── Icon Grid Middle Stroke 3.svg
│ │ │ ├── Icon Grid Outer Stroke 2.svg
│ │ │ ├── Icon Grid Middle Circle.svg
│ │ │ ├── Icon Grid Outer Circle.svg
│ │ │ ├── Meshtastic Logo.svg
│ │ │ └── Icon Grid Inner Stroke.svg
│ └── AppIcon_Chirpy.icon
│ │ └── icon.json
├── Assets.xcassets
│ ├── Contents.json
│ ├── UNPHONE.imageset
│ │ ├── UNPHONE.png
│ │ └── Contents.json
│ ├── AppIcon.appiconset
│ │ ├── logo-3.png
│ │ ├── logo-dark.png
│ │ ├── logo-tinted.png
│ │ └── Contents.json
│ ├── AppIconDebug.appiconset
│ │ ├── DevIcon.png
│ │ ├── DevIcon-Dark.png
│ │ ├── DevIcon-Tinted.png
│ │ └── Contents.json
│ ├── SOLAR_NODE.imageset
│ │ ├── solar_node.png
│ │ └── Contents.json
│ ├── AppIcon_Ham.appiconset
│ │ ├── logo-dark 1.png
│ │ ├── logo-dark.png
│ │ ├── meshtastic_ham.jpg
│ │ └── Contents.json
│ ├── AppIcon_MPowered.appiconset
│ │ ├── MPowered.png
│ │ ├── MPowered 1.png
│ │ ├── MPowered 2.png
│ │ └── Contents.json
│ ├── AppIcon_Thumb.imageset
│ │ ├── AppIcon_Thumb.png
│ │ └── Contents.json
│ ├── AppIcon_Chirpy.appiconset
│ │ ├── AppIcon_Chirpy.png
│ │ ├── AppIcon_Chirpy_Dark.png
│ │ └── Contents.json
│ ├── AppIcon_Ham_Dark_Thumb.imageset
│ │ ├── logo-dark.png
│ │ └── Contents.json
│ ├── AppIcon_Ham_Thumb.imageset
│ │ ├── meshtastic_ham.jpg
│ │ └── Contents.json
│ ├── AppIcon_MPowered_Thumb.imageset
│ │ ├── MPowered.png
│ │ └── Contents.json
│ ├── ANDROIDSIM.imageset
│ │ ├── play_store_icon_114px-4.png
│ │ └── Contents.json
│ ├── AppIcon_Dark_Thumb.imageset
│ │ ├── AppIcon_Dark_Thumb.png
│ │ └── Contents.json
│ ├── AppIcon_MPowered_Dark_Thumb.imageset
│ │ ├── MPowered.png
│ │ └── Contents.json
│ ├── AppIcon_Chirpy_Thumb.imageset
│ │ ├── AppIcon_Chirpy_Thumb.png
│ │ └── Contents.json
│ ├── Symbol.symbolset
│ │ └── Contents.json
│ ├── AppIcon_Chirpy_Dark_Thumb.imageset
│ │ ├── AppIcon_Chirpy_Dark_Thumb.png
│ │ └── Contents.json
│ ├── STATIONG1.imageset
│ │ ├── meshtastic_mesh_device_station_edition_overview 1.png
│ │ └── Contents.json
│ ├── RPIPICO.imageset
│ │ └── Contents.json
│ ├── TBEAM.imageset
│ │ └── Contents.json
│ ├── TDECK.imageset
│ │ └── Contents.json
│ ├── TECHO.imageset
│ │ └── Contents.json
│ ├── UNSET.imageset
│ │ └── Contents.json
│ ├── HELTECV4.imageset
│ │ └── Contents.json
│ ├── PROMICRO.imageset
│ │ └── Contents.json
│ ├── RAK11310.imageset
│ │ └── Contents.json
│ ├── TLORAC6.imageset
│ │ └── Contents.json
│ ├── HELTECV3.imageset
│ │ └── Contents.json
│ ├── MUZIR1NEO.imageset
│ │ └── Contents.json
│ ├── RAK4631.imageset
│ │ └── Contents.json
│ ├── STATIONG2.imageset
│ │ └── Contents.json
│ ├── TWATCHS3.imageset
│ │ └── Contents.json
│ ├── HELTECWSLV3.imageset
│ │ └── Contents.json
│ ├── NANOG2ULTRA.imageset
│ │ └── Contents.json
│ ├── SEEEDSOLARNODE.imageset
│ │ └── Contents.json
│ ├── SEEEDXIAOS3.imageset
│ │ └── Contents.json
│ ├── THINKNODEM1.imageset
│ │ └── Contents.json
│ ├── THINKNODEM2.imageset
│ │ └── Contents.json
│ ├── THINKNODEM3.imageset
│ │ └── Contents.json
│ ├── THINKNODEM4.imageset
│ │ └── Contents.json
│ ├── TLORAT3S3V1.imageset
│ │ └── Contents.json
│ ├── TLORAV211P6.imageset
│ │ └── Contents.json
│ ├── TLORAV211P8.imageset
│ │ └── Contents.json
│ ├── WIOWM1110.imageset
│ │ └── Contents.json
│ ├── WISMESHTAP.imageset
│ │ └── Contents.json
│ ├── logo-black.imageset
│ │ ├── Contents.json
│ │ └── Mesh_Logo_Black.svg
│ ├── logo-white.imageset
│ │ ├── Contents.json
│ │ └── Mesh_Logo_White.svg
│ ├── LILYGOTBEAMS3CORE.imageset
│ │ └── Contents.json
│ ├── NANOG1.imageset
│ │ ├── 2022-04-01T18-01-04.120Z-meshtastic_mesh_device_nano_edition_G1_P1 1.png
│ │ └── Contents.json
│ ├── TLORAT3S3EPAPER.imageset
│ │ └── Contents.json
│ ├── TRACKERT1000E.imageset
│ │ └── Contents.json
│ ├── XIAONRF52KIT.imageset
│ │ └── Contents.json
│ ├── HELTECHT62.imageset
│ │ └── Contents.json
│ ├── HELTECMESHPOCKET.imageset
│ │ └── Contents.json
│ ├── SEEEDWIOTRACKERL1.imageset
│ │ └── Contents.json
│ ├── custom.bluetooth.symbolset
│ │ └── Contents.json
│ ├── custom.link.slash.symbolset
│ │ └── Contents.json
│ ├── soil.moisture.symbolset
│ │ └── Contents.json
│ ├── soil.temperature.symbolset
│ │ └── Contents.json
│ ├── HELTECWIRELESSPAPER.imageset
│ │ └── Contents.json
│ ├── SENSECAPINDICATOR.imageset
│ │ └── Contents.json
│ ├── progress.ring.dashed.symbolset
│ │ └── Contents.json
│ ├── HELTECMESHNODET114.imageset
│ │ └── Contents.json
│ ├── HELTECVISIONMASTERE213.imageset
│ │ └── Contents.json
│ ├── HELTECVISIONMASTERE290.imageset
│ │ └── Contents.json
│ ├── HELTECWIRELESSTRACKER.imageset
│ │ └── Contents.json
│ ├── AccentColor.colorset
│ │ └── Contents.json
│ └── Color.colorset
│ │ └── Contents.json
├── Preview Content
│ └── Preview Assets.xcassets
│ │ └── Contents.json
├── RELEASENOTES.md
├── Meshtastic.xcdatamodeld
│ └── .xccurrentversion
├── Extensions
│ ├── Protobufs
│ │ └── NodeInfoExtensions.swift
│ ├── Character.swift
│ ├── Data.swift
│ ├── Bool.swift
│ ├── Double.swift
│ ├── Int.swift
│ ├── CoreData
│ │ ├── RangeTestConfigEntityExtension.swift
│ │ ├── StoreForwardConfigEntityExtension.swift
│ │ ├── LocationEntityExtension.swift
│ │ ├── TraceRouteEntityExtension.swift
│ │ ├── SerialConfigEntityExtension.swift
│ │ ├── DeviceMetadataEntityExtension.swift
│ │ ├── MQTTConfigEntityExtension.swift
│ │ └── MyInfoEntityExtension.swift
│ ├── Measurement.swift
│ ├── Constants.swift
│ ├── OSLogEntryLog.swift
│ ├── UIImage.swift
│ ├── CLLocation.swift
│ ├── Date.swift
│ ├── Bundle.swift
│ ├── Float.swift
│ └── UIColor.swift
├── Enums
│ ├── MessageDestination.swift
│ ├── EthernetModes.swift
│ ├── TelemetryWeather.swift
│ ├── ChannelRoles.swift
│ ├── BluetoothModes.swift
│ ├── RouteEnums.swift
│ ├── KeyBackupStatus.swift
│ ├── MessagingEnums.swift
│ └── DetectionSensorEnums.swift
├── Views
│ ├── Messages
│ │ ├── TextMessageField
│ │ │ ├── AlertButton.swift
│ │ │ ├── RequestPositionButton.swift
│ │ │ └── TextMessageSize.swift
│ │ └── TapbackResponses.swift
│ ├── Nodes
│ │ └── Helpers
│ │ │ ├── ScrollToBottomButton.swift
│ │ │ ├── Actions
│ │ │ ├── NodeAlertsButton.swift
│ │ │ ├── ClientHistoryButton.swift
│ │ │ ├── TraceRouteButton.swift
│ │ │ └── IgnoreNodeButton.swift
│ │ │ ├── PreferenceKeys
│ │ │ └── TileHeightKeys.swift
│ │ │ └── NodeFilterParameters.swift
│ ├── Helpers
│ │ ├── Weather
│ │ │ └── IAQScale.swift
│ │ ├── DistanceText.swift
│ │ ├── DateTimeText.swift
│ │ ├── Messages
│ │ │ └── MessageTemplate.swift
│ │ ├── Help
│ │ │ └── AckErrors.swift
│ │ ├── Compact Widgets
│ │ │ ├── WeightCompactWidget.swift
│ │ │ ├── DistanceCompactWidget.swift
│ │ │ ├── RadiationCompactWidget.swift
│ │ │ ├── CurrentConditionsCompact.swift
│ │ │ ├── PressureCompactWidget.swift
│ │ │ ├── WeatherConditionsCompactWidget.swift
│ │ │ ├── HumidityCompactWidget.swift
│ │ │ └── WindCompactWidget.swift
│ │ ├── ChannelLock.swift
│ │ └── MeshtasticLogo.swift
│ └── Settings
│ │ ├── AppIconPicker.swift
│ │ ├── AppIconButton.swift
│ │ └── Config
│ │ └── ConfigHeader.swift
├── Helpers
│ ├── CommonRegex.swift
│ ├── BluetoothManager.swift
│ ├── Preferences.swift
│ ├── Logger.swift
│ └── EmojiOnlyTextField.swift
├── Tips
│ ├── BluetoothTips.swift
│ ├── MessagesTips.swift
│ ├── PersistantTips.swift
│ └── ChannelTips.swift
├── Measurement
│ └── CustomFormatters.swift
├── Export
│ ├── LogDocument.swift
│ └── CsvDocument.swift
├── Model
│ ├── PeripheralModel.swift
│ └── CoreData
│ │ └── TelemetryEntity+CoreDataProperties.swift
├── AppIntents
│ ├── AppIntentErrors.swift
│ ├── DisconnectNodeIntent.swift
│ ├── TracerouteIntent.swift
│ ├── RestartNodeIntent.swift
│ ├── ShutDownNodeIntent.swift
│ ├── MessageNodeIntent.swift
│ ├── ShortcutsProvider.swift
│ └── AddContactIntent.swift
├── Accessory
│ ├── Helpers
│ │ ├── LogRecord+StringRepresentation.swift
│ │ └── ManualConnectionList.swift
│ └── Protocols
│ │ └── Connection.swift
├── AppState.swift
├── Meshtastic.entitlements
└── Router
│ └── NavigationState.swift
├── .github
├── ISSUE_TEMPLATE
│ ├── config.yml
│ └── feature.yml
├── workflows
│ ├── swiftlint.yml
│ └── stale.yml
├── FUNDING.yml
└── pull_request_template.md
├── scripts
├── hooks
│ └── pre-commit
├── thebenternify.sh
├── unthebenternify.sh
├── gen_protos.sh
├── setup-hooks.sh
└── create-release-branch.sh
├── meshtastic-1080x1080.png
├── .gitmodules
├── Widgets
├── Assets.xcassets
│ ├── Contents.json
│ ├── AccentColor.colorset
│ │ └── Contents.json
│ ├── WidgetBackground.colorset
│ │ └── Contents.json
│ ├── AppIcon.appiconset
│ │ └── Contents.json
│ ├── LightIndigo.colorset
│ │ └── Contents.json
│ ├── AccentColorDimmed.colorset
│ │ └── Contents.json
│ ├── m-logo-black.imageset
│ │ ├── Contents.json
│ │ ├── Mesh_Logo_Black.svg
│ │ ├── Mesh_Logo_Black_Large.svg
│ │ └── Mesh_Logo_Black_Small.svg
│ ├── m-logo-white.imageset
│ │ ├── Contents.json
│ │ ├── Mesh_Logo_White.svg
│ │ ├── Mesh_Logo_White_Large.svg
│ │ └── Mesh_Logo_White_Small.svg
│ └── LiveActivityBackground.colorset
│ │ └── Contents.json
├── WidgetsExtension.entitlements
├── Info.plist
├── WidgetsBundle.swift
└── MeshActivityAttributes.swift
├── Settings.bundle
├── en.lproj
│ └── Root.strings
└── de.lproj
│ └── Root.strings
├── MeshtasticProtobufs
├── .gitignore
├── Package.resolved
└── Package.swift
├── Meshtastic.xcodeproj
└── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ ├── WorkspaceSettings.xcsettings
│ └── IDEWorkspaceChecks.plist
├── Meshtastic.xcworkspace
├── xcshareddata
│ ├── WorkspaceSettings.xcsettings
│ └── IDEWorkspaceChecks.plist
└── contents.xcworkspacedata
├── ci_scripts
└── ci_pre_xcodebuild.sh
├── README.md
├── .swiftlint-precommit.yml
├── .swiftlint.yml
└── RELEASING.md
/Meshtastic/ShowTime.swift:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: false
2 |
--------------------------------------------------------------------------------
/scripts/hooks/pre-commit:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | ./scripts/lint/lint-fix-changes.sh
--------------------------------------------------------------------------------
/meshtastic-1080x1080.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meshtastic/Meshtastic-Apple/HEAD/meshtastic-1080x1080.png
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "protobufs"]
2 | path = protobufs
3 | url = https://github.com/meshtastic/protobufs.git
4 |
--------------------------------------------------------------------------------
/Meshtastic/Resources/alpha.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meshtastic/Meshtastic-Apple/HEAD/Meshtastic/Resources/alpha.png
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Widgets/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Settings.bundle/en.lproj/Root.strings:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meshtastic/Meshtastic-Apple/HEAD/Settings.bundle/en.lproj/Root.strings
--------------------------------------------------------------------------------
/Meshtastic/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Meshtastic/Resources/Assets.xcassets/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Meshtastic/Resources/AppIcon_Ham.icon/Assets/ham.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meshtastic/Meshtastic-Apple/HEAD/Meshtastic/Resources/AppIcon_Ham.icon/Assets/ham.png
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/UNPHONE.imageset/UNPHONE.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meshtastic/Meshtastic-Apple/HEAD/Meshtastic/Assets.xcassets/UNPHONE.imageset/UNPHONE.png
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/AppIcon.appiconset/logo-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meshtastic/Meshtastic-Apple/HEAD/Meshtastic/Assets.xcassets/AppIcon.appiconset/logo-3.png
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/AppIcon.appiconset/logo-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meshtastic/Meshtastic-Apple/HEAD/Meshtastic/Assets.xcassets/AppIcon.appiconset/logo-dark.png
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/AppIcon.appiconset/logo-tinted.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meshtastic/Meshtastic-Apple/HEAD/Meshtastic/Assets.xcassets/AppIcon.appiconset/logo-tinted.png
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/AppIconDebug.appiconset/DevIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meshtastic/Meshtastic-Apple/HEAD/Meshtastic/Assets.xcassets/AppIconDebug.appiconset/DevIcon.png
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/SOLAR_NODE.imageset/solar_node.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meshtastic/Meshtastic-Apple/HEAD/Meshtastic/Assets.xcassets/SOLAR_NODE.imageset/solar_node.png
--------------------------------------------------------------------------------
/Meshtastic/Resources/Assets.xcassets/TBEAM.imageset/tbeam.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meshtastic/Meshtastic-Apple/HEAD/Meshtastic/Resources/Assets.xcassets/TBEAM.imageset/tbeam.jpg
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/AppIcon_Ham.appiconset/logo-dark 1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meshtastic/Meshtastic-Apple/HEAD/Meshtastic/Assets.xcassets/AppIcon_Ham.appiconset/logo-dark 1.png
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/AppIcon_Ham.appiconset/logo-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meshtastic/Meshtastic-Apple/HEAD/Meshtastic/Assets.xcassets/AppIcon_Ham.appiconset/logo-dark.png
--------------------------------------------------------------------------------
/Meshtastic/Resources/Assets.xcassets/TBEAM.imageset/tbeam-1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meshtastic/Meshtastic-Apple/HEAD/Meshtastic/Resources/Assets.xcassets/TBEAM.imageset/tbeam-1.jpg
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/AppIconDebug.appiconset/DevIcon-Dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meshtastic/Meshtastic-Apple/HEAD/Meshtastic/Assets.xcassets/AppIconDebug.appiconset/DevIcon-Dark.png
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/AppIcon_MPowered.appiconset/MPowered.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meshtastic/Meshtastic-Apple/HEAD/Meshtastic/Assets.xcassets/AppIcon_MPowered.appiconset/MPowered.png
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/AppIcon_Thumb.imageset/AppIcon_Thumb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meshtastic/Meshtastic-Apple/HEAD/Meshtastic/Assets.xcassets/AppIcon_Thumb.imageset/AppIcon_Thumb.png
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/AppIconDebug.appiconset/DevIcon-Tinted.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meshtastic/Meshtastic-Apple/HEAD/Meshtastic/Assets.xcassets/AppIconDebug.appiconset/DevIcon-Tinted.png
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/AppIcon_Ham.appiconset/meshtastic_ham.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meshtastic/Meshtastic-Apple/HEAD/Meshtastic/Assets.xcassets/AppIcon_Ham.appiconset/meshtastic_ham.jpg
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/AppIcon_MPowered.appiconset/MPowered 1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meshtastic/Meshtastic-Apple/HEAD/Meshtastic/Assets.xcassets/AppIcon_MPowered.appiconset/MPowered 1.png
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/AppIcon_MPowered.appiconset/MPowered 2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meshtastic/Meshtastic-Apple/HEAD/Meshtastic/Assets.xcassets/AppIcon_MPowered.appiconset/MPowered 2.png
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/AppIcon_Chirpy.appiconset/AppIcon_Chirpy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meshtastic/Meshtastic-Apple/HEAD/Meshtastic/Assets.xcassets/AppIcon_Chirpy.appiconset/AppIcon_Chirpy.png
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/AppIcon_Ham_Dark_Thumb.imageset/logo-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meshtastic/Meshtastic-Apple/HEAD/Meshtastic/Assets.xcassets/AppIcon_Ham_Dark_Thumb.imageset/logo-dark.png
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/AppIcon_Ham_Thumb.imageset/meshtastic_ham.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meshtastic/Meshtastic-Apple/HEAD/Meshtastic/Assets.xcassets/AppIcon_Ham_Thumb.imageset/meshtastic_ham.jpg
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/AppIcon_MPowered_Thumb.imageset/MPowered.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meshtastic/Meshtastic-Apple/HEAD/Meshtastic/Assets.xcassets/AppIcon_MPowered_Thumb.imageset/MPowered.png
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/ANDROIDSIM.imageset/play_store_icon_114px-4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meshtastic/Meshtastic-Apple/HEAD/Meshtastic/Assets.xcassets/ANDROIDSIM.imageset/play_store_icon_114px-4.png
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/AppIcon_Chirpy.appiconset/AppIcon_Chirpy_Dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meshtastic/Meshtastic-Apple/HEAD/Meshtastic/Assets.xcassets/AppIcon_Chirpy.appiconset/AppIcon_Chirpy_Dark.png
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/AppIcon_Dark_Thumb.imageset/AppIcon_Dark_Thumb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meshtastic/Meshtastic-Apple/HEAD/Meshtastic/Assets.xcassets/AppIcon_Dark_Thumb.imageset/AppIcon_Dark_Thumb.png
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/AppIcon_MPowered_Dark_Thumb.imageset/MPowered.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meshtastic/Meshtastic-Apple/HEAD/Meshtastic/Assets.xcassets/AppIcon_MPowered_Dark_Thumb.imageset/MPowered.png
--------------------------------------------------------------------------------
/Meshtastic/Resources/Assets.xcassets/Assets.xcassets/tbeam.imageset/tbeam.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meshtastic/Meshtastic-Apple/HEAD/Meshtastic/Resources/Assets.xcassets/Assets.xcassets/tbeam.imageset/tbeam.jpg
--------------------------------------------------------------------------------
/Meshtastic/Resources/Assets.xcassets/Assets.xcassets/techo.imageset/techo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meshtastic/Meshtastic-Apple/HEAD/Meshtastic/Resources/Assets.xcassets/Assets.xcassets/techo.imageset/techo.jpg
--------------------------------------------------------------------------------
/Meshtastic/Resources/Assets.xcassets/Assets.xcassets/AppIcon.appiconset/1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meshtastic/Meshtastic-Apple/HEAD/Meshtastic/Resources/Assets.xcassets/Assets.xcassets/AppIcon.appiconset/1024.png
--------------------------------------------------------------------------------
/Meshtastic/Resources/Assets.xcassets/Assets.xcassets/AppIcon.appiconset/120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meshtastic/Meshtastic-Apple/HEAD/Meshtastic/Resources/Assets.xcassets/Assets.xcassets/AppIcon.appiconset/120.png
--------------------------------------------------------------------------------
/Meshtastic/Resources/Assets.xcassets/Assets.xcassets/AppIcon.appiconset/152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meshtastic/Meshtastic-Apple/HEAD/Meshtastic/Resources/Assets.xcassets/Assets.xcassets/AppIcon.appiconset/152.png
--------------------------------------------------------------------------------
/Meshtastic/Resources/Assets.xcassets/Assets.xcassets/AppIcon.appiconset/167.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meshtastic/Meshtastic-Apple/HEAD/Meshtastic/Resources/Assets.xcassets/Assets.xcassets/AppIcon.appiconset/167.png
--------------------------------------------------------------------------------
/Meshtastic/Resources/Assets.xcassets/Assets.xcassets/AppIcon.appiconset/180.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meshtastic/Meshtastic-Apple/HEAD/Meshtastic/Resources/Assets.xcassets/Assets.xcassets/AppIcon.appiconset/180.png
--------------------------------------------------------------------------------
/Meshtastic/Resources/Assets.xcassets/Assets.xcassets/AppIcon.appiconset/20.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meshtastic/Meshtastic-Apple/HEAD/Meshtastic/Resources/Assets.xcassets/Assets.xcassets/AppIcon.appiconset/20.png
--------------------------------------------------------------------------------
/Meshtastic/Resources/Assets.xcassets/Assets.xcassets/AppIcon.appiconset/29.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meshtastic/Meshtastic-Apple/HEAD/Meshtastic/Resources/Assets.xcassets/Assets.xcassets/AppIcon.appiconset/29.png
--------------------------------------------------------------------------------
/Meshtastic/Resources/Assets.xcassets/Assets.xcassets/AppIcon.appiconset/40-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meshtastic/Meshtastic-Apple/HEAD/Meshtastic/Resources/Assets.xcassets/Assets.xcassets/AppIcon.appiconset/40-1.png
--------------------------------------------------------------------------------
/Meshtastic/Resources/Assets.xcassets/Assets.xcassets/AppIcon.appiconset/40-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meshtastic/Meshtastic-Apple/HEAD/Meshtastic/Resources/Assets.xcassets/Assets.xcassets/AppIcon.appiconset/40-2.png
--------------------------------------------------------------------------------
/Meshtastic/Resources/Assets.xcassets/Assets.xcassets/AppIcon.appiconset/40.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meshtastic/Meshtastic-Apple/HEAD/Meshtastic/Resources/Assets.xcassets/Assets.xcassets/AppIcon.appiconset/40.png
--------------------------------------------------------------------------------
/Meshtastic/Resources/Assets.xcassets/Assets.xcassets/AppIcon.appiconset/58-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meshtastic/Meshtastic-Apple/HEAD/Meshtastic/Resources/Assets.xcassets/Assets.xcassets/AppIcon.appiconset/58-1.png
--------------------------------------------------------------------------------
/Meshtastic/Resources/Assets.xcassets/Assets.xcassets/AppIcon.appiconset/58.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meshtastic/Meshtastic-Apple/HEAD/Meshtastic/Resources/Assets.xcassets/Assets.xcassets/AppIcon.appiconset/58.png
--------------------------------------------------------------------------------
/Meshtastic/Resources/Assets.xcassets/Assets.xcassets/AppIcon.appiconset/60.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meshtastic/Meshtastic-Apple/HEAD/Meshtastic/Resources/Assets.xcassets/Assets.xcassets/AppIcon.appiconset/60.png
--------------------------------------------------------------------------------
/Meshtastic/Resources/Assets.xcassets/Assets.xcassets/AppIcon.appiconset/76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meshtastic/Meshtastic-Apple/HEAD/Meshtastic/Resources/Assets.xcassets/Assets.xcassets/AppIcon.appiconset/76.png
--------------------------------------------------------------------------------
/Meshtastic/Resources/Assets.xcassets/Assets.xcassets/AppIcon.appiconset/80-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meshtastic/Meshtastic-Apple/HEAD/Meshtastic/Resources/Assets.xcassets/Assets.xcassets/AppIcon.appiconset/80-1.png
--------------------------------------------------------------------------------
/Meshtastic/Resources/Assets.xcassets/Assets.xcassets/AppIcon.appiconset/80.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meshtastic/Meshtastic-Apple/HEAD/Meshtastic/Resources/Assets.xcassets/Assets.xcassets/AppIcon.appiconset/80.png
--------------------------------------------------------------------------------
/Meshtastic/Resources/Assets.xcassets/Assets.xcassets/AppIcon.appiconset/87.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meshtastic/Meshtastic-Apple/HEAD/Meshtastic/Resources/Assets.xcassets/Assets.xcassets/AppIcon.appiconset/87.png
--------------------------------------------------------------------------------
/Meshtastic/Resources/Assets.xcassets/Assets.xcassets/tbeam.imageset/tbeam-1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meshtastic/Meshtastic-Apple/HEAD/Meshtastic/Resources/Assets.xcassets/Assets.xcassets/tbeam.imageset/tbeam-1.jpg
--------------------------------------------------------------------------------
/Meshtastic/Resources/Assets.xcassets/Assets.xcassets/tbeam.imageset/tbeam-2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meshtastic/Meshtastic-Apple/HEAD/Meshtastic/Resources/Assets.xcassets/Assets.xcassets/tbeam.imageset/tbeam-2.jpg
--------------------------------------------------------------------------------
/Meshtastic/Resources/Assets.xcassets/Assets.xcassets/techo.imageset/techo-1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meshtastic/Meshtastic-Apple/HEAD/Meshtastic/Resources/Assets.xcassets/Assets.xcassets/techo.imageset/techo-1.jpg
--------------------------------------------------------------------------------
/Meshtastic/Resources/Assets.xcassets/Assets.xcassets/techo.imageset/techo-2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meshtastic/Meshtastic-Apple/HEAD/Meshtastic/Resources/Assets.xcassets/Assets.xcassets/techo.imageset/techo-2.jpg
--------------------------------------------------------------------------------
/Meshtastic/Resources/Assets.xcassets/Assets.xcassets/tlorav1.imageset/tlora.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meshtastic/Meshtastic-Apple/HEAD/Meshtastic/Resources/Assets.xcassets/Assets.xcassets/tlorav1.imageset/tlora.jpeg
--------------------------------------------------------------------------------
/MeshtasticProtobufs/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | xcuserdata/
5 | DerivedData/
6 | .swiftpm/configuration/registries.json
7 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
8 | .netrc
9 |
--------------------------------------------------------------------------------
/Meshtastic.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/AppIcon_Chirpy_Thumb.imageset/AppIcon_Chirpy_Thumb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meshtastic/Meshtastic-Apple/HEAD/Meshtastic/Assets.xcassets/AppIcon_Chirpy_Thumb.imageset/AppIcon_Chirpy_Thumb.png
--------------------------------------------------------------------------------
/Meshtastic/Resources/Assets.xcassets/Assets.xcassets/AppIcon.appiconset/120-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meshtastic/Meshtastic-Apple/HEAD/Meshtastic/Resources/Assets.xcassets/Assets.xcassets/AppIcon.appiconset/120-1.png
--------------------------------------------------------------------------------
/Meshtastic/Resources/Assets.xcassets/Assets.xcassets/tlorav1.imageset/tlora-1.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meshtastic/Meshtastic-Apple/HEAD/Meshtastic/Resources/Assets.xcassets/Assets.xcassets/tlorav1.imageset/tlora-1.jpeg
--------------------------------------------------------------------------------
/Meshtastic/Resources/Assets.xcassets/Assets.xcassets/tlorav1.imageset/tlora-2.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meshtastic/Meshtastic-Apple/HEAD/Meshtastic/Resources/Assets.xcassets/Assets.xcassets/tlorav1.imageset/tlora-2.jpeg
--------------------------------------------------------------------------------
/scripts/thebenternify.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | sed -i '' -e 's/GCH7VS5Y9R/6YF6QJH524/g' ./Meshtastic.xcodeproj/project.pbxproj
4 | sed -i '' -e 's/gvh.Meshtastic/thebentern.Meshtastic/g' ./Meshtastic.xcodeproj/project.pbxproj
5 |
--------------------------------------------------------------------------------
/scripts/unthebenternify.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | sed -i '' -e 's/6YF6QJH524/GCH7VS5Y9R/g' ./Meshtastic.xcodeproj/project.pbxproj
4 | sed -i '' -e 's/thebentern.Meshtastic/gvh.Meshtastic/g' ./Meshtastic.xcodeproj/project.pbxproj
5 |
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/Symbol.symbolset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | },
6 | "symbols" : [
7 | {
8 | "idiom" : "universal"
9 | }
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/Widgets/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Widgets/Assets.xcassets/WidgetBackground.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/AppIcon_Chirpy_Dark_Thumb.imageset/AppIcon_Chirpy_Dark_Thumb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meshtastic/Meshtastic-Apple/HEAD/Meshtastic/Assets.xcassets/AppIcon_Chirpy_Dark_Thumb.imageset/AppIcon_Chirpy_Dark_Thumb.png
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/STATIONG1.imageset/meshtastic_mesh_device_station_edition_overview 1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meshtastic/Meshtastic-Apple/HEAD/Meshtastic/Assets.xcassets/STATIONG1.imageset/meshtastic_mesh_device_station_edition_overview 1.png
--------------------------------------------------------------------------------
/Meshtastic.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/RPIPICO.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "pico.svg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/TBEAM.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "tbeam.svg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/TDECK.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "t-deck.svg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/TECHO.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "t-echo.svg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/UNSET.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "unknown.svg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/HELTECV4.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "heltec_v4.svg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/PROMICRO.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "promicro.svg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/RAK11310.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "rak11310.svg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/TLORAC6.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "tlora-c6.svg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/UNPHONE.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "UNPHONE.png",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/HELTECV3.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "heltec-v3-case.svg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/MUZIR1NEO.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "muzi_r1_neo.svg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/RAK4631.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "rak4631_case.svg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/SOLAR_NODE.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "solar_node.png",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/STATIONG2.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "station-g2.svg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/TWATCHS3.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "t-watch-s3.svg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/HELTECWSLV3.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "heltec-wsl-v3.svg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/NANOG2ULTRA.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "nano-g2-ultra.svg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/SEEEDSOLARNODE.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "seeed_solar.svg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/SEEEDXIAOS3.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "seeed-xiao-s3.svg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/THINKNODEM1.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "thinknode_m1.svg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/THINKNODEM2.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "thinknode_m2.svg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/THINKNODEM3.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "thinknode_m3.svg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/THINKNODEM4.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "thinknode_m4.svg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/TLORAT3S3V1.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "tlora-t3s3-v1.svg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/TLORAV211P6.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "tlora-v2-1-1_6.svg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/TLORAV211P8.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "tlora-v2-1-1_8.svg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/WIOWM1110.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "wio-tracker-wm1110.svg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/WISMESHTAP.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "rak-wismeshtap.svg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/logo-black.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Mesh_Logo_Black.svg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/logo-white.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Mesh_Logo_White.svg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Meshtastic/Resources/Assets.xcassets/Assets.xcassets/HELTECV20.imageset/655DCEC0-309D-430A-AF50-2453B6ADB1F6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meshtastic/Meshtastic-Apple/HEAD/Meshtastic/Resources/Assets.xcassets/Assets.xcassets/HELTECV20.imageset/655DCEC0-309D-430A-AF50-2453B6ADB1F6.png
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/LILYGOTBEAMS3CORE.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "tbeam-s3-core.svg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/NANOG1.imageset/2022-04-01T18-01-04.120Z-meshtastic_mesh_device_nano_edition_G1_P1 1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meshtastic/Meshtastic-Apple/HEAD/Meshtastic/Assets.xcassets/NANOG1.imageset/2022-04-01T18-01-04.120Z-meshtastic_mesh_device_nano_edition_G1_P1 1.png
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/TLORAT3S3EPAPER.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "tlora-t3s3-epaper.svg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/TRACKERT1000E.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "tracker-t1000-e.svg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/XIAONRF52KIT.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "seeed_xiao_nrf52_kit.svg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Meshtastic/Resources/Assets.xcassets/Assets.xcassets/HELTECV20.imageset/655DCEC0-309D-430A-AF50-2453B6ADB1F6-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meshtastic/Meshtastic-Apple/HEAD/Meshtastic/Resources/Assets.xcassets/Assets.xcassets/HELTECV20.imageset/655DCEC0-309D-430A-AF50-2453B6ADB1F6-1.png
--------------------------------------------------------------------------------
/Meshtastic/Resources/Assets.xcassets/Assets.xcassets/HELTECV20.imageset/655DCEC0-309D-430A-AF50-2453B6ADB1F6-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meshtastic/Meshtastic-Apple/HEAD/Meshtastic/Resources/Assets.xcassets/Assets.xcassets/HELTECV20.imageset/655DCEC0-309D-430A-AF50-2453B6ADB1F6-2.png
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/ANDROIDSIM.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "play_store_icon_114px-4.png",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/HELTECHT62.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "heltec-ht62-esp32c3-sx1262.svg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/HELTECMESHPOCKET.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "heltec_mesh_pocket.svg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/SEEEDWIOTRACKERL1.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "wio_tracker_l1_case.svg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/custom.bluetooth.symbolset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | },
6 | "symbols" : [
7 | {
8 | "filename" : "custom.bluetooth.svg",
9 | "idiom" : "universal"
10 | }
11 | ]
12 | }
13 |
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/custom.link.slash.symbolset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | },
6 | "symbols" : [
7 | {
8 | "filename" : "custom.link.slash.svg",
9 | "idiom" : "universal"
10 | }
11 | ]
12 | }
13 |
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/soil.moisture.symbolset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | },
6 | "symbols" : [
7 | {
8 | "filename" : "soilMoisture.variable.svg",
9 | "idiom" : "universal"
10 | }
11 | ]
12 | }
13 |
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/soil.temperature.symbolset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | },
6 | "symbols" : [
7 | {
8 | "filename" : "soilTemp.variable.svg",
9 | "idiom" : "universal"
10 | }
11 | ]
12 | }
13 |
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/HELTECWIRELESSPAPER.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "heltec-wireless-paper.svg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/SENSECAPINDICATOR.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "seeed-sensecap-indicator.svg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/progress.ring.dashed.symbolset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | },
6 | "symbols" : [
7 | {
8 | "filename" : "progress.ring.dashed.svg",
9 | "idiom" : "universal"
10 | }
11 | ]
12 | }
13 |
--------------------------------------------------------------------------------
/Widgets/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "platform" : "ios",
6 | "size" : "1024x1024"
7 | }
8 | ],
9 | "info" : {
10 | "author" : "xcode",
11 | "version" : 1
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/HELTECMESHNODET114.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "heltec-mesh-node-t114-case.svg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/HELTECVISIONMASTERE213.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "heltec-vision-master-e213.svg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/HELTECVISIONMASTERE290.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "heltec-vision-master-e290.svg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/HELTECWIRELESSTRACKER.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "heltec-wireless-tracker.svg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/STATIONG1.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "meshtastic_mesh_device_station_edition_overview 1.png",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Meshtastic.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/NANOG1.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "2022-04-01T18-01-04.120Z-meshtastic_mesh_device_nano_edition_G1_P1 1.png",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Meshtastic/RELEASENOTES.md:
--------------------------------------------------------------------------------
1 | # 1.27.8
2 |
3 | * Update NodeList SwipeAction Button to be role: Destructive
4 | * Added com.apple.security.files.user-selected.read-write entitlement to AppSandbox for MacOS for Mesh log download
5 | * Cleaned up bluetooth connecting timeout errors and logic, run 10 2 second timers now
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Meshtastic.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Meshtastic.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | _XCCurrentVersionName
6 | MeshtasticDataModelV 55.xcdatamodel
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Meshtastic/Extensions/Protobufs/NodeInfoExtensions.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import MeshtasticProtobufs
3 |
4 | extension NodeInfo {
5 | var isValidPosition: Bool {
6 | hasPosition &&
7 | position.longitudeI != 0 &&
8 | position.latitudeI != 0 &&
9 | position.latitudeI != 373346000 &&
10 | position.longitudeI != -1220090000
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Widgets/WidgetsExtension.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.network.client
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/Widgets/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | NSExtension
6 |
7 | NSExtensionPointIdentifier
8 | com.apple.widgetkit-extension
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/Meshtastic.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/Meshtastic/Resources/Assets.xcassets/Assets.xcassets/rak4631.imageset/RAK7205_Enclosure-With-Solar-Panel_Top-View_01_9ed42002-fb51-4c49-a69e-43fcef692ef6_739x@2x.progressive.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meshtastic/Meshtastic-Apple/HEAD/Meshtastic/Resources/Assets.xcassets/Assets.xcassets/rak4631.imageset/RAK7205_Enclosure-With-Solar-Panel_Top-View_01_9ed42002-fb51-4c49-a69e-43fcef692ef6_739x@2x.progressive.png
--------------------------------------------------------------------------------
/Widgets/WidgetsBundle.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WidgetsBundle.swift
3 | // Widgets
4 | //
5 | // Created by Garth Vander Houwen on 2/28/23.
6 | //
7 |
8 | import WidgetKit
9 | import SwiftUI
10 |
11 | @main
12 | struct WidgetsBundle: WidgetBundle {
13 | var body: some Widget {
14 | // Widgets()
15 | #if canImport(ActivityKit)
16 | WidgetsLiveActivity()
17 | #endif
18 |
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Meshtastic/Resources/Assets.xcassets/Assets.xcassets/rak4631.imageset/RAK7205_Enclosure-With-Solar-Panel_Top-View_01_9ed42002-fb51-4c49-a69e-43fcef692ef6_739x@2x.progressive-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meshtastic/Meshtastic-Apple/HEAD/Meshtastic/Resources/Assets.xcassets/Assets.xcassets/rak4631.imageset/RAK7205_Enclosure-With-Solar-Panel_Top-View_01_9ed42002-fb51-4c49-a69e-43fcef692ef6_739x@2x.progressive-1.png
--------------------------------------------------------------------------------
/Meshtastic/Extensions/Character.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Character.swift
3 | // Meshtastic
4 | //
5 | // Copyright(c) Garth Vander Houwen 4/25/23.
6 | //
7 |
8 | import Foundation
9 |
10 | extension Character {
11 | var isEmoji: Bool {
12 | guard let scalar = unicodeScalars.first else { return false }
13 | return scalar.properties.isEmoji && (scalar.value >= 0x203C || unicodeScalars.count > 1)
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | },
6 | {
7 | "appearances" : [
8 | {
9 | "appearance" : "luminosity",
10 | "value" : "dark"
11 | }
12 | ],
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Meshtastic/Resources/Assets.xcassets/Assets.xcassets/UNSET.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "scale" : "2x"
10 | },
11 | {
12 | "idiom" : "universal",
13 | "scale" : "3x"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Meshtastic/Resources/Assets.xcassets/Assets.xcassets/TLORAV2.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "scale" : "2x"
10 | },
11 | {
12 | "idiom" : "universal",
13 | "scale" : "3x"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Meshtastic/Resources/Assets.xcassets/Assets.xcassets/TLORAV211p6.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "scale" : "2x"
10 | },
11 | {
12 | "idiom" : "universal",
13 | "scale" : "3x"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Meshtastic/Extensions/Data.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Data.swift
3 | // Meshtastic
4 | //
5 | // Copyright(c) Garth Vander Houwen 4/25/23.
6 | //
7 |
8 | import Foundation
9 |
10 | extension Data {
11 | var macAddressString: String {
12 | let mac: String = reduce("") {$0 + String(format: "%02x:", $1)}
13 | return String(mac.dropLast())
14 | }
15 | var hexDescription: String {
16 | return reduce("") {$0 + String(format: "%02x", $1)}
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Meshtastic/Resources/Assets.xcassets/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | },
6 | {
7 | "appearances" : [
8 | {
9 | "appearance" : "luminosity",
10 | "value" : "dark"
11 | }
12 | ],
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/AppIcon_Thumb.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "AppIcon_Thumb.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/AppIcon_Ham_Dark_Thumb.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "logo-dark.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/AppIcon_Ham_Thumb.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "meshtastic_ham.jpg",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/AppIcon_MPowered_Thumb.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "MPowered.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Widgets/Assets.xcassets/LightIndigo.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.56",
9 | "green" : "0.96",
10 | "red" : "0.32"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/AppIcon_Dark_Thumb.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "AppIcon_Dark_Thumb.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/AppIcon_MPowered_Dark_Thumb.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "MPowered.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/AppIcon_Chirpy_Thumb.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "AppIcon_Chirpy_Thumb.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Widgets/Assets.xcassets/AccentColorDimmed.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.420",
9 | "green" : "0.470",
10 | "red" : "0.000"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/AppIcon_Chirpy_Dark_Thumb.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "AppIcon_Chirpy_Dark_Thumb.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/MeshtasticProtobufs/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "originHash" : "a2385deee281bd55bce80722a1f2b020f7b745c02005befa8ccbf58a39ef4002",
3 | "pins" : [
4 | {
5 | "identity" : "swift-protobuf",
6 | "kind" : "remoteSourceControl",
7 | "location" : "https://github.com/apple/swift-protobuf.git",
8 | "state" : {
9 | "revision" : "d72aed98f8253ec1aa9ea1141e28150f408cf17f",
10 | "version" : "1.29.0"
11 | }
12 | }
13 | ],
14 | "version" : 3
15 | }
16 |
--------------------------------------------------------------------------------
/.github/workflows/swiftlint.yml:
--------------------------------------------------------------------------------
1 | name: SwiftLint
2 |
3 | on:
4 | pull_request:
5 | paths:
6 | - '.github/workflows/swiftlint.yml'
7 | - '.swiftlint.yml'
8 | - '**/*.swift'
9 |
10 | jobs:
11 | SwiftLint:
12 | runs-on: ubuntu-latest
13 | steps:
14 | - uses: actions/checkout@v1
15 | - name: GitHub Action for SwiftLint (Only files changed in the PR)
16 | uses: norio-nomura/action-swiftlint@3.2.1
17 | env:
18 | DIFF_BASE: ${{ github.base_ref }}
--------------------------------------------------------------------------------
/Meshtastic/Enums/MessageDestination.swift:
--------------------------------------------------------------------------------
1 | /// Helper abstraction for sharing functionality between channel and direct messaging.
2 | enum MessageDestination {
3 | case user(UserEntity)
4 | case channel(ChannelEntity)
5 |
6 | var userNum: Int64 {
7 | switch self {
8 | case let .user(user): return user.num
9 | case .channel: return 0
10 | }
11 | }
12 |
13 | var channelNum: Int32 {
14 | switch self {
15 | case .user: return 0
16 | case let .channel(channel): return channel.index
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/Meshtastic/Views/Messages/TextMessageField/AlertButton.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | struct AlertButton: View {
4 | let action: () -> Void
5 |
6 | var body: some View {
7 | Button(action: action) {
8 | Text("Alert")
9 | Image(systemName: "bell.fill")
10 | .symbolRenderingMode(.hierarchical)
11 | .imageScale(.large)
12 | .foregroundColor(.accentColor)
13 | }
14 | }
15 | }
16 |
17 | struct AlertButtonPreview: PreviewProvider {
18 | static var previews: some View {
19 | AlertButton {}
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Meshtastic/Helpers/CommonRegex.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CommonRegex.swift
3 | // Meshtastic
4 | //
5 | // Created by Ben Meadors on 7/2/24.
6 | //
7 |
8 | import Foundation
9 | import RegexBuilder
10 |
11 | class CommonRegex {
12 | static let COORDS_REGEX = Regex {
13 | Capture {
14 | Regex {
15 | "lat="
16 | OneOrMore(.digit)
17 | }
18 | }
19 | Capture {" "}
20 | Capture {
21 | Regex {
22 | "long="
23 | OneOrMore(.digit)
24 | }
25 | }
26 | }
27 | .anchorsMatchLineEndings()
28 | }
29 |
--------------------------------------------------------------------------------
/scripts/gen_protos.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # simple sanity checking for executable
4 | if [ ! -x "$(which protoc)" ]; then
5 | brew install swift-protobuf
6 | fi
7 |
8 | git submodule update --init --recursive
9 |
10 | git submodule foreach --recursive git pull origin master
11 |
12 | protoc --proto_path=./protobufs --swift_opt=Visibility=Public --swift_out=./MeshtasticProtobufs/Sources ./protobufs/meshtastic/*.proto
13 |
14 | echo "Done generating the swift files from the proto files."
15 | echo "Build, test, and commit changes."
16 |
--------------------------------------------------------------------------------
/Meshtastic/Resources/Assets.xcassets/Assets.xcassets/tbeam.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "tbeam-2.jpg",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "tbeam-1.jpg",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "tbeam.jpg",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Meshtastic/Resources/Assets.xcassets/Assets.xcassets/techo.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "techo-2.jpg",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "techo-1.jpg",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "techo.jpg",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Meshtastic/Resources/Assets.xcassets/Assets.xcassets/tlorav1.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "tlora-2.jpeg",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "tlora-1.jpeg",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "tlora.jpeg",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Widgets/Assets.xcassets/m-logo-black.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Mesh_Logo_Black_Small.svg",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "Mesh_Logo_Black.svg",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "Mesh_Logo_Black_Large.svg",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Widgets/Assets.xcassets/m-logo-white.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Mesh_Logo_White_Small.svg",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "Mesh_Logo_White.svg",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "Mesh_Logo_White_Large.svg",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Meshtastic/Extensions/Bool.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Bool.swift
3 | // Meshtastic
4 | //
5 | // Copyright(c) Garth Vander Houwen 9/6/25.
6 |
7 | extension Bool {
8 |
9 | static var iOS18: Bool {
10 | guard #available(iOS 18, *) else {
11 | return true
12 | }
13 | return false
14 | }
15 |
16 | static var masOS15: Bool {
17 | guard #available(macOS 15, *) else {
18 | return true
19 | }
20 | return false
21 | }
22 |
23 | static var os26: Bool {
24 | guard #available(iOS 26, macOS 26, *) else {
25 | return true
26 | }
27 | return false
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/Meshtastic/Extensions/Double.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Double.swift
3 | // Meshtastic
4 | //
5 | // Copyright(c) Garth Vander Houwen on 4/25/23.
6 | //
7 | import Foundation
8 |
9 | extension Double {
10 | var toBytes: String {
11 | let formatter = MeasurementFormatter()
12 | let measurement = Measurement(value: self, unit: UnitInformationStorage.bytes)
13 | formatter.unitStyle = .short
14 | formatter.unitOptions = .naturalScale
15 | formatter.numberFormatter.maximumFractionDigits = 0
16 | return formatter.string(from: measurement.converted(to: .megabytes))
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Meshtastic/Extensions/Int.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Int.swift
3 | // Meshtastic
4 | //
5 | // Copyright(c) Garth Vander Houwen 4/25/23.
6 | //
7 |
8 | extension Int {
9 |
10 | func numberOfDigits() -> Int {
11 | if abs(self) < 10 {
12 | return 1
13 | } else {
14 | return 1 + (self/10).numberOfDigits()
15 | }
16 | }
17 | }
18 |
19 | extension UInt32 {
20 | func toHex() -> String {
21 | return String(format: "!%08X", self).lowercased()
22 | }
23 | }
24 |
25 | extension Int64 {
26 | func toHex() -> String {
27 | return String(format: "!%08X", self).lowercased()
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/Meshtastic/Extensions/CoreData/RangeTestConfigEntityExtension.swift:
--------------------------------------------------------------------------------
1 | import CoreData
2 | import MeshtasticProtobufs
3 |
4 | extension RangeTestConfigEntity {
5 | convenience init(
6 | context: NSManagedObjectContext,
7 | config: ModuleConfig.RangeTestConfig
8 | ) {
9 | self.init(context: context)
10 | self.sender = Int32(config.sender)
11 | self.enabled = config.enabled
12 | self.save = config.save
13 | }
14 |
15 | func update(with config: ModuleConfig.RangeTestConfig) {
16 | sender = Int32(config.sender)
17 | enabled = config.enabled
18 | save = config.save
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Meshtastic/Tips/BluetoothTips.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BluetoothTips.swift
3 | // Meshtastic
4 | //
5 | // Copyright(c) Garth Vander Houwen 8/31/23.
6 | //
7 | import SwiftUI
8 | import TipKit
9 |
10 | struct ConnectionTip: Tip {
11 |
12 | var id: String {
13 | return "tip.connect"
14 | }
15 | var title: Text {
16 | Text("Connected Radio")
17 | }
18 | var message: Text? {
19 | Text("Shows information for the connected Lora radio. You can swipe left to disconnect the radio and long press to start the live activity.")
20 | }
21 | var image: Image? {
22 | Image(systemName: "flipphone")
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Meshtastic/Measurement/CustomFormatters.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CustomFormatters.swift
3 | // Meshtastic
4 | //
5 | // Created by Garth Vander Houwen on 8/4/24.
6 | //
7 |
8 | import Foundation
9 |
10 | /// Custom altitude formatter that always returns the provided unit
11 | /// Needs to be used in conjunction with logic that checks for metric and displays the right value.
12 | public var altitudeFormatter: MeasurementFormatter {
13 | let formatter = MeasurementFormatter()
14 | formatter.unitOptions = .providedUnit
15 | formatter.unitStyle = .long
16 | formatter.numberFormatter.maximumFractionDigits = 1
17 | return formatter
18 | }
19 |
--------------------------------------------------------------------------------
/scripts/setup-hooks.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | # Define the source and destination paths
5 | SOURCE_PATH="./scripts/hooks/pre-commit"
6 | HOOKS_DIR=".git/hooks"
7 | DEST_PATH="$HOOKS_DIR/pre-commit"
8 |
9 | # Check if the hooks directory exists
10 | if [ ! -d "$HOOKS_DIR" ]; then
11 | echo "Error: .git/hooks directory not found. Make sure you're in the root of a Git repository."
12 | exit 1
13 | fi
14 |
15 | # Copy the script to the hooks directory
16 | cp "$SOURCE_PATH" "$DEST_PATH"
17 |
18 | # Make the hook script executable
19 | chmod +x "$DEST_PATH"
20 |
21 | echo "Pre-commit hooks have been set up successfully."
22 |
--------------------------------------------------------------------------------
/Meshtastic/Resources/Assets.xcassets/Assets.xcassets/HELTECV20.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "655DCEC0-309D-430A-AF50-2453B6ADB1F6.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "655DCEC0-309D-430A-AF50-2453B6ADB1F6-1.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "655DCEC0-309D-430A-AF50-2453B6ADB1F6-2.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Meshtastic/Views/Messages/TextMessageField/RequestPositionButton.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | struct RequestPositionButton: View {
4 | let action: () -> Void
5 |
6 | var body: some View {
7 | Button(action: action) {
8 | Image(systemName: "mappin.and.ellipse")
9 | .accessibilityLabel("Position Exchange Requested".localized)
10 | .symbolRenderingMode(.hierarchical)
11 | .imageScale(.large)
12 | .foregroundColor(.accentColor)
13 | }
14 | .padding(.trailing)
15 | }
16 | }
17 |
18 | struct RequestPositionButtonPreview: PreviewProvider {
19 | static var previews: some View {
20 | RequestPositionButton {}
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/Meshtastic/Extensions/Measurement.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Measurement.swift
3 | // Meshtastic
4 | //
5 | // Copyright(c) Garth Vander Houwen 11/17/23.
6 | //
7 |
8 | import Foundation
9 | import Charts
10 |
11 | struct PlottableMeasurement {
12 | var measurement: Measurement
13 | }
14 |
15 | extension PlottableMeasurement: Plottable where UnitType == UnitLength {
16 | var primitivePlottable: Double {
17 | self.measurement.converted(to: .meters).value
18 | }
19 |
20 | init?(primitivePlottable: Double) {
21 | self.init(
22 | measurement: Measurement(
23 | value: primitivePlottable,
24 | unit: .meters
25 | )
26 | )
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/.github/workflows/stale.yml:
--------------------------------------------------------------------------------
1 | name: process stale Issues and PR's
2 | on:
3 | schedule:
4 | - cron: 0 6 * * *
5 | workflow_dispatch: {}
6 |
7 | permissions:
8 | issues: write
9 | pull-requests: write
10 | actions: write
11 |
12 | jobs:
13 | stale_issues:
14 | name: Close Stale Issues
15 | runs-on: ubuntu-latest
16 |
17 | steps:
18 | - name: Stale PR+Issues
19 | uses: actions/stale@v9.0.0
20 | with:
21 | days-before-stale: 30
22 | exempt-issue-labels: 'has sponsor,needs sponsor,help wanted,backlog,security issue'
23 | exempt-pr-labels: 'has sponsor,needs sponsor,help wanted,backlog,security issue'
24 |
--------------------------------------------------------------------------------
/Meshtastic/Resources/Assets.xcassets/Assets.xcassets/rak4631.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "RAK7205_Enclosure-With-Solar-Panel_Top-View_01_9ed42002-fb51-4c49-a69e-43fcef692ef6_739x@2x.progressive-1.png",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "filename" : "RAK7205_Enclosure-With-Solar-Panel_Top-View_01_9ed42002-fb51-4c49-a69e-43fcef692ef6_739x@2x.progressive.png",
14 | "idiom" : "universal",
15 | "scale" : "3x"
16 | }
17 | ],
18 | "info" : {
19 | "author" : "xcode",
20 | "version" : 1
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/Meshtastic/Tips/MessagesTips.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MessagesTips.swift
3 | // Meshtastic
4 | //
5 | // Copyright(c) Garth Vander Houwen 9/15/23.
6 | //
7 | import SwiftUI
8 | import TipKit
9 |
10 | struct MessagesTip: Tip {
11 |
12 | var id: String {
13 | return "tip.messages"
14 | }
15 | var title: Text {
16 | Text("Messages")
17 | }
18 | var message: Text? {
19 | Text("You can send and receive channel (group chats) and direct messages. From any message you can long press to see available actions like copy, reply, tapback and delete as well as delivery details.")
20 | }
21 | var image: Image? {
22 | Image(systemName: "bubble.left.and.bubble.right")
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/MeshtasticProtobufs/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version: 5.10
2 |
3 | import PackageDescription
4 |
5 | let package = Package(
6 | name: "MeshtasticProtobufs",
7 | products: [
8 | .library(
9 | name: "MeshtasticProtobufs",
10 | targets: ["MeshtasticProtobufs"]
11 | ),
12 | ],
13 | dependencies: [
14 | .package(url: "https://github.com/apple/swift-protobuf.git", from: "1.33.3"),
15 | ],
16 | targets: [
17 | .target(
18 | name: "MeshtasticProtobufs",
19 | dependencies: [
20 | .product(name: "SwiftProtobuf", package: "swift-protobuf")
21 | ]
22 | )
23 | ]
24 | )
25 |
--------------------------------------------------------------------------------
/ci_scripts/ci_pre_xcodebuild.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | echo "Stage: PRE-Xcode Build is activated .... "
4 |
5 | # Move to the place where the scripts are located.
6 | # This is important because the position of the subsequently mentioned files depend of this origin.
7 | cd $CI_PRIMARY_REPOSITORY_PATH/ci_scripts || exit 1
8 |
9 | # Write a JSON File containing all the environment variables and secrets.
10 | printf "{\"PUBLIC_MQTT_USERNAME\":\"%s\",\"PUBLIC_MQTT_PASSWORD\":\"%s\"}" "$PUBLIC_MQTT_USERNAME" "$PUBLIC_MQTT_PASSWORD" >> .\ $CI_PRIMARY_REPOSITORY_PATH/SupportingFiles/secrets.json
11 |
12 | echo "Wrote Secrets.json file."
13 |
14 | echo "Stage: PRE-Xcode Build is DONE .... "
15 |
16 | exit 0
17 |
--------------------------------------------------------------------------------
/Meshtastic/Views/Nodes/Helpers/ScrollToBottomButton.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ScrollToBottomButtonView.swift
3 | // Meshtastic
4 | //
5 | // Created by Benjamin Faershtein on 4/2/25.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct ScrollToBottomButtonView: View {
11 | var body: some View {
12 | HStack(spacing: 4) {
13 | Text("Jump to present")
14 | .font(.caption)
15 | .padding(.horizontal, 8)
16 | .padding(.vertical, 4)
17 | .cornerRadius(12)
18 | Image(systemName: "arrow.down")
19 | .font(.title2)
20 | .symbolRenderingMode(.hierarchical)
21 |
22 | }
23 | .foregroundColor(.accentColor)
24 | .shadow(radius: 2)
25 | }
26 | }
27 |
28 | #Preview {
29 | ScrollToBottomButtonView()
30 | }
31 |
--------------------------------------------------------------------------------
/Settings.bundle/de.lproj/Root.strings:
--------------------------------------------------------------------------------
1 | // german localization
2 |
3 | "Will share your phone GPS location with your node." = "Teile den GPS-Standort deines Handys mit deinem Funkgerät.";
4 | "Share Location" = "Standort teilen";
5 | "Interval" = "Intervall";
6 | "Ten Seconds" = "10 s";
7 | "Fifteen Seconds" = "15 s";
8 | "Thirty Seconds" = "30 s";
9 | "Forty Five Seconds" = "45 s";
10 | "One Minute" = "1 min";
11 | "Five Minutes" = "5 min";
12 | "Ten Minutes" = "10 min";
13 | "Fifteen Minutes" = "15 min";
14 | "Accurate Locations Only" = "Nur genaue Standorte";
15 | "Notifications" = "Mitteilungen";
16 | "Channel Messages" = "Kanalnachrichten";
17 | "New Nodes" = "Neu entdeckte Knoten";
18 | "Low Battery" = "Niedriger Akkustand";
19 |
--------------------------------------------------------------------------------
/Meshtastic/Helpers/BluetoothManager.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BluetoothManager.swift
3 | // MeshtasticClient
4 | //
5 | // Created by Garth Vander Houwen on 12/1/21.
6 | //
7 |
8 | import Combine
9 | import CoreBluetooth
10 |
11 | final class BluetoothManager: NSObject {
12 |
13 | private var centralManager: CBCentralManager!
14 |
15 | var stateSubject: PassthroughSubject = .init()
16 | var peripheralSubject: PassthroughSubject = .init()
17 |
18 | func start() {
19 | centralManager = .init(delegate: self, queue: .main)
20 | }
21 |
22 | func connect(_ peripheral: CBPeripheral) {
23 | centralManager.stopScan()
24 | peripheral.delegate = self
25 | centralManager.connect(peripheral)
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/Meshtastic/Export/LogDocument.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 | import UniformTypeIdentifiers
3 |
4 | struct LogDocument: FileDocument {
5 | static var readableContentTypes: [UTType] {[.plainText]}
6 |
7 | var logFile: String
8 |
9 | init(logFile: String) {
10 | self.logFile = logFile
11 | }
12 |
13 | init(configuration: ReadConfiguration) throws {
14 | guard let data = configuration.file.regularFileContents,
15 | let string = String(data: data, encoding: .utf8)
16 | else {
17 | throw CocoaError(.fileReadCorruptFile)
18 | }
19 | logFile = string
20 | }
21 |
22 | func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper {
23 | return FileWrapper(regularFileWithContents: logFile.data(using: .utf8)!)
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Meshtastic/Extensions/Constants.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import SwiftUI
3 | enum Constants {
4 | /// `UInt32.max` or FFFF,FFFF in hex is used to identify messages that are being
5 | /// sent to a channel and are not a DM to an individual user. This is used
6 | /// in the `to` field of some mesh packets.
7 | static let maximumNodeNum = UInt32.max
8 | /// Based on the NUM_RESERVED from the firmware.
9 | /// https://github.com/meshtastic/firmware/blob/46d7b82ac1a4292ba52ca690e1a433d3a501a9e5/src/mesh/NodeDB.cpp#L522
10 | static let minimumNodeNum = 4
11 |
12 | // String used to render a nil value. If changed, mark the new entry in
13 | // Localizable.xcstrings as "do not translate" and remove the old key.
14 | static let nilValueIndicator = "--"
15 | }
16 |
--------------------------------------------------------------------------------
/Meshtastic/Model/PeripheralModel.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import CoreBluetooth
3 |
4 | struct Peripheral: Identifiable {
5 | var id: String
6 | var num: Int64
7 | var name: String
8 | var shortName: String
9 | var longName: String
10 | var firmwareVersion: String
11 | var rssi: Int
12 | var lastUpdate: Date
13 | var peripheral: CBPeripheral
14 |
15 | func getSignalStrength() -> BLESignalStrength {
16 | if NSNumber(value: rssi).compare(NSNumber(-65)) == ComparisonResult.orderedDescending {
17 | return BLESignalStrength.strong
18 | } else if NSNumber(value: rssi).compare(NSNumber(-85)) == ComparisonResult.orderedDescending {
19 | return BLESignalStrength.normal
20 | } else {
21 | return BLESignalStrength.weak
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Meshtastic/Helpers/Preferences.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Preferences.swift
3 | // MeshtasticClient
4 | //
5 | // Created by Garth Vander Houwen on 12/16/21.
6 | //
7 | // import Foundation
8 | // import Combine
9 | // import SwiftUI
10 | //
11 | // class Prefs
12 | // {
13 | // private let defaults = UserDefaults.standard
14 | //
15 | // private let keyIntExample = "intExample"
16 | //
17 | // var intExample = {
18 | // set {
19 | // defaults.setValue(newValue, forKey: keyIntExample)
20 | // }
21 | // get {
22 | // return defaults.integer(forKey: keyIntExample)
23 | // }
24 | // }
25 | //
26 | // class var shared: Prefs {
27 | // struct Static {
28 | // static let instance = Prefs()
29 | // }
30 | //
31 | // return Static.instance
32 | // }
33 | // }
34 |
--------------------------------------------------------------------------------
/Meshtastic/AppIntents/AppIntentErrors.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppIntentErrors.swift
3 | // Meshtastic
4 | //
5 | // Created by Benjamin Faershtein on 8/11/24.
6 | //
7 |
8 | import Foundation
9 | import OSLog
10 |
11 | class AppIntentErrors {
12 | enum AppIntentError: Swift.Error, CustomLocalizedStringResourceConvertible {
13 | case notConnected
14 | case message(_ message: String)
15 |
16 | var localizedStringResource: LocalizedStringResource {
17 | switch self {
18 | case let .message(message):
19 | Logger.services.error("App Intent: \(message, privacy: .public)")
20 | return "Error: \(message)"
21 | case .notConnected:
22 | Logger.services.error("App Intent: No Connected Node")
23 | return "No Connected Node"
24 | }
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/Meshtastic/Helpers/Logger.swift:
--------------------------------------------------------------------------------
1 | import OSLog
2 |
3 | extension Logger {
4 |
5 | /// The logger's subsystem.
6 | private static var subsystem = Bundle.main.bundleIdentifier!
7 |
8 | /// All logs related to data such as decoding error, parsing issues, etc.
9 | public static let data = Logger(subsystem: subsystem, category: "🗄️ Data")
10 |
11 | /// All logs related to the mesh
12 | public static let mesh = Logger(subsystem: subsystem, category: "🕸️ Mesh")
13 |
14 | /// All logs related to services such as network calls, location, etc.
15 | public static let services = Logger(subsystem: subsystem, category: "🍏 Services")
16 |
17 | /// All logs related to tracking and analytics.
18 | public static let statistics = Logger(subsystem: subsystem, category: "📈 Stats")
19 | }
20 |
--------------------------------------------------------------------------------
/Meshtastic/Views/Helpers/Weather/IAQScale.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IAQScale.swift
3 | // Meshtastic
4 | //
5 | // Created by Garth Vander Houwen on 4/24/24.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct IAQScale: View {
11 |
12 | var body: some View {
13 | VStack(alignment: .leading) {
14 | Text("Indoor Air Quality (IAQ)")
15 | .font(.title3)
16 | ForEach(Iaq.allCases) { iaq in
17 | HStack {
18 | RoundedRectangle(cornerRadius: 5)
19 | .fill(iaq.color)
20 | .frame(width: 30, height: 20)
21 | Text(iaq.description)
22 | .font(.callout)
23 | }
24 | }
25 | }
26 | .padding()
27 | }
28 | }
29 |
30 | struct IAQSCalePreviews: PreviewProvider {
31 | static var previews: some View {
32 | VStack {
33 | IAQScale()
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/Meshtastic/Enums/EthernetModes.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NetworkEnums.swift
3 | // Meshtastic
4 | //
5 | // Copyright(C) Garth Vander Houwen 11/25/22.
6 | //
7 |
8 | import Foundation
9 | import MeshtasticProtobufs
10 |
11 | enum EthernetMode: Int, CaseIterable, Identifiable {
12 |
13 | case dhcp = 0
14 | case staticip = 1
15 |
16 | var id: Int { self.rawValue }
17 | var description: String {
18 |
19 | switch self {
20 | case .dhcp:
21 | return "DHCP"
22 | case .staticip:
23 | return "Static IP"
24 | }
25 | }
26 | func protoEnumValue() -> Config.NetworkConfig.AddressMode {
27 |
28 | switch self {
29 |
30 | case .dhcp:
31 | return Config.NetworkConfig.AddressMode.dhcp
32 | case .staticip:
33 | return Config.NetworkConfig.AddressMode.static
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: [garthvh]
4 | patreon: # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
14 |
--------------------------------------------------------------------------------
/Meshtastic/Views/Helpers/DistanceText.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DistanceText.swift
3 | // Meshtastic
4 | //
5 | // Copyright(c) Garth Vander Houwen 8/19/22.
6 | //
7 |
8 | import SwiftUI
9 | import CoreLocation
10 | import MapKit
11 |
12 | struct DistanceText: View {
13 |
14 | var meters: CLLocationDistance
15 |
16 | var body: some View {
17 |
18 | let distanceFormatter = MKDistanceFormatter()
19 | Text("\(distanceFormatter.string(fromDistance: Double(meters))) away")
20 | }
21 | }
22 | struct DistanceText_Previews: PreviewProvider {
23 | static var previews: some View {
24 |
25 | VStack {
26 | DistanceText(meters: 100)
27 | DistanceText(meters: 1000)
28 | DistanceText(meters: 10000)
29 | DistanceText(meters: 100000)
30 | DistanceText(meters: 1000000)
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Meshtastic/Views/Nodes/Helpers/Actions/NodeAlertsButton.swift:
--------------------------------------------------------------------------------
1 | import CoreData
2 | import OSLog
3 | import SwiftUI
4 |
5 | struct NodeAlertsButton: View {
6 | var context: NSManagedObjectContext
7 |
8 | @ObservedObject
9 | var node: NodeInfoEntity
10 |
11 | @ObservedObject
12 | var user: UserEntity
13 |
14 | var body: some View {
15 | Button {
16 | user.mute = !user.mute
17 | context.refresh(node, mergeChanges: true)
18 | do {
19 | try context.save()
20 | } catch {
21 | context.rollback()
22 | Logger.data.error("Save User Mute Error")
23 | }
24 | } label: {
25 | Label {
26 | Text(user.mute ? "Show alerts" : "Hide alerts")
27 | } icon: {
28 | Image(systemName: user.mute ? "bell.slash" : "bell")
29 | .symbolRenderingMode(.hierarchical)
30 | }
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Meshtastic/Views/Nodes/Helpers/PreferenceKeys/TileHeightKeys.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TileHeightKeys.swift
3 | // Meshtastic
4 | //
5 | // Created by Garth Vander Houwen on 9/21/25.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct WeatherKitTilesHeightKey: PreferenceKey {
11 | static var defaultValue: CGFloat = 0
12 |
13 | static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
14 | // This method combines values from multiple child views if needed
15 | value = max(value, nextValue())
16 | }
17 | }
18 |
19 | struct EnvironmentMetricsTilesHeightKey: PreferenceKey {
20 | static var defaultValue: CGFloat = 0
21 |
22 | static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
23 | // This method combines values from multiple child views if needed
24 | value = max(value, nextValue())
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/Meshtastic/Accessory/Helpers/LogRecord+StringRepresentation.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LogRecord+StringRepresentation.swift
3 | // Meshtastic
4 | //
5 | // Created by Jake Bordens on 7/29/25.
6 | //
7 |
8 | import Foundation
9 | import MeshtasticProtobufs
10 |
11 | extension LogRecord {
12 | var stringRepresentation: String {
13 | var message = self.source.isEmpty ? self.message : "[\(self.source)] \(self.message)"
14 | switch self.level {
15 | case .debug:
16 | message = "DEBUG | \(message)"
17 | case .info:
18 | message = "INFO | \(message)"
19 | case .warning:
20 | message = "WARN | \(message)"
21 | case .error:
22 | message = "ERROR | \(message)"
23 | case .critical:
24 | message = "CRIT | \(message)"
25 | default:
26 | message = "DEBUG | \(message)"
27 | }
28 | return message
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/Meshtastic/Enums/TelemetryWeather.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TelemetryWeather.swift
3 | // Meshtastic
4 | //
5 | // Copyright Garth Vander Houwen 2/4/23.
6 | //
7 |
8 | enum WeatherConditions: Int, CaseIterable, Identifiable {
9 |
10 | case clear = 0
11 | case cloudy = 1
12 | case frigid = 2
13 | case hot = 3
14 | case rain = 4
15 | case smoky = 5
16 | case snow = 6
17 |
18 | var id: Int { self.rawValue }
19 | var symbolName: String {
20 | switch self {
21 |
22 | case .clear:
23 | return "sparkle"
24 | case .cloudy:
25 | return "cloud"
26 | case .hot:
27 | return "sun.max.trianglebadge.exclamationmark.fill"
28 | case .rain:
29 | return "cloud.rain"
30 | case .frigid:
31 | return "thermometer.snowflake"
32 | case .smoky:
33 | return "smoke"
34 | case .snow:
35 | return "cloud.snow"
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Meshtastic/Views/Messages/TextMessageField/TextMessageSize.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | struct TextMessageSize: View {
4 | let maxbytes: Int
5 | let totalBytes: Int
6 |
7 | var body: some View {
8 | ProgressView("\("Bytes".localized): \(totalBytes) / \(maxbytes)", value: Double(totalBytes), total: Double(maxbytes))
9 | .accessibilityLabel(NSLocalizedString("Message Size", comment: "VoiceOver label for message size"))
10 | .accessibilityValue(String(format: NSLocalizedString("Bytes Used", comment: "VoiceOver value for bytes used"), totalBytes, maxbytes))
11 | .frame(width: 130)
12 | .padding(5)
13 | .font(.subheadline)
14 | .accentColor(.accentColor)
15 | }
16 | }
17 |
18 | struct TextMessageSizePreview: PreviewProvider {
19 | static var previews: some View {
20 | TextMessageSize(maxbytes: 200, totalBytes: 100)
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/Meshtastic/AppIntents/DisconnectNodeIntent.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DisconnectNodeIntent.swift
3 | // Meshtastic
4 | //
5 | // Created by Benjamin Faershtein on 4/2/25.
6 | //
7 |
8 | import Foundation
9 | import AppIntents
10 |
11 | struct DisconnectNodeIntent: AppIntent {
12 | static let title: LocalizedStringResource = "Disconnect Node"
13 |
14 | static let description: IntentDescription = "Disconnect the currently connected node"
15 |
16 | func perform() async throws -> some IntentResult {
17 | let isConnected = await AccessoryManager.shared.isConnected
18 | if !isConnected {
19 | throw AppIntentErrors.AppIntentError.notConnected
20 | }
21 |
22 | do {
23 | try await AccessoryManager.shared.disconnect()
24 | } catch {
25 | throw AppIntentErrors.AppIntentError.message("Error disconnecting node")
26 | }
27 |
28 | return .result()
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/Meshtastic/AppState.swift:
--------------------------------------------------------------------------------
1 | import Combine
2 | import SwiftUI
3 |
4 | class AppState: ObservableObject {
5 |
6 | @Published var router: Router
7 | @Published var unreadChannelMessages: Int
8 | @Published var unreadDirectMessages: Int
9 |
10 | var totalUnreadMessages: Int {
11 | unreadChannelMessages + unreadDirectMessages
12 | }
13 | private var cancellables: Set = []
14 |
15 | init(router: Router) {
16 | self.router = router
17 | self.unreadChannelMessages = 0
18 | self.unreadDirectMessages = 0
19 |
20 | // Keep app icon badge count in sync with messages read status
21 | $unreadChannelMessages.combineLatest($unreadDirectMessages)
22 | .sink(receiveValue: { badgeCounts in
23 | UNUserNotificationCenter.current()
24 | .setBadgeCount(badgeCounts.0 + badgeCounts.1)
25 | })
26 | .store(in: &cancellables)
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/Meshtastic/Extensions/CoreData/StoreForwardConfigEntityExtension.swift:
--------------------------------------------------------------------------------
1 | import CoreData
2 | import MeshtasticProtobufs
3 |
4 | extension StoreForwardConfigEntity {
5 | convenience init(
6 | context: NSManagedObjectContext,
7 | config: ModuleConfig.StoreForwardConfig
8 | ) {
9 | self.init(context: context)
10 | self.enabled = config.enabled
11 | self.heartbeat = config.heartbeat
12 | self.records = Int32(config.records)
13 | self.historyReturnMax = Int32(config.historyReturnMax)
14 | self.historyReturnWindow = Int32(config.historyReturnWindow)
15 | }
16 |
17 | func update(with config: ModuleConfig.StoreForwardConfig) {
18 | enabled = config.enabled
19 | heartbeat = config.heartbeat
20 | records = Int32(config.records)
21 | historyReturnMax = Int32(config.historyReturnMax)
22 | historyReturnWindow = Int32(config.historyReturnWindow)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Meshtastic/Extensions/CoreData/LocationEntityExtension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LocationEntityExtension.swift
3 | // Meshtastic
4 | //
5 | // Copyright (c) Garth Vander Houwen 11/21/23.
6 | //
7 |
8 | import CoreData
9 | import CoreLocation
10 | import MapKit
11 | import SwiftUI
12 |
13 | extension LocationEntity {
14 |
15 | var latitude: Double? {
16 |
17 | let d = Double(latitudeI)
18 | if d == 0 {
19 | return 0
20 | }
21 | return d / 1e7
22 | }
23 |
24 | var longitude: Double? {
25 |
26 | let d = Double(longitudeI)
27 | if d == 0 {
28 | return 0
29 | }
30 | return d / 1e7
31 | }
32 |
33 | var locationCoordinate: CLLocationCoordinate2D? {
34 | if latitudeI != 0 && longitudeI != 0 {
35 | let coord = CLLocationCoordinate2D(latitude: latitude!, longitude: longitude!)
36 | return coord
37 | } else {
38 | return nil
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/Meshtastic/Extensions/CoreData/TraceRouteEntityExtension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TraceRouteEntityExtension.swift
3 | // Meshtastic
4 | //
5 | // Copyright(c) Garth Vander Houwen 12/7/23.
6 | //
7 |
8 | import CoreData
9 | import CoreLocation
10 | import MapKit
11 | import SwiftUI
12 |
13 | extension TraceRouteHopEntity {
14 |
15 | var latitude: Double? {
16 |
17 | let d = Double(latitudeI)
18 | if d == 0 {
19 | return 0
20 | }
21 | return d / 1e7
22 | }
23 |
24 | var longitude: Double? {
25 |
26 | let d = Double(longitudeI)
27 | if d == 0 {
28 | return 0
29 | }
30 | return d / 1e7
31 | }
32 |
33 | var coordinate: CLLocationCoordinate2D? {
34 | if latitudeI != 0 && longitudeI != 0 {
35 | let coord = CLLocationCoordinate2D(latitude: latitude!, longitude: longitude!)
36 | return coord
37 | } else {
38 | return nil
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/Meshtastic/Views/Helpers/DateTimeText.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DateTimeText.swift
3 | // Meshtastic Apple
4 | //
5 | // Copyright(C) Garth Vander Houwen 5/30/22.
6 | //
7 |
8 | import SwiftUI
9 | //
10 | // LastHeardText.swift
11 | // Meshtastic Apple
12 | //
13 | // Created by Garth Vander Houwen on 5/25/22.
14 | //
15 | struct DateTimeText: View {
16 | var dateTime: Date?
17 |
18 | let sixMonthsAgo = Calendar.current.date(byAdding: .month, value: -6, to: Date())
19 | let localeDateFormat = DateFormatter.dateFormat(fromTemplate: "yyMMddjmmssa", options: 0, locale: Locale.current)
20 |
21 | var body: some View {
22 | let dateFormatString = (localeDateFormat ?? "MM/dd/YY j:mm:ss a")
23 |
24 | if dateTime != nil && dateTime! >= sixMonthsAgo! {
25 | Text(" \(dateTime!.formattedDate(format: dateFormatString))")
26 | } else {
27 | Text("Unknown Age")
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/Meshtastic/Export/CsvDocument.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CsvDocument.swift
3 | // Meshtastic
4 | //
5 | // Copyright(c) Garth Vander Houwen 7/15/22.
6 | //
7 |
8 | import SwiftUI
9 | import UniformTypeIdentifiers
10 |
11 | struct CsvDocument: FileDocument {
12 |
13 | static let readableContentTypes = [UTType.commaSeparatedText]
14 |
15 | @State var csvData: String
16 |
17 | init(emptyCsv: String = "" ) {
18 |
19 | csvData = emptyCsv
20 | }
21 |
22 | init(configuration: ReadConfiguration) throws {
23 | if let data = configuration.file.regularFileContents {
24 | csvData = String(data: data, encoding: .utf8) ?? ""
25 |
26 | } else {
27 | throw CocoaError(.fileReadCorruptFile)
28 | }
29 | }
30 |
31 | func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper {
32 | let data = Data(csvData.utf8)
33 | return FileWrapper(regularFileWithContents: data)
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/Meshtastic/Extensions/CoreData/SerialConfigEntityExtension.swift:
--------------------------------------------------------------------------------
1 | import CoreData
2 | import MeshtasticProtobufs
3 |
4 | extension SerialConfigEntity {
5 | convenience init(
6 | context: NSManagedObjectContext,
7 | config: ModuleConfig.SerialConfig
8 | ) {
9 | self.init(context: context)
10 | self.enabled = config.enabled
11 | self.echo = config.echo
12 | self.rxd = Int32(config.rxd)
13 | self.txd = Int32(config.txd)
14 | self.baudRate = Int32(config.baud.rawValue)
15 | self.timeout = Int32(config.timeout)
16 | self.mode = Int32(config.mode.rawValue)
17 | }
18 |
19 | func update(with config: ModuleConfig.SerialConfig) {
20 | enabled = config.enabled
21 | echo = config.echo
22 | rxd = Int32(config.rxd)
23 | txd = Int32(config.txd)
24 | baudRate = Int32(config.baud.rawValue)
25 | timeout = Int32(config.timeout)
26 | mode = Int32(config.mode.rawValue)
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/Meshtastic/Resources/AppIcon.icon/Assets/Icon Grid Inner Circle.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
--------------------------------------------------------------------------------
/Meshtastic/Resources/AppIconDebug.icon/Assets/Icon Grid Inner Circle.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/Color.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "1.000",
9 | "green" : "1.000",
10 | "red" : "1.000"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "1.000",
27 | "green" : "1.000",
28 | "red" : "1.000"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Meshtastic/Extensions/OSLogEntryLog.swift:
--------------------------------------------------------------------------------
1 | //
2 | // OSLogEntryLog.swift
3 | // Meshtastic
4 | //
5 | // Copyright(c) Garth Vander Houwen 6/28/24.
6 | //
7 |
8 | import OSLog
9 | import SwiftUI
10 |
11 | /// Extensions to allow rendering of the emoji string and log leve coloring in the grid of OSLogEntryLog items
12 | extension OSLogEntryLog.Level {
13 | var description: String {
14 | switch self {
15 | case .undefined: "undefined"
16 | case .debug: "🪲 Debug"
17 | case .info: "ℹ️ Info"
18 | case .notice: "⚠️ Notice"
19 | case .error: "🚨 Error"
20 | case .fault: "💥 Fault"
21 | @unknown default: "Default".localized
22 | }
23 | }
24 | var color: Color {
25 | switch self {
26 | case .undefined: .green
27 | case .debug: .indigo
28 | case .info: .green
29 | case .notice: .orange
30 | case .error: .red
31 | case .fault: .red
32 | @unknown default: .green
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/Meshtastic/AppIntents/TracerouteIntent.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import AppIntents
3 |
4 | struct TracerouteIntent: AppIntent {
5 | static var title: LocalizedStringResource = "Send a Traceroute"
6 |
7 | static var description: IntentDescription = "Send a traceroute request to a certain Meshtastic node"
8 |
9 | @Parameter(title: "Node Number")
10 | var nodeNumber: Int
11 |
12 | static var parameterSummary: some ParameterSummary {
13 | Summary("Send traceroute to \(\.$nodeNumber)")
14 | }
15 |
16 | func perform() async throws -> some IntentResult {
17 | if !BLEManager.shared.isConnected {
18 | throw AppIntentErrors.AppIntentError.notConnected
19 | }
20 |
21 | if !BLEManager.shared.sendTraceRouteRequest(destNum: Int64(nodeNumber), wantResponse: true) {
22 | throw AppIntentErrors.AppIntentError.message("Failed to send traceroute request")
23 | }
24 |
25 | return .result()
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/AppIcon_Chirpy.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "AppIcon_Chirpy.png",
5 | "idiom" : "universal",
6 | "platform" : "ios",
7 | "size" : "1024x1024"
8 | },
9 | {
10 | "appearances" : [
11 | {
12 | "appearance" : "luminosity",
13 | "value" : "dark"
14 | }
15 | ],
16 | "filename" : "AppIcon_Chirpy_Dark.png",
17 | "idiom" : "universal",
18 | "platform" : "ios",
19 | "size" : "1024x1024"
20 | },
21 | {
22 | "appearances" : [
23 | {
24 | "appearance" : "luminosity",
25 | "value" : "tinted"
26 | }
27 | ],
28 | "idiom" : "universal",
29 | "platform" : "ios",
30 | "size" : "1024x1024"
31 | }
32 | ],
33 | "info" : {
34 | "author" : "xcode",
35 | "version" : 1
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/Widgets/Assets.xcassets/LiveActivityBackground.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.56",
9 | "green" : "0.96",
10 | "red" : "0.32"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "0.420",
27 | "green" : "0.470",
28 | "red" : "0.000"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Meshtastic/Views/Settings/AppIconPicker.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | struct AppIconPicker: View {
4 | private var idiom: UIUserInterfaceIdiom { UIDevice.current.userInterfaceIdiom }
5 | @Environment(\.managedObjectContext) var context
6 | @Binding var isPresenting: Bool
7 | @State private var didError = false
8 | @State private var errorDetails: String?
9 | var iconNames: [String?: String] = [nil: "Default", "AppIcon_MPowered": "Meshtastic Powered", "AppIcon_Chirpy": "Chirpy", "AppIcon_Ham": "Ham"]
10 |
11 | // MARK: View
12 | var body: some View {
13 | List {
14 | Section(header: Text("Icons")) {
15 | ForEach(Array(iconNames.enumerated()), id: \.offset) { _, icon in
16 | AppIconButton(iconDescription: .constant(icon.value), iconName: .constant(icon.key), isPresenting: $isPresenting)
17 | }
18 | }
19 | }
20 | }
21 | }
22 |
23 | #Preview{
24 | AppIconPicker(isPresenting: .constant(true))
25 | }
26 |
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "logo-3.png",
5 | "idiom" : "universal",
6 | "platform" : "ios",
7 | "size" : "1024x1024"
8 | },
9 | {
10 | "appearances" : [
11 | {
12 | "appearance" : "luminosity",
13 | "value" : "dark"
14 | }
15 | ],
16 | "filename" : "logo-dark.png",
17 | "idiom" : "universal",
18 | "platform" : "ios",
19 | "size" : "1024x1024"
20 | },
21 | {
22 | "appearances" : [
23 | {
24 | "appearance" : "luminosity",
25 | "value" : "tinted"
26 | }
27 | ],
28 | "filename" : "logo-tinted.png",
29 | "idiom" : "universal",
30 | "platform" : "ios",
31 | "size" : "1024x1024"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Meshtastic/Enums/ChannelRoles.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ChannelRoles.swift
3 | // Meshtastic
4 | //
5 | // Copyright(c) Garth Vander Houwen 9/21/22.
6 | //
7 | import Foundation
8 | import MeshtasticProtobufs
9 |
10 | // Default of 0 is Client
11 | enum ChannelRoles: Int, CaseIterable, Identifiable {
12 |
13 | case disabled = 0
14 | case primary = 1
15 | case secondary = 2
16 |
17 | var id: Int { self.rawValue }
18 | var description: String {
19 | switch self {
20 |
21 | case .disabled:
22 | return "Disabled".localized
23 | case .primary:
24 | return "Primary".localized
25 | case .secondary:
26 | return "Secondary".localized
27 | }
28 | }
29 | func protoEnumValue() -> Channel.Role {
30 |
31 | switch self {
32 |
33 | case .disabled:
34 | return Channel.Role.disabled
35 | case .primary:
36 | return Channel.Role.primary
37 | case .secondary:
38 | return Channel.Role.secondary
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/Meshtastic/Resources/Assets.xcassets/Assets.xcassets/Color.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "1.000",
9 | "green" : "1.000",
10 | "red" : "1.000"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "1.000",
27 | "green" : "1.000",
28 | "red" : "1.000"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/AppIconDebug.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "DevIcon.png",
5 | "idiom" : "universal",
6 | "platform" : "ios",
7 | "size" : "1024x1024"
8 | },
9 | {
10 | "appearances" : [
11 | {
12 | "appearance" : "luminosity",
13 | "value" : "dark"
14 | }
15 | ],
16 | "filename" : "DevIcon-Dark.png",
17 | "idiom" : "universal",
18 | "platform" : "ios",
19 | "size" : "1024x1024"
20 | },
21 | {
22 | "appearances" : [
23 | {
24 | "appearance" : "luminosity",
25 | "value" : "tinted"
26 | }
27 | ],
28 | "filename" : "DevIcon-Tinted.png",
29 | "idiom" : "universal",
30 | "platform" : "ios",
31 | "size" : "1024x1024"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/AppIcon_Ham.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "meshtastic_ham.jpg",
5 | "idiom" : "universal",
6 | "platform" : "ios",
7 | "size" : "1024x1024"
8 | },
9 | {
10 | "appearances" : [
11 | {
12 | "appearance" : "luminosity",
13 | "value" : "dark"
14 | }
15 | ],
16 | "filename" : "logo-dark.png",
17 | "idiom" : "universal",
18 | "platform" : "ios",
19 | "size" : "1024x1024"
20 | },
21 | {
22 | "appearances" : [
23 | {
24 | "appearance" : "luminosity",
25 | "value" : "tinted"
26 | }
27 | ],
28 | "filename" : "logo-dark 1.png",
29 | "idiom" : "universal",
30 | "platform" : "ios",
31 | "size" : "1024x1024"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/AppIcon_MPowered.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "MPowered.png",
5 | "idiom" : "universal",
6 | "platform" : "ios",
7 | "size" : "1024x1024"
8 | },
9 | {
10 | "appearances" : [
11 | {
12 | "appearance" : "luminosity",
13 | "value" : "dark"
14 | }
15 | ],
16 | "filename" : "MPowered 1.png",
17 | "idiom" : "universal",
18 | "platform" : "ios",
19 | "size" : "1024x1024"
20 | },
21 | {
22 | "appearances" : [
23 | {
24 | "appearance" : "luminosity",
25 | "value" : "tinted"
26 | }
27 | ],
28 | "filename" : "MPowered 2.png",
29 | "idiom" : "universal",
30 | "platform" : "ios",
31 | "size" : "1024x1024"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Meshtastic/Tips/PersistantTips.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Untitled.swift
3 | // Meshtastic
4 | //
5 | // Created by Garth Vander Houwen on 6/19/25.
6 | //
7 | import TipKit
8 |
9 | struct PersistentTip: TipViewStyle {
10 | func makeBody(configuration: Configuration) -> some View {
11 | VStack {
12 | HStack(alignment: .top) {
13 | if let image = configuration.image {
14 | image
15 | .font(.system(size: 42))
16 | .foregroundColor(.accentColor)
17 | .padding(.trailing, 5)
18 | }
19 | VStack(alignment: .leading) {
20 | if let title = configuration.title {
21 | title
22 | .bold()
23 | .font(.headline)
24 | }
25 | if let message = configuration.message {
26 | message
27 | .foregroundStyle(.secondary)
28 | .font(.callout)
29 | }
30 | }
31 | }
32 | }
33 | .frame(maxWidth: .infinity)
34 | .backgroundStyle(.thinMaterial)
35 | .padding()
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/Meshtastic/Resources/AppIcon_Ham.icon/icon.json:
--------------------------------------------------------------------------------
1 | {
2 | "fill" : {
3 | "automatic-gradient" : "display-p3:0.54507,0.90596,0.61177,1.00000"
4 | },
5 | "groups" : [
6 | {
7 | "layers" : [
8 | {
9 | "blend-mode" : "normal",
10 | "glass" : false,
11 | "hidden" : false,
12 | "image-name" : "ham.png",
13 | "name" : "ham",
14 | "position" : {
15 | "scale" : 1.65,
16 | "translation-in-points" : [
17 | -2.0625,
18 | 7.703125
19 | ]
20 | }
21 | }
22 | ],
23 | "shadow" : {
24 | "kind" : "neutral",
25 | "opacity" : 0.5
26 | },
27 | "translucency" : {
28 | "enabled" : true,
29 | "value" : 0.5
30 | }
31 | }
32 | ],
33 | "supported-platforms" : {
34 | "circles" : [
35 | "watchOS"
36 | ],
37 | "squares" : "shared"
38 | }
39 | }
--------------------------------------------------------------------------------
/Meshtastic/Extensions/UIImage.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIImage.swift
3 | // Meshtastic
4 | //
5 | // Copyright(c) Garth Vander Houwen 4/25/23.
6 | //
7 |
8 | import Foundation
9 | import UIKit
10 |
11 | extension UIImage {
12 | func rotate(radians: Float) -> UIImage? {
13 | var newSize = CGRect(origin: CGPoint.zero, size: self.size).applying(CGAffineTransform(rotationAngle: CGFloat(radians))).size
14 | newSize.width = floor(newSize.width)
15 | newSize.height = floor(newSize.height)
16 | UIGraphicsBeginImageContextWithOptions(newSize, false, self.scale)
17 | let context = UIGraphicsGetCurrentContext()!
18 | context.translateBy(x: newSize.width/2, y: newSize.height/2)
19 | context.rotate(by: CGFloat(radians))
20 | self.draw(in: CGRect(x: -self.size.width/2, y: -self.size.height/2, width: self.size.width, height: self.size.height))
21 | let newImage = UIGraphicsGetImageFromCurrentImageContext()
22 | UIGraphicsEndImageContext()
23 |
24 | return newImage
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/Meshtastic/Views/Helpers/Messages/MessageTemplate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MessageTemplate.swift
3 | // Meshtastic
4 | //
5 | // Copyright(c) Garth Vander Houwen 9/18/22.
6 | //
7 | import SwiftUI
8 |
9 | struct MessageTemplate: View {
10 |
11 | var user: UserEntity
12 | var message: MessageEntity
13 | var messageReply: MessageEntity?
14 |
15 | var body: some View {
16 |
17 | // Display the message being replied to and the arrow
18 | if message.replyID > 0 {
19 |
20 | HStack {
21 |
22 | Text(messageReply?.messagePayload ?? "EMPTY MESSAGE").foregroundColor(.blue).font(.caption2)
23 | .padding(10)
24 | .overlay(
25 | RoundedRectangle(cornerRadius: 18)
26 | .stroke(Color.blue, lineWidth: 0.5)
27 | )
28 | Image(systemName: "arrowshape.turn.up.left.fill")
29 | .symbolRenderingMode(.hierarchical)
30 | .imageScale(.large).foregroundColor(.blue)
31 | .padding(.trailing)
32 | }
33 | }
34 |
35 | // Message
36 |
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Meshtastic/Enums/BluetoothModes.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BluetoothModes.swift
3 | // Meshtastic
4 | //
5 | // Copyright(c) Garth Vander Houwen 8/19/22.
6 | //
7 | import Foundation
8 | import MeshtasticProtobufs
9 |
10 | enum BluetoothModes: Int, CaseIterable, Identifiable {
11 |
12 | case randomPin = 0
13 | case fixedPin = 1
14 | case noPin = 2
15 |
16 | var id: Int { self.rawValue }
17 | var description: String {
18 | switch self {
19 | case .randomPin:
20 | return "Random Pin".localized
21 | case .fixedPin:
22 | return "Fixed Pin".localized
23 | case .noPin:
24 | return "No PIN (Just Works)".localized
25 | }
26 | }
27 | func protoEnumValue() -> Config.BluetoothConfig.PairingMode {
28 | switch self {
29 | case .randomPin:
30 | return Config.BluetoothConfig.PairingMode.randomPin
31 | case .fixedPin:
32 | return Config.BluetoothConfig.PairingMode.fixedPin
33 | case .noPin:
34 | return Config.BluetoothConfig.PairingMode.noPin
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 | ## What changed?
2 |
3 |
4 | ## Why did it change?
5 |
6 |
7 | ## How is this tested?
8 |
9 |
10 | ## Screenshots/Videos (when applicable)
11 |
12 |
13 |
14 | ## Checklist
15 |
16 | - [ ] My code adheres to the project's coding and style guidelines.
17 | - [ ] I have conducted a self-review of my code.
18 | - [ ] I have commented my code, particularly in complex areas.
19 | - [ ] I have verified whether these changes require an update to existing documentation or if new documentation is needed, and created an issue in the [docs repo](http://github.com/meshtastic/meshtastic/issues) if applicable.
20 | - [ ] I have tested the change to ensure that it works as intended.
21 |
22 |
--------------------------------------------------------------------------------
/Meshtastic/Extensions/CLLocation.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CLLocation.swift
3 | // Meshtastic
4 | //
5 | // Copyright(c) Garth Vander Houwen 8/4/24.
6 | //
7 | import Foundation
8 | import MapKit
9 |
10 | func degreesToRadians(degrees: Double) -> Double { return degrees * .pi / 180.0 }
11 | func radiansToDegrees(radians: Double) -> Double { return radians * 180.0 / .pi }
12 |
13 | func getBearingBetweenTwoPoints(point1: CLLocation, point2: CLLocation) -> Double {
14 |
15 | let lat1 = degreesToRadians(degrees: point1.coordinate.latitude)
16 | let lon1 = degreesToRadians(degrees: point1.coordinate.longitude)
17 |
18 | let lat2 = degreesToRadians(degrees: point2.coordinate.latitude)
19 | let lon2 = degreesToRadians(degrees: point2.coordinate.longitude)
20 |
21 | let dLon = lon2 - lon1
22 |
23 | let y = sin(dLon) * cos(lat2)
24 | let x = cos(lat1) * sin(lat2) - sin(lat1) * cos(lat2) * cos(dLon)
25 | let radiansBearing = atan2(y, x)
26 |
27 | return radiansToDegrees(radians: radiansBearing)
28 | }
29 |
--------------------------------------------------------------------------------
/Meshtastic/Resources/AppIcon.icon/Assets/Icon Grid Middle Stroke 3.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
--------------------------------------------------------------------------------
/Meshtastic/Resources/AppIcon.icon/Assets/Icon Grid Outer Stroke 2.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
--------------------------------------------------------------------------------
/Meshtastic/Resources/AppIconDebug.icon/Assets/Icon Grid Middle Stroke 3.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
--------------------------------------------------------------------------------
/Meshtastic/Resources/AppIconDebug.icon/Assets/Icon Grid Outer Stroke 2.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
--------------------------------------------------------------------------------
/Meshtastic/Resources/AppIcon.icon/Assets/Icon Grid Middle Circle.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
--------------------------------------------------------------------------------
/Meshtastic/Resources/AppIcon.icon/Assets/Icon Grid Outer Circle.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
--------------------------------------------------------------------------------
/Meshtastic/Resources/AppIconDebug.icon/Assets/Icon Grid Middle Circle.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
--------------------------------------------------------------------------------
/Meshtastic/Resources/AppIconDebug.icon/Assets/Icon Grid Outer Circle.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
--------------------------------------------------------------------------------
/Meshtastic/Views/Helpers/Help/AckErrors.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IAQScale.swift
3 | // Meshtastic
4 | //
5 | // Copyright Garth Vander Houwen 4/24/24.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct AckErrors: View {
11 |
12 | var body: some View {
13 | VStack(alignment: .leading) {
14 | Text("Message Status Options")
15 | .font(.title2)
16 | HStack {
17 | RoundedRectangle(cornerRadius: 5)
18 | .fill(.orange)
19 | .frame(width: 20, height: 12)
20 | Text("Acknowledged by another node")
21 | .font(.caption)
22 | .foregroundStyle(.orange)
23 | }
24 | ForEach(RoutingError.allCases) { re in
25 | HStack {
26 | RoundedRectangle(cornerRadius: 5)
27 | .fill(re.color)
28 | .frame(width: 20, height: 12)
29 | Text(re.display)
30 | .font(.caption)
31 | .foregroundStyle(re.color)
32 | }
33 | }
34 | }
35 | }
36 | }
37 |
38 | struct AckErrorsPreviews: PreviewProvider {
39 | static var previews: some View {
40 | VStack {
41 | AckErrors()
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/Meshtastic/Extensions/CoreData/DeviceMetadataEntityExtension.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import CoreData
3 | import MeshtasticProtobufs
4 |
5 | extension DeviceMetadataEntity {
6 | convenience init(
7 | context: NSManagedObjectContext,
8 | metadata: DeviceMetadata
9 | ) {
10 | self.init(context: context)
11 | self.time = Date()
12 | self.deviceStateVersion = Int32(metadata.deviceStateVersion)
13 | self.canShutdown = metadata.canShutdown
14 | self.hasWifi = metadata.hasWifi_p
15 | self.hasBluetooth = metadata.hasBluetooth_p
16 | self.hasEthernet = metadata.hasEthernet_p
17 | self.role = Int32(metadata.role.rawValue)
18 | self.positionFlags = Int32(metadata.positionFlags)
19 | // Swift does strings weird, this does work to get the version without the github hash
20 | let lastDotIndex = metadata.firmwareVersion.lastIndex(of: ".")
21 | var version = metadata.firmwareVersion[...(lastDotIndex ?? String.Index(utf16Offset: 6, in: metadata.firmwareVersion))]
22 | version = version.dropLast()
23 | self.firmwareVersion = String(version)
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Meshtastic/Views/Helpers/Compact Widgets/WeightCompactWidget.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WeightCompactWidget.swift
3 | // Meshtastic
4 | //
5 | // Created by Jake Bordens on 3/14/25.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct WeightCompactWidget: View {
11 | let weight: String
12 | let unit: String
13 |
14 | var body: some View {
15 | VStack(alignment: .leading) {
16 | HStack(alignment: .firstTextBaseline) {
17 | Image(systemName: "scalemass")
18 | .imageScale(.small)
19 | .foregroundColor(.accentColor)
20 | Text("Weight")
21 | .textCase(.uppercase)
22 | .font(.callout)
23 | }
24 | HStack {
25 | Text("\(weight)")
26 | .font(weight.length < 4 ? .system(size: 50) : .system(size: 40) )
27 | Text(unit)
28 | .font(.system(size: 14))
29 | }
30 | }
31 | .frame(minWidth: 100, idealWidth: 125, maxWidth: 150, minHeight: 120, idealHeight: 130, maxHeight: 140)
32 | .padding()
33 | .background(.tertiary, in: RoundedRectangle(cornerRadius: 20, style: .continuous))
34 | }
35 | }
36 |
37 | #Preview {
38 | WeightCompactWidget(weight: "123", unit: "kg")
39 | }
40 |
--------------------------------------------------------------------------------
/Meshtastic/Accessory/Protocols/Connection.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Connection.swift
3 | // Meshtastic
4 | //
5 | // Created by Jake Bordens on 7/10/25.
6 | //
7 |
8 | import Foundation
9 | import MeshtasticProtobufs
10 |
11 | protocol Connection: Actor {
12 | var type: TransportType { get }
13 |
14 | var isConnected: Bool { get }
15 | func send(_ data: ToRadio) async throws
16 | func connect() async throws -> AsyncStream
17 | func disconnect(withError: Error?, shouldReconnect: Bool) throws
18 | func drainPendingPackets() async throws
19 | func startDrainPendingPackets() throws
20 |
21 | func appDidEnterBackground()
22 | func appDidBecomeActive()
23 | }
24 |
25 | enum ConnectionEvent {
26 | case data(FromRadio)
27 | case logMessage(String)
28 | case rssiUpdate(Int)
29 | case error(Error)
30 | case errorWithoutReconnect(Error)
31 | case disconnected(shouldReconnect: Bool)
32 | }
33 |
34 | enum ConnectionState: Equatable, Codable {
35 | case disconnected
36 | case connecting
37 | case connected
38 | }
39 |
40 | enum ConnectionError: Error, LocalizedError {
41 | case ioError(String)
42 | }
43 |
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/logo-black.imageset/Mesh_Logo_Black.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
13 |
--------------------------------------------------------------------------------
/Meshtastic/Views/Helpers/Compact Widgets/DistanceCompactWidget.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DistanceCompactWidget.swift
3 | // Meshtastic
4 | //
5 | // Created by Jake Bordens on 3/14/25.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct DistanceCompactWidget: View {
11 | let distance: String
12 | let unit: String
13 |
14 | var body: some View {
15 | VStack(alignment: .leading) {
16 | HStack(alignment: .firstTextBaseline) {
17 | Image(systemName: "ruler")
18 | .imageScale(.small)
19 | .foregroundColor(.accentColor)
20 | Text("Distance")
21 | .textCase(.uppercase)
22 | .font(.callout)
23 | }
24 | HStack {
25 | Text("\(distance)")
26 | .font(distance.length < 4 ? .system(size: 50) : .system(size: 40) )
27 | Text(unit)
28 | .font(.system(size: 14))
29 | }
30 | }
31 | .frame(minWidth: 100, idealWidth: 125, maxWidth: 150, minHeight: 120, idealHeight: 130, maxHeight: 140)
32 | .padding()
33 | .background(.tertiary, in: RoundedRectangle(cornerRadius: 20, style: .continuous))
34 | }
35 | }
36 |
37 | #Preview {
38 | DistanceCompactWidget(distance: "123", unit: "mm")
39 | }
40 |
--------------------------------------------------------------------------------
/Widgets/Assets.xcassets/m-logo-black.imageset/Mesh_Logo_Black.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
13 |
--------------------------------------------------------------------------------
/Widgets/Assets.xcassets/m-logo-black.imageset/Mesh_Logo_Black_Large.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
13 |
--------------------------------------------------------------------------------
/Widgets/Assets.xcassets/m-logo-black.imageset/Mesh_Logo_Black_Small.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
13 |
--------------------------------------------------------------------------------
/Meshtastic/Views/Helpers/Compact Widgets/RadiationCompactWidget.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RadiationCompactWidget.swift
3 | // Meshtastic
4 | //
5 | // Created by Jake Bordens on 3/14/25.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct RadiationCompactWidget: View {
11 | let radiation: String
12 | let unit: String
13 |
14 | var body: some View {
15 | VStack(alignment: .leading) {
16 | HStack(alignment: .firstTextBaseline) {
17 | Text(verbatim: "☢")
18 | .font(.system(size: 30, design: .monospaced))
19 | .tint(.accentColor)
20 | Text("Radiation")
21 | .textCase(.uppercase)
22 | .font(.callout)
23 | }
24 | HStack {
25 | Text("\(radiation)")
26 | .font(radiation.length < 4 ? .system(size: 50) : .system(size: 34) )
27 | Text(unit)
28 | .font(.system(size: 14))
29 | }
30 | }
31 | .frame(minWidth: 100, idealWidth: 125, maxWidth: 150, minHeight: 120, idealHeight: 130, maxHeight: 140)
32 | .padding()
33 | .background(.tertiary, in: RoundedRectangle(cornerRadius: 20, style: .continuous))
34 | }
35 | }
36 |
37 | #Preview {
38 | RadiationCompactWidget(radiation: "15", unit: "µR/hr")
39 | }
40 |
--------------------------------------------------------------------------------
/Meshtastic/Views/Helpers/ChannelLock.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ChannelLock.swift
3 | // Meshtastic
4 | //
5 | // Copyright(c) Garth Vander Houwen 6/22/25.
6 | //
7 | import SwiftUI
8 |
9 | struct ChannelLock: View {
10 |
11 | @ObservedObject var channel: ChannelEntity
12 | var body: some View {
13 | /// Unencrypted - using no key at all or a known 1 byte key
14 | if channel.psk?.hexDescription.count ?? 0 < 3 {
15 | let preciseLoction = 17...32
16 | // Using precise location and have MQTT uplink enabled
17 | if channel.uplinkEnabled && preciseLoction ~= (Int(channel.positionPrecision)) {
18 | Image(systemName: "lock.open.trianglebadge.exclamationmark.fill")
19 | .foregroundColor(.red)
20 | // Using precise location
21 | } else if preciseLoction ~= (Int(channel.positionPrecision)) {
22 | Image(systemName: "lock.open.fill")
23 | .foregroundColor(.red)
24 | // Just unencrypted without any location or MQTT
25 | } else {
26 | Image(systemName: "lock.open.fill")
27 | .foregroundColor(.yellow)
28 | }
29 | } else {
30 | Image(systemName: "lock.fill")
31 | .foregroundColor(.green)
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/Meshtastic/Views/Nodes/Helpers/Actions/ClientHistoryButton.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 | import OSLog
3 | struct ClientHistoryButton: View {
4 | @EnvironmentObject var accessoryManager: AccessoryManager
5 |
6 | var connectedNode: NodeInfoEntity
7 |
8 | var node: NodeInfoEntity
9 |
10 | @State
11 | private var isPresentingAlert = false
12 |
13 | var body: some View {
14 | Button {
15 | Task {
16 | do {
17 | try await accessoryManager.requestStoreAndForwardClientHistory(
18 | fromUser: connectedNode.user!,
19 | toUser: node.user!
20 | )
21 | Task { @MainActor in
22 | isPresentingAlert = true
23 | }
24 | } catch {
25 | Logger.mesh.warning("Failed to send client history request: \(error)")
26 | }
27 | }
28 | } label: {
29 | Label(
30 | "Client History",
31 | systemImage: "envelope.arrow.triangle.branch"
32 | )
33 | }.alert(
34 | "Client History Request Sent",
35 | isPresented: $isPresentingAlert
36 | ) {
37 | Button("OK") { }.keyboardShortcut(.defaultAction)
38 | } message: {
39 | Text("Any missed messages will be delivered again.")
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/Widgets/MeshActivityAttributes.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MeshActivityAttributes.swift
3 | // Meshtastic
4 | //
5 | // Created by Garth Vander Houwen on 3/1/23.
6 | //
7 | #if !targetEnvironment(macCatalyst)
8 | #if canImport(ActivityKit)
9 |
10 | import ActivityKit
11 | import WidgetKit
12 | import SwiftUI
13 |
14 | struct MeshActivityAttributes: ActivityAttributes {
15 | public typealias MeshActivityStatus = ContentState
16 | public struct ContentState: Codable, Hashable {
17 | // Dynamic stateful properties about your activity go here!
18 | var uptimeSeconds: UInt32?
19 | var channelUtilization: Float?
20 | var airtime: Float?
21 | var sentPackets: UInt32
22 | var receivedPackets: UInt32
23 | var badReceivedPackets: UInt32
24 | var dupeReceivedPackets: UInt32
25 | var packetsSentRelay: UInt32
26 | var packetsCanceledRelay: UInt32
27 | var nodesOnline: UInt32
28 | var totalNodes: UInt32
29 |
30 | public var numTxRelayCanceled: UInt32 = 0
31 | var timerRange: ClosedRange
32 | }
33 |
34 | // Fixed non-changing properties about your activity go here!
35 | var nodeNum: Int
36 | var name: String
37 | }
38 | #endif
39 | #endif
40 |
--------------------------------------------------------------------------------
/Meshtastic/Assets.xcassets/logo-white.imageset/Mesh_Logo_White.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
13 |
--------------------------------------------------------------------------------
/Widgets/Assets.xcassets/m-logo-white.imageset/Mesh_Logo_White.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
13 |
--------------------------------------------------------------------------------
/Widgets/Assets.xcassets/m-logo-white.imageset/Mesh_Logo_White_Large.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
13 |
--------------------------------------------------------------------------------
/Widgets/Assets.xcassets/m-logo-white.imageset/Mesh_Logo_White_Small.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
13 |
--------------------------------------------------------------------------------
/Meshtastic/Extensions/Date.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Date.swift
3 | // Meshtastic
4 | //
5 | // Copyright(c) Garth Vander Houwen 4/25/23.
6 | //
7 |
8 | import Foundation
9 |
10 | extension Date {
11 |
12 | var lastHeard: String {
13 | if self.timeIntervalSince1970 > 0 && self < Calendar.current.date(byAdding: .year, value: 1, to: Date())! {
14 | formatted()
15 | } else {
16 | "Unknown Age".localized
17 | }
18 | }
19 |
20 | func formattedDate(format: String) -> String {
21 | let dateformat = DateFormatter()
22 | dateformat.dateFormat = format
23 | if self.timeIntervalSince1970 > 0 && self < Calendar.current.date(byAdding: .year, value: 1, to: Date())! {
24 | return dateformat.string(from: self)
25 | } else {
26 | return "Unknown Age".localized
27 | }
28 | }
29 | func relativeTimeOfDay() -> String {
30 | let hour = Calendar.current.component(.hour, from: self)
31 |
32 | switch hour {
33 | case 6..<12: return "Morning".localized
34 | case 12: return "Midday".localized
35 | case 13..<17: return "Afternoon".localized
36 | case 17..<22: return "Evening".localized
37 | default: return "Nighttime".localized
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/Meshtastic/Resources/AppIcon.icon/Assets/Meshtastic Logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
13 |
--------------------------------------------------------------------------------
/Meshtastic/Resources/AppIconDebug.icon/Assets/Meshtastic Logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
13 |
--------------------------------------------------------------------------------
/Meshtastic/Extensions/Bundle.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Bundle.swift
3 | // Meshtastic
4 | //
5 | // Created by Garth Vander Houwen on 12/25/23.
6 | //
7 |
8 | import Foundation
9 |
10 | extension Bundle {
11 | public var appName: String { getInfo("CFBundleName") }
12 | public var displayName: String { getInfo("CFBundleDisplayName") }
13 | public var language: String { getInfo("CFBundleDevelopmentRegion") }
14 | public var identifier: String { getInfo("CFBundleIdentifier") }
15 | public var copyright: String { getInfo("NSHumanReadableCopyright").replacingOccurrences(of: "\\\\n", with: "\n") }
16 |
17 | public var appBuild: String { getInfo("CFBundleVersion") }
18 | public var appVersionLong: String { getInfo("CFBundleShortVersionString") }
19 | // public var appVersionShort: String { getInfo("CFBundleShortVersion") }
20 |
21 | fileprivate func getInfo(_ str: String) -> String { infoDictionary?[str] as? String ?? "⚠️" }
22 |
23 | public var isTestFlight: Bool {
24 | return appStoreReceiptURL?.lastPathComponent == "sandboxReceipt"
25 | }
26 |
27 | public var isDebug: Bool {
28 | #if DEBUG
29 | return true
30 | #else
31 | return false
32 | #endif
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/Meshtastic/Resources/AppIcon.icon/Assets/Icon Grid Inner Stroke.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
--------------------------------------------------------------------------------
/Meshtastic/Resources/AppIconDebug.icon/Assets/Icon Grid Inner Stroke.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
--------------------------------------------------------------------------------
/Meshtastic/Meshtastic.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.developer.associated-domains
6 |
7 | applinks:meshtastic.org/e/*
8 | applinks:meshtastic.org/v/*
9 |
10 | com.apple.developer.carplay-communication
11 |
12 | com.apple.developer.usernotifications.critical-alerts
13 |
14 | com.apple.developer.weatherkit
15 |
16 | com.apple.security.app-sandbox
17 |
18 | com.apple.security.device.bluetooth
19 |
20 | com.apple.security.device.serial
21 |
22 | com.apple.security.device.usb
23 |
24 | com.apple.security.files.user-selected.read-write
25 |
26 | com.apple.security.network.client
27 |
28 | com.apple.security.personal-information.location
29 |
30 | keychain-access-groups
31 |
32 | $(AppIdentifierPrefix)gvh.MeshtasticClient
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/Meshtastic/Views/Helpers/Compact Widgets/CurrentConditionsCompact.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CurrentConditionsCompact.swift
3 | // Meshtastic
4 | //
5 | // Copyright(c) Garth Vander Houwen 2/5/23.
6 | //
7 | import SwiftUI
8 |
9 | struct CurrentConditionsCompact: View {
10 | var temp: Float
11 | var condition: WeatherConditions
12 |
13 | var body: some View {
14 | Label("\(String(temp.formattedTemperature()))", systemImage: condition.symbolName)
15 | .font(.caption)
16 | .foregroundColor(.gray)
17 | .symbolRenderingMode(.multicolor)
18 | }
19 | }
20 |
21 | struct CurrentConditionsCompact_Previews: PreviewProvider {
22 | static var previews: some View {
23 |
24 | VStack {
25 | CurrentConditionsCompact(temp: 22, condition: WeatherConditions.clear)
26 | CurrentConditionsCompact(temp: 17, condition: WeatherConditions.cloudy)
27 | CurrentConditionsCompact(temp: -5, condition: WeatherConditions.frigid)
28 | CurrentConditionsCompact(temp: 38, condition: WeatherConditions.hot)
29 | CurrentConditionsCompact(temp: 10, condition: WeatherConditions.rain)
30 | CurrentConditionsCompact(temp: 30, condition: WeatherConditions.smoky)
31 | CurrentConditionsCompact(temp: -2, condition: WeatherConditions.snow)
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/Meshtastic/Extensions/Float.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Float.swift
3 | // Meshtastic
4 | //
5 | // Copyright(c) Garth Vander Houwen 4/25/23.
6 | //
7 |
8 | import Foundation
9 |
10 | extension Float {
11 |
12 | func formattedTemperature() -> String {
13 | let temperature = Measurement(value: Double(self), unit: .celsius)
14 | let mf = MeasurementFormatter()
15 | mf.numberFormatter.maximumFractionDigits = 0
16 | return mf.string(from: temperature)
17 | }
18 | func shortFormattedTemperature() -> String {
19 | let temperature = Measurement(value: Double(self), unit: .celsius)
20 | let mf = MeasurementFormatter()
21 | mf.unitStyle = .short
22 | mf.numberFormatter.maximumFractionDigits = 0
23 | return mf.string(from: temperature)
24 | }
25 | func localeTemperature() -> Double {
26 | let temperature = Measurement(value: Double(self), unit: .celsius)
27 | let locale = NSLocale.current as NSLocale
28 | let localeUnit = locale.object(forKey: NSLocale.Key(rawValue: "kCFLocaleTemperatureUnitKey"))
29 | var format: UnitTemperature = .celsius
30 |
31 | if localeUnit! as? String == "Fahrenheit" {
32 | format = .fahrenheit
33 | }
34 | return temperature.converted(to: format).value
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/scripts/create-release-branch.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Check if the release version number is provided
4 | if [ -z "$1" ]; then
5 | echo "Usage: $0 "
6 | exit 1
7 | fi
8 |
9 | # Set the release version number
10 | RELEASE_VERSION=$1
11 |
12 | # Check if the release branch already exists on the remote repository
13 | if git ls-remote --exit-code --heads origin $RELEASE_BRANCH; then
14 | echo "The branch $RELEASE_BRANCH already exists on the remote repository."
15 | exit 1
16 | fi
17 |
18 | # Prompt the user for confirmation
19 | echo "You are about to create and push the release branch ${RELEASE_VERSION}-release."
20 | read -p "Are you sure you want to proceed? (Y/n): " confirmation
21 |
22 | # Check the user's response
23 | if [[ ! "$confirmation" =~ ^[Yy]$ ]]; then
24 | echo "Operation cancelled."
25 | exit 0
26 | fi
27 |
28 | # Check out the main branch and pull the latest changes
29 | git checkout main
30 | git pull origin main
31 |
32 | # Create a new branch for the release
33 | RELEASE_BRANCH="${RELEASE_VERSION}-release"
34 | git checkout -b $RELEASE_BRANCH
35 |
36 | # Push the new release branch to the remote repository
37 | git push origin $RELEASE_BRANCH
38 |
39 | echo "Release branch $RELEASE_BRANCH created and pushed successfully."
40 |
--------------------------------------------------------------------------------
/Meshtastic/Extensions/UIColor.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIColor.swift
3 | // Meshtastic
4 | //
5 | // Copyright(c) Garth Vander Houwen 8/31/23.
6 | //
7 | import Foundation
8 | import Swift
9 | import UIKit
10 |
11 | extension UIColor {
12 |
13 | private func makeColor(componentDelta: CGFloat) -> UIColor {
14 | var red: CGFloat = 0
15 | var blue: CGFloat = 0
16 | var green: CGFloat = 0
17 | var alpha: CGFloat = 0
18 |
19 | getRed(&red, green: &green, blue: &blue, alpha: &alpha)
20 |
21 | return UIColor(
22 | red: add(componentDelta, toComponent: red),
23 | green: add(componentDelta, toComponent: green),
24 | blue: add(componentDelta, toComponent: blue),
25 | alpha: alpha
26 | )
27 | }
28 |
29 | func lighter(componentDelta: CGFloat = 0.1) -> UIColor {
30 | return makeColor(componentDelta: componentDelta)
31 | }
32 |
33 | func darker(componentDelta: CGFloat = 0.1) -> UIColor {
34 | return makeColor(componentDelta: -1*componentDelta)
35 | }
36 |
37 | private func add(_ value: CGFloat, toComponent: CGFloat) -> CGFloat {
38 | return max(0, min(1, toComponent + value))
39 | }
40 |
41 | static var random: UIColor {
42 | return UIColor(
43 | red: .random(in: 0...1),
44 | green: .random(in: 0...1),
45 | blue: .random(in: 0...1),
46 | alpha: 1.0
47 | )
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/Meshtastic/Resources/AppIcon_Chirpy.icon/icon.json:
--------------------------------------------------------------------------------
1 | {
2 | "fill" : {
3 | "automatic-gradient" : "extended-gray:1.00000,1.00000"
4 | },
5 | "groups" : [
6 | {
7 | "layers" : [
8 | {
9 | "image-name" : "chirpy-2.svg",
10 | "name" : "chirpy-2",
11 | "position-specializations" : [
12 | {
13 | "idiom" : "square",
14 | "value" : {
15 | "scale" : 0.48,
16 | "translation-in-points" : [
17 | -18.390625,
18 | 127.2421875
19 | ]
20 | }
21 | },
22 | {
23 | "idiom" : "watchOS",
24 | "value" : {
25 | "scale" : 0.52,
26 | "translation-in-points" : [
27 | -7,
28 | 145.9296875
29 | ]
30 | }
31 | }
32 | ]
33 | }
34 | ],
35 | "shadow" : {
36 | "kind" : "neutral",
37 | "opacity" : 0.5
38 | },
39 | "translucency" : {
40 | "enabled" : true,
41 | "value" : 0.5
42 | }
43 | }
44 | ],
45 | "supported-platforms" : {
46 | "circles" : [
47 | "watchOS"
48 | ],
49 | "squares" : "shared"
50 | }
51 | }
--------------------------------------------------------------------------------
/Meshtastic/Enums/RouteEnums.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteEnums.swift
3 | // Meshtastic
4 | //
5 | // Copyright(c) Garth Vander Houwen 4/14/24.
6 | //
7 |
8 | import Foundation
9 | import SwiftUI
10 |
11 | enum ActivityType: Int, CaseIterable, Identifiable {
12 | case walking = 0
13 | case hiking = 1
14 | case biking = 2
15 | case driving = 3
16 | case overlanding = 4
17 | case skiing = 5
18 |
19 | var id: Int { self.rawValue }
20 | var description: String {
21 | switch self {
22 | case .walking:
23 | return "Walking".localized
24 | case .hiking:
25 | return "Hiking".localized
26 | case .biking:
27 | return "Biking".localized
28 | case .driving:
29 | return "Driving".localized
30 | case .overlanding:
31 | return "Overlanding".localized
32 | case .skiing:
33 | return "Skiing".localized
34 | }
35 | }
36 |
37 | var fileNameString: String {
38 | switch self {
39 | case .walking:
40 | return "Walking".localized.lowercased()
41 | case .hiking:
42 | return "Hiking".localized.lowercased()
43 | case .biking:
44 | return "Biking".localized.lowercased()
45 | case .driving:
46 | return "Driving".localized.lowercased()
47 | case .overlanding:
48 | return "Overlanding".localized.lowercased()
49 | case .skiing:
50 | return "Skiing".localized.lowercased()
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/Meshtastic/Enums/KeyBackupStatus.swift:
--------------------------------------------------------------------------------
1 | //
2 | // iCloudStats.swift
3 | // Meshtastic
4 | //
5 | // Copyright(c) Garth Vander Houwen 6/18/25.
6 | //
7 |
8 | enum KeyBackupStatus: String, CaseIterable, Equatable, Decodable {
9 | case saved
10 | case restored
11 | case deleted
12 | case saveFailed
13 | case restoreFailed
14 | case deleteFailed
15 | var description: String {
16 | switch self {
17 | case .saved:
18 | return "Private Key saved successfully to iCloud keychain.".localized
19 | case .restored:
20 | return "Private Key restored successfully from iCloud keychain.".localized
21 | case .deleted:
22 | return "Private Key deleted successfully from iCloud keychain.".localized
23 | case .saveFailed:
24 | return "Private Key failed to save to iCloud keychain.".localized
25 | case .restoreFailed:
26 | return "Private Key value not found in iCloud keychain.".localized
27 | case .deleteFailed:
28 | return "Private Key failed to delete from iCloud keychain.".localized
29 | }
30 | }
31 | var success: Bool {
32 | switch self {
33 | case .saved:
34 | return true
35 | case .restored:
36 | return true
37 | case .deleted:
38 | return true
39 | case .saveFailed:
40 | return false
41 | case .restoreFailed:
42 | return false
43 | case .deleteFailed:
44 | return false
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/Meshtastic/Views/Helpers/Compact Widgets/PressureCompactWidget.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PressureCompactWidget.swift
3 | // Meshtastic
4 | //
5 | // Created by Jake Bordens on 3/14/25.
6 | //
7 | import SwiftUI
8 |
9 | struct PressureCompactWidget: View {
10 | let pressure: String
11 | let unit: String
12 | let low: Bool
13 | var body: some View {
14 | VStack(alignment: .leading) {
15 | HStack(spacing: 5.0) {
16 | Image(systemName: "gauge")
17 | .foregroundColor(.accentColor)
18 | .font(.callout)
19 | Text("Pressure")
20 | .textCase(.uppercase)
21 | .font(.caption)
22 | }
23 | Text(pressure)
24 | .font(pressure.length < 7 ? .system(size: 35) : .system(size: 30) )
25 | Text(low ? "LOW" : "HIGH")
26 | .padding(.bottom, 10)
27 | Text(unit)
28 | }
29 | .frame(minWidth: 100, idealWidth: 125, maxWidth: 150, minHeight: 120, idealHeight: 130, maxHeight: 140)
30 | .padding()
31 | .background(.tertiary, in: RoundedRectangle(cornerRadius: 20, style: .continuous))
32 | }
33 | }
34 |
35 | #Preview {
36 | let gridItemLayout = Array(repeating: GridItem(.flexible(), spacing: 10), count: 2)
37 | Form {
38 | LazyVGrid(columns: gridItemLayout) {
39 | PressureCompactWidget(pressure: "1004.76", unit: "hPA", low: true)
40 | PressureCompactWidget(pressure: "1004.76", unit: "hPA", low: false)
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/Meshtastic/Model/CoreData/TelemetryEntity+CoreDataProperties.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TelemetryEntity+CoreDataProperties.swift
3 | //
4 | //
5 | // Created by Jake Bordens on 12/26/24.
6 | //
7 | //
8 |
9 | import Foundation
10 | import CoreData
11 |
12 | // Manual implementation of the TelemetryEntry object for CoreData.
13 | // Add non-optional scalar types here using the standard @NSManaged proprty wrapper
14 | // Add optional/non-optional object types here using the standard @NSManaged proprty wrapper
15 | // CoreData is based on Objective-C which natively supports optionals for class types and
16 | // non-optional scalars.
17 |
18 | extension TelemetryEntity {
19 |
20 | @nonobjc public class func fetchRequest() -> NSFetchRequest {
21 | return NSFetchRequest(entityName: "TelemetryEntity")
22 | }
23 |
24 | @NSManaged public var time: Date?
25 | @NSManaged public var metricsType: Int32
26 | @NSManaged public var numOnlineNodes: Int32
27 | @NSManaged public var numPacketsRx: Int32
28 | @NSManaged public var numPacketsRxBad: Int32
29 | @NSManaged public var numPacketsTx: Int32
30 | @NSManaged public var numRxDupe: Int32
31 | @NSManaged public var numTotalNodes: Int32
32 | @NSManaged public var numTxRelay: Int32
33 | @NSManaged public var numTxRelayCanceled: Int32
34 | @NSManaged public var nodeTelemetry: NodeInfoEntity?
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/Meshtastic/AppIntents/RestartNodeIntent.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RestartNodeIntent.swift
3 | // Meshtastic
4 | //
5 | // Created by Benjamin Faershtein on 8/24/24.
6 | //
7 |
8 | import Foundation
9 | import AppIntents
10 |
11 | struct RestartNodeIntent: AppIntent {
12 | static let title: LocalizedStringResource = "Restart"
13 |
14 | static let description: IntentDescription = "Restart to the node you are connected to"
15 |
16 | func perform() async throws -> some IntentResult {
17 |
18 | if !(await AccessoryManager.shared.isConnected) {
19 | throw AppIntentErrors.AppIntentError.notConnected
20 | }
21 | // Safely unwrap the connectedNode using if let
22 | if let connectedPeripheralNum = await AccessoryManager.shared.activeDeviceNum,
23 | let connectedNode = getNodeInfo(id: connectedPeripheralNum, context: PersistenceController.shared.container.viewContext),
24 | let fromUser = connectedNode.user,
25 | let toUser = connectedNode.user {
26 |
27 | // Attempt to send shutdown, throw an error if it fails
28 | do {
29 | try await AccessoryManager.shared.sendReboot(fromUser: fromUser, toUser: toUser)
30 | } catch {
31 | throw AppIntentErrors.AppIntentError.message("Failed to restart")
32 | }
33 | } else {
34 | throw AppIntentErrors.AppIntentError.message("Failed to retrieve connected node or required data")
35 | }
36 | return .result()
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Meshtastic/Views/Helpers/Compact Widgets/WeatherConditionsCompactWidget.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WeatherConditionsCompactWidget.swift
3 | // Meshtastic
4 | //
5 | // Created by Jake Bordens on 3/14/25.
6 | //
7 | import SwiftUI
8 |
9 | struct WeatherConditionsCompactWidget: View {
10 | let temperature: String
11 | let symbolName: String
12 | let description: String
13 | var body: some View {
14 | VStack(alignment: .leading) {
15 | HStack(spacing: 5.0) {
16 | Image(systemName: symbolName)
17 | .foregroundColor(.accentColor)
18 | .font(.callout)
19 | Text(description)
20 | .lineLimit(2)
21 | .allowsTightening(/*@START_MENU_TOKEN@*/true/*@END_MENU_TOKEN@*/)
22 | .fixedSize(horizontal: false, vertical: true)
23 | .font(.caption)
24 | }
25 | Text(temperature)
26 | .font(temperature.length < 4 ? .system(size: 72) : .system(size: 54) )
27 | }
28 | .frame(minWidth: 100, idealWidth: 125, maxWidth: 150, minHeight: 120, idealHeight: 130, maxHeight: 140)
29 | .padding()
30 | .background(.tertiary, in: RoundedRectangle(cornerRadius: 20, style: .continuous))
31 | }
32 | }
33 |
34 | #Preview {
35 | let gridItemLayout = Array(repeating: GridItem(.flexible(), spacing: 10), count: 2)
36 | Form {
37 | LazyVGrid(columns: gridItemLayout) {
38 | WeatherConditionsCompactWidget(temperature: "24°F", symbolName: "sun.rain.fill", description: "Raining")
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/Meshtastic/Views/Nodes/Helpers/NodeFilterParameters.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NodeListFilterParameters.swift
3 | // Meshtastic
4 | //
5 | // Created by jake on 9/4/25.
6 | //
7 |
8 | import SwiftUI
9 |
10 | @MainActor
11 | final class NodeFilterParameters: ObservableObject {
12 | // Public variables
13 | @Published var searchText = ""
14 | @Published var isOnline = false
15 | @Published var isPkiEncrypted = false
16 | @Published var isFavorite = false
17 | @Published var isIgnored = false
18 | @Published var isEnvironment = false
19 | @Published var distanceFilter = false
20 | @Published var maxDistance: Double = 800_000
21 | @Published var hopsAway: Double = -1.0
22 | @Published var roleFilter = false
23 | @Published var deviceRoles: Set = []
24 |
25 | // Private backing vars
26 | @Published private var _viaLora = true
27 | @Published private var _viaMqtt = true
28 |
29 | // Public computed wrappers with enforcement
30 | var viaLora: Bool {
31 | get { _viaLora }
32 | set {
33 | objectWillChange.send()
34 | _viaLora = newValue
35 | if !_viaLora && !_viaMqtt {
36 | _viaMqtt = true // enforce at least one ON
37 | }
38 | }
39 | }
40 |
41 | var viaMqtt: Bool {
42 | get { _viaMqtt }
43 | set {
44 | objectWillChange.send()
45 | _viaMqtt = newValue
46 | if !_viaLora && !_viaMqtt {
47 | _viaLora = true // enforce at least one ON
48 | }
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/Meshtastic/Views/Nodes/Helpers/Actions/TraceRouteButton.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 | import OSLog
3 | struct TraceRouteButton: View {
4 | @EnvironmentObject var accessoryManager: AccessoryManager
5 |
6 | var node: NodeInfoEntity
7 |
8 | @State
9 | private var isPresentingTraceRouteSentAlert: Bool = false
10 |
11 | var body: some View {
12 | RateLimitedButton(key: "traceroute", rateLimit: 30.0) {
13 | Task {
14 | do {
15 | try await accessoryManager.sendTraceRouteRequest(
16 | destNum: node.user?.num ?? 0,
17 | wantResponse: true
18 | )
19 | Task {
20 | isPresentingTraceRouteSentAlert = true
21 | }
22 | } catch {
23 | Logger.mesh.warning("Failed to send traceroute request: \(error)")
24 | }
25 | }
26 | } label: { completion in
27 | if let completion, completion.percentComplete > 0.0 {
28 | Label {
29 | Text("Trace Route (in \(completion.secondsRemaining.formatted(.number.precision(.fractionLength(0))))s)")
30 | .foregroundStyle(.secondary)
31 | } icon: {
32 | Image("progress.ring.dashed", variableValue: completion.percentComplete)
33 | .foregroundStyle(.secondary)
34 | }.disabled(true)
35 | } else {
36 | Label {
37 | Text("Trace Route")
38 | } icon: {
39 | Image(systemName: "signpost.right.and.left")
40 | .symbolRenderingMode(.hierarchical)
41 | }
42 | }
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Meshtastic Apple Clients
2 |
3 | ## Overview
4 |
5 | SwiftUI client applications for iOS, iPadOS and macOS.
6 |
7 | ## Getting Started
8 |
9 | This project always uses the latest release version of XCode.
10 |
11 | 1. Clone the repo.
12 | ```sh
13 | git clone git@github.com:meshtastic/Meshtastic-Apple.git
14 | ```
15 | 2. Open the local directory.
16 | ```sh
17 | cd Meshtastic-Apple
18 | ```
19 | 3. Set up git hooks to automatically lint the project when you commit changes.
20 | ```sh
21 | ./scripts/setup-hooks.sh
22 | ```
23 | 4. Open `Meshtastic.xcworkspace`
24 | ```sh
25 | open Meshtastic.xcworkspace
26 | ```
27 | 5. Build and run the `Meshtastic` target.
28 |
29 | ## Technical Standards
30 |
31 | ### Supported Operating Systems
32 |
33 | The last two major operating system versions are supported on iOS, iPadOS and macOS.
34 |
35 | ### Code Standards
36 |
37 | - Use SwiftUI
38 | - Use SFSymbols for icons
39 | - Use Core Data for persistence
40 |
41 | ## Updating Protobufs:
42 |
43 | 1. run
44 | ```bash
45 | ./scripts/gen_protos.sh
46 | ```
47 | 2. Build, test, and commit the changes.
48 |
49 | ## Release Process
50 |
51 | For more information on how a new release of Meshtastic is managed, please refer to [RELEASING.md](./RELEASING.md)
52 |
53 | ## License
54 |
55 | This project is licensed under the GPL v3. See the [LICENSE](LICENSE) file for details.
56 |
--------------------------------------------------------------------------------
/Meshtastic/Enums/MessagingEnums.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MessagingEnums.swift
3 | // Meshtastic
4 | //
5 | // Copyright(c) Garth Vander Houwen 9/30/22.
6 | //
7 | import Foundation
8 |
9 | enum BubblePosition {
10 | case left
11 | case right
12 | }
13 |
14 | enum Tapbacks: Int, CaseIterable, Identifiable {
15 |
16 | case wave = 0
17 | case heart = 1
18 | case thumbsUp = 2
19 | case thumbsDown = 3
20 | case haHa = 4
21 | case exclamation = 5
22 | case question = 6
23 | case poop = 7
24 |
25 | var id: Int { self.rawValue }
26 | var emojiString: String {
27 | switch self {
28 | case .wave:
29 | return "👋"
30 | case .heart:
31 | return "❤️"
32 | case .thumbsUp:
33 | return "👍"
34 | case .thumbsDown:
35 | return "👎"
36 | case .haHa:
37 | return "🤣"
38 | case .exclamation:
39 | return "‼️"
40 | case .question:
41 | return "❓"
42 | case .poop:
43 | return "💩"
44 | }
45 | }
46 | var description: String {
47 | switch self {
48 | case .wave:
49 | return "Wave".localized
50 | case .heart:
51 | return "Heart".localized
52 | case .thumbsUp:
53 | return "Thumbs Up".localized
54 | case .thumbsDown:
55 | return "Thumbs Down".localized
56 | case .haHa:
57 | return "HaHa".localized
58 | case .exclamation:
59 | return "Exclamation".localized
60 | case .question:
61 | return "Question".localized
62 | case .poop:
63 | return "Poop".localized
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/Meshtastic/Views/Helpers/Compact Widgets/HumidityCompactWidget.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HumidityCompactWidget.swift
3 | // Meshtastic
4 | //
5 | // Created by Jake Bordens on 3/14/25.
6 | //
7 | import SwiftUI
8 |
9 | struct HumidityCompactWidget: View {
10 | let humidity: Int
11 | let dewPoint: String?
12 | var body: some View {
13 | VStack(alignment: .leading) {
14 | HStack(spacing: 5.0) {
15 | Image(systemName: "humidity")
16 | .foregroundColor(.accentColor)
17 | .font(.callout)
18 | Text("Humidity")
19 | .textCase(.uppercase)
20 | .font(.caption)
21 | }
22 | Text("\(humidity)%")
23 | .font(.largeTitle)
24 | .padding(.bottom, 5)
25 | if let dewPoint {
26 | Text("The dew point is \(dewPoint) right now.")
27 | .lineLimit(3)
28 | .allowsTightening(true)
29 | .fixedSize(horizontal: false, vertical: true)
30 | .font(.caption2)
31 | }
32 | }
33 | .frame(minWidth: 100, idealWidth: 125, maxWidth: 150, minHeight: 120, idealHeight: 130, maxHeight: 140)
34 | .padding()
35 | .background(.tertiary, in: RoundedRectangle(cornerRadius: 20, style: .continuous))
36 | }
37 | }
38 |
39 | #Preview {
40 | let gridItemLayout = Array(repeating: GridItem(.flexible(), spacing: 10), count: 2)
41 | Form {
42 | LazyVGrid(columns: gridItemLayout) {
43 | HumidityCompactWidget(humidity: 27, dewPoint: "32°")
44 | HumidityCompactWidget(humidity: 27, dewPoint: nil)
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/Meshtastic/Views/Helpers/MeshtasticLogo.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MeshtasticLogo.swift
3 | // Meshtastic
4 | //
5 | // Copyright(c) Garth Vander Houwen 10/6/22.
6 | //
7 | import SwiftUI
8 |
9 | struct MeshtasticLogo: View {
10 |
11 | @Environment(\.colorScheme) var colorScheme
12 |
13 | var body: some View {
14 | #if targetEnvironment(macCatalyst)
15 | VStack {
16 | Link(destination: URL(string: "meshtastic:///settings/about")!) {
17 | if #available(iOS 26.0, macOS 26.0, *) {
18 | Image(colorScheme == .dark ? "logo-white" : "logo-black")
19 | .resizable()
20 | .foregroundColor(.accentColor)
21 | .scaledToFit()
22 | } else {
23 | Image("logo-white")
24 | .resizable()
25 | .foregroundColor(.accentColor)
26 | .scaledToFit()
27 | }
28 | }
29 | }
30 | .padding(.bottom, 5)
31 | .padding(.top, 5)
32 | #else
33 | if #available(iOS 26.0, macOS 26.0, *) {
34 | VStack {
35 | Link(destination: URL(string: "meshtastic:///settings/about")!) {
36 | Image(colorScheme == .dark ? "logo-white" : "logo-black")
37 | .resizable()
38 | .scaledToFit()
39 | }
40 | }
41 | } else {
42 | VStack {
43 | Link(destination: URL(string: "meshtastic:///settings/about")!) {
44 | Image(colorScheme == .dark ? "logo-white" : "logo-black")
45 | .resizable()
46 | .scaledToFit()
47 | }
48 | }
49 | .padding(.bottom, 5)
50 | }
51 | #endif
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/Meshtastic/Views/Messages/TapbackResponses.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 | import OSLog
3 |
4 | struct TapbackResponses: View {
5 | @Environment(\.managedObjectContext) var context
6 |
7 | let message: MessageEntity
8 | let onRead: () -> Void
9 |
10 | @ViewBuilder
11 | var body: some View {
12 | let tapbacks = message.tapbacks
13 | if !tapbacks.isEmpty {
14 | VStack(alignment: .trailing) {
15 | HStack {
16 | ForEach( tapbacks ) { (tapback: MessageEntity) in
17 | VStack {
18 | let image = tapback.messagePayload!.image(fontSize: 20)
19 | Image(uiImage: image!).font(.caption)
20 | Text("\(tapback.fromUser?.shortName ?? "?")")
21 | .font(.caption2)
22 | .foregroundColor(.gray)
23 | .fixedSize()
24 | .padding(.bottom, 1)
25 | }
26 | .onAppear {
27 | guard !tapback.read else {
28 | return
29 | }
30 |
31 | tapback.read = true
32 | do {
33 | try context.save()
34 | Logger.data.info("📖 Read tapback \(tapback.messageId, privacy: .public) ")
35 | onRead()
36 | } catch {
37 | Logger.data.error("Failed to read tapback \(tapback.messageId, privacy: .public): \(error.localizedDescription, privacy: .public)")
38 | }
39 | }
40 | }
41 | }
42 | .padding(10)
43 | .overlay(
44 | RoundedRectangle(cornerRadius: 18)
45 | .stroke(Color.gray, lineWidth: 1)
46 | )
47 | }
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/Meshtastic/AppIntents/ShutDownNodeIntent.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ShutDownNodeIntent.swift
3 | // Meshtastic
4 | //
5 | // Created by Benjamin Faershtein on 8/24/24.
6 | //
7 |
8 | import Foundation
9 | import AppIntents
10 |
11 | struct ShutDownNodeIntent: AppIntent {
12 | static let title: LocalizedStringResource = "Shut Down"
13 |
14 | static let description: IntentDescription = "Send a shutdown to the node you are connected to"
15 |
16 | func perform() async throws -> some IntentResult {
17 | try await requestConfirmation(result: .result(dialog: "Shut Down Node?"))
18 |
19 | if !(await AccessoryManager.shared.isConnected) {
20 | throw AppIntentErrors.AppIntentError.notConnected
21 | }
22 |
23 | // Safely unwrap the connectedNode using if let
24 | if let connectedPeripheralNum = await AccessoryManager.shared.activeDeviceNum,
25 | let connectedNode = getNodeInfo(id: connectedPeripheralNum, context: PersistenceController.shared.container.viewContext),
26 | let fromUser = connectedNode.user,
27 | let toUser = connectedNode.user {
28 |
29 | // Attempt to send shutdown, throw an error if it fails
30 | do {
31 | try await AccessoryManager.shared.sendShutdown(fromUser: fromUser, toUser: toUser)
32 | } catch {
33 | throw AppIntentErrors.AppIntentError.message("Failed to shut down")
34 | }
35 | } else {
36 | throw AppIntentErrors.AppIntentError.message("Failed to retrieve connected node or required data")
37 | }
38 | return .result()
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/Meshtastic/Views/Settings/AppIconButton.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppIconButton.swift
3 | // Meshtastic
4 | //
5 | // Created by Chase Christiansen on 7/21/25.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct AppIconButton: View {
11 | @Binding var iconDescription: String
12 | @Binding var iconName: String?
13 | @Binding var isPresenting: Bool
14 | @State var errorDetails: String?
15 | @State var didError = false
16 |
17 | @Environment(\.colorScheme) var colorScheme
18 |
19 | var body: some View {
20 | Button {
21 | UIApplication.shared.setAlternateIconName(iconName) { error in
22 | if let error = error {
23 | errorDetails = error.localizedDescription
24 | didError = true
25 | } else {
26 | self.isPresenting = false
27 | }
28 | }
29 | } label: {
30 | HStack(alignment: .center) {
31 | let imageName = colorScheme == .dark ? "\(iconName ?? "AppIcon")_Dark_Thumb" : "\(iconName ?? "AppIcon")_Thumb"
32 |
33 | if let image = UIImage(named: imageName) {
34 | Image(uiImage: image)
35 | .resizable()
36 | .aspectRatio(contentMode: .fill)
37 | .background(.thickMaterial)
38 | .frame(width: 50, height: 50)
39 | .clipShape(RoundedRectangle(cornerRadius: 8))
40 | }
41 |
42 | VStack(alignment: .leading) {
43 | Text(iconDescription)
44 | }
45 | }
46 | }
47 | }
48 | }
49 |
50 | #Preview {
51 | List {
52 | AppIconButton(iconDescription: .constant("Default"), iconName: .constant("AppIcon"), isPresenting: .constant(true))
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/Meshtastic/Views/Helpers/Compact Widgets/WindCompactWidget.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WindCompactWidget.swift
3 | // Meshtastic
4 | //
5 | // Created by Jake Bordens on 3/14/25.
6 | //
7 | import SwiftUI
8 |
9 | struct WindCompactWidget: View {
10 | let speed: String
11 | let gust: String?
12 | let direction: String?
13 |
14 | var body: some View {
15 | let hasGust = ((gust ?? "").isEmpty == false)
16 | VStack(alignment: .leading) {
17 | Label { Text("Wind").textCase(.uppercase) } icon: { Image(systemName: "wind").foregroundColor(.accentColor) }
18 | if let direction {
19 | Text("\(direction)")
20 | .font(!hasGust ? .callout : .caption)
21 | .padding(.bottom, 10)
22 | }
23 | Text(speed)
24 | .font(.system(size: 35))
25 | if let gust, !gust.isEmpty {
26 | Text("Gusts \(gust)")
27 | }
28 | }
29 | .frame(minWidth: 100, idealWidth: 125, maxWidth: 150, minHeight: 120, idealHeight: 130, maxHeight: 140)
30 | .padding()
31 | .background(.tertiary, in: RoundedRectangle(cornerRadius: 20, style: .continuous))
32 | }
33 | }
34 |
35 | #Preview {
36 | let gridItemLayout = Array(repeating: GridItem(.flexible(), spacing: 10), count: 2)
37 | Form {
38 | LazyVGrid(columns: gridItemLayout) {
39 | WindCompactWidget(speed: "12 mph", gust: "15 mph", direction: "SW")
40 | WindCompactWidget(speed: "12 mph", gust: nil, direction: "SW")
41 | WindCompactWidget(speed: "12 mph", gust: "15 mph", direction: nil)
42 | WindCompactWidget(speed: "12 mph", gust: nil, direction: nil)
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/Meshtastic/Extensions/CoreData/MQTTConfigEntityExtension.swift:
--------------------------------------------------------------------------------
1 | import CoreData
2 | import MeshtasticProtobufs
3 |
4 | extension MQTTConfigEntity {
5 | convenience init(
6 | context: NSManagedObjectContext,
7 | config: ModuleConfig.MQTTConfig
8 | ) {
9 | self.init(context: context)
10 | self.enabled = config.enabled
11 | self.proxyToClientEnabled = config.proxyToClientEnabled
12 | self.address = config.address
13 | self.username = config.username
14 | self.password = config.password
15 | self.root = config.root
16 | self.encryptionEnabled = config.encryptionEnabled
17 | self.jsonEnabled = config.jsonEnabled
18 | self.tlsEnabled = config.tlsEnabled
19 | self.mapReportingEnabled = config.mapReportingEnabled
20 | self.mapPositionPrecision = Int32(config.mapReportSettings.positionPrecision)
21 | self.mapPublishIntervalSecs = Int32(config.mapReportSettings.publishIntervalSecs)
22 | }
23 |
24 | func update(with config: ModuleConfig.MQTTConfig) {
25 | enabled = config.enabled
26 | proxyToClientEnabled = config.proxyToClientEnabled
27 | address = config.address
28 | username = config.username
29 | password = config.password
30 | root = config.root
31 | encryptionEnabled = config.encryptionEnabled
32 | jsonEnabled = config.jsonEnabled
33 | tlsEnabled = config.tlsEnabled
34 | mapReportingEnabled = config.mapReportingEnabled
35 | mapPositionPrecision = Int32(config.mapReportSettings.positionPrecision)
36 | mapPublishIntervalSecs = Int32(config.mapReportSettings.publishIntervalSecs)
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Meshtastic/Router/NavigationState.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | // MARK: Messages
4 |
5 | enum MessagesNavigationState: Hashable {
6 | case channels(
7 | channelId: Int32? = nil,
8 | messageId: Int64? = nil
9 | )
10 | case directMessages(
11 | userNum: Int64? = nil,
12 | messageId: Int64? = nil
13 | )
14 | }
15 |
16 | // MARK: Map
17 |
18 | enum MapNavigationState: Hashable {
19 | case selectedNode(Int64)
20 | case waypoint(Int64)
21 | }
22 |
23 | // MARK: Settings
24 |
25 | enum SettingsNavigationState: String {
26 | case about
27 | case appSettings
28 | case routes
29 | case routeRecorder
30 | case lora
31 | case channels
32 | case shareQRCode
33 | case user
34 | case bluetooth
35 | case device
36 | case display
37 | case network
38 | case position
39 | case power
40 | case ambientLighting
41 | case cannedMessages
42 | case detectionSensor
43 | case externalNotification
44 | case mqtt
45 | case rangeTest
46 | case paxCounter
47 | case ringtone
48 | case serial
49 | case security
50 | case storeAndForward
51 | case telemetry
52 | case debugLogs
53 | case appFiles
54 | case firmwareUpdates
55 | }
56 |
57 | struct NavigationState: Hashable {
58 | enum Tab: String, Hashable {
59 | case messages
60 | case connect
61 | case nodes
62 | case map
63 | case settings
64 | }
65 |
66 | var selectedTab: Tab = .connect
67 | var messages: MessagesNavigationState?
68 | var nodeListSelectedNodeNum: Int64?
69 | var map: MapNavigationState?
70 | var settings: SettingsNavigationState?
71 | }
72 |
--------------------------------------------------------------------------------
/.swiftlint-precommit.yml:
--------------------------------------------------------------------------------
1 | # Exclude automatically generated Swift files
2 | excluded:
3 | - MeshtasticProtobufs
4 |
5 | line_length: 400
6 |
7 | type_name:
8 | min_length: 1
9 | max_length:
10 | warning: 60
11 | error: 70
12 | excluded: iPhone # excluded via string
13 | allowed_symbols: ["_"] # these are allowed in type names
14 | identifier_name:
15 | min_length: 1
16 | max_length:
17 | warning: 60
18 | allowed_symbols: ["_"] # these are allowed in type names
19 |
20 | # TODO: should review
21 | force_try:
22 | severity: warning # explicitly
23 |
24 | # TODO: should review
25 | file_length:
26 | warning: 3500
27 | error: 4000
28 |
29 | # TODO: should review
30 | cyclomatic_complexity:
31 | warning: 70
32 | error: 80
33 | ignores_case_statements: true
34 |
35 | # TODO: should review
36 | function_body_length:
37 | warning: 200
38 |
39 | # TODO: should review
40 | type_body_length:
41 | warning: 400
42 |
43 | # TODO: should review
44 | disabled_rules: # rule identifiers to exclude from running
45 | - operator_whitespace
46 | - multiple_closures_with_trailing_closure
47 | - todo
48 |
49 | # TODO: should review
50 | nesting:
51 | type_level:
52 | warning: 3
53 |
54 | custom_rules:
55 | disable_print:
56 | included: ".*\\.swift"
57 | name: "Disable `print()`"
58 | regex: "((\\bprint)|(Swift\\.print))\\s*\\("
59 | message: "Consider using a dedicated log message or the Xcode debugger instead of using `print`. ex. logger.debug(...)"
60 | severity: warning
61 |
--------------------------------------------------------------------------------
/Meshtastic/AppIntents/MessageNodeIntent.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MessageNodeIntent.swift
3 | // Meshtastic
4 | //
5 | // Created by Benjamin Faershtein on 11/9/24.
6 | //
7 |
8 | import Foundation
9 | import AppIntents
10 |
11 | struct MessageNodeIntent: AppIntent {
12 | static var title: LocalizedStringResource = "Send a Direct Message"
13 |
14 | static var description: IntentDescription = "Send a message to a certain meshtastic node"
15 |
16 | @Parameter(title: "Message")
17 | var messageContent: String
18 |
19 | @Parameter(title: "Node Number")
20 | var nodeNumber: Int
21 |
22 | static var parameterSummary: some ParameterSummary {
23 | Summary("Send \(\.$messageContent) to \(\.$nodeNumber)")
24 | }
25 | func perform() async throws -> some IntentResult {
26 | if await !AccessoryManager.shared.isConnected {
27 | throw AppIntentErrors.AppIntentError.notConnected
28 | }
29 |
30 | // Convert messageContent to data and check its length
31 | guard let messageData = messageContent.data(using: .utf8) else {
32 | throw AppIntentErrors.AppIntentError.message("Failed to encode message content")
33 | }
34 |
35 | if messageData.count > 200 {
36 | throw $messageContent.needsValueError("Message content exceeds 200 bytes.")
37 | }
38 |
39 | do {
40 | try await AccessoryManager.shared.sendMessage(message: messageContent, toUserNum: Int64(nodeNumber), channel: 0, isEmoji: false, replyID: 0)
41 | } catch {
42 | throw AppIntentErrors.AppIntentError.message("Failed to send message")
43 | }
44 |
45 | return .result()
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/.swiftlint.yml:
--------------------------------------------------------------------------------
1 | # Exclude automatically generated Swift files
2 | excluded:
3 | - MeshtasticProtobufs
4 |
5 | line_length: 400
6 |
7 | type_name:
8 | min_length: 1
9 | max_length:
10 | warning: 60
11 | error: 70
12 | excluded: iPhone # excluded via string
13 | allowed_symbols: ["_"] # these are allowed in type names
14 | identifier_name:
15 | min_length: 1
16 | max_length:
17 | warning: 60
18 | allowed_symbols: ["_"] # these are allowed in type names
19 |
20 | # TODO: should review
21 | force_try:
22 | severity: warning # explicitly
23 |
24 | # TODO: should review
25 | file_length:
26 | warning: 3500
27 | error: 4000
28 |
29 | # TODO: should review
30 | cyclomatic_complexity:
31 | warning: 60
32 | error: 70
33 | ignores_case_statements: true
34 |
35 | # TODO: should review
36 | function_body_length:
37 | warning: 200
38 |
39 | # TODO: should review
40 | type_body_length:
41 | warning: 400
42 |
43 | # TODO: should review
44 | disabled_rules: # rule identifiers to exclude from running
45 | - operator_whitespace
46 | - multiple_closures_with_trailing_closure
47 | - todo
48 | - trailing_whitespace
49 |
50 | # TODO: should review
51 | nesting:
52 | type_level:
53 | warning: 3
54 |
55 | custom_rules:
56 | disable_print:
57 | included: ".*\\.swift"
58 | name: "Disable `print()`"
59 | regex: "((\\bprint)|(Swift\\.print))\\s*\\("
60 | message: "Consider using a dedicated log message or the Xcode debugger instead of using `print`. ex. logger.debug(...)"
61 | severity: warning
62 |
--------------------------------------------------------------------------------
/Meshtastic/Views/Nodes/Helpers/Actions/IgnoreNodeButton.swift:
--------------------------------------------------------------------------------
1 | import CoreData
2 | import OSLog
3 | import SwiftUI
4 |
5 | struct IgnoreNodeButton: View {
6 | @Environment(\.managedObjectContext) var context
7 | @EnvironmentObject var accessoryManager: AccessoryManager
8 |
9 | @ObservedObject
10 | var node: NodeInfoEntity
11 |
12 | var body: some View {
13 | Button(role: .destructive) {
14 | guard let connectedNodeNum = accessoryManager.activeDeviceNum else { return }
15 | Task {
16 | do {
17 | if node.ignored {
18 | try await accessoryManager.removeIgnoredNode(
19 | node: node,
20 | connectedNodeNum: Int64(connectedNodeNum)
21 | )
22 | } else {
23 | try await accessoryManager.setIgnoredNode(
24 | node: node,
25 | connectedNodeNum: Int64(connectedNodeNum)
26 | )
27 | }
28 | Task {@MainActor in
29 | // CoreData Stuff
30 | node.ignored = !node.ignored
31 | do {
32 | try context.save()
33 | } catch {
34 | context.rollback()
35 | Logger.data.error("Save Ignored Node Error")
36 | }
37 | }
38 | Logger.data.debug("Ignored a node")
39 | } catch {
40 | Logger.mesh.error("Faile to Ignored/Un-ignore a node")
41 | }
42 | }
43 | } label: {
44 | Label {
45 | Text(node.ignored ? "Remove from ignored" : "Ignore Node")
46 | } icon: {
47 | Image(systemName: node.ignored ? "minus.circle.fill" : "minus.circle")
48 | .symbolRenderingMode(.multicolor)
49 | }
50 | // Accessibility: Label for VoiceOver
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/Meshtastic/Enums/DetectionSensorEnums.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DetectionSensorEnums.swift
3 | // Meshtastic
4 | //
5 | // Copyright(c) Garth Vander Houwen 10/11/24.
6 | //
7 | import MeshtasticProtobufs
8 |
9 | enum TriggerTypes: Int, CaseIterable, Identifiable {
10 |
11 | case logicLow = 0
12 | case logicHigh = 1
13 | case fallingEdge = 2
14 | case risingEdge = 3
15 | case eitherEdgeActiveLow = 4
16 | case eitherEdgeActiveHigh = 5
17 |
18 | var id: Int { self.rawValue }
19 |
20 | var name: String {
21 | switch self {
22 | case .logicLow:
23 | return "Low"
24 | case .logicHigh:
25 | return "High"
26 | case .fallingEdge:
27 | return "Falling Edge"
28 | case .risingEdge:
29 | return "Rising Edge"
30 | case .eitherEdgeActiveLow:
31 | return "Either Edge Low"
32 | case .eitherEdgeActiveHigh:
33 | return "Either Edge Hight"
34 | }
35 | }
36 | func protoEnumValue() -> ModuleConfig.DetectionSensorConfig.TriggerType {
37 |
38 | switch self {
39 | case .logicLow:
40 | return ModuleConfig.DetectionSensorConfig.TriggerType.logicLow
41 | case .logicHigh:
42 | return ModuleConfig.DetectionSensorConfig.TriggerType.logicHigh
43 | case .fallingEdge:
44 | return ModuleConfig.DetectionSensorConfig.TriggerType.fallingEdge
45 | case .risingEdge:
46 | return ModuleConfig.DetectionSensorConfig.TriggerType.risingEdge
47 | case .eitherEdgeActiveLow:
48 | return ModuleConfig.DetectionSensorConfig.TriggerType.eitherEdgeActiveLow
49 | case .eitherEdgeActiveHigh:
50 | return ModuleConfig.DetectionSensorConfig.TriggerType.eitherEdgeActiveHigh
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/Meshtastic/Helpers/EmojiOnlyTextField.swift:
--------------------------------------------------------------------------------
1 | //
2 | // EmojiKeyboard.swift
3 | // Meshtastic
4 | //
5 | // Copyright(c) Garth Vander Houwen 1/10/23.
6 | //
7 | import SwiftUI
8 |
9 | class SwiftUIEmojiTextField: UITextField {
10 |
11 | func setEmoji() {
12 | _ = self.textInputMode
13 | }
14 |
15 | override var textInputContextIdentifier: String? {
16 | return ""
17 | }
18 |
19 | override var textInputMode: UITextInputMode? {
20 | for mode in UITextInputMode.activeInputModes where mode.primaryLanguage == "emoji" {
21 | self.keyboardType = .default // do not remove this
22 | return mode
23 | }
24 | return nil
25 | }
26 | }
27 |
28 | struct EmojiOnlyTextField: UIViewRepresentable {
29 | @Binding var text: String
30 | var placeholder: String = ""
31 |
32 | func makeUIView(context: Context) -> SwiftUIEmojiTextField {
33 | let emojiTextField = SwiftUIEmojiTextField()
34 | emojiTextField.placeholder = placeholder
35 | emojiTextField.text = text
36 | emojiTextField.delegate = context.coordinator
37 | return emojiTextField
38 | }
39 |
40 | func updateUIView(_ uiView: SwiftUIEmojiTextField, context: Context) {
41 | uiView.text = text
42 | }
43 |
44 | func makeCoordinator() -> Coordinator {
45 | Coordinator(parent: self)
46 | }
47 |
48 | class Coordinator: NSObject, UITextFieldDelegate {
49 | var parent: EmojiOnlyTextField
50 | init(parent: EmojiOnlyTextField) {
51 | self.parent = parent
52 | }
53 | func textFieldDidChangeSelection(_ textField: UITextField) {
54 | DispatchQueue.main.async { [weak self] in
55 | self?.parent.text = textField.text ?? ""
56 | }
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/Meshtastic/Accessory/Helpers/ManualConnectionList.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ManualConnectionList.swift
3 | // Meshtastic
4 | //
5 | // Created by jake on 10/26/25.
6 | //
7 |
8 | import Foundation
9 |
10 | // Maintains an observable list of devices that's backed by UserDefaults
11 | public class ManualConnectionList: ObservableObject {
12 | static let shared = ManualConnectionList()
13 |
14 | @Published private var _list: [Device]
15 |
16 | private init() {
17 | _list = UserDefaults.manualConnections
18 | }
19 |
20 | var connectionsList: [Device] {
21 | get {
22 | return _list
23 | }
24 | }
25 |
26 | func insert(device: Device) {
27 | // Don't insert if already there
28 | guard !_list.contains(where: {$0.id == device.id}) else {
29 | return
30 | }
31 |
32 | // Add the new entry
33 | var list = _list
34 | list.append(device)
35 | _list = list
36 | UserDefaults.manualConnections = list
37 | }
38 |
39 | func updateDevice(deviceId: UUID, key: WritableKeyPath, value: T) where T: Equatable {
40 | var list = _list
41 | if let deviceIndex = list.firstIndex(where: {$0.id == deviceId}) {
42 | list[deviceIndex][keyPath: key] = value
43 | _list = list
44 | UserDefaults.manualConnections = list
45 | }
46 | }
47 |
48 | func remove(device: Device) {
49 | var list = _list
50 | list.removeAll(where: {$0.id == device.id})
51 | _list = list
52 | UserDefaults.manualConnections = list
53 | }
54 |
55 | func remove(atOffsets: IndexSet) {
56 | var list = _list
57 | list.remove(atOffsets: atOffsets)
58 | _list = list
59 | UserDefaults.manualConnections = list
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/Meshtastic/Views/Settings/Config/ConfigHeader.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 | import CoreData
3 |
4 | struct ConfigHeader: View {
5 | @EnvironmentObject var accessoryManager: AccessoryManager
6 |
7 | let title: String
8 | let config: KeyPath
9 | let node: NodeInfoEntity?
10 | let onAppear: () -> Void
11 |
12 | var body: some View {
13 | if node != nil && node?.metadata == nil && node?.num ?? 0 != accessoryManager.activeDeviceNum ?? 0 {
14 | Text("There has been no response to a request for device metadata via PKC admin for this node.")
15 | .font(.callout)
16 | .foregroundColor(.orange)
17 |
18 | } else if node != nil && node?.num ?? 0 != accessoryManager.activeDeviceNum ?? 0 {
19 | // Let users know what is going on if they are using remote admin and don't have the config yet
20 | let expiration = node?.sessionExpiration ?? Date()
21 | if node?[keyPath: config] == nil || expiration < node?.sessionExpiration ?? Date() {
22 | Text("\(title) config data was requested via PKC admin but no response has been returned from the remote node.")
23 | .font(.callout)
24 | .foregroundColor(.orange)
25 | } else {
26 | Text("Remote administration for: \(node?.user?.longName ?? "Unknown")")
27 | .onFirstAppear(onAppear)
28 | .font(.title3)
29 | }
30 | } else if node != nil && node?.num ?? 0 == accessoryManager.activeDeviceNum ?? -1 {
31 | Text("Configuration for: \(node?.user?.longName ?? "Unknown")")
32 | .onFirstAppear(onAppear)
33 | } else {
34 | Text("Please connect to a radio to configure settings.")
35 | .font(.callout)
36 | .foregroundColor(.orange)
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/Meshtastic/AppIntents/ShortcutsProvider.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ShortcutsProvider.swift
3 | // Meshtastic
4 | //
5 | // Created by Benjamin Faershtein on 8/24/24.
6 | //
7 |
8 | import Foundation
9 | import AppIntents
10 |
11 | struct ShortcutsProvider: AppShortcutsProvider {
12 | static var appShortcuts: [AppShortcut] {
13 | AppShortcut(intent: ShutDownNodeIntent(),
14 | phrases: ["Shut down \(.applicationName) node",
15 | "Shut down my \(.applicationName) node",
16 | "Turn off \(.applicationName) node",
17 | "Power down \(.applicationName) node",
18 | "Deactivate \(.applicationName) node"],
19 | shortTitle: "Shut Down",
20 | systemImageName: "power")
21 |
22 | AppShortcut(intent: RestartNodeIntent(),
23 | phrases: ["Restart \(.applicationName) node",
24 | "Restart my \(.applicationName) node",
25 | "Reboot \(.applicationName) node",
26 | "Reboot my \(.applicationName) node"],
27 | shortTitle: "Restart",
28 | systemImageName: "arrow.circlepath")
29 |
30 | AppShortcut(intent: MessageChannelIntent(),
31 | phrases: ["Message a \(.applicationName) channel",
32 | "Send a \(.applicationName) group message"],
33 | shortTitle: "Group Message",
34 | systemImageName: "message")
35 | AppShortcut(intent: DisconnectNodeIntent(),
36 | phrases: ["Disconnect \(.applicationName) node",
37 | "Disconnect my \(.applicationName) node",
38 | "Disconnect from \(.applicationName)",
39 | "Disconnect \(.applicationName)"],
40 | shortTitle: "Disconnect",
41 | systemImageName: "antenna.radiowaves.left.and.right.slash")
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/Meshtastic/AppIntents/AddContactIntent.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AddContactIntent.swift
3 | // Meshtastic
4 | //
5 | // Created by Benjamin Faershtein on 5/13/25.
6 | //
7 |
8 | import AppIntents
9 | import MeshtasticProtobufs
10 |
11 | struct AddContactIntent: AppIntent {
12 | static let title: LocalizedStringResource = "Add Contact"
13 | static let description: IntentDescription = "Takes a Meshtastic contact URL and saves it to the nodes database"
14 |
15 | @Parameter(title: "Contact URL", description: "The URL for the node to add")
16 | var contactUrl: URL
17 |
18 | // Define the function that performs the main logic
19 | func perform() async throws -> some IntentResult {
20 | // Ensure the BLE Manager is connected
21 | if !(await AccessoryManager.shared.isConnected) {
22 | throw AppIntentErrors.AppIntentError.notConnected
23 | }
24 |
25 | if contactUrl.absoluteString.lowercased().contains("meshtastic.org/v/#") {
26 | let components = self.contactUrl.absoluteString.components(separatedBy: "#")
27 | // Extract contact information from the URL
28 | if let contactData = components.last {
29 | let decodedString = contactData.base64urlToBase64()
30 | if let _ = Data(base64Encoded: decodedString) {
31 | do {
32 | try await AccessoryManager.shared.addContactFromURL(base64UrlString: contactData)
33 | } catch {
34 | throw AppIntentErrors.AppIntentError.message("Failed to add/parse contact data: \(error.localizedDescription)")
35 | }
36 | }
37 | }
38 | // Return a success result
39 | return .result()
40 | } else {
41 | throw AppIntentErrors.AppIntentError.message("The URL is not a valid Meshtastic contact link")
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/Meshtastic/Tips/ChannelTips.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ChannelTips.swift
3 | // Meshtastic
4 | //
5 | // Copyright(c) Garth Vander Houwen 8/31/23.
6 | //
7 | import SwiftUI
8 | import TipKit
9 |
10 | struct ShareChannelsTip: Tip {
11 |
12 | var id: String {
13 | return "tip.channels.share"
14 | }
15 | var title: Text {
16 | Text("Sharing Meshtastic Channels")
17 | }
18 | var message: Text? {
19 | Text("A Meshtastic QR code contains the LoRa config and channel values needed for radios to communicate. You can share a complete channel configuration using the Replace Channels option, if you choose Add Channels your shared channels will be added to the channels on the receiving radio.")
20 | }
21 | var image: Image? {
22 | Image(systemName: "qrcode")
23 | }
24 | }
25 |
26 | struct CreateChannelsTip: Tip {
27 |
28 | var id: String {
29 | return "tip.channels.create"
30 | }
31 | var title: Text {
32 | Text("Manage Channels")
33 | }
34 | var message: Text? {
35 | Text("Most data on your mesh is sent over the primary channel. You can set up secondary channels to create additional messaging groups secured by their own key. [Channel config tips](https://meshtastic.org/docs/configuration/tips/)")
36 | }
37 | var image: Image? {
38 | Image(systemName: "fibrechannel")
39 | }
40 | }
41 |
42 | struct AdminChannelTip: Tip {
43 |
44 | var id: String {
45 | return "tip.channel.admin"
46 | }
47 | var title: Text {
48 | Text("Administration Enabled")
49 | }
50 | var message: Text? {
51 | Text("Select a node from the drop down to manage connected or remote devices.")
52 | }
53 | var image: Image? {
54 | Image(systemName: "fibrechannel")
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature.yml:
--------------------------------------------------------------------------------
1 | name: "🚀 Feature Request"
2 | description: Request a new feature
3 | title: "🚀 [Feature Request]: "
4 | labels: ["enhancement"]
5 | body:
6 | - type: markdown
7 | attributes:
8 | value: |
9 | Thanks for your request this will not gurantee that we will implement it, but it will be reviewed.
10 | - type: dropdown
11 | id: soc
12 | attributes:
13 | label: OS
14 | description: What OS will support your feature?
15 | multiple: true
16 | options:
17 | - iOS
18 | - iPadOS
19 | - macOS
20 | validations:
21 | required: true
22 | - type: textarea
23 | id: body
24 | attributes:
25 | label: Description
26 | description: Please provide details about your enhancement.
27 | validations:
28 | required: true
29 | - type: checkboxes
30 | attributes:
31 | label: Participation
32 | description: (Features without participation go to the backlog.)
33 | options:
34 | - label: I am willing to pay to sponsor this feature.
35 | required: false
36 | - label: I am willing to submit a pull request for this feature.
37 | required: false
38 | - type: textarea
39 | attributes:
40 | label: Additional comments
41 | description: Is there anything else that's important for the team to know?
42 | - type: checkboxes
43 | id: terms
44 | attributes:
45 | label: Code of Conduct
46 | description: By submitting this issue, you agree to follow our [Code of Conduct](https://meshtastic.org/docs/legal/conduct/).
47 | options:
48 | - label: I agree to follow this project's Code of Conduct
49 | required: true
50 |
--------------------------------------------------------------------------------
/Meshtastic/Extensions/CoreData/MyInfoEntityExtension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MyInfoEntityExtension.swift
3 | // Meshtastic
4 | //
5 | // Copyright(c) Garth Vander Houwen 9/3/23.
6 | //
7 |
8 | import Foundation
9 | import CoreData
10 |
11 | extension MyInfoEntity {
12 | var messagePredicate: NSPredicate {
13 | return NSPredicate(format: "toUser == nil AND isEmoji == false")
14 | }
15 |
16 | var messageFetchRequest: NSFetchRequest {
17 | let fetchRequest = MessageEntity.fetchRequest()
18 | fetchRequest.sortDescriptors = [NSSortDescriptor(key: "messageTimestamp", ascending: true)]
19 | fetchRequest.predicate = messagePredicate
20 | return fetchRequest
21 | }
22 |
23 | var messageList: [MessageEntity] {
24 | let context = PersistenceController.shared.container.viewContext
25 | let fetchRequest = messageFetchRequest
26 |
27 | return (try? context.fetch(messageFetchRequest)) ?? [MessageEntity]()
28 | }
29 |
30 | func unreadMessages(context: NSManagedObjectContext) -> Int {
31 | // Returns the count of unread *channel* messages
32 | let fetchRequest = messageFetchRequest
33 | fetchRequest.sortDescriptors = [] // sort is irrelevant.
34 | fetchRequest.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [fetchRequest.predicate!, NSPredicate(format: "read == false")])
35 |
36 | return (try? context.count(for: fetchRequest)) ?? 0
37 | }
38 |
39 | // Backwards-compatible property (uses viewContext)
40 | var unreadMessages: Int { unreadMessages(context: PersistenceController.shared.container.viewContext) }
41 |
42 | var hasAdmin: Bool {
43 | let adminChannel = channels?.filter { ($0 as AnyObject).name?.lowercased() == "admin" }
44 | return adminChannel?.count ?? 0 > 0
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/RELEASING.md:
--------------------------------------------------------------------------------
1 | # Releasing Meshtastic
2 |
3 | This document outlines the process for preparing and making a release for Meshtastic.
4 |
5 | ## Table of Contents
6 |
7 | 1. [Branching Strategy](#branching-strategy)
8 | 2. [Preparing for a Release](#preparing-for-a-release)
9 | 3. [Creating a Release Branch](#creating-a-release-branch)
10 | 4. [Finalizing the Release](#finalizing-the-release)
11 |
12 | ## Branching Strategy
13 |
14 | - **Main Branch (`main`)**: This is the main development branch where daily development occurs.
15 | - **Release Branch (`X.YY.ZZ-release`)**: This branch is created from `main` for preparing a specific release version.
16 |
17 | ## Preparing for a Release
18 |
19 | 1. Ensure all desired features and fixes are merged into the `main` branch.
20 | 2. Update the version number in the relevant files.
21 | 3. Update the project documentation to reflect the upcoming release.
22 |
23 | ## Creating a Release Branch
24 |
25 | 1. Create a release branch from `main`.
26 | ```sh
27 | ./scripts/create-release-branch.sh
28 | ```
29 |
30 | ## Finalizing the Release
31 |
32 | 1. Perform final testing and quality checks on the `X.YY.ZZ-release` branch.
33 | a. If any hotfix changes are required, merge those changes into `X.YY.ZZ-release`.
34 | b. After merging these changes into the release branch, cherry-pick the changes onto `main`.
35 | 2. Once everything is ready, create a final tag for the release:
36 | ```sh
37 | git tag -a X.YY.ZZ -m "Release version X.Y.Z"
38 | git push origin X.YY.ZZ
39 | ```
40 |
41 | Thank you for following the release process and helping to ensure the stability and quality of Meshtastic!
42 |
43 | ---
44 |
45 | Feel free to modify this template to better fit your project's specific needs.
--------------------------------------------------------------------------------