Skip to content

A simple, offline first data gathering app using react-native, react-native-elements, redux, and redux-persist.

License

Notifications You must be signed in to change notification settings

ScriptGadget/gather

Repository files navigation

Gather

Gather is a simple, offline first, data gathering app for iOS and Android.

Introduction

This app solves the problem of gathering measurements from locations where there may be instruments which are not online and in locations where there may be no cell data service.

Technicians follow a route, shown on a map to each site, where they can enter readings for each measurement point at the site. Measurement Points are grouped to a particular piece of equipment, e.g. a tank or a pump. Each piece of equipment might have multiple things to measure. Examples might include a dipstick and a subtractive scale for a tank, or a pressure guage and flow meter on a compressor.

To Start Developing

Install expo on a mobile device.

Setup a node.js development environment.

Clone this repository.

Assuming the repository is in a directory named 'Gather'

cd Gather
npm install
exp start

Install the expo app on your mobile device.

Open the app and scan the barcode displayed by exp start

Enjoy.

Architecture

This system has three pieces.

  • Gather - the app that runs on mobile devices. This repository.

  • Store - the backend server which manages user logins, route information and collects readings from Gather.

  • Analyze - a system used by Managers to analyze readings from Technicians and automated remote sensors. Analyze also manages and creates all routes and Technician accounts. Discussion of Analyze is beyond the scope of this document.

Both Gather and Store are written in JavaScript.

Gather is a React Nativeapplication, which uses [https://react-native-training.github.io/react-native-elements/](React Native Elements), Redux and Redux-Persist. Gather was created using Expo.

Store is a Loopback application.

API

The Gather application talks to the Store server over HTTPS using a REST interface. Gather uses three endpoints.

Login:

POST https://store.domain.com/Technicians

JSON BODY

{
"email": "[email protected]",
"password", "badpass"
}

Returns a JSON object containing user information and the access_token as "id"

{
"id": "Ez1OTRFfQ2r6ETEY6ZoFYk6jM160TFuU061aKsyxZF0bissRYjVBVZ8TyRoFRbxY",
"ttl": 1209600,
"created": "2018-07-27T21:36:01.664Z",
"userId": "5283d7d9-0d0d-4e10-ba56-28621f4c934c"
}

Retrieve the Route

GET https://store.domain.com/Routes/mine?access_token=abc123

Returns a JSON object containing the sites on a Technician's route, the Machines at those sites, the Measurement Points on those machines and any previous readings for those Measurement Points. The information is normalized in the style of the normalizr Node library, but this project does not need or use normalizr. Note that each ID("id") is a UUID. Readings are created by the Analyze system for all entities except Readings. Readings are created on the Gather system and Gather creates the ID.

Returns a 401 error if the access_token is missing or invalid.

{
  "mine": {
    "entities": {
      "sites": {
        "byId": {
          "4ff02671-ab30-4e3e-a2cc-da2403d1e3a3": {
            "id": "4ff02671-ab30-4e3e-a2cc-da2403d1e3a3",
            "name": "Mesa Flats",
            "description": "Example Panhandle site.",
            "location": {
              "lat": 35.1260368,
              "lng": -102.0157754
            }
          },
          "6601c3f3-11e3-43ef-9eac-dc3cdca1d37b": {
            "id": "6601c3f3-11e3-43ef-9eac-dc3cdca1d37b",
            "name": "Oglala Draw",
            "description": "Example site with two Machines.",
            "location": {
              "lat": 34.5685931,
              "lng": -101.9881576
            }
          }
        },
        "allIds": [
          "4ff02671-ab30-4e3e-a2cc-da2403d1e3a3",
          "6601c3f3-11e3-43ef-9eac-dc3cdca1d37b"
        ]
      },
      "machines": {
        "byId": {
          "2cc12d0e-390d-4a06-943d-578940d600da": {
            "id": "2cc12d0e-390d-4a06-943d-578940d600da",
            "name": "Tar Pit",
            "siteId": "6601c3f3-11e3-43ef-9eac-dc3cdca1d37b"
          },
          "5391d00e-6bde-492f-b245-7f61c6b21047": {
            "id": "5391d00e-6bde-492f-b245-7f61c6b21047",
            "name": "Compressor",
            "siteId": "6601c3f3-11e3-43ef-9eac-dc3cdca1d37b"
          },
          "9beb6dab-c385-4453-8a2e-a7df68271d70": {
            "id": "9beb6dab-c385-4453-8a2e-a7df68271d70",
            "name": "Water Well",
            "siteId": "4ff02671-ab30-4e3e-a2cc-da2403d1e3a3"
          }
        },
        "allIds": [
          "2cc12d0e-390d-4a06-943d-578940d600da",
          "5391d00e-6bde-492f-b245-7f61c6b21047",
          "9beb6dab-c385-4453-8a2e-a7df68271d70"
        ]
      },
      "points": {
        "byId": {
          "035d720e-66a9-41b9-b8ef-de611ed84a7e": {
            "id": "035d720e-66a9-41b9-b8ef-de611ed84a7e",
            "name": "Battery",
            "unit": "volts",
            "machineId": "5391d00e-6bde-492f-b245-7f61c6b21047"
          },
          "38e1d02d-4ed3-47ca-820a-eb486d3e4988": {
            "id": "38e1d02d-4ed3-47ca-820a-eb486d3e4988",
            "name": "Salinity Probe",
            "unit": "ppm",
            "machineId": "9beb6dab-c385-4453-8a2e-a7df68271d70"
          },
          "42d89c8b-9da9-4242-9f7c-c305c9d55f7b": {
            "id": "42d89c8b-9da9-4242-9f7c-c305c9d55f7b",
            "name": "Dipstick",
            "unit": "feet",
            "machineId": "2cc12d0e-390d-4a06-943d-578940d600da"
          },
          "a5111248-e93a-4141-9bfa-1dca038971d9": {
            "id": "a5111248-e93a-4141-9bfa-1dca038971d9",
            "name": "Pressure",
            "unit": "psi",
            "machineId": "5391d00e-6bde-492f-b245-7f61c6b21047"
          }
        },
        "allIds": [
          "035d720e-66a9-41b9-b8ef-de611ed84a7e",
          "38e1d02d-4ed3-47ca-820a-eb486d3e4988",
          "42d89c8b-9da9-4242-9f7c-c305c9d55f7b",
          "a5111248-e93a-4141-9bfa-1dca038971d9"
        ]
      },
      "readings": {
        "byId": {
          "16b673e4-5f30-4f25-823b-858664c09db8": {
            "id": "16b673e4-5f30-4f25-823b-858664c09db8",
            "value": 82,
            "mark": "2018-05-23T10:03:45.734Z",
            "pointId": "a5111248-e93a-4141-9bfa-1dca038971d9"
          },
          "19f33000-873c-4c6f-ad65-430f4e099b73": {
            "id": "19f33000-873c-4c6f-ad65-430f4e099b73",
            "value": 22.1,
            "mark": "2018-05-23T10:03:45.734Z",
            "pointId": "035d720e-66a9-41b9-b8ef-de611ed84a7e"
          },
          "3121aa12-afa2-43e5-a824-124b8053eafe": {
            "id": "3121aa12-afa2-43e5-a824-124b8053eafe",
            "value": 3.5,
            "mark": "2018-05-23T10:03:45.734Z",
            "pointId": "42d89c8b-9da9-4242-9f7c-c305c9d55f7b"
          },
          "5c66b423-6e60-47ad-b6e9-ecdb1661baba": {
            "id": "5c66b423-6e60-47ad-b6e9-ecdb1661baba",
            "value": 87,
            "mark": "2018-05-25T03:11:01.613Z",
            "pointId": "a5111248-e93a-4141-9bfa-1dca038971d9"
          },
          "61f8d0cb-a798-4919-a168-6276122f3323": {
            "id": "61f8d0cb-a798-4919-a168-6276122f3323",
            "value": 131,
            "mark": "2018-05-24T02:15:01.613Z",
            "pointId": "38e1d02d-4ed3-47ca-820a-eb486d3e4988"
          },
          "7c6739ff-bf7c-4e22-bcc7-1b2533f77098": {
            "id": "7c6739ff-bf7c-4e22-bcc7-1b2533f77098",
            "value": 3.7,
            "mark": "2018-05-25T03:11:01.613Z",
            "pointId": "42d89c8b-9da9-4242-9f7c-c305c9d55f7b"
          },
          "a5111248-e93a-4141-9bfa-1dca038971d9": {
            "id": "a5111248-e93a-4141-9bfa-1dca038971d9",
            "value": 23.05,
            "mark": "2018-05-25T03:11:01.613Z",
            "pointId": "035d720e-66a9-41b9-b8ef-de611ed84a7e"
          },
          "e959f44a-bb37-4a4e-a154-37f301b6f0fc": {
            "id": "e959f44a-bb37-4a4e-a154-37f301b6f0fc",
            "value": 120,
            "mark": "2018-05-22T01:50:45.734Z",
            "pointId": "38e1d02d-4ed3-47ca-820a-eb486d3e4988"
          }
        },
        "allIds": [
          "16b673e4-5f30-4f25-823b-858664c09db8",
          "19f33000-873c-4c6f-ad65-430f4e099b73",
          "3121aa12-afa2-43e5-a824-124b8053eafe",
          "5c66b423-6e60-47ad-b6e9-ecdb1661baba",
          "61f8d0cb-a798-4919-a168-6276122f3323",
          "7c6739ff-bf7c-4e22-bcc7-1b2533f77098",
          "a5111248-e93a-4141-9bfa-1dca038971d9",
          "e959f44a-bb37-4a4e-a154-37f301b6f0fc"
        ]
      }
    }
  }
}

Store a new reading

POST https://store.domain.com/Readings?access_token=abc123

{
  "id": "a5111248-e93a-4141-9bfa-1dca038971d9",
  "value": 23.05,
  "mark": "2018-05-25T03:11:01.613Z",
  "pointId": "035d720e-66a9-41b9-b8ef-de611ed84a7e"
}

This returns the created reading on success.

Returns a 401 error if the access_token is missing or invalid.

{
    "id": "a5111248-e93a-4141-9bfa-1dca038971d9",
    "value": 23.05,
    "mark": "2018-05-25T03:11:01.613Z",
    "pointId": "035d720e-66a9-41b9-b8ef-de611ed84a7e"
}

Store has a much larger API than what is used by Gather. These API calls are used only by Analyze right now, or for debugging. For more information see Store.

Structure

Gather is a React-Native application which uses Redux for state. This is a pretty common configuration, so we won't discuss the basics of React, React-Native or Redux here. For more information, see the links in the Architecture section.

The project directory is organized like this:


.
├── App.js
├── app.json
├── assets
│   ├── expo.symbol.white.png
│   ├── icon.png
│   ├── icons
│   │   ├── app-icon.png
│   │   └── loading-icon.png
│   └── splash.png
├── components
│   ├── Machine.js
│   ├── MeasurementPoint.js
│   ├── Settings.js
│   ├── SignIn.js
│   ├── Site.js
│   └── SiteList.js
├── config
│   ├── Data.js
│   └── Router.js
├── package.json
├── package-lock.json
├── README.md
├── redux
│   ├── Actions.js
│   ├── Reducers.js
│   └── Store.js
├── __samples__
│   └── Sample-data.js
├── __tests__
│   └── Actions-test.js
└── TODO

  • assets - contains static assets like icons and graphics.

  • components - contains React-Native components. Think of each as a "screen" in the mobile application.

  • config - contains routing information (how we move from one screen to another) for react-navigation. You can read more about react-navigation at that project's webiste.

  • redux - contains the Actions, Reducers and Store elements of a typical Redux project. All of the external API calls are in Actions.

  • __samples__ - contains sample data used for unit tests.

  • __tests__ - contains unit tests based on the jest framework.

About

A simple, offline first data gathering app using react-native, react-native-elements, redux, and redux-persist.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published