Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial commit for Cors System Organisation #801

Open
wants to merge 20 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added vendor/corssystem/corssystem.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
129 changes: 129 additions & 0 deletions vendor/corssystem/cortu-gen-2.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
name: CorTU Genesis 2 # Device name can not contain the vendor name
description: Remote Data Acquisition Board with Support for LoRaWAN and RF

# Hardware versions (optional, use when you have revisions)
hardwareVersions:
- version: '1.0'
numeric: 1
- version: '1.0-rev-A'
numeric: 2

# Firmware versions (at least one is mandatory)
firmwareVersions:
- # Firmware version
version: '1.0'
numeric: 1
# Corresponding hardware versions (optional)
hardwareVersions:
- '1.0'

# Firmware features (optional)
# Valid values are: remote rejoin (trigger a join from the application layer), transmission interval (configure how
# often he device sends a message).
features:
- remote rejoin
- transmission interval

# LoRaWAN Device Profiles per region
# Supported regions are EU863-870, US902-928, AU915-928, AS923, CN779-787, EU433, CN470-510, KR920-923, IN865-867,
# RU864-870
profiles:
EU863-870:
# Optional identifier of the vendor of the profile. When you specify the vendorID, the profile is loaded from
# the vendorID's folder. This allows you to reuse profiles from module or LoRaWAN end device stack vendors.
# If vendorID is empty, the current vendor ID is used. In this example, the vendorID is the current vendor ID,
# which is verbose.
# vendorID: example
# Identifier of the profile (lowercase, alphanumeric with dashes, max 36 characters)
id: cortugenesis-profile
# lorawanCertified: true
codec: cortugenesis-codec
US902-928:
id: cortugenesis-profile
# lorawanCertified: true
codec: cortugenesis-codec

# Type of device (optional)
# Valid values are: devkit, module, cots
deviceType: cots

# Sensors that this device features (optional)
# Valid values are:
# 4-20 ma, accelerometer, altitude, analog input, auxiliary, barometer, battery, button, bvoc, co, co2, conductivity, current, digital input,
# digital output, dissolved oxygen, distance, dust, energy, gps, gyroscope, h2s, hall effect, humidity, iaq, infrared, leaf wetness, level,
# light, lightning, link, magnetometer, moisture, motion, nfc, no, no2, o3, occupancy, optical meter, particulate matter, ph, pir,
# pm2.5, pm10, potentiometer, power, precipitation, pressure, proximity, pulse count, pulse frequency, radar, rainfall, reed switch, rssi,
# sap flow, smart valve, smoke, snr, so2, solar radiation, sound, strain, surface temperature, switch, temperature, tilt, time, turbidity,
# tvoc, uv, vapor pressure, velocity, vibration, voltage, water potential, water, weight, wifi ssid, wind direction, wind speed.
sensors:
- 4-20 ma
- analog input
- digital input

# Additional radios that this device has (optional)
# Valid values are: ble, nfc, wifi, cellular.
# additionalRadios:
# - ble
# - cellular

# Bridge interfaces (optional)
# Valid values are: modbus, m-bus, can bus, rs-485, sdi-12, analog, ethernet.
bridgeInterfaces:
- modbus
- rs-485
- analog

# Dimensions in mm (optional)
# Use width, height, length and/or diameter
dimensions:
width: 110
length: 110
height: 40

# Weight in grams (optional)
weight: 350

# Operating conditions (optional)
operatingConditions:
# Temperature (Celsius)
temperature:
min: -30
max: 85
# Relative humidity (fraction of 1)
relativeHumidity:
min: 0
max: 0.97

# IP rating (optional)
# ipCode: IP64

# Key provisioning (optional)
# Valid values are: custom (user can configure keys), join server and manifest.
keyProvisioning:
- custom
- join server

# Key programming (optional)
# Valid values are: bluetooth, nfc, wifi, ethernet (via a webpage), serial (when the user has a serial interface to set the keys)
# and firmware (when the user should change the firmware to set the keys).
keyProgramming:
- serial
- firmware

# Key security (optional)
# Valid values are: none, read protected and secure element.
keySecurity: none

# Firmware programming (optional)
# Valid values are: serial (when the user has a serial interface to update the firmware), ethernet, fuota lorawan (when the device
# supports LoRaWAN FUOTA via standard interfaces) and fuota other (other wireless update mechanism).
firmwareProgramming:
- serial
# - fuota lorawan

# Product and data sheet URLs (optional)
productURL: https://corssystem.com/products/cortu-v1-0/

# Photos
photos:
main: cortugenesis.png # Image needs to have a transparent background
3 changes: 3 additions & 0 deletions vendor/corssystem/cortugenesis-codec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Uplink decoder decodes binary data uplink into a JSON object (optional)
uplinkDecoder:
fileName: cortugenesis.js
17 changes: 17 additions & 0 deletions vendor/corssystem/cortugenesis-profile.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# LoRaWAN MAC version: 1.0, 1.0.1, 1.0.2, 1.0.3, 1.0.4 or 1.1
macVersion: '1.0.3'
regionalParametersVersion: 'RP001-1.0.3-RevA'

# Whether the end device supports join (OTAA) or not (ABP)
supportsJoin: true

# Maximum EIRP
maxEIRP: 16
# Whether the end device supports 32-bit frame counters
supports32bitFCnt: false

# Whether the end device supports class B
supportsClassB: false

# Whether the end device supports class C
supportsClassC: false
174 changes: 174 additions & 0 deletions vendor/corssystem/cortugenesis.js
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file contains the decoder for the example wind sensor as well. Do you have your own decoder or?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've made all the changes

Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@

//CorTU Payload Formatter
function parseIncomingData(data) {
const delimiter = "\n";
// No need to use TextDecoder as data is already a string
const dataSets = data.split(delimiter).filter(Boolean);
return dataSets.map(dataSet => {
const [huffmanCodesStr, encodedData] = dataSet.split("|");
const huffmanCodesArray = huffmanCodesStr.trim().split(';'); // Assuming ';' separates the codes
const huffmanCodes = {};
for (let i = 0; i <(huffmanCodesArray.length)-1; i++) {
const [key, value] = huffmanCodesArray[i].split(':');
huffmanCodes[key] = value;
}
return {
huffmanCodes,
encodedData
};
});
}
// Function to decode a Huffman-encoded bit stream using the provided codes
function decodeHuffman(encodedData, huffmanCodes) {
let decodedMessage = [];
let currentCode = "";
// Iterate over each bit in the encoded data
for (let bit of encodedData) {
currentCode += bit;
// Check if the current code matches any entry in the Huffman codes
if (currentCode in huffmanCodes) {
// Append the corresponding character to the decoded message
decodedMessage.push(huffmanCodes[currentCode]);
// Reset the current code
currentCode = "";
}
}
// Return the decoded message as a string
return decodedMessage.join('');
}
function lppDecode(bytes) {
var sensor_types = {
0 : {'size': 1, 'name': 'digital_in', 'signed': false, 'divisor': 1},
1 : {'size': 1, 'name': 'digital_out', 'signed': false, 'divisor': 1},
2 : {'size': 2, 'name': 'analog_in', 'signed': true , 'divisor': 100},
3 : {'size': 2, 'name': 'analog_out', 'signed': true , 'divisor': 100},
100: {'size': 4, 'name': 'generic', 'signed': false, 'divisor': 1},
101: {'size': 2, 'name': 'illuminance', 'signed': false, 'divisor': 1},
102: {'size': 1, 'name': 'presence', 'signed': false, 'divisor': 1},
103: {'size': 2, 'name': 'temperature', 'signed': true , 'divisor': 10},
104: {'size': 1, 'name': 'humidity', 'signed': false, 'divisor': 2},
113: {'size': 6, 'name': 'accelerometer', 'signed': true , 'divisor': 1000},
115: {'size': 2, 'name': 'barometer', 'signed': false, 'divisor': 10},
116: {'size': 2, 'name': 'voltage', 'signed': false, 'divisor': 100},
117: {'size': 2, 'name': 'current', 'signed': false, 'divisor': 1000},
118: {'size': 4, 'name': 'frequency', 'signed': false, 'divisor': 1},
120: {'size': 1, 'name': 'percentage', 'signed': false, 'divisor': 1},
121: {'size': 2, 'name': 'altitude', 'signed': true, 'divisor': 1},
125: {'size': 2, 'name': 'concentration', 'signed': false, 'divisor': 1},
128: {'size': 2, 'name': 'power', 'signed': false, 'divisor': 1},
130: {'size': 4, 'name': 'distance', 'signed': false, 'divisor': 1000},
131: {'size': 4, 'name': 'energy', 'signed': false, 'divisor': 1000},
132: {'size': 2, 'name': 'direction', 'signed': false, 'divisor': 1},
133: {'size': 4, 'name': 'time', 'signed': false, 'divisor': 1},
134: {'size': 6, 'name': 'gyrometer', 'signed': true , 'divisor': 100},
135: {'size': 3, 'name': 'colour', 'signed': false, 'divisor': 1},
136: {'size': 9, 'name': 'gps', 'signed': true, 'divisor': [10000,10000,100]},
142: {'size': 1, 'name': 'switch', 'signed': false, 'divisor': 1},
};
function arrayToDecimal(stream, is_signed, divisor) {
var value = 0;
for (var i = 0; i < stream.length; i++) {
if (stream[i] > 0xFF)
throw 'Byte value overflow!';
value = (value << 8) | stream[i];
}
if (is_signed) {
var edge = 1 << (stream.length) * 8; // 0x1000..
var max = (edge - 1) >> 1; // 0x0FFF.. >> 1
value = (value > max) ? value - edge : value;
}
value /= divisor;
return value;
}
var sensors = [];
var i = 0;
while (i < bytes.length) {
var s_no = bytes[i++];
var s_type = bytes[i++];
if (typeof sensor_types[s_type] == 'undefined') {
throw 'Sensor type error!: ' + s_type;
}
var s_value = 0;
var type = sensor_types[s_type];
switch (s_type) {
case 113: // Accelerometer
case 134: // Gyrometer
s_value = {
'x': arrayToDecimal(bytes.slice(i+0, i+2), type.signed, type.divisor),
'y': arrayToDecimal(bytes.slice(i+2, i+4), type.signed, type.divisor),
'z': arrayToDecimal(bytes.slice(i+4, i+6), type.signed, type.divisor)
};
break;
case 136: // GPS Location
s_value = {
'latitude': arrayToDecimal(bytes.slice(i+0, i+3), type.signed, type.divisor[0]),
'longitude': arrayToDecimal(bytes.slice(i+3, i+6), type.signed, type.divisor[1]),
'altitude': arrayToDecimal(bytes.slice(i+6, i+9), type.signed, type.divisor[2])
};
break;
case 135: // Colour
s_value = {
'r': arrayToDecimal(bytes.slice(i+0, i+1), type.signed, type.divisor),
'g': arrayToDecimal(bytes.slice(i+1, i+2), type.signed, type.divisor),
'b': arrayToDecimal(bytes.slice(i+2, i+3), type.signed, type.divisor)
};
break;
default: // All the rest
s_value = arrayToDecimal(bytes.slice(i, i + type.size), type.signed, type.divisor);
break;
}
sensors.push({
'channel': s_no,
'type': s_type,
'name': type.name,
'value': s_value
});
i += type.size;
}
return sensors;
}
// Convert hex string to byte array
function hexStringToByteArray(hexString) {
var bytes = [];
for (var i = 0; i < hexString.length; i += 2) {
bytes.push(parseInt(hexString.substr(i, 2), 16));
}
return bytes;
}
function decodeUplink(input)
{
// Convert decimal bytes to ASCII string
var dataString = '';
for (var i = 0; i < input.bytes.length; i++) {
dataString += String.fromCharCode(input.bytes[i]);
}
//Separate the Huffman codes from the encoded data
let dataset = parseIncomingData(dataString);
// Initialize an object to store all Huffman codes
let allHuffmanCodes = {};
// Merge all Huffman codes into one object
dataset.forEach(item => {
Object.assign(allHuffmanCodes, item.huffmanCodes);
});
//String to store encoded (compressed) data
let allencodedData = dataset.map(item => item.encodedData).join('');
//Decoding the data
let decodedData = decodeHuffman(allencodedData, allHuffmanCodes);
//Converting string data output from Huffman encode to hex byte array hex
let bytes = hexStringToByteArray(decodedData);
// flat output (like original Cayenne lpp decoder):
var response = {};
lppDecode(bytes, 1).forEach(function(field) {
response[field.name + '_' + field.channel] = field.value;
});
return {
data: {
bytes: input.bytes,
decoded: {
message: response
}
},
warnings: [],
errors: []
};
}
Binary file added vendor/corssystem/cortugenesis.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions vendor/corssystem/index.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
endDevices:
- cortu-gen-2
11 changes: 11 additions & 0 deletions vendor/index.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2070,6 +2070,16 @@ vendors:
linkedin: https://www.linkedin.com/company/cicicom-ltd
facebook: https://www.facebook.com/cicicom


- id: corssystem
name: Cors System Technologies
description: Cors System Technologies is a Nigerian digital transformation company, with a focus in bringing existing industries into Industry 4.0.
logo: corssystem.png
website: https://corssystem.com/
social:
linkedin: https://www.linkedin.com/company/cors-system/
github: corssystem

- id: atomsenses
name: Atomsenses
description: Atomsenses is a specialist IoT solution provider focusing on Lorawan sensors for indoor air quality monitoring, our vision is to transform how we manage and maintain healthy indoor environments.
Expand All @@ -2088,3 +2098,4 @@ vendors:
social:
facebook: https://www.facebook.com/jooby.eu
github: jooby-dev

Loading