diff --git a/Source/Meadow.Samples.sln b/Source/Meadow.Samples.sln index 3c40401c..a4839ada 100644 --- a/Source/Meadow.Samples.sln +++ b/Source/Meadow.Samples.sln @@ -435,6 +435,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DesktopModbusClient", "Modb EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MeadowModbusServer", "Modbus\MeadowModbusServer\MeadowModbusServer.csproj", "{F6BA3311-6502-4A7E-89A9-D943245DCA00}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CellularSample", "ProjectLab\CellularSample\CellularSample.csproj", "{F7301799-6DAF-4A80-A034-0267DF5426AE}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -1916,6 +1918,15 @@ Global {F6BA3311-6502-4A7E-89A9-D943245DCA00}.Simulation|Any CPU.ActiveCfg = Debug|Any CPU {F6BA3311-6502-4A7E-89A9-D943245DCA00}.Simulation|Any CPU.Build.0 = Debug|Any CPU {F6BA3311-6502-4A7E-89A9-D943245DCA00}.Simulation|Any CPU.Deploy.0 = Debug|Any CPU + {F7301799-6DAF-4A80-A034-0267DF5426AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F7301799-6DAF-4A80-A034-0267DF5426AE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F7301799-6DAF-4A80-A034-0267DF5426AE}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {F7301799-6DAF-4A80-A034-0267DF5426AE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F7301799-6DAF-4A80-A034-0267DF5426AE}.Release|Any CPU.Build.0 = Release|Any CPU + {F7301799-6DAF-4A80-A034-0267DF5426AE}.Release|Any CPU.Deploy.0 = Release|Any CPU + {F7301799-6DAF-4A80-A034-0267DF5426AE}.Simulation|Any CPU.ActiveCfg = Debug|Any CPU + {F7301799-6DAF-4A80-A034-0267DF5426AE}.Simulation|Any CPU.Build.0 = Debug|Any CPU + {F7301799-6DAF-4A80-A034-0267DF5426AE}.Simulation|Any CPU.Deploy.0 = Debug|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -2123,6 +2134,7 @@ Global {029D7D51-257B-4CA9-80AC-F8B0238B55DF} = {3B2ADC8C-1ACB-49F3-8A3C-4F453FF9FE75} {4A4C3198-BF16-47DB-BA01-25DDCF77B2F5} = {3B2ADC8C-1ACB-49F3-8A3C-4F453FF9FE75} {F6BA3311-6502-4A7E-89A9-D943245DCA00} = {3B2ADC8C-1ACB-49F3-8A3C-4F453FF9FE75} + {F7301799-6DAF-4A80-A034-0267DF5426AE} = {DA0CC626-D072-457F-89B7-C22427D4C775} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {E3F002EA-1A25-487F-9A5D-93D1E7EC6E31} diff --git a/Source/ProjectLab/CellularSample/.vscode/launch.json b/Source/ProjectLab/CellularSample/.vscode/launch.json new file mode 100644 index 00000000..43067bdc --- /dev/null +++ b/Source/ProjectLab/CellularSample/.vscode/launch.json @@ -0,0 +1,14 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Deploy", + "type": "meadow", + "request": "launch", + "preLaunchTask": "meadow: Build" + } + ] +} \ No newline at end of file diff --git a/Source/ProjectLab/CellularSample/Assets/img-cell-0.bmp b/Source/ProjectLab/CellularSample/Assets/img-cell-0.bmp new file mode 100644 index 00000000..94a07a65 Binary files /dev/null and b/Source/ProjectLab/CellularSample/Assets/img-cell-0.bmp differ diff --git a/Source/ProjectLab/CellularSample/Assets/img-cell-1.bmp b/Source/ProjectLab/CellularSample/Assets/img-cell-1.bmp new file mode 100644 index 00000000..28381671 Binary files /dev/null and b/Source/ProjectLab/CellularSample/Assets/img-cell-1.bmp differ diff --git a/Source/ProjectLab/CellularSample/Assets/img-cell-2.bmp b/Source/ProjectLab/CellularSample/Assets/img-cell-2.bmp new file mode 100644 index 00000000..66e655aa Binary files /dev/null and b/Source/ProjectLab/CellularSample/Assets/img-cell-2.bmp differ diff --git a/Source/ProjectLab/CellularSample/Assets/img-cell-3.bmp b/Source/ProjectLab/CellularSample/Assets/img-cell-3.bmp new file mode 100644 index 00000000..06339e2b Binary files /dev/null and b/Source/ProjectLab/CellularSample/Assets/img-cell-3.bmp differ diff --git a/Source/ProjectLab/CellularSample/Assets/img-cell-4.bmp b/Source/ProjectLab/CellularSample/Assets/img-cell-4.bmp new file mode 100644 index 00000000..e521dce3 Binary files /dev/null and b/Source/ProjectLab/CellularSample/Assets/img-cell-4.bmp differ diff --git a/Source/ProjectLab/CellularSample/CellularSample.csproj b/Source/ProjectLab/CellularSample/CellularSample.csproj new file mode 100644 index 00000000..53748901 --- /dev/null +++ b/Source/ProjectLab/CellularSample/CellularSample.csproj @@ -0,0 +1,41 @@ + + + netstandard2.1 + true + Library + App + 10.0 + + + + + + + + + + + + + + + + + + + + + + Always + + + Always + + + Always + + + Always + + + \ No newline at end of file diff --git a/Source/ProjectLab/CellularSample/DisplayController.cs b/Source/ProjectLab/CellularSample/DisplayController.cs new file mode 100644 index 00000000..337d8a01 --- /dev/null +++ b/Source/ProjectLab/CellularSample/DisplayController.cs @@ -0,0 +1,94 @@ +using Meadow; +using Meadow.Foundation.Graphics; +using Meadow.Foundation.Graphics.MicroLayout; +using Meadow.Peripherals.Displays; + +namespace CellularSample; + +public class DisplayController +{ + DisplayScreen screen; + + private readonly Image imgSignal0Bar = Image.LoadFromResource("CellularSample.Assets.img-cell-0.bmp"); + private readonly Image imgSignal1Bar = Image.LoadFromResource("CellularSample.Assets.img-cell-1.bmp"); + private readonly Image imgSignal2Bar = Image.LoadFromResource("CellularSample.Assets.img-cell-2.bmp"); + private readonly Image imgSignal3Bar = Image.LoadFromResource("CellularSample.Assets.img-cell-3.bmp"); + private readonly Image imgSignal4Bar = Image.LoadFromResource("CellularSample.Assets.img-cell-4.bmp"); + + private Label status; + private Label ipAddress; + private Picture signalBars; + + public DisplayController(IPixelDisplay _display) + { + screen = new DisplayScreen(_display, RotationType._270Degrees) + { + BackgroundColor = Color.FromHex("14607F") + }; + + screen.Controls.Add(new Box(5, 5, screen.Width - 10, screen.Height - 10) + { + IsFilled = false, + ForeColor = Color.FromHex("F9E000") + }); + + signalBars = new Picture(105, 33, 110, 103, imgSignal0Bar); + screen.Controls.Add(signalBars); + + status = new Label(60, 149, 200, 24) + { + Text = "OFFLINE...", + TextColor = Color.FromHex("F9E000"), + Font = new Font16x24(), + HorizontalAlignment = HorizontalAlignment.Center, + }; + screen.Controls.Add(status); + + ipAddress = new Label(35, 183, 250, 28) + { + Text = "---.---.---.---", + TextColor = Color.FromHex("14607F"), + Font = new Font16x24(), + BackColor = Color.FromHex("F9E000"), + HorizontalAlignment = HorizontalAlignment.Center, + VerticalAlignment = VerticalAlignment.Bottom, + }; + screen.Controls.Add(ipAddress); + } + + public void UpdateSignalBar(int strength) + { + Resolver.Log.Info("Signal Strength: " + strength); + + switch (strength) + { + case int n when (n >= -70 && n <= -20): + signalBars.Image = imgSignal4Bar; + break; + case int n when (n >= -80 && n <= -71): + signalBars.Image = imgSignal3Bar; + break; + case int n when (n >= -90 && n <= -81): + signalBars.Image = imgSignal2Bar; + break; + case int n when (n >= -100 && n <= -91): + signalBars.Image = imgSignal1Bar; + break; + default: + signalBars.Image = imgSignal0Bar; + break; + } + } + + public void UpdateStatus(string status) + { + this.status.Text = status; + } + + public void UpdateIpAddress(string ipAddress) + { + this.ipAddress.Text = string.IsNullOrEmpty(ipAddress) + ? "---.---.---.---" + : ipAddress; + } +} \ No newline at end of file diff --git a/Source/ProjectLab/CellularSample/MeadowApp.cs b/Source/ProjectLab/CellularSample/MeadowApp.cs new file mode 100644 index 00000000..21e6e112 --- /dev/null +++ b/Source/ProjectLab/CellularSample/MeadowApp.cs @@ -0,0 +1,145 @@ +using Meadow; +using Meadow.Devices; +using Meadow.Foundation; +using Meadow.Hardware; +using System; +using System.Diagnostics; +using System.Net.Http; +using System.Threading.Tasks; + +namespace CellularSample; + +public class MeadowApp : ProjectLabCoreComputeApp +{ + private DisplayController? displayController; + + public override Task Initialize() + { + Resolver.Log.Info("Initialize..."); + + var cell = Hardware.ComputeModule.NetworkAdapters.Primary(); + cell.NetworkConnected += CellAdapterNetworkConnected; + cell.NetworkConnecting += CellNetworkConnecting; + cell.NetworkDisconnected += CellAdapterNetworkDisconnected; + cell.NetworkConnectFailed += CellNetworkConnectFailed; + + Resolver.Log.Info($"Running on ProjectLab Hardware {Hardware.RevisionString}"); + + if (Hardware.RgbLed is { } rgbLed) + { + rgbLed.SetColor(Color.Blue); + } + + if (Hardware.Display is { } display) + { + displayController = new DisplayController(display); + + displayController.UpdateSignalBar(cell.GetSignalQuality()); + displayController.UpdateStatus(cell.IsConnected ? "CONNECTED" : "DISCONNECTED"); + displayController.UpdateIpAddress(cell.IsConnected ? cell.IpAddress.ToString() : "---.---.---.---"); + } + + return Task.CompletedTask; + } + + private async void CellAdapterNetworkConnected(INetworkAdapter networkAdapter, NetworkConnectionEventArgs e) + { + var cell = networkAdapter as ICellNetworkAdapter; + + if (cell != null) + { + Resolver.Log.Info("Cell CSQ at the time of connection (dbm): " + cell.Csq); + Resolver.Log.Info("Cell IMEI: " + cell.Imei); + + displayController.UpdateStatus("CONNECTED"); + displayController.UpdateIpAddress(cell.IpAddress.ToString()); + displayController.UpdateSignalBar(cell.Csq); + + await GetWebPageViaHttpClient("https://postman-echo.com/get?fool=bar1&foo2=bar2"); + } + } + + private void CellNetworkConnecting(INetworkAdapter sender) + { + displayController.UpdateStatus("CONNECTING"); + displayController.UpdateIpAddress("---.---.---.---"); + displayController.UpdateSignalBar(-9999); + } + + private void CellAdapterNetworkDisconnected(INetworkAdapter sender, NetworkDisconnectionEventArgs args) + { + displayController.UpdateStatus("DISCONNECTED"); + displayController.UpdateIpAddress("---.---.---.---"); + displayController.UpdateSignalBar(-9999); + } + + private void CellNetworkConnectFailed(INetworkAdapter sender) + { + displayController.UpdateStatus("RECONNECT FAILED"); + displayController.UpdateIpAddress("---.---.---.---"); + displayController.UpdateSignalBar(-9999); + } + + private async Task GetWebPageViaHttpClient(string uri) + { + Resolver.Log.Info($"Requesting {uri} - {DateTime.Now}"); + Stopwatch stopwatch = new Stopwatch(); + stopwatch.Start(); + + using (HttpClient client = new HttpClient()) + { + // In weak signal connections and/or large download scenarios, it's recommended to increase the client timeout + client.Timeout = TimeSpan.FromMinutes(5); + using (HttpResponseMessage response = await client.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead)) + { + try + { + response.EnsureSuccessStatusCode(); + + var contentLength = response.Content.Headers.ContentLength ?? -1L; + var progress = new Progress(totalBytes => + { + Resolver.Log.Info($"{totalBytes} bytes downloaded ({(double)totalBytes / contentLength:P2})"); + }); + + using (var stream = await response.Content.ReadAsStreamAsync()) + { + var buffer = new byte[4096]; + long totalBytesRead = 0; + int bytesRead; + + while ((bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length)) > 0) + { + totalBytesRead += bytesRead; + ((IProgress)progress).Report(totalBytesRead); + } + } + + stopwatch.Stop(); + Resolver.Log.Info($"Download complete. Time taken: {stopwatch.Elapsed.TotalSeconds:F2} seconds"); + } + catch (TaskCanceledException) + { + Resolver.Log.Info("Request timed out."); + } + catch (Exception e) + { + Resolver.Log.Info($"Request went sideways: {e.Message}"); + } + } + } + } + + public override Task Run() + { + Resolver.Log.Info("Run..."); + + if (Hardware?.RgbLed is { } rgbLed) + { + Resolver.Log.Info("starting blink"); + _ = rgbLed.StartBlink(WildernessLabsColors.PearGreen, TimeSpan.FromMilliseconds(500), TimeSpan.FromMilliseconds(2000), 0.5f); + } + + return Task.CompletedTask; + } +} \ No newline at end of file diff --git a/Source/ProjectLab/CellularSample/app.build.yaml b/Source/ProjectLab/CellularSample/app.build.yaml new file mode 100644 index 00000000..7b9be619 --- /dev/null +++ b/Source/ProjectLab/CellularSample/app.build.yaml @@ -0,0 +1,2 @@ +Deploy: + NoLink: [ ProjectLab ] \ No newline at end of file diff --git a/Source/ProjectLab/CellularSample/app.config.yaml b/Source/ProjectLab/CellularSample/app.config.yaml new file mode 100644 index 00000000..137b4dc5 --- /dev/null +++ b/Source/ProjectLab/CellularSample/app.config.yaml @@ -0,0 +1,36 @@ +# Uncomment additional options as needed. +# To learn more about these config options, including custom application configuration settings, check out the Application Settings Configuration documentation. +# http://developer.wildernesslabs.co/Meadow/Meadow.OS/Configuration/Application_Settings_Configuration/ + +# App lifecycle configuration. +Lifecycle: + + # Control whether Meadow will restart when an unhandled app exception occurs. Combine with Lifecycle > AppFailureRestartDelaySeconds to control restart timing. + RestartOnAppFailure: true + + # When app set to restart automatically on app failure, +# AppFailureRestartDelaySeconds: 15 + +# Logging configuration. +Logging: + + # Adjust the level of logging detail. + LogLevel: + + # Trace, Debug, Information, Warning, or Error + Default: Trace + +# Meadow.Cloud configuration. +#MeadowCloud: + + # Enable Logging, Events, Command + Control +# Enabled: false + + # Enable Over-the-air Updates +# EnableUpdates: false + + # Enable Health Metrics +# EnableHealthMetrics: false + + # How often to send metrics to Meadow.Cloud +# HealthMetricsIntervalMinutes: 60 \ No newline at end of file diff --git a/Source/ProjectLab/CellularSample/cell.config.yaml b/Source/ProjectLab/CellularSample/cell.config.yaml new file mode 100644 index 00000000..25657f7a --- /dev/null +++ b/Source/ProjectLab/CellularSample/cell.config.yaml @@ -0,0 +1,5 @@ +Settings: + APN: teal # (required) Access Point Name + Module: BG95M3 # (required) Module model (BG770A, BG95M3 or M95) + Interface: COM1 # (required) Serial interface (COM1 (default), COM4 or COM6) + EnablePin: A3 # (required) Enable MCU pin to turn the module on/off. \ No newline at end of file diff --git a/Source/ProjectLab/CellularSample/meadow.config.yaml b/Source/ProjectLab/CellularSample/meadow.config.yaml new file mode 100644 index 00000000..afebdca7 --- /dev/null +++ b/Source/ProjectLab/CellularSample/meadow.config.yaml @@ -0,0 +1,39 @@ +# Acceptable values for true: true, 1, yes +# Acceptable values for false: false, 0, no + +# Main Device Config +Device: + + # Name of the device on the network. + Name: MeadowDevice + + # Corresponding MCU pin names for the reserved pins + # (COMX_RX pin, COM_TX pin, ENABLE pin) + # Examples: + # Using mikroBUS 1 on Project Lab v3 with Quectel BG95-M3 (Meadow AN pin as enable pin), + # reserve the following: + ReservedPins: B15;B14;A3 + +# Network configuration. +Network: + + # Which interface should be used? + DefaultInterface: Cell + + # Automatically attempt to get the time at startup? + GetNetworkTimeAtStartup: true + + # Time synchronization period in seconds. + NtpRefreshPeriod: 600 + + # Name of the NTP servers. + NtpServers: + - 0.pool.ntp.org + - 1.pool.ntp.org + - 2.pool.ntp.org + - 3.pool.ntp.org + + # IP addresses of the DNS servers. + DnsServers: + - 1.1.1.1 + - 8.8.8.8 \ No newline at end of file