Skip to content

Commit

Permalink
Adding MClimate CO2 Display and Fan Coil Thermostat (#698)
Browse files Browse the repository at this point in the history
* MClimate - Vicki,T-valve,Flood,Ht,AQI

* update aqi sensors

* update device names

* remove scripts

* Key security fix

* fix yaml,

* fix reseller urls

* remove scripts

* remove reseller vicki

* remove ressellerUrls

* profile

* fix sensors

* img

* change flood img

* aqi, ht img

* prettier

* prettier

* prettier

* uplink decoders, update aqi info

* prettier again...

* Pretttier again...

* new Vicki uplinkDecoder, fix versions

* return shorter decoder

* update uplink decoders

* New mclimate device CO2 sensor

* new Vicki version and decoder

* new version Vicki, new decoder ht/co2, remove AQI

* add MClimate wireless thermostat

* mclimate wireless thermostat fix image name

* MClimate wireless thermostat prettier

* vicki 4.2

* fix vicki codec

* fix Mclimate wireless thermostat decoder

* MClimate CO2 Display + Fan Coil Thermostat

* Prettier

* prettied

---------

Co-authored-by: Yordan Zaychev <[email protected]>
Co-authored-by: Johan Stokking <[email protected]>
Co-authored-by: Jaime Trinidad <[email protected]>
  • Loading branch information
4 people authored Nov 24, 2023
1 parent c0550c5 commit 6e7a481
Show file tree
Hide file tree
Showing 12 changed files with 928 additions and 0 deletions.
16 changes: 16 additions & 0 deletions vendor/mclimate/co2-display-codec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
uplinkDecoder:
fileName: co2-display.js
examples:
- description: Periodic uplink
input:
fPort: 2
bytes: [0x01, 0x02, 0x67, 0x82, 0x0B, 0x7C, 0x93, 0x20, 0x00, 0x37, 0x00]
output:
data:
sensorTemperature: 21.5
relativeHumidity: 50.78
batteryVoltage: 2.94
ppm: 1171
powerSourceStatus: 0
lux: 55
pir: false
8 changes: 8 additions & 0 deletions vendor/mclimate/co2-display-profile.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
vendorProfileID: 824
supportsClassB: false
supportsClassC: false
macVersion: 1.0.3
regionalParametersVersion: RP001-1.0.3-RevA
supportsJoin: true
maxEIRP: 16
supports32bitFCnt: true
229 changes: 229 additions & 0 deletions vendor/mclimate/co2-display.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
function decodeUplink(input) {
try{
var bytes = input.bytes;
var data = {};
const toBool = value => value == '1';
var calculateTemperature = function (rawData){return (rawData - 400) / 10};
var calculateHumidity = function(rawData){return (rawData * 100) / 256};
var decbin = function (number) {
if (number < 0) {
number = 0xFFFFFFFF + number + 1
}
number = number.toString(2);
return "00000000".substr(number.length) + number;
}
function handleKeepalive(bytes, data){
var tempHex = '0' + bytes[1].toString(16) + bytes[2].toString(16);
var tempDec = parseInt(tempHex, 16);
var temperatureValue = calculateTemperature(tempDec);
var humidityValue = calculateHumidity(bytes[3]);
var batteryHex = '0' + bytes[4].toString(16) + bytes[5].toString(16);
var batteryVoltageCalculated = parseInt(batteryHex, 16)/1000;
var temperature = temperatureValue;
var humidity = humidityValue;
var batteryVoltage = batteryVoltageCalculated;
var ppmBin = decbin(bytes[6]);
var ppmBin2 = decbin(bytes[7]);
ppmBin = `${ppmBin2.slice(0,5)}${ppmBin}`
var ppm = parseInt(ppmBin, 2);
var powerSourceStatus = ppmBin2.slice(5,8);
var lux = parseInt('0' + bytes[8].toString(16) + bytes[9].toString(16), 16);
var pir = toBool(bytes[10]);
data.sensorTemperature = Number(temperature.toFixed(2));
data.relativeHumidity = Number(humidity.toFixed(2));
data.batteryVoltage = Number(batteryVoltage.toFixed(3));
data.ppm = ppm;
data.powerSourceStatus = parseInt(powerSourceStatus,2);
data.lux = lux;
data.pir = pir;
return data;
}

function handleResponse(bytes, data){
var commands = bytes.map(function(byte){
return ("0" + byte.toString(16)).substr(-2);
});
commands = commands.slice(0,-11);
var command_len = 0;

commands.map(function (command, i) {
switch (command) {
case '04':
{
command_len = 2;
var hardwareVersion = commands[i + 1];
var softwareVersion = commands[i + 2];
data.deviceVersions = { hardware: Number(hardwareVersion), software: Number(softwareVersion) };
}
break;
case '12':
{
command_len = 1;
data.keepAliveTime = parseInt(commands[i + 1], 16);
}
break;
case '14':
{
command_len = 1;
data.childLock = toBool(parseInt(commands[i + 1], 16)) ;
}
break;
case '19':
{
command_len = 1;
var commandResponse = parseInt(commands[i + 1], 16);
var periodInMinutes = commandResponse * 5 / 60;
data.joinRetryPeriod = periodInMinutes;
}
break;
case '1b':
{
command_len = 1;
data.uplinkType = parseInt(commands[i + 1], 16) ;
}
break;
case '1d':
{
command_len = 2;
var deviceKeepAlive = 5;
var wdpC = commands[i + 1] == '00' ? false : commands[i + 1] * deviceKeepAlive + 7;
var wdpUc = commands[i + 2] == '00' ? false : parseInt(commands[i + 2], 16);
data.watchDogParams= { wdpC: wdpC, wdpUc: wdpUc } ;
}
break;
case '1f':
{
command_len = 4;
var good_medium = parseInt(commands[i + 1] +'' + commands[i + 2], 16);
var medium_bad = parseInt(commands[i + 3] +'' + commands[i + 4], 16);

data.boundaryLevels = { good_medium: Number(good_medium), medium_bad: Number(medium_bad) } ;
}
break;
case '21':
{
command_len = 2;
data.autoZeroValue = parseInt(commands[i + 1] +'' + commands[i + 2], 16);
}
break;
case '25':
{

command_len = 3;
var good_zone = parseInt(commands[i + 1], 16);
var medium_zone = parseInt(commands[i + 2], 16);
var bad_zone = parseInt(commands[i + 3], 16);

data.measurementPeriod = { good_zone: Number(good_zone), medium_zone: Number(medium_zone), bad_zone: Number(bad_zone) } ;
}
break;
case '2b':
{
command_len = 1;
data.autoZeroPeriod = parseInt(commands[i + 1], 16);
}
break;
case '34':
{
command_len = 1;
data.displayRefreshPeriod = parseInt(commands[i + 1], 16) ;
}
break;
case '3d':
{
command_len = 1;
data.pirSensorStatus = parseInt(commands[i + 1], 16) ;
}
break;
case '3f':
{
command_len = 1;
data.pirSensorSensitivity = parseInt(commands[i + 1], 16) ;
}
break;
case '41':
{
command_len = 1;
data.currentTemperatureVisibility = parseInt(commands[i + 1], 16) ;
}
break;
case '43':
{
command_len = 1;
data.humidityVisibility = parseInt(commands[i + 1], 16) ;
}
break;
case '45':
{
command_len = 1;
data.lightIntensityVisibility = parseInt(commands[i + 1], 16) ;
}
break;
case '47':
{
command_len = 1;
data.pirInitPeriod = parseInt(commands[i + 1], 16) ;
}
break;
case '49':
{
command_len = 1;
data.pirMeasurementPeriod = parseInt(commands[i + 1], 16) ;
}
break;
case '4b':
{
command_len = 1;
data.pirCheckPeriod = parseInt(commands[i + 1], 16) ;
}
break;
case '4d':
{
command_len = 1;
data.pirBlindPeriod = parseInt(commands[i + 1], 16) ;
}
break;
case '80':
{
command_len = 1;
data.measurementBlindTime = parseInt(commands[i + 1], 16) ;
}
break;
case '83':
{
command_len = 1;
var bin = decbin(parseInt(commands[i + 1], 16));
var chart = Number(bin[5]);
var digital_value = Number(bin[6]);
var emoji = Number(bin[7]);
data.imagesVisibility = { chart, digital_value, emoji } ;
}
break;
case 'a0':
{
command_len = 4;
var fuota_address = parseInt(`${commands[i + 1]}${commands[i + 2]}${commands[i + 3]}${commands[i + 4]}`, 16)
var fuota_address_raw = `${commands[i + 1]}${commands[i + 2]}${commands[i + 3]}${commands[i + 4]}`
data.fuota = { fuota_address, fuota_address_raw };
}
break;

default:
break;
}
commands.splice(i,command_len);
});
return data;
}
if (bytes[0] == 1) {
data = handleKeepalive(bytes, data);
}else{
data = handleResponse(bytes,data);
bytes = bytes.slice(-11);
data = handleKeepalive(bytes, data);
}
return {data: data};
} catch (e) {
throw new Error('Unhandled data');
}
}
Binary file added vendor/mclimate/co2-display.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
120 changes: 120 additions & 0 deletions vendor/mclimate/co2-display.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
name: MClimate CO2 Display
description: MClimate CO2 Display is a stand-alone CO2 sensor powered entirely by solar energy using an organic solar panel. The device features a 2.9" e-ink screen, sensor for movement (PIR), temperature and humidity sensor, LUX sensor and NDIR CO2 sensor.

# Hardware versions (optional, use when you have revisions)
hardwareVersions:
- version: '2.6'
numeric: 1

# Firmware versions (at least one is mandatory)
firmwareVersions:
- # Firmware version
version: '1.1'
numeric: 1
hardwareVersions:
- '2.6'

# 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:
# Unique identifier of the profile (lowercase, alphanumeric with dashes, max 36 characters)
id: co2-display-profile
lorawanCertified: false
codec: co2-display-codec

# Sensors that this device features (optional)
# Valid values are: accelerometer, altitude, auxiliary, barometer, battery, button, co2, distance, dust, gps, gyroscope,
# humidity, light, link, magnetometer, moisture, ph, pir, proximity, rssi, snr, sound, temperature, tvoc, velocity,
# vibration, water, wind direction and wind speed.
sensors:
- temperature
- humidity
- pir
- light
- co2
# Dimensions in mm (optional)
# Use width, height, length and/or diameter
dimensions:
width: 115
length: 23
height: 105

# Weight in grams (optional)
weight: 170

# Battery information (optional)
battery:
replaceable: true
type: AA

# Operating conditions (optional)
operatingConditions:
# Temperature (Celsius)
temperature:
min: 0
max: 50
# Relative humidity (fraction of 1)
relativeHumidity:
min: 0.00
max: 0.80

# IP rating (optional)
ipCode: IP30

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

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

# Product and data sheet URLs (optional)
productURL: https://docs.mclimate.eu/mclimate-lorawan-devices/devices/mclimate-co2-display
dataSheetURL: https://3008755228-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-McDr-jr9h3qA888r1Yp%2Fuploads%2FayhC2LiSECIybZVCubgn%2FMClimate-CO2-Display-Datasheet.pdf?alt=media&token=94ce5f00-de21-4049-9209-0ffde58f4975
# resellerURLs:
# - name: 'Connected Things'
# region:
# - United Kingdom
# url: https://connectedthings.store/
# - name: 'Concept13'
# region:
# - United Kingdom
# url: https://www.concept13.co.uk
# - name: 'RG2i'
# region:
# - France
# url: https://connectedthings.store/
# - name: 'Wideco'
# region:
# - Sweden
# url: https://wideco.se
# - name: 'iioote'
# region:
# - Sweden
# url: https://iiooote.com

# Photos
photos:
main: co2-display.png

# Regulatory compliances (optional)
compliances:
safety:
- body: IEC
norm: EN
standard: 62479:2010
radioEquipment:
- body: ETSI
norm: EN
standard: 60950-1
- body: ETSI
norm: EN
standard: 301-489
version: 2.1.1
- body: ETSI
norm: EN
standard: 300-220
version: 3.1.1
17 changes: 17 additions & 0 deletions vendor/mclimate/fan-coil-thermostat-codec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
uplinkDecoder:
fileName: fan-coil-thermostat.js
examples:
- description: Periodic uplink
input:
fPort: 2
bytes: [0x01, 0x02, 0x88, 0x75, 0x01, 0x3B, 0x01, 0x00, 0x06, 0x01, 0x01]
output:
data:
sensorTemperature: 24.8
relativeHumidity: 45.7
targetTemperature: 31.5
operationalMode: 1
displayedFanSpeed: 0
actualFanSpeed: 6
valveStatus: 1
deviceStatus: 1
Loading

0 comments on commit 6e7a481

Please sign in to comment.