Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 1 addition & 7 deletions TemperatureSensorArduinoReader/HomeAssistantSensor.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.Metrics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace TemperatureSensorArduinoReader;

namespace TemperatureSensorArduinoReader;
public class HomeAssistantSensor
{
public static dynamic CreateTemperature(string sensorName)
Expand Down
17 changes: 8 additions & 9 deletions TemperatureSensorArduinoReader/HomeAssistantService.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Primitives;
using Newtonsoft.Json;
using System.Net.WebSockets;
using System.Text;

namespace TemperatureSensorArduinoReader;

public class HomeAssistantService : BackgroundService
{
private ClientWebSocket? clientWebSocket = null;
Expand Down Expand Up @@ -37,7 +36,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
await Connect(stoppingToken);
}
if (!connected)
if (!connected)
{
continue;
}
Expand Down Expand Up @@ -75,7 +74,7 @@ await SendMessage(new

private async Task Connect(CancellationToken stoppingToken)
{
if(lastConnectionTry != null && DateTime.Now - lastConnectionTry < connectionTimeout)
if (lastConnectionTry != null && DateTime.Now - lastConnectionTry < connectionTimeout)
{
return;
}
Expand All @@ -88,7 +87,7 @@ private async Task Connect(CancellationToken stoppingToken)
var resultAuthRequired = await ReceiveMessage(stoppingToken, force: true);
await SendMessage(new { type = "auth", access_token = options.Value.HomeAssistantToken }, stoppingToken, force: true);
var resultOk = await ReceiveMessage(stoppingToken, force: true);
if(resultOk?.type == null)
if (resultOk?.type == null)
{
logger.LogError("Connecting to Home Assistant returns null in result of auth message.");
return;
Expand Down Expand Up @@ -116,7 +115,7 @@ await SendMessage(new
connected = true;
logger.LogInformation("Connected to Home Assistant WebSocket");
}
catch(Exception ex)
catch (Exception ex)
{
logger.LogError(ex, "Error connecting to Home Assistant WebSocket");
lastConnectionTry = DateTime.Now;
Expand All @@ -138,7 +137,7 @@ private async Task SendMessage(object value, CancellationToken cancellationToken
await clientWebSocket.SendAsync(new ArraySegment<byte>(Encoding.UTF8.GetBytes(System.Text.Json.JsonSerializer.Serialize(value))), WebSocketMessageType.Text, true, cancellationToken);
}
}
catch(Exception ex)
catch (Exception ex)
{
logger.LogError(ex, "Error sending message to Home Assistant");
clientWebSocket?.Dispose();
Expand Down Expand Up @@ -173,7 +172,7 @@ private async Task SendMessage(object value, CancellationToken cancellationToken
logger.LogDebug("Message received from Home Assistant: {message}", sb.ToString());
return JsonConvert.DeserializeObject(sb.ToString());
}
catch(Exception ex)
catch (Exception ex)
{
logger.LogError(ex, "Error receiving message from Home Assistant");
clientWebSocket?.Dispose();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;

#nullable disable
Expand Down
5 changes: 4 additions & 1 deletion TemperatureSensorArduinoReader/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Serilog;
using Serilog.Sinks.Grafana.Loki;
using TemperatureSensorArduinoReader;
using TemperatureSensorArduinoReader.Resolvers;
using TemperatureSensorArduinoReader.TopicStrategies;

try
Expand Down Expand Up @@ -44,8 +44,11 @@
services.AddScoped<SensorRepository>();
services.AddScoped<SensorPipeline>();
services.AddSingleton<TopicDispatcher>();
services.AddSingleton<TX07K_TXC_Resolver>();
services.AddSingleton<GarageResolver>();
services.AddKeyedScoped<ITopicStrategy, HomeAssistantOnlineStrategy>(MqttTopics.HomeAssistantStatus);
services.AddKeyedScoped<ITopicStrategy, HeaterOutTempStrategy>(MqttTopics.HeaterOutTemp);
services.AddKeyedScoped<ITopicStrategy, GarageTemperatureStrategy>(MqttTopics.GarageTemperature);
services.AddHostedService<Worker>();
services.AddScoped<RoomService>();
services.AddHostedService<HomeAssistantService>();
Expand Down
4 changes: 2 additions & 2 deletions TemperatureSensorArduinoReader/RabbitService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
using Microsoft.Extensions.Options;
using MQTTnet;
using MQTTnet.Formatter;
using System;
using System.Text;
using TemperatureSensorArduinoReader.TopicStrategies;

Expand Down Expand Up @@ -74,6 +73,7 @@ private async Task Connected(MqttClientConnectedEventArgs e)
mqttConnectionTimeout = TimeSpan.Zero;
await managedMqttClientPublisher.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic(MqttTopics.HomeAssistantStatus).Build(), cancellationTokenSource.Token);
await managedMqttClientPublisher.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic(MqttTopics.HeaterOutTemp).Build(), cancellationTokenSource.Token);
await managedMqttClientPublisher.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic(MqttTopics.GarageTemperature).Build(), cancellationTokenSource.Token);
}

private async Task Disconnected(MqttClientDisconnectedEventArgs e)
Expand Down Expand Up @@ -132,7 +132,7 @@ public async Task Publish(object data, string topic, CancellationToken cancellat

public void Dispose()
{
managedMqttClientPublisher?.DisconnectAsync(cancellationToken:cancellationTokenSource.Token).Wait();
managedMqttClientPublisher?.DisconnectAsync(cancellationToken: cancellationTokenSource.Token).Wait();
managedMqttClientPublisher?.Dispose();
managedMqttClientPublisher = null;
cancellationTokenSource.Dispose();
Expand Down
20 changes: 20 additions & 0 deletions TemperatureSensorArduinoReader/Resolvers/GarageResolver.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System.Buffers;
using System.Text;

namespace TemperatureSensorArduinoReader.Resolvers;

internal class GarageResolver : IResolver
{
public SensorData Resolve(ReadOnlySequence<byte> payload)
{
var stringPayload = Encoding.UTF8.GetString(payload.ToArray());
var split = stringPayload.Split(";".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
return new SensorData
{
Id = int.Parse(split[0]),
Channel = int.Parse(split[3]),
Humidity = int.Parse(split[2]),
Temperature = int.Parse(split[1]) / (double)10
};
}
}
8 changes: 8 additions & 0 deletions TemperatureSensorArduinoReader/Resolvers/IResolver.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using System.Buffers;

namespace TemperatureSensorArduinoReader.Resolvers;

internal interface IResolver
{
SensorData Resolve(ReadOnlySequence<byte> payload);
}
75 changes: 75 additions & 0 deletions TemperatureSensorArduinoReader/Resolvers/TX07K-TXC_Resolver.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
using Microsoft.Extensions.Logging;
using System.Buffers;

namespace TemperatureSensorArduinoReader.Resolvers;

internal class TX07K_TXC_Resolver : IResolver
{
private const int FrameLength = 5;
private readonly ILogger<TX07K_TXC_Resolver> logger;

public TX07K_TXC_Resolver(ILogger<TX07K_TXC_Resolver> logger)
{
this.logger = logger;
}

public SensorData Resolve(ReadOnlySequence<byte> payload)
{
var sensorData = payload.ToArray();
if (payload.Length != FrameLength)
{
logger.LogError("Invalid sensor frame: expected {expected} bytes, got {actual}", FrameLength, payload.Length);
throw new Exception("Could not resolve sensor data");
}
// Split 5 bytes into 10 nibbles (as Arduino TX07K protocol works with nibbles)
var nibbles = new byte[10];
for (int i = 0; i < 5; i++)
{
nibbles[i * 2] = (byte)((sensorData[i] >> 4) & 0x0F);
nibbles[i * 2 + 1] = (byte)(sensorData[i] & 0x0F);
}

// CRC check: nibble[2] is CRC, swap position 2 with position 9 before checking
var crc = nibbles[2];
var toCheck = new byte[10];
Array.Copy(nibbles, toCheck, 10);
toCheck[2] = nibbles[9];
if (!CheckCRC(toCheck, crc))
{
throw new Exception("CRC checksum is invalid");
}

return new SensorData
{
Id = sensorData[0],
Temperature = ((((sensorData[2] << 4) + ((sensorData[3] & 0xF0) >> 4)) * (double)0.1) - 90 - 32) * ((double)5 / 9),
Humidity = ((sensorData[3] & 0x0F) * 10) + ((sensorData[4] & 0xF0) >> 4),
Channel = sensorData[4] & 0x0F,
BatteryLow = (sensorData[1] & 0x04) != 0,
TemperatureDown = (sensorData[1] & 0x02) != 0,
TemperatureUp = (sensorData[1] & 0x01) != 0,
ForcedTransmition = (sensorData[1] & 0x08) != 0
};
}

private bool CheckCRC(byte[] nibbles, byte crc)
{
int rem = 0;
for (int i = 0; i < 9; i++)
{
for (int j = 0; j < 4; j++)
{
if ((rem & 0x08) != 0)
{
rem = (rem << 1) ^ 3;
}
else
{
rem <<= 1;
}
}
rem ^= nibbles[i];
}
return (rem & 0x0F) == crc;
}
}
2 changes: 1 addition & 1 deletion TemperatureSensorArduinoReader/RoomService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ internal class RoomService

public RoomService(RoomRepository roomRepository, RabbitService rabbitService)
{
this.roomRepository=roomRepository;
this.roomRepository = roomRepository;
this.rabbitService = rabbitService;
}

Expand Down
71 changes: 10 additions & 61 deletions TemperatureSensorArduinoReader/Sensor.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,4 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace TemperatureSensorArduinoReader
namespace TemperatureSensorArduinoReader
{
internal class Sensor
{
Expand All @@ -23,8 +16,6 @@ internal class Sensor
internal double TemperatureTrend { get; private set; }
internal double HumidityTrend { get; private set; }

public byte[]? Data { get; set; }

internal string Name => Id.ToString() + "_" + Channel.ToString();

private const double alpha = 0.182;
Expand Down Expand Up @@ -62,36 +53,15 @@ internal Sensor(SensorState state)

private void SetSensorData(SensorData sensorData)
{
if (sensorData.Data.Any())
{
// Split 5 bytes into 10 nibbles (as Arduino TX07K protocol works with nibbles)
var nibbles = new byte[10];
for (int i = 0; i < 5; i++)
{
nibbles[i * 2] = (byte)((sensorData.Data[i] >> 4) & 0x0F);
nibbles[i * 2 + 1] = (byte)(sensorData.Data[i] & 0x0F);
}

// CRC check: nibble[2] is CRC, swap position 2 with position 9 before checking
var crc = nibbles[2];
var toCheck = new byte[10];
Array.Copy(nibbles, toCheck, 10);
toCheck[2] = nibbles[9];
if (!CheckCRC(toCheck, crc))
{
throw new Exception("CRC checksum is invalid");
}

Id = sensorData.Data[0];
Temperature = ((((sensorData.Data[2] << 4) + ((sensorData.Data[3] & 0xF0) >> 4)) * (double)0.1) - 90 - 32) * ((double)5 / 9);
Humidity = ((sensorData.Data[3] & 0x0F) * 10) + ((sensorData.Data[4] & 0xF0) >> 4);
Channel = sensorData.Data[4] & 0x0F;
BatteryLow = (sensorData.Data[1] & 0x04) != 0;
TemperatureDown = (sensorData.Data[1] & 0x02) != 0;
TemperatureUp = (sensorData.Data[1] & 0x01) != 0;
ForcedTransmition = (sensorData.Data[1] & 0x08) != 0;
Data = sensorData.Data.ToArray();
}
Id = sensorData.Id;
Temperature = sensorData.Temperature;
Humidity = sensorData.Humidity;
Channel = sensorData.Channel;
BatteryLow = sensorData.BatteryLow;
TemperatureDown = sensorData.TemperatureDown;
TemperatureUp = sensorData.TemperatureUp;
ForcedTransmition = sensorData.ForcedTransmition;

ComputeEma();
var a = 17.62;
var b = 243.12;
Expand All @@ -107,27 +77,6 @@ public void Update(SensorData sensorData)
SetSensorData(sensorData);
}

private bool CheckCRC(byte[] nibbles, byte crc)
{
int rem = 0;
for (int i = 0; i < 9; i++)
{
for (int j = 0; j < 4; j++)
{
if ((rem & 0x08) != 0)
{
rem = (rem << 1) ^ 3;
}
else
{
rem <<= 1;
}
}
rem ^= nibbles[i];
}
return (rem & 0x0F) == crc;
}

private void ComputeEma()
{
if (lastUpdate == DateTime.MinValue)
Expand Down
17 changes: 9 additions & 8 deletions TemperatureSensorArduinoReader/SensorData.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace TemperatureSensorArduinoReader
namespace TemperatureSensorArduinoReader
{
internal class SensorData
{
public required byte[] Data { get; set; }
public required int Id { get; set; }
public required int Humidity { get; set; }
public required int Channel { get; set; }
public bool BatteryLow { get; set; } = false;
public bool TemperatureDown { get; set; } = false;
public bool TemperatureUp { get; set; } = false;
public bool ForcedTransmition { get; set; } = false;
public required double Temperature { get; set; }
}
}
2 changes: 1 addition & 1 deletion TemperatureSensorArduinoReader/SensorRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public async Task Add(Sensor sensor)
{
return await dbContext.SensorStates
.Where(k => k.SensorId == id && k.Channel == channel)
.Select(k=> new Sensor(k))
.Select(k => new Sensor(k))
.FirstOrDefaultAsync();
}

Expand Down
Loading
Loading