β· Current status: 0% ββββββββββββββββββββββββββββββ 100% (= first running version)
Hi.
Welcome to HealthcoreβοΈ
But wait: what the hell is Healthcoreβ
The Healthcore is part of the open software and hardware architecture of bulp.io. With bulp, healthcare devices from any manufacturer can communicate with each other and any interface through a variety of protocols and APIs. In this way, bulp centrally captures a person's condition in many different ways, reacts automatically to changes in their environment, and optionally informs caregivers, nurses or family members. bulp.io follows the open-core approach: the software core is open source, while revenue is generated through hardware and services.
The software core is called "Healthcore" (obviously!) and it standardizes the data from various devices, processes scenarios, and triggers actions. A simple API allows interfaces such as apps to visualize the device data.
Healthcore can run on any hardware β a Raspberry Pi, a PC, or any other device with a Linux system or Windows. The choice is yours.
Just imagine something like Home Assistant or OpenHAB, but specialized for healthcare. Thatβs exactly what this is.
In addition, this repository also contains an app for Android and iOS that demonstrates how Healthcore can communicate with an interface.
So letβs democratize and de-monopolize the healthcare sector. Make healthcare devices and infrastructure affordable for everyone!
π€HEALTHCORE!!!π€
- ποΈ Architecture
- π» Installation (software)
- π Folder structure
- π§ Installation (hardware)
- π Healthcheck - a monitor for Healthcore
- π§© Own converters
- π API communication
- π Security
- π± App
Letβs take a look at the architecture of bulp.io:

In the middle β thatβs the Healthcore. The Healthcore consists of several Node.js servers with different tasks. The Node.js servers communicate with each other via MQTT. The most important thing is that there is a separate bridge for each protocol, which standardizes the incoming and outgoing data of the devices. The Healthcore supports the following protocols:
- Bluetooth
- ZigBee
- LoRa P2P
- HTTP
- Thread (planned)
And now the best part: you can add your own devices to the Healthcore! Each bridge includes a list of classes for devices. So you can handle the data transformation with simple JavaScript in a class for your device (= very cool).
On the left, you can see how various interfaces communicate bi-directionally with the Healthcore via a standardized API and visualize the data, for example. Just bring your own interface (or use the bulp.io app - see below).
Prerequisites
- Node.js (v22 or higher) and npm
Project setup
- Clone/download the repository and
cdinto its root. - Create
.env.localto override defaults; fill in:- Adapter paths:
CONF_zigBeeAdapterPort,CONF_zigBeeAdapterName,CONF_loRaAdapterPath,
- Adapter paths:
- Install dependencies:
npm install
Start services (each in its own terminal or managed via a process manager):
# MQTT broker
node broker/app.js
# Server
node server/app.js
# Bridges
node "bridge - bluetooth/app.js"
node "bridge - zigbee/app.js"
node "bridge - lora/app.js"
node "bridge - http/app.js"If you want to use it for production, just run
.\production-start.shproduction-start.sh uses the process manager, so that a service is restarted if it crashes. The relevant logs can be found in the logs folder.
βββ broker/ # MQTT broker
βββ server/ # Server
β βββ routes/ # Routes for communication Interface via SSE β Server β Interface via API
β βββ middleware/ # Middleware features
β βββ libs/ # Additionally libraries
βββ bridge - bluetooth/ # Bluetooth β MQTT bridge
β βββ converters/ # Common and own converters
βββ bridge - zigbee/ # ZigBee β MQTT bridge
β βββ converters/ # Common and own converters
βββ bridge - lora/ # LoRa β MQTT bridge
β βββ converters/ # Common and own converters
βββ bridge - http/ # HTTP β MQTT bridge
β βββ converters/ # Common and own converters
βββ tests/ # Example device firmware (for Arduino) and other testing scripts
βββ healthcheck/ # Healthcheck (see below)
βββ app/ # App
- Host platform
- Raspberry Pi 4 or (or better) or any Linux/Windows PC with network access
- Adapters
- Bluetooth: Built-in BLE or USB dongle
- ZigBee: USB coordinator (e.g. CC2531, ConBee II, Sonoff Zigbee 3.0 USB stick)
- LoRa: USB or serial LoRa adapter (e.g. Dragino LA66 LoRaWAN USB Adapter)
- Connections
- Plug adapters into host; note device paths (e.g.
/dev/ttyUSB0orCOMx) and set in.env.local
- Plug adapters into host; note device paths (e.g.
Healthcore has an integrated interface (= healthcheck) to view the status of the individual bridges, brokers and servers and to start and stop them. All outputs are displayed in a console. Healthcheck only works locally.
How to start healthcheck:
node healthcheck/app.jsThen open a browser und type:
http://localhost:9990(9990 is the standard port healthcheck and localhost the standard base URL, configured in .env - overwrite it in .env.local if you want)
The Own converters subsystem lets you transform raw device data (e.g., binary BLE characteristic values) into structured JSON properties that your interface (i.e. your app) can use. Each bridge (Bluetooth, ZigBee, LoRa, HTTP) has its own converters/ folder with individual converter classes extending a shared ConverterStandard base. Below is a detailed Bluetooth bridge example:
-
Create a new JS file in the bridgeβs
converters/folder (e.g.Converter_MyConverter.js). -
Extend
ConverterStandard:In
Converter_MyConverter.js, import the base and declare your class:const { ConverterStandard } = require("./ConverterStandard.js"); class Converter_MyConverter extends ConverterStandard { // always extend "ConverterStandard" static productName = "bulp-AZ-123"; // static property to identify the product name this converter is for constructor() { super(); // call the parent class constructor this.powerType = "MAINS"; // set the power type for this device ("MAINS" or "BATTERY") // Define the properties supported by this device, using their Bluetooth UUIDs as keys. Each property object contains metadata used for conversion and access control. this.properties["19b10000e8f2537e4f6cd104768a1217"] = { name: "rotary_switch", // property name (easy to understand) notify: true, // notify healthcore if this value changes read: true, // read access write: false, // write access anyValue: 0, // pre-defined value standard: false, // is this a standard value? (https://www.bluetooth.com/wp-content/uploads/Files/Specification/Assigned_Numbers.html) valueType: "Numeric" // Numeric or String or Options }; this.properties["19b10000e8f2537e4f6cd104768a1218"] = { name: "speaker", notify: false, read: true, write: true, anyValue: ["on", "off"], valueType: "Options" }; // ... } // GET: Converts a raw value from the device into a higher-level representation, based on the property metadata. get(property, value) { if (property.read === false) { return undefined; // property is not readable, so return undefined } else { if (property.standard === true) { // if this is a standard property then use common converter return this.getStandard(property, value); } else { // device-specific conversion logic switch (property.name) { case "rotary_switch": const buf = Buffer.from(value); return {"value": buf[0], "valueAsNumeric": buf[0]}; case "speaker": return value[0] === 1 ? {"value": "on", "valueAsNumeric": 1} : {"value": "off", "valueAsNumeric": 0}; default: return undefined; } } } } // SET: Converts a higher-level value into a format suitable for writing to the device, based on the property metadata. set(property, value) { if (property.write === false) { return undefined; // if property is not writable, so return undefined } else { switch (property.name) { case "speaker": if (property.anyValue.includes(value)) { return Buffer.from([value === "on" ? 1 : 0]); } else { return undefined; } case "led": if (property.anyValue.includes(value)) { return Buffer.from([value === "on" ? 1 : 0]); } else { return undefined; } default: return undefined; } } } } module.exports = { Converter_BulpAZ123 };
-
Auto-load:
Converters.jsdynamically requires all files inconverters/(excludingConverterStandard.js), detects the staticproductName, and registers your class.
Healthcore provides a comprehensive API that allows you to control all data and devices in a standardized way. Here is a complete example of connecting to a ZigBee device.
You can explore all APIs using Swagger:
http://localhost:9998/api-docs/(9998 is the standard server port and localhost the standard base URL, configured in .env - overwrite it in .env.local if you want)
Example for ZigBee:
Example coming soon.If you need to find the IP address of the server on the local network: The Healthcore server uses a Bonjour service to make itself known on the network. The default identifier is βhealthcoreβ, but it can be customized in the .env file with CONF_serverIDBonjour.
By default, Healthcore is initially unsecured to facilitate configuration and development. If CONF_apiKey, CONF_corsURL, CONF_brokerUsername/CONF_brokerPassword and/or CONF_tlsPath remain empty in the .env.local file, security measures are inactive; however, they can be enabled as follows:
-
CORS: Cross-Origin Resource Sharing (CORS) is a mechanism that enables Healthcore to specify which origins (domain, scheme, or port) are authorized to access the API. To define these permitted origins, the respective values must be entered as a comma-separated list under
CONF_corsURLin the.env.localfile. Please ensure that URLs do not include a trailing slash (/), as this is generally not required and may lead to configuration errors. -
API: To implement API key authentication, the key must be defined in the
.env.localfile underCONF_apiKey. Subsequent requests to the API must include thex-api-key headercontaining the specified key. -
TLS (HTTPS): To further secure API communication, a certificate can be used. This can be created using https://github.com/FiloSottile/mkcert. The created files must be named
cert.pemandkey.pem. Please keep these files outside the repository, so there is no chance to commit them accidentally. You can change the path in.env.localviaCONF_tlsPath. Default is same level as the repository.
If a certificate is set, then automatically MQTTS instead of MQTT is used. So you have to change CONF_brokerAddress to mqtts://localhost:9999 in .env.local.
- MQTT: To use an authentification for MQTT, set
CONF_brokerUsernameandCONF_brokerPasswordin.env.local.
Yes, there is also an app in this repository. More specifically, it is the official bulp.io app or rather the source code for it.
It is fully functional and can be used until you have programmed your own interface.
The app is located in the app/ folder and has its own package.json. So you have to install it separately from Healthcore via npm. Here are the steps. By the way: the app is programmed in the Ionic Framework/Capacitor with Vanilla JS.
Installing and compiling the app:
-
Change to folder
app/ -
Install dependencies:
npm install
-
Run locally in web browser:
npm run dev
-
Open in browser:
http://localhost:5173/(the correct url is shown in the console) -
Deploy to Android:
-
To setup the environment for Android, follow the instructions here
-
If you want to use Firebase Cloud Messaging (= push notifications), you need to generate and save two files:
google-services.json(https://support.google.com/firebase/answer/7015592?hl=en) toapp/android/app/push-firebase-admin.json(https://firebase.google.com/docs/admin/setup?hl=de#initialize_the_sdk_in_non-google_environments) outside the repository, so there is no chance to commit it accidentally. You can change the path in.env.localviaCONF_pushFirebaseKeyPath. Default is same level as the repository.
-
Build:
npm run build
-
Sync:
npx cap sync
-
Compile and deploy to device (Android device must be connected - maybe first start Android Studio once and make sure that debug mode on device is activated, then connect device with Android Studio):
npx cap run android
-
-
Deploy to iOS: Coming soon (but it's nearly the same like Android)
-
To see the console output of the app: Use Chrome and type
chrome://inspect/ -
Make changes to app config (if you want): See
app/public/assets/config.json
That's it. Basically, it is of course advisable to familiarize yourself with Ionic and Capacitor. Many problems encountered during compilation have already been discussed and, in the best case, solved somewhere in those forums.