├── LICENSE
├── README.md
├── app.js
├── assets
├── AssetManifest.json
├── FontManifest.json
├── NOTICES
├── fonts
│ └── MaterialIcons-Regular.otf
├── img
│ ├── ThatProject_Logo.png
│ └── ThatProject_Rectangle.png
└── packages
│ ├── cupertino_icons
│ └── assets
│ │ └── CupertinoIcons.ttf
│ └── line_icons
│ └── lib
│ └── assets
│ └── fonts
│ └── LineIcons.ttf
├── favicon.png
├── flutter_service_worker.js
├── icons
├── Icon-192.png
├── Icon-512.png
├── Icon-maskable-192.png
└── Icon-maskable-512.png
├── index.html
├── main.dart.js
├── manifest.json
├── misc
└── demo.gif
├── styles.css
└── version.json
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Eric
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Map Viewer for MCU with LoRa & GPS Data
2 |
3 | * [ThatProject Channel](https://youtube.com/ThatProject)
4 |
5 | LoRa module has caught the attention of many people for a number of reasons. Being able to send data to a device that is far away is very interesting. This viewer I made is to display the GPS data sent to the LoRa module on a map. If you pass the data according to the JSON format I predefined, you can display the GPS data on the map without any problems.
6 |
7 | You can try it right away from here (But need to prepare MCU with LoRa and GPS module).
8 |
9 |
10 |
11 |
12 | ## For More Detail
13 |
14 | * [LoRa, GPS Data showing on the Map](https://youtu.be/zJvDw4UVDLc)
15 |
16 | ### Created & Maintained By
17 |
18 | [Eric Nam](https://github.com/0015)
19 | ([Youtube](https://youtube.com/ThatProject))
20 | ([Facebook](https://www.facebook.com/groups/138965931539175))
--------------------------------------------------------------------------------
/app.js:
--------------------------------------------------------------------------------
1 | let port;
2 | var keepReading = false;
3 | var reader;
4 | var inputDone;
5 | var outputDone;
6 | var outputStream;
7 |
8 | window.onload = function() {
9 | if(!detectBrowser()){
10 | alert("Please Note\nTo access the Serial Port through the Web Serial API, you must use Google Chrome browser or Microsoft Edge for now.");
11 | }
12 | };
13 |
14 | function startSerial() {
15 | openSerial()
16 | }
17 |
18 | async function closeSerial() {
19 | keepReading = false;
20 |
21 | reader.cancel();
22 | await inputDone.catch(() => {});
23 |
24 | outputStream.close();
25 | await outputDone;
26 |
27 | await port.close();
28 | window.SuccessMsg("Serial Port Closed", "The connected serial port is closed properly.");
29 | window.setSerialStatus(false);
30 | }
31 |
32 | function alertMessage(text) {
33 | alert(text)
34 | }
35 |
36 | function detectBrowser() {
37 | if(navigator.userAgent.indexOf("Chrome") != -1 ) {
38 | return true;
39 | }else {
40 | return false;
41 | }
42 | }
43 |
44 | async function openSerial() {
45 | try {
46 | port = await navigator.serial.requestPort({});
47 | await port.open({ baudRate: 115200, bufferSize: 10000 });
48 |
49 | const encoder = new TextEncoderStream();
50 | outputDone = encoder.readable.pipeTo(port.writable);
51 | outputStream = encoder.writable;
52 |
53 | let decoder = new TextDecoderStream();
54 | inputDone = port.readable.pipeTo(decoder.writable);
55 | inputStream = decoder.readable.pipeThrough(
56 | new TransformStream(new LineBreakTransformer())
57 | );
58 |
59 | reader = inputStream.getReader();
60 | keepReading = true;
61 | readLoop();
62 |
63 | } catch (e) {
64 | window.ErrorMsg("Serial Error", e.toString());
65 | }
66 | }
67 |
68 | async function readLoop() {
69 | window.SuccessMsg("Serial Read", "Begin to read data from the connected serial port.");
70 | window.setSerialStatus(true);
71 | while (keepReading) {
72 | try{
73 | const { value, done } = await reader.read();
74 | if (value) {
75 | window.readSerialFunc(value);
76 | if (done) {
77 | reader.releaseLock();
78 | break;
79 | }
80 | }
81 | }catch(e){
82 | keepReading = false;
83 | window.ErrorMsg("Serial Error", e.toString());
84 | }
85 | }
86 | }
87 |
88 | class LineBreakTransformer {
89 | constructor() {
90 | this.container = "";
91 | }
92 |
93 | transform(chunk, controller) {
94 | this.container += chunk;
95 | const lines = this.container.split("\r\n");
96 | this.container = lines.pop();
97 | lines.forEach((line) => controller.enqueue(line));
98 | }
99 |
100 | flush(controller) {
101 | controller.enqueue(this.container);
102 | }
103 | }
--------------------------------------------------------------------------------
/assets/AssetManifest.json:
--------------------------------------------------------------------------------
1 | {"img/ThatProject_Logo.png":["img/ThatProject_Logo.png"],"img/ThatProject_Rectangle.png":["img/ThatProject_Rectangle.png"],"packages/cupertino_icons/assets/CupertinoIcons.ttf":["packages/cupertino_icons/assets/CupertinoIcons.ttf"],"packages/line_icons/lib/assets/fonts/LineIcons.ttf":["packages/line_icons/lib/assets/fonts/LineIcons.ttf"]}
--------------------------------------------------------------------------------
/assets/FontManifest.json:
--------------------------------------------------------------------------------
1 | [{"family":"MaterialIcons","fonts":[{"asset":"fonts/MaterialIcons-Regular.otf"}]},{"family":"packages/cupertino_icons/CupertinoIcons","fonts":[{"asset":"packages/cupertino_icons/assets/CupertinoIcons.ttf"}]},{"family":"packages/line_icons/Awesome Line Icons 1.3.0","fonts":[{"asset":"packages/line_icons/lib/assets/fonts/LineIcons.ttf"}]}]
--------------------------------------------------------------------------------
/assets/fonts/MaterialIcons-Regular.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0015/LoRa_GPS_Viewer/b492b7a40212b8724cb142f9045fa6c81649d137/assets/fonts/MaterialIcons-Regular.otf
--------------------------------------------------------------------------------
/assets/img/ThatProject_Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0015/LoRa_GPS_Viewer/b492b7a40212b8724cb142f9045fa6c81649d137/assets/img/ThatProject_Logo.png
--------------------------------------------------------------------------------
/assets/img/ThatProject_Rectangle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0015/LoRa_GPS_Viewer/b492b7a40212b8724cb142f9045fa6c81649d137/assets/img/ThatProject_Rectangle.png
--------------------------------------------------------------------------------
/assets/packages/cupertino_icons/assets/CupertinoIcons.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0015/LoRa_GPS_Viewer/b492b7a40212b8724cb142f9045fa6c81649d137/assets/packages/cupertino_icons/assets/CupertinoIcons.ttf
--------------------------------------------------------------------------------
/assets/packages/line_icons/lib/assets/fonts/LineIcons.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0015/LoRa_GPS_Viewer/b492b7a40212b8724cb142f9045fa6c81649d137/assets/packages/line_icons/lib/assets/fonts/LineIcons.ttf
--------------------------------------------------------------------------------
/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0015/LoRa_GPS_Viewer/b492b7a40212b8724cb142f9045fa6c81649d137/favicon.png
--------------------------------------------------------------------------------
/flutter_service_worker.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const MANIFEST = 'flutter-app-manifest';
3 | const TEMP = 'flutter-temp-cache';
4 | const CACHE_NAME = 'flutter-app-cache';
5 | const RESOURCES = {
6 | "version.json": "fc02c12240b8f0294c6f9692fe31734a",
7 | "index.html": "0ec1d7b36e342c308c36d9eacfd183cf",
8 | "/": "0ec1d7b36e342c308c36d9eacfd183cf",
9 | "styles.css": "01ddf5811b8593a3d721ede630e064d4",
10 | "main.dart.js": "52c415513a681be4cb2c0a6e8ec6599a",
11 | "favicon.png": "1e40889e06d69f317710d81c8e258a24",
12 | "icons/Icon-192.png": "c3cb040d036bcefa9ff841d5efb2a66a",
13 | "icons/Icon-maskable-192.png": "c3cb040d036bcefa9ff841d5efb2a66a",
14 | "icons/Icon-maskable-512.png": "eff34ea4d72e666dba390ee95a7d6df9",
15 | "icons/Icon-512.png": "eff34ea4d72e666dba390ee95a7d6df9",
16 | "manifest.json": "cc17fa77a11254d0ca3ab22b76ea51c2",
17 | "assets/AssetManifest.json": "07b3ef7468b4e1307a687edee8cef9bb",
18 | "assets/NOTICES": "da2eddb9070d7f53d75c36c711352254",
19 | "assets/img/ThatProject_Rectangle.png": "45b12f2fc5bb7faee306083dc1e616a4",
20 | "assets/img/ThatProject_Logo.png": "123bbdc30646627585866a220e26dd46",
21 | "assets/FontManifest.json": "e024588c84b5d20cb7869d6f908130e8",
22 | "assets/packages/line_icons/lib/assets/fonts/LineIcons.ttf": "23621397bc1906a79180a918e98f35b2",
23 | "assets/packages/cupertino_icons/assets/CupertinoIcons.ttf": "6d342eb68f170c97609e9da345464e5e",
24 | "assets/fonts/MaterialIcons-Regular.otf": "7e7a6cccddf6d7b20012a548461d5d81",
25 | "app.js": "e6e741cc8a2fd7ae665a97cb03857a86"
26 | };
27 |
28 | // The application shell files that are downloaded before a service worker can
29 | // start.
30 | const CORE = [
31 | "main.dart.js",
32 | "index.html",
33 | "assets/NOTICES",
34 | "assets/AssetManifest.json",
35 | "assets/FontManifest.json"];
36 | // During install, the TEMP cache is populated with the application shell files.
37 | self.addEventListener("install", (event) => {
38 | self.skipWaiting();
39 | return event.waitUntil(
40 | caches.open(TEMP).then((cache) => {
41 | return cache.addAll(
42 | CORE.map((value) => new Request(value, {'cache': 'reload'})));
43 | })
44 | );
45 | });
46 |
47 | // During activate, the cache is populated with the temp files downloaded in
48 | // install. If this service worker is upgrading from one with a saved
49 | // MANIFEST, then use this to retain unchanged resource files.
50 | self.addEventListener("activate", function(event) {
51 | return event.waitUntil(async function() {
52 | try {
53 | var contentCache = await caches.open(CACHE_NAME);
54 | var tempCache = await caches.open(TEMP);
55 | var manifestCache = await caches.open(MANIFEST);
56 | var manifest = await manifestCache.match('manifest');
57 | // When there is no prior manifest, clear the entire cache.
58 | if (!manifest) {
59 | await caches.delete(CACHE_NAME);
60 | contentCache = await caches.open(CACHE_NAME);
61 | for (var request of await tempCache.keys()) {
62 | var response = await tempCache.match(request);
63 | await contentCache.put(request, response);
64 | }
65 | await caches.delete(TEMP);
66 | // Save the manifest to make future upgrades efficient.
67 | await manifestCache.put('manifest', new Response(JSON.stringify(RESOURCES)));
68 | return;
69 | }
70 | var oldManifest = await manifest.json();
71 | var origin = self.location.origin;
72 | for (var request of await contentCache.keys()) {
73 | var key = request.url.substring(origin.length + 1);
74 | if (key == "") {
75 | key = "/";
76 | }
77 | // If a resource from the old manifest is not in the new cache, or if
78 | // the MD5 sum has changed, delete it. Otherwise the resource is left
79 | // in the cache and can be reused by the new service worker.
80 | if (!RESOURCES[key] || RESOURCES[key] != oldManifest[key]) {
81 | await contentCache.delete(request);
82 | }
83 | }
84 | // Populate the cache with the app shell TEMP files, potentially overwriting
85 | // cache files preserved above.
86 | for (var request of await tempCache.keys()) {
87 | var response = await tempCache.match(request);
88 | await contentCache.put(request, response);
89 | }
90 | await caches.delete(TEMP);
91 | // Save the manifest to make future upgrades efficient.
92 | await manifestCache.put('manifest', new Response(JSON.stringify(RESOURCES)));
93 | return;
94 | } catch (err) {
95 | // On an unhandled exception the state of the cache cannot be guaranteed.
96 | console.error('Failed to upgrade service worker: ' + err);
97 | await caches.delete(CACHE_NAME);
98 | await caches.delete(TEMP);
99 | await caches.delete(MANIFEST);
100 | }
101 | }());
102 | });
103 |
104 | // The fetch handler redirects requests for RESOURCE files to the service
105 | // worker cache.
106 | self.addEventListener("fetch", (event) => {
107 | if (event.request.method !== 'GET') {
108 | return;
109 | }
110 | var origin = self.location.origin;
111 | var key = event.request.url.substring(origin.length + 1);
112 | // Redirect URLs to the index.html
113 | if (key.indexOf('?v=') != -1) {
114 | key = key.split('?v=')[0];
115 | }
116 | if (event.request.url == origin || event.request.url.startsWith(origin + '/#') || key == '') {
117 | key = '/';
118 | }
119 | // If the URL is not the RESOURCE list then return to signal that the
120 | // browser should take over.
121 | if (!RESOURCES[key]) {
122 | return;
123 | }
124 | // If the URL is the index.html, perform an online-first request.
125 | if (key == '/') {
126 | return onlineFirst(event);
127 | }
128 | event.respondWith(caches.open(CACHE_NAME)
129 | .then((cache) => {
130 | return cache.match(event.request).then((response) => {
131 | // Either respond with the cached resource, or perform a fetch and
132 | // lazily populate the cache.
133 | return response || fetch(event.request).then((response) => {
134 | cache.put(event.request, response.clone());
135 | return response;
136 | });
137 | })
138 | })
139 | );
140 | });
141 |
142 | self.addEventListener('message', (event) => {
143 | // SkipWaiting can be used to immediately activate a waiting service worker.
144 | // This will also require a page refresh triggered by the main worker.
145 | if (event.data === 'skipWaiting') {
146 | self.skipWaiting();
147 | return;
148 | }
149 | if (event.data === 'downloadOffline') {
150 | downloadOffline();
151 | return;
152 | }
153 | });
154 |
155 | // Download offline will check the RESOURCES for all files not in the cache
156 | // and populate them.
157 | async function downloadOffline() {
158 | var resources = [];
159 | var contentCache = await caches.open(CACHE_NAME);
160 | var currentContent = {};
161 | for (var request of await contentCache.keys()) {
162 | var key = request.url.substring(origin.length + 1);
163 | if (key == "") {
164 | key = "/";
165 | }
166 | currentContent[key] = true;
167 | }
168 | for (var resourceKey of Object.keys(RESOURCES)) {
169 | if (!currentContent[resourceKey]) {
170 | resources.push(resourceKey);
171 | }
172 | }
173 | return contentCache.addAll(resources);
174 | }
175 |
176 | // Attempt to download the resource online before falling back to
177 | // the offline cache.
178 | function onlineFirst(event) {
179 | return event.respondWith(
180 | fetch(event.request).then((response) => {
181 | return caches.open(CACHE_NAME).then((cache) => {
182 | cache.put(event.request, response.clone());
183 | return response;
184 | });
185 | }).catch((error) => {
186 | return caches.open(CACHE_NAME).then((cache) => {
187 | return cache.match(event.request).then((response) => {
188 | if (response != null) {
189 | return response;
190 | }
191 | throw error;
192 | });
193 | });
194 | })
195 | );
196 | }
197 |
--------------------------------------------------------------------------------
/icons/Icon-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0015/LoRa_GPS_Viewer/b492b7a40212b8724cb142f9045fa6c81649d137/icons/Icon-192.png
--------------------------------------------------------------------------------
/icons/Icon-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0015/LoRa_GPS_Viewer/b492b7a40212b8724cb142f9045fa6c81649d137/icons/Icon-512.png
--------------------------------------------------------------------------------
/icons/Icon-maskable-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0015/LoRa_GPS_Viewer/b492b7a40212b8724cb142f9045fa6c81649d137/icons/Icon-maskable-192.png
--------------------------------------------------------------------------------
/icons/Icon-maskable-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0015/LoRa_GPS_Viewer/b492b7a40212b8724cb142f9045fa6c81649d137/icons/Icon-maskable-512.png
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |