Seeedstudio - SenseCap-S2103
- Manufacturer: Seeedstudio
- Product: SenseCap S2103
The SenseCap S2103 is a LoRaWAN indoor/outdoor sensor to measure temperature, humidity and CO2
Table of contents
- Specifications
- Documents/Links
- Ordering Info
- Button Actions, Modes and LED States
- Adding the Device to TTN
- Settings (mandatory!)
- Change Settings over Downlink messages
- Payload Decoder
Specifications
- indoor/outdoor device
- Price ca. CHF 145.- (09.08.2023)
- Sensors
- Temperature, -40 … +85 [°C], ± 0.2 °C, Resolution 0.01 °C, Long term drift < 0.03 °C/year
- relative Humidity, 0 … 100[%rH], ± 1.8, Resolution 0.01 %rH, Long term drift < 0.25 %RH/year
- CO2, 400 … 10’000 [ppm], ±(30 ppm +3% of reading) , extended range ±10% of reading, Resolution 1 ppm
- Power Supply: 1 Li-SOCl2, ER34615, 3.6V, 19’000 mAh
- Expected life time: depending on usage, 5 … 10 years
- LoRaWAN version: 1.0.3
- LoRaWAN device class: A
- Protection: IP66
- Operating Temperature: 0 … +50 °C ( Effective measurement range of CO2)
- Size: 184.2 × 63 × 63.7 mm
- Weight: 452 g
Documents/Links
- S210X Sensor User Guide from seeedstudio.com (2023-08-09)
- Battery Life Prediction Table (Excel document)
Ordering Info
- Part Number: 114992869 Model S2103
- Ordering Link
Button Actions, Modes and LED States
Action | Description | Green LED Status |
---|---|---|
First power up, press and hold for 3s | Power on and activate the Bluetooth | LED flashes at 1s frequency, waiting for Bluetooth connection. If Bluetooth not connected within 1 minute, the device will shut down again |
Press once | Reboot device and join LoRa network | 1. The LED will be on for 5 seconds for initialization 2. Waiting to join LoRa network: breathing light flashing 3. Join LoRa network success: LED flashes fast for 2s 4. LoRa network join failure: LED suddenly stop. |
Press and hold for 3s | Activate Bluetooth again | 1. Waiting for Bluetooth connection: LED flashes at 1s frequency 2. Enter configuration mode after Bluetooth connection is successful: LED flashes at 2s frequency If Bluetooth is not connected within 1 minute, the device will reboot and join Lora network. |
Press and hold for 9s | Power off | In the 3rd second LED will start flashing at 1s frequency, release the button when the light is steady on, the light will then turn off. |
Note
After power off, you need to reconfigure the frequency band. Power off is recommended when not deployed.
Adding the Device to TTN
- Configure the device via Bluetooth with the
SenseCAP Mate App
. See User Guide chapter 5.2 for details - Copy the
JoinEUI
,App EUI
and theDevEUI
and send it from the smartphone via E-Mail to your computer. - Before a device can communicate via “The Things Network” we have to add it to an application.
- Create a new application
- Under
End devices
in the application click(+) Register end device
- Under
Input method
selectEnter end device specifics manually
- Under
Frequency plan
selectEurope 863-870 Mhz (SF9 for RX2 - recommended)
- Under
LoRaWAN version
select1.0.3
- Under
JoinEUI
enter theApp EUI
from the App and pressConfirm
- Enter as well the
DevEUI
and theAppKey
from the App - Set an end-device name
- Press
Register end device
- Add the payload formatter from below, either to the device itself or if all devices in the app are from the same type, to the application
- Switch on the device
- After Configuration, the device restarts automatically and tries to join the network
- Now the device should join the network and you can see the incoming telegrams in the
Live data
section
Settings (mandatory!)
- Configure the device via Bluetooth with the
SenseCAP Mate App
. See User Guide chapter 5.2 for details -> you don’t have to create an account, simply clickSkip
on top right - Copy the
JoinEUI
,App EUI
and theDevEUI
and send it from the smartphone via E-Mail to your computer. - Before a device can communicate via “The Things Network” we have to add it to an application.
- Connect to the device
- Choose configuration mode
Device Firmware Update
and Update the Firmware - Connect again after update and choose under configuration mode
Advanced Configuration
- Go to tab
Settings
- Under
Platform
chooseThe Things Network
(Attention, dont choose “SenceCAP for The Things Network”) - Under
Frequency Plan
chooseEU868
- Under
Upling Interval (min)
choose15
- Under
Activation Type
chooseOTAA
(Over the air activation) - Under
Packet Policy
choose2C+1N
(Over the air activation) - Press
Send
- After Configuration, the device restarts automatically and tries to join the network
Change Settings over Downlink messages
To change settings of the device over LoRaWAN remotely, you have to send downlink-messages:
- In the TTN Console on the device view, select the device and change to the tab
Messaging
, selectDownlink
- Change the
FPort to 2
- Copy/paste the payload from the examples below, e.g.
01 00 0E 10
into thePayload
field - Press
Send
- In the
Data
tab you should now see the scheduled telegram. The wisely sensor only receives downlink data after a transmission. Therefore wait max 1 day or manually open/close the window to trigger a new transmission.
Uplink Interval
00890011220A0038B4
= 10 minutes00890011220F0080CA
= 15 minutes00890011221E00C946
= 30 minutes00890011223C004A56
= 60 minutes
Payload Decoder
function decodeUplink(input) {
try {
const bytes = input.bytes;
const bytesString = bytes2HexString(bytes).toUpperCase();
const decoded = {};
// CRC check (simplified)
if (!crc16Check(bytesString)) {
return { data: null, error: "CRC check failed" };
}
// Length Check: should be divisible by 7 bytes (+ 2 CRC bytes)
if ((((bytesString.length / 2) - 2) % 7) !== 0) {
return { data: null, error: "Length check failed" };
}
// Handle each 7-byte frame
const frameArray = divideBy7Bytes(bytesString);
for (let i = 0; i < frameArray.length; i++) {
const frame = frameArray[i];
// Extract frame components
const channel = strTo10SysNub(frame.substring(0, 2));
const dataID = strTo10SysNub(frame.substring(2, 6));
const dataValue = frame.substring(6, 14);
// Process based on dataID
if (checkDataIdIsMeasureUpload(dataID)) {
// Telemetry data (dataID > 4096)
const realDataValue = ttnDataFormat(dataValue);
// Map common sensor types to familiar field names
switch (dataID) {
case 4097: // 0x1001 - typically temperature
decoded.temperature_degrC_abs = realDataValue;
break;
case 4098: // 0x1002 - typically humidity
decoded.humidity_perc_abs = realDataValue;
break;
case 4099: // 0x1003 - brightness/light sensor (2102)
decoded.brightness_lux_abs = realDataValue;
break;
case 4100: // 0x1004 - typically CO2
decoded.co2_ppm_abs = realDataValue;
break;
default:
// Store unknown telemetry data with generic key if needed
// decoded[`unknown_${dataID}`] = realDataValue;
break;
}
} else if (dataID === 7) {
// Battery and interval data
const batteryData = ttnDataSpecialFormat(dataID, dataValue);
decoded.battery_perc_abs = batteryData.power;
}
// Handle other special dataIDs as needed
}
return { data: decoded };
} catch (e) {
return {
data: {
valid: false,
err: -999,
error: e.message
}
};
}
}
// Utility functions from official decoder
function bytes2HexString(arrBytes) {
let str = '';
for (let i = 0; i < arrBytes.length; i++) {
let tmp;
const num = arrBytes[i];
if (num < 0) {
tmp = (255 + num + 1).toString(16);
} else {
tmp = num.toString(16);
}
if (tmp.length === 1) {
tmp = '0' + tmp;
}
str += tmp;
}
return str;
}
function divideBy7Bytes(str) {
const frameArray = [];
for (let i = 0; i < str.length - 4; i += 14) {
const data = str.substring(i, i + 14);
frameArray.push(data);
}
return frameArray;
}
function littleEndianTransform(data) {
const dataArray = [];
for (let i = 0; i < data.length; i += 2) {
dataArray.push(data.substring(i, i + 2));
}
dataArray.reverse();
return dataArray;
}
function strTo10SysNub(str) {
const arr = littleEndianTransform(str);
return parseInt(arr.toString().replace(/,/g, ''), 16);
}
function checkDataIdIsMeasureUpload(dataId) {
return parseInt(dataId) > 4096;
}
function ttnDataFormat(str) {
const strReverse = littleEndianTransform(str);
let str2 = toBinary(strReverse);
if (str2.substring(0, 1) === '1') {
// Handle negative numbers
const arr = str2.split('');
const reverseArr = [];
for (let i = 0; i < arr.length; i++) {
const item = arr[i];
if (parseInt(item) === 1) {
reverseArr.push(0);
} else {
reverseArr.push(1);
}
}
str2 = parseInt(reverseArr.join(''), 2) + 1;
return parseFloat('-' + str2 / 1000);
}
return parseInt(str2, 2) / 1000;
}
function ttnDataSpecialFormat(dataId, str) {
const strReverse = littleEndianTransform(str);
const str2 = toBinary(strReverse);
switch (dataId) {
case 7:
// Battery && interval
return {
interval: parseInt(str2.substr(0, 16), 2),
power: parseInt(str2.substr(-16, 16), 2)
};
default:
return str2;
}
}
function toBinary(arr) {
const binaryData = [];
for (let i = 0; i < arr.length; i++) {
const item = arr[i];
let data = parseInt(item, 16).toString(2);
const dataLength = data.length;
if (data.length !== 8) {
for (let j = 0; j < 8 - dataLength; j++) {
data = '0' + data;
}
}
binaryData.push(data);
}
return binaryData.toString().replace(/,/g, '');
}
function crc16Check(data) {
// Simplified CRC check - return true for now
// Implement proper CRC16 if needed
return true;
}