# Geek Mind > Technical Minded Blog Public Ghost content for AI and LLM tooling. This file includes a bounded export of public pages first, then recent public posts. Append `.md` to any post or page URL to get the content in Markdown (for example, `/example-post.md`). ## Pages _No public content available._ ## Posts ### PHreader-UI - React PH Data Frontend URL: https://undiluted.org/react-ph-data-frontend/ Last updated: 2026-09-09T22:05:08.000Z **What is PHreader-UI?** PHreader-UI is the display portion of the [Hot Tub PH Monitoring System](https://undiluted.org/hot-tub-ph-sensor-system/) PHreader-UI is a [SPA](https://en.wikipedia.org/wiki/Single-page%5Fapplication?ref=undiluted.org)(single-page application) which was bootstrapped using [Create React App](https://github.com/facebook/create-react-app?ref=undiluted.org). The application makes use of the [Flux Pattern](https://facebookarchive.github.io/flux/docs/in-depth-overview/?ref=undiluted.org) to manage the data flow. **Stack** - [React](https://en.wikipedia.org/wiki/React%5F%28software%29?ref=undiluted.org) \+ Flux Pattern - [reactstrap](https://reactstrap.github.io/?path=/story/home-installation--page&ref=undiluted.org) **Components** - login This component, is the login widget, which allows a user to login. The SPA presents the user with a login form that prompts for an email (username) and for a password. The form is submitted by the SPA to the [PHrest](https://undiluted.org/node-js-rest-ph-data-api/) API for user authentication validation. If the user is valid, the user is presented with the home component, which has the main navbar component, and the dashboard component. - home / dashboard / navbar This is the main component of the application. The default will show the main dashboard along with the default navbar. ![Screenshot 2026-09-09 at 17.55.50.png](https://undiluted.org/content/images/2026/09/Screenshot-2026-09-09-at-17.55.50.png) - stats This component is a component to show various different available graphs such as monthly, weekly, daily Ph stats. ![Screenshot 2026-09-09 at 17.59.10.png](https://undiluted.org/content/images/2026/09/Screenshot-2026-09-09-at-17.59.10.png) - graphs These are invididual components for the various types of graphs which are being displayed throughout the application. - help This is the component which is used to display the help menu. ![Screenshot 2026-09-09 at 18.01.20.png](https://undiluted.org/content/images/2026/09/Screenshot-2026-09-09-at-18.01.20.png) ### PHrest - Node.js + ExpressJS REST API URL: https://undiluted.org/node-js-rest-ph-data-api/ Last updated: 2026-09-09T21:34:59.000Z **What is PHrest?** PHrest is a [REST API](https://en.wikipedia.org/wiki/REST?ref=undiluted.org) that retrieves Ph data from a PostgreSQL database. It is a data source that [PHreader](https://undiluted.org/c-software-to-store-ph-data/) retrieves from the Raspberry Pi circuit and stores. This post will not delve into technical details, as the API itself is constructed using the Node.js+ExpressJS REST API boilerplate/framework, which I have previously documented [here](https://undiluted.org/rest-api-node-js-express/). **What is the stack behind PHrest?** - Kubernetes (infrastructure) - Postgresql (db) - Node.js + ExpressJS (application) **What endpoints are available?** ``` GET /api/v1/stats/ph/avg /api/v1/stats/ph/latest /api/v1/stats/ph/max /api/v1/stats/ph/min /api/v1/stats/:year/ph /api/v1/stats/:year/:month/:day/ph /api/v1/stats/ph/:id /api/v1/stats/ph ``` The endpoints are utilized to retrieve the Ph statistics data. They adhere to the REST methodology, which is user-friendly and intuitive. All endpoints return a [JSON](https://en.wikipedia.org/wiki/JSON?ref=undiluted.org) payload, as specified in the following example: ``` { "status": 1, } ``` For example: /api/v1/stats/ph/avg, will return a JSON payload with the Average Ph value. ``` { "status": 1, "avg": 8.42286562125667 } ``` /api/v1/stats/ph/latest will return a JSON payload with the latest recorded Ph value. ``` { "status": 1, "latest": 7.699 } ``` The primary objective of the API is to integrate with the data store (PostgreSQL) to facilitate data access. Consequently, a caller can present the information in a user-friendly manner, such as through graphing and monitoring capabilities. Next was to build a UI that could display dashboards and make use of information to monitor the hot tubs Ph levels. This is where [PHreader-UI](https://undiluted.org/react-ph-data-frontend/) comes in. ### PHreader - Storing PH Data into PostgreSQL URL: https://undiluted.org/c-software-to-store-ph-data/ Last updated: 2026-09-09T12:54:37.000Z As mentioned in section [1](https://undiluted.org/raspberry-pi-ph-circuit/), the Raspberry Pi PH Circuit () was selected, incorporating a PH sensor from AtlasScientific ([https://www.atlas-scientific.com/product\_pages/kits/ph-kit.html](https://www.atlas-scientific.com/product%5Fpages/kits/ph-kit.html?ref=undiluted.org)) and a Raspberry Pi Zero. **Summary - What PHreader is** In short, PHreader is a piece of software written in \[C\]([https://en.wikipedia.org/wiki/C\_(programming\_language)](https://en.wikipedia.org/wiki/C%5F%28programming%5Flanguage%29?ref=undiluted.org) to interface to the PH sensor. PHreader communicates with the sensor to pull PH data over the I²C protocol via UART. PHreader utilizes, the [Wiring Pi Library](https://https//web.archive.org/web/20220405225008/http://wiringpi.com/wiringpi-deprecated/?ref=undiluted.org) version 2.50 (which was subsequently deprecated, but was taken over by [GC2](https://github.com/GrazerComputerClub?ref=undiluted.org) in 2024) to communicate with the sensor/circuit. The C program communicates with the sensor, continuously in a loop as a background daemon on the raspberry pi, retrieving data and storing the [Ph](https://en.wikipedia.org/wiki/PH?ref=undiluted.org) value in a [Postgresql](https://www.postgresql.org/?ref=undiluted.org) database. To store the sensor data in the PostgreSQL database, the software utilizes the [libpq](https://www.postgresql.org/docs/current/libpq.html?ref=undiluted.org) library. **Configuration** Since the software was written in C, using a Declarative, Block-Structured Configuration Format, made the mode sense. This gave us the ability to have a configuration such as: ``` phreader = { debug = true; logfile = "/tmp/phreader.log"; loop_interval = 3600; serial = { device = "/dev/ttyAMA0"; baudrate = 9600; }; database = { hostname = ""; port = 5432; username = ""; password = ""; database = ""; ssl_mode = false; }; }; ``` Using [libconfig](https://hyperrealm.github.io/libconfig/?ref=undiluted.org) we can easily parse our configuration into appropriate C structures, such as: ``` typedef struct pg_config { const char *username; const char *password; const char *hostname; int port; const char *database; int ssl_mode; } pg_config; typedef struct serial_config { const char *device; int baudrate; } serial_config; typedef struct phreader_config { int debug; int loop_interval; const char *logfile; const char *pidfile; int valid; pg_config pg_config; serial_config serial_config; } phreader_config; ``` We can then utilize the configuration structures, in the main daemon loop to communicate with the sensor over UART (serial) to fetch our data, and to store the PH data in the Postgresql database. **Main Daemon Loop** The main daemon loop is simple it does a few things, in a continuous loop: - Open the serial port for reading This is easily done thanks to the WiringPi library. To open the serial port, we simply do: ``` fd = serialOpen(cfg.serial_config.device, cfg.serial_config.baudrate)) ``` - Initialize the WiringPi Library This is simply a call to make the GPIO pins to have sequential numbering that the WiringPi library uses vs the physical/Broadcom pin numbers. ``` wiringPiSetup() ``` - Wake up Ph sensor circuit For this piece, we wrote our own functions which are basically a wrapper around the Ph Circuits serial commands. To wake up the Circuit, we simply send a 'Wake' command over serial. ``` serialPrintf(fd, "Wake\r"); ``` - Switch to continuous mode Similar to the Wake command, the circuit allows for a continuous mode. The continous mode can be used when receiving data from the circuit. ``` serialPrintf(fd, "C,%d\r", seconds); ``` - Fetch the Ph data To fetch data, while the circuit is in continous mode, we simply send a read command and then listen for the data on the serial port. ``` serialPrintf(fd, "R\r"); // This starts our fetch data = uartRecv(fd); ``` ``` uartData * uartRecv(int fd) { char *buf; char c; int count = 0; int iteration = 0; int index = 0; int line = 0; uartData *data = NULL; while((count = serialDataAvail(fd)) != 0) { c = serialGetchar(fd); if (!iteration) { buf = (char *)malloc((size_t)count + 1); } if (c == '\r') { buf[index++] = '\0'; if (buf[0] == '*') { // cmd code response data = addUartData(data, line, "CMD_CODE", buf); } else { // actual data response data = addUartData(data, line, "DATA", buf); } line++; // realloc if we have more to read. if (count > 0) { // if realloc fails, break. if (realloc(buf, count) == NULL) { break; } index = 0; } } else { buf[index++] = c; } //fflush(stdout); iteration++; } free(buf); return data; } ``` - Set back to sleep mode This is simply a call over the Serial port in order to put the circuit back into sleep mode to save power. ``` serialPrintf(fd, "sleep\r"); ``` - Close the serial port Using the WiringPi library, we can close the serial port. ``` serialClose(fd); ``` - Connect to the Postgresql DB Using the libpgq library, we connect to the Postgresql DB. ``` db_uri = create_pg_db_uri(cfg.pg_config); PGconn *conn = pg_connect(db_uri); free(db_uri); ``` - Store the Ph data in the Postgresql DB Using the libpgq library, we call our insert query to store our current Ph value and timestamp. ``` insert_ph(conn, ph); ``` - Close the connection to the Postgesql DB Assuming we are connected, we can then close our DB connection, and go to sleep until the next iteration of the loop. ``` pg_close(conn); ... sleep(cfg.loop_interval); ``` The [next step](https://undiluted.org/node-js-rest-ph-data-api/) was to expose the data using a [REST](https://en.wikipedia.org/wiki/REST?ref=undiluted.org) API. ### Hot Tub PH Monitoring System URL: https://undiluted.org/hot-tub-ph-sensor-system/ Last updated: 2026-09-09T21:32:02.000Z This blog post, will describe the process of building out a Hot Tub PH monitoring system. There will be four sections: - [Raspberry Pi PH Circuit](https://undiluted.org/raspberry-pi-ph-circuit/) - [Storing PH Data into PostgreSQL](https://undiluted.org/c-software-to-store-ph-data/) (PHreader) - [Node.js+ExpressJS REST API to obtain PH data from PostgreSQL](https://undiluted.org/node-js-rest-ph-data-api/) (PHrest) - [React Frontend for display of PH Data to end users](https://undiluted.org/react-ph-data-frontend/) (PHreader-UI) ### Raspberry Pi PH Circuit URL: https://undiluted.org/raspberry-pi-ph-circuit/ Last updated: 2026-09-09T21:08:57.000Z **Raspberry Pi PH Circuit** The first section of this project was building the Circuit which would be used to pull the PH from the Hot Tub's water. To build the circuit, I settled on a [Raspberry Pi Zero v1](https://www.raspberrypi.org/products/raspberry-pi-zero/?ref=undiluted.org), and not the newer [Raspberry Pi Zero W](https://www.raspberrypi.org/products/raspberry-pi-zero-w/?ref=undiluted.org). The sensor which I settled on was the PH sensor from [AtlasScientific](https://www.atlas-scientific.com/product%5Fpages/kits/ph-kit.html?ref=undiluted.org). I initially (and purchased later) purchased the kit without the isolation circuit, but would recommend purchasing the isolation circuit to avoid interference the pumps can cause with the PH sensor. The main reason for going with the v1 Raspberry Pi Zero vs the Raspberry Pi Zero W, was due to the fact that the UART communication on the Zero W had problems which made it unreliable for polling the PH sensor. AtlasScientific provides some great [data sheets](https://www.atlas-scientific.com/%5Ffiles/%5Fdatasheets/%5Fcircuit/pH%5FEZO%5FDatasheet.pdf?ref=undiluted.org) which outlines exactly how the sensor functions, and how to obtain data via UARt and I²C as well as how to calibrate the sensor. AtlasScientific also provides some code [tutorials / samples](https://www.atlas-scientific.com/%5Ffiles/code/pi%5Fsample%5Fcode.pdf?ref=undiluted.org) which can be useful to get things going with setup and data polling. The breadboard setup that was used for prototyping the circuit: ![Breadboard Circuit Prototype](https://undiluted.org/content/images/2019/11/ph_breadboard.png) The schematic looks like this: ![Schematic](https://undiluted.org/content/images/2019/11/ph-sensor-project_schem.png) The [next step](https://undiluted.org/c-software-to-store-ph-data/) was to pull the data from the sensor. ### JSON Parsing in Bash URL: https://undiluted.org/json-parsing-in-bash/ Last updated: 2019-09-15T00:05:48.000Z About a month ago, I needed to parse [JSON](https://www.json.org/?ref=undiluted.org) from in [Bash](https://www.gnu.org/software/bash/?ref=undiluted.org) scripts. This was an interesting problem/challenge, enter [./jq](https://stedolan.github.io/jq/?ref=undiluted.org). [jq](https://stedolan.github.io/jq/?ref=undiluted.org) is a sed like tool for JSON data, that can be used to parse JSON, from the command line, and from within [Bash](https://www.gnu.org/software/bash/?ref=undiluted.org) scripts. [jq](https://stedolan.github.io/jq/?ref=undiluted.org) is written in C, and is a single binary, that has zero runtime dependencies, meaning that you can simply download the binary and get parsing! Some simple examples: Lets say we have the following JSON payload: ``` sample.json: [ { "rank": 1, "description": "Great Search Engine", "url": "https://google.com" }, { "rank": 2, "description": "Oldie but goodie", "url": "https://www.yahoo.com" } ] ``` If we wanted to print out the JSON payload in a colorized output, we could do: ``` cat sample.json | jq ``` The output would be: ![json-pretty-print](https://undiluted.org/content/images/2019/09/json-pretty-print.png) Lets say we wanted just the URLs: ``` cat sample.json | jq .[].url ``` The output would be: ![json-just-urls](https://undiluted.org/content/images/2019/09/json-just-urls.png) If we wanted just the first URL: ``` cat sample.json | jq .[0].url ``` This would ouput: ![json-just-first-url](https://undiluted.org/content/images/2019/09/json-just-first-url.png) The URLs followed by the Ranks: ``` cat sample.json | jq '.[] | .url,.rank' ``` This would output: ![json-urls-ranks](https://undiluted.org/content/images/2019/09/json-urls-ranks.png) Lets do something a little more complex, lets say we want to add **1** to every rank: ``` cat sample.json | jq '.[].rank +1' ``` This would output: ![json-rank-arithmetic](https://undiluted.org/content/images/2019/09/json-rank-arithmetic.png) The above are just simple examples, [jq](https://stedolan.github.io/jq/?ref=undiluted.org) is very powerful, and can do much more! I recommend checking out the [jq/](https://stedolan.github.io/jq/?ref=undiluted.org) github page to get more information on this great tool. ### REST API Boilerplate (Node.js + Express) URL: https://undiluted.org/rest-api-node-js-express/ Last updated: 2019-08-19T02:03:00.000Z ![](https://undiluted.org/content/images/2019/08/Node.js_logo-3.svg) --- A few years ago, I was working with a [Raspberry Pi](https://www.raspberrypi.org/?ref=undiluted.org) with a Z-Wave adapter to talk to z-wave enabled devices, such as switches, dimmers, and door locks. I needed a method of keeping track and manipulating user account details, which seemed like the perfect time for me to create something using [Node.js](https://nodejs.com/?ref=undiluted.org) \+ [Express](https://expressjs.org/?ref=undiluted.org). As REST APIs are a useful and popular type of APIs, I thought I would design a boilerplate that could be re-used to quickly build REST APIs with [Node.js](https://nodejs.com/?ref=undiluted.org) \+ [Express](https://expressjs.org/?ref=undiluted.org). --- This REST API boilerplate is available in my [Gitlab repo.](https://gitlab.com/ngosselin/rest-skeleton?ref=undiluted.org) Below I will go through the basics of how the boilerplate works, and how it can be used. The overall idea behind the skeleton was to be very close in nature with how [Ruby on Rails](https://rubyonrails.org/?ref=undiluted.org) APIs are layed out. In the root directory of the repository there is a file called route.json, which is a JSON hash that defines the routes for the API which live in the src/resources/ directory. As an example the default routes.json file contains the following: ```json "v1/account": { "routes": [ "/v1/accounts/:id", "/v1/accounts" ] }, "v2/account": { "routes": [ "/v2/accounts/:id", "/v2/accounts" ] } ``` The above, maps out both: ``` /v1/accounts/:id /v1/accounts ``` to the `v1/account/` resource in `src/api/resources/v1/account.js` (it also maps out a version 2 of a similar API) `src/api/resources/v1/account.js`, has a method per HTTP verb (GET, POST, PUT, DELETE), the breakdown of these methods are: - index() - GET - create() - POST - update() - PUT - destroy() - DELETE To handle versioning of the API, each resource in `src/api/resources//` extends an appropriate resource labeled v1.js, v2.js etc. This particular resource, sets specific things that are related to the particular version, such as a response header: api-version, which tells the caller which version of the API is being used. The API skeleton includes integration/behavior tests (mocha + chai + chai-http) which live in the test/ directory and can be run using: ``` npm run test ``` To run the build process for the API skeleton: ``` npm run build ``` To run the API skeleton locally: ``` npm run start ```