Compare commits

...

27 Commits

Author SHA1 Message Date
2d564b2670 I'm trying to learn stm32cube framework :sadge: 2025-12-06 22:56:56 +01:00
5a67e90143 Changed issue 7 as well as some other tweaks
correctly set variable to set influxdb
2025-10-18 08:52:30 +02:00
224fae39f7 checked PC_CONTROL_CODES with mypy 2025-08-09 10:53:17 +02:00
45459ee231 Added constant def and return types 2025-08-09 10:37:35 +02:00
978dd0dc5d Added error catching for KeyError for Influx. 2025-07-16 17:53:18 +02:00
1f96e10f76 Added thread locking instead of variable lock 2025-07-16 17:47:42 +02:00
c5782018b8 Added a special token to avoid usage conflicts. 2025-07-16 17:16:20 +02:00
67874de37a Simple Grafana dashboard 2025-07-05 21:55:21 +02:00
582925b8a1 Finished InfluxDB integration 2025-07-02 22:43:46 +02:00
b6c91e4ac4 working with influxdb implementation 2025-07-02 21:26:06 +02:00
627cc9bc24 Fixed main docker image so that it actually works 2025-07-02 21:25:51 +02:00
2ee9a26abd Added main docker-compose file 2025-07-02 21:25:37 +02:00
7ce3f5efc2 It's working yet again 2025-07-02 14:53:15 +02:00
b45180d50d Added env files and fixed docker-compose 2025-07-02 12:44:49 +02:00
f0c90c831f Changed time interval to 1 second from 5 minutes 2025-07-02 12:39:39 +02:00
69cce7be3e Added a very simple script that just works. 2025-06-10 22:59:21 +02:00
YuruC3
18427298bc Finally got what's the difference between : and =
under environments in docker-compose.
2025-05-10 16:58:18 +02:00
YuruC3
247848285f Some more README changes under docker section 2025-05-10 13:48:58 +02:00
YuruC3
641b5d2f68 small change to README 2025-05-10 13:45:57 +02:00
YuruC3
fa4153d68d Docker is now working 2025-05-10 13:44:21 +02:00
YuruC3
964236b6b5 main.py is fully working. 2025-05-10 11:55:25 +02:00
YuruC3
5dc92e9ca4 Some changes to main README file. 2025-05-10 10:28:18 +02:00
YuruC3
6741f77615 Working with it. 2025-05-10 10:20:38 +02:00
YuruC3
a98acc9060 Sketch version of systemd.
Copied from Netflow repo.
2025-05-10 10:20:04 +02:00
YuruC3
c619488738 Segregate stuff. 2025-05-10 10:19:38 +02:00
YuruC3
a17eb70ce0 Also propably needs a MAX2323 to work. 2025-05-10 10:19:10 +02:00
YuruC3
8525da4004 Have to read some more about UART on arduino.
Propably MAX2323 is needed for TTL conversion.
2025-05-10 10:18:14 +02:00
48 changed files with 62769 additions and 206 deletions

2
.gitignore vendored
View File

@@ -1,2 +0,0 @@
STM32_CONTROL/.vscode
STM32_CONTROL/.pio

18
.vscode/c_cpp_properties.json vendored Normal file
View File

@@ -0,0 +1,18 @@
{
"configurations": [
{
"name": "linux-gcc-x64",
"includePath": [
"${workspaceFolder}/**"
],
"compilerPath": "/usr/bin/gcc",
"cStandard": "${default}",
"cppStandard": "${default}",
"intelliSenseMode": "linux-gcc-x64",
"compilerArgs": [
""
]
}
],
"version": 4
}

24
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,24 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "C/C++ Runner: Debug Session",
"type": "cppdbg",
"request": "launch",
"args": [],
"stopAtEntry": false,
"externalConsole": false,
"cwd": "/hdd/GiTea-REPO/MD1200/PC_CONTROL_CODE/CPP/noInflux",
"program": "/hdd/GiTea-REPO/MD1200/PC_CONTROL_CODE/CPP/noInflux/build/Debug/outDebug",
"MIMode": "gdb",
"miDebuggerPath": "gdb",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
]
}
]
}

59
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,59 @@
{
"C_Cpp_Runner.cCompilerPath": "gcc",
"C_Cpp_Runner.cppCompilerPath": "g++",
"C_Cpp_Runner.debuggerPath": "gdb",
"C_Cpp_Runner.cStandard": "",
"C_Cpp_Runner.cppStandard": "",
"C_Cpp_Runner.msvcBatchPath": "",
"C_Cpp_Runner.useMsvc": false,
"C_Cpp_Runner.warnings": [
"-Wall",
"-Wextra",
"-Wpedantic",
"-Wshadow",
"-Wformat=2",
"-Wcast-align",
"-Wconversion",
"-Wsign-conversion",
"-Wnull-dereference"
],
"C_Cpp_Runner.msvcWarnings": [
"/W4",
"/permissive-",
"/w14242",
"/w14287",
"/w14296",
"/w14311",
"/w14826",
"/w44062",
"/w44242",
"/w14905",
"/w14906",
"/w14263",
"/w44265",
"/w14928"
],
"C_Cpp_Runner.enableWarnings": true,
"C_Cpp_Runner.warningsAsError": false,
"C_Cpp_Runner.compilerArgs": [],
"C_Cpp_Runner.linkerArgs": [],
"C_Cpp_Runner.includePaths": [],
"C_Cpp_Runner.includeSearch": [
"*",
"**/*"
],
"C_Cpp_Runner.excludeSearch": [
"**/build",
"**/build/**",
"**/.*",
"**/.*/**",
"**/.vscode",
"**/.vscode/**"
],
"C_Cpp_Runner.useAddressSanitizer": false,
"C_Cpp_Runner.useUndefinedSanitizer": false,
"C_Cpp_Runner.useLeakSanitizer": false,
"C_Cpp_Runner.showCompilationTime": false,
"C_Cpp_Runner.useLinkTimeOptimization": false,
"C_Cpp_Runner.msvcSecureNoWarnings": false
}

BIN
58FO8iY.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

6
ARDUINO_NANO_CONTROLL/.gitignore vendored Normal file
View File

@@ -0,0 +1,6 @@
.pio
.vscode/.browse.c_cpp.db*
.vscode/c_cpp_properties.json
.vscode/launch.json
.vscode/ipch
.vscode

View File

@@ -0,0 +1,37 @@
This directory is intended for project header files.
A header file is a file containing C declarations and macro definitions
to be shared between several project source files. You request the use of a
header file in your project source file (C, C++, etc) located in `src` folder
by including it, with the C preprocessing directive `#include'.
```src/main.c
#include "header.h"
int main (void)
{
...
}
```
Including a header file produces the same results as copying the header file
into each source file that needs it. Such copying would be time-consuming
and error-prone. With a header file, the related declarations appear
in only one place. If they need to be changed, they can be changed in one
place, and programs that include the header file will automatically use the
new version when next recompiled. The header file eliminates the labor of
finding and changing all the copies as well as the risk that a failure to
find one copy will result in inconsistencies within a program.
In C, the convention is to give header files names that end with `.h'.
Read more about using header files in official GCC documentation:
* Include Syntax
* Include Operation
* Once-Only Headers
* Computed Includes
https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html

View File

@@ -0,0 +1,46 @@
This directory is intended for project specific (private) libraries.
PlatformIO will compile them to static libraries and link into the executable file.
The source code of each library should be placed in a separate directory
("lib/your_library_name/[Code]").
For example, see the structure of the following example libraries `Foo` and `Bar`:
|--lib
| |
| |--Bar
| | |--docs
| | |--examples
| | |--src
| | |- Bar.c
| | |- Bar.h
| | |- library.json (optional. for custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
| |
| |--Foo
| | |- Foo.c
| | |- Foo.h
| |
| |- README --> THIS FILE
|
|- platformio.ini
|--src
|- main.c
Example contents of `src/main.c` using Foo and Bar:
```
#include <Foo.h>
#include <Bar.h>
int main (void)
{
...
}
```
The PlatformIO Library Dependency Finder will find automatically dependent
libraries by scanning project source files.
More information about PlatformIO Library Dependency Finder
- https://docs.platformio.org/page/librarymanager/ldf.html

View File

@@ -0,0 +1,18 @@
; PlatformIO Project Configuration File
;
; Build options: build flags, source filter
; Upload options: custom upload port, speed and extra flags
; Library options: dependencies, extra library storages
; Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html
[env:nanoatmega328]
platform = atmelavr
board = nanoatmega328
framework = arduino
lib_deps =
adafruit/DHT sensor library@^1.4.6
featherfly/SoftwareSerial@^1.0
board_build.mcu = atmega328p

View File

@@ -0,0 +1,285 @@
#include <Arduino.h>
#include <Adafruit_Sensor.h>
#include <DHT.h>
#include <DHT_U.h>
#include <SoftwareSerial.h>
// -------------------------------------------------------------
// DHT PreConfiguration
#define DHTPIN 4 // Digital pin connected to the DHT sensor
//#define DHTPIN PB0
// Pin 15 can work but DHT must be disconnected during program upload.
#define DHTTYPE DHT22 // DHT 22 (AM2302), AM2321
// -------------------------------------------------------------
// Initialize DHT sensor.
// Note that older versions of this library took an optional third parameter to
// tweak the timings for faster processors. This parameter is no longer needed
// as the current DHT reading algorithm adjusts itself to work on faster procs.
DHT dht(DHTPIN, DHTTYPE);
// VARS
const int MD1200BAUDS = 38400; // From what I've read it is always 38400
//const int EPYSLEEPY = 600000; / 10 minutes
const long EPYSLEEPY = 300000; // 5 minutes
//const int EPYSLEEPY = 150000; // 2,5 minutes
#define RX_PIN 3
#define TX_PIN 2
SoftwareSerial MDSerial(RX_PIN, TX_PIN); // Goes up to 115200 bauds
// declarations
int getTemp();
int setFanTrsh(int);
float dhtRead();
void setup() {
// Setup connection to MD1200
// Serial because we're using RX/TX pins
MDSerial.begin(MD1200BAUDS);
// Just debug
Serial.begin(9600);
dht.begin();
Serial.println("setup end");
}
void loop() {
Serial.println("Loop start");
int fanPercnt = getTemp();
if (fanPercnt < 10) {
setFanTrsh(fanPercnt);
}
/*
check temperature and
set fan speed every X minutes
*/
delay(EPYSLEEPY);
}
// Get current temperature
int getTemp() {
Serial.println("getTemp start");
int bp1 = 0;
int bp2 = 0;
// int exp0 = 0;
// int exp1 = 0;
// int simm0 = 0;
// int simm1 = 0;
String MD1200output;
MDSerial.println("_temp_rd");
// wait for MD1200 to answer
delay(30);
Serial.println("start parsing temperature start");
while (MDSerial.available()) {
MD1200output = MDSerial.readStringUntil('\n');
// Check backplane 1
if (MD1200output.startsWith("BP_1")) {
// check index number of =
int eq = MD1200output.indexOf('=');
// check index number of c
int c = MD1200output.indexOf('c');
// check if both exists
if (eq != -1 && c != -1) {
/*
take value between "= " and "c".
NOTICE that eq + 1 is there because in
"BP_1[2] = 25c" there is a space between = and 25.
*/
bp1 = MD1200output.substring(eq + 1, c).toInt();
Serial.println("Backplane 1 " + String(bp1));
}
}
// Check backplane 2
if (MD1200output.startsWith("BP_2")) {
int eq = MD1200output.indexOf('=');
int c = MD1200output.indexOf('c');
if (eq != -1 && c != -1) {
bp2 = MD1200output.substring(eq + 1, c).toInt();
Serial.println("Backplane 2 " + String(bp2));
}
}
// Uncomment if you want to also get temperature for expanders
/*
// Check expander 0
if (MD1200output.startsWith("EXP0")) {
int eq = MD1200output.indexOf('=');
int c = MD1200output.indexOf('c');
if (eq != -1 && c != -1) {
exp0 = MD1200output.substring(eq + 1, c).toInt();
}
}
// Check expander 1
if (MD1200output.startsWith("EXP1")) {
int eq = MD1200output.indexOf('=');
int c = MD1200output.indexOf('c');
if (eq != -1 && c != -1) {
exp1 = MD1200output.substring(eq + 1, c).toInt();
}
}
// Check controller 0
if (MD1200output.startsWith("SIM0")) {
int eq = MD1200output.indexOf('=');
int c = MD1200output.indexOf('c');
if (eq != -1 && c != -1) {
simm0 = MD1200output.substring(eq + 1, c).toInt();
}
}
// Check controller 1
if (MD1200output.startsWith("SIM1")) {
int eq = MD1200output.indexOf('=');
int c = MD1200output.indexOf('c');
if (eq != -1 && c != -1) {
simm1 = MD1200output.substring(eq + 1, c).toInt();
}
}
*/
// Stop when prompt returns
if (MD1200output.endsWith(">")) {
Serial.println("stopping temp reading");
break;
}
}
// Do (BP_1 + BP_2) / 2 to get the average of backplane
if (bp1 != -1 && bp2 != -1) {
int bpAvg = (bp1 + bp2) / 2;
Serial.println("Average temp is " + String(bpAvg));
// define default
int outPrcntg = 21;
// check external sensor temp
int ownReadTemp = int(dhtRead());
Serial.println("Checking DHT temp");
if (ownReadTemp <= 35) {
return 40;
}
// a
switch (bpAvg) {
case 23:
outPrcntg = 21;
break;
/*
Minimum is 21 (akhsually 20)
BUT
https://forums.servethehome.com/index.php?threads/fun-with-an-md1200-md1220-sc200-sc220.27487/
*/
case 25:
outPrcntg = 23;
break;
case 27:
outPrcntg = 24;
break;
case 29:
outPrcntg = 26;
break;
case 31:
outPrcntg = 27;
break;
case 33:
outPrcntg = 30;
break;
case 35:
outPrcntg = 34;
break;
case 37:
outPrcntg = 38;
break;
/*
I don't wan't to become deaf so max is 40.
*/
default:
return -1;
}
Serial.println("Returnning " + String(outPrcntg) + " %");
return outPrcntg;
} else {
return -1; // failed to read temp
}
/*
BP_1[2] - Back plane, maybe left
BP_2[3] - Back plane, maybe right
SIM0[0] - Controller A
SIM1[1] - Controller B
EXP0[4] - Expander 0
EXP1[5] - Expander 1
AVG - average of all sensors
*/
}
// take percentage as int and set it.
int setFanTrsh(int fanTrshInp) {
Serial.println("Setting fan speed");
String outputStatement = "set_speed " + String(fanTrshInp);
// MDSerial.println("Sending " + outputStatement + " to MD1200");
if (MDSerial.println(outputStatement)) {
Serial.println("Setting fan speed success");
return 1;
}
else {
Serial.println("Setting fan speed unsuccess");
return -1;
}
}
float dhtRead() {
// Reading temperature or humidity takes about 250 milliseconds!
// Sensor readings may also be up to 2 seconds 'old' (its a very slow sensor)
// float humidt = dht.readHumidity();
// Read temperature as Celsius (the default)
float tempr = dht.readTemperature();
// Read temperature as Fahrenheit (isFahrenheit = true)
// f = dht.readTemperature(true);
// Check if any reads failed and exit early (to try again).
// if (isnan(humidt) || isnan(tempr))
if (isnan(tempr))
{
// MDSerial.println("Failed to read from DHT sensor!");
return -1;
}
else {
return tempr;
}
// Compute heat index in Fahrenheit (the default)
// hif = dht.computeHeatIndex(f, h);
// Compute heat index in Celsius (isFahreheit = false)
// hicc = dht.computeHeatIndex(tempr, humidt, false);
}

View File

@@ -0,0 +1,11 @@
This directory is intended for PlatformIO Test Runner and project tests.
Unit Testing is a software testing method by which individual units of
source code, sets of one or more MCU program modules together with associated
control data, usage procedures, and operating procedures, are tested to
determine whether they are fit for use. Unit testing finds problems early
in the development cycle.
More information about PlatformIO Unit Testing:
- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html

2
PC_CONTROL_CODE/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
testing
cpp

View File

@@ -0,0 +1,2 @@
# Run every minute
* * * * /location/to/bash/simple.sh

View File

@@ -0,0 +1,9 @@
#!/bin/bash
for i in {1..59}
do
stty -F /dev/ttyUSB0 speed 38400 cs8 -ixon raw
echo -ne "_shutup 24\n\r" > /dev/ttyUSB0
sleep 1
done
exit

Binary file not shown.

1
PC_CONTROL_CODE/docker/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
.mypy_cache

View File

@@ -0,0 +1,28 @@
FROM alpine:latest
# https://docs.docker.com/reference/dockerfile/#environment-replacement
# ENV MD1200BAUD=38400
# ENV SERIALADAPTER=/dev/ttyUSB0
# ENV EPPYSLEEPY=300
# VOLUME [""]
RUN apk update && \
apk add python3 py3-pip
RUN mkdir /etc/MD1200FAN/
WORKDIR /etc/MD1200FAN/
COPY ./mainDocker.py /etc/MD1200FAN/
# COPY ./requirements.txt /etc/MD1200FAN/
RUN python3 -m venv venv && \
venv/bin/python3 -m pip install --upgrade pip && \
venv/bin/pip3 install PySerial
# venv/bin/pip3 install -r requirements.txt
# VOLUME ["/etc/MD1200FAN/"]
CMD ["venv/bin/python3", "mainDocker.py"]
# CMD ["/etc/MD1200FAN/venv/bin/python3", "/etc/MD1200FAN/mainDocker.py"]

View File

@@ -0,0 +1,18 @@
---
services:
mdfanchanger:
container_name: MD_Fan_Changer
image: yuruc3/md1200_fan_controll:v1.2.1
environment:
# - MD1200BAUD=
- SERIALADAPTER=/dev/ttyUSB0
- TEMP_FACTOR=17
- EPPYSLEEPY=0.25
- MDSERIALTIMEOUT=0.75
# - LOW_FAN_TRSHD=
# - HIGH_FAN_TRSHD=
devices:
- /dev/ttyUSB0:/dev/ttyUSB0
restart: unless-stopped
privileged: false

View File

@@ -0,0 +1,194 @@
import serial, time, os
from typing import Final
# setting consts that can be customized
# baud rate. Prob not needed as 38400 is standard
MD1200BAUD: Final[int] = int(os.getenv("MD1200BAUD", 38400))
# used if you want to run it on multiple JBODs
SERIALADAPTER: Final[str] = os.getenv("SERIALADAPTER", "/dev/ttyUSB0")
# Factor that defines how aggressive the temperature curve is
TEMP_FACTOR: Final[int] = int(os.getenv("TEMP_FACTOR", 16))
# time between sending command to get temp and storing it. It's there to allow JBOD to answer
EPPYSLEEPY: Final[float] = float(os.getenv("EPPYSLEEPY", 1))
LOW_FAN_TRSHD: Final[int] = int(os.getenv("LOW_FAN_TRSHD", 21))
HIGH_FAN_TRSHD: Final[int] = int(os.getenv("HIGH_FAN_TRSHD", 40))
GETTMPCMND: Final[str] = os.getenv("GETTMPCMND", "_temp_rd")
SETFANCMND: Final[str] = os.getenv("SETFANCMND", "set_speed")
DEFOUTPRCNTG: Final[int] = int(os.getenv("DEFOUTPRCNTG", 24))
MDSERIALTIMEOUT: Final[float] = float(os.getenv("MDSERIALTIMEOUT", 1))
TEMPREADINTERVAL: Final[int] = int(os.getenv("TEMPREADINTERVAL", 15))
GETTEMPTIMESLEEP: Final[int] = int(os.getenv("GETTEMPTIMESLEEP", 1))
# init
MDserial = serial.Serial(
port=SERIALADAPTER,\
baudrate=MD1200BAUD,\
parity=serial.PARITY_NONE,\
stopbits=serial.STOPBITS_ONE,\
bytesize=serial.EIGHTBITS,\
timeout=MDSERIALTIMEOUT)
lastTempReading = time.time()
MDtempDict = {}
def getTemp() -> dict:
MDserial.write(f"{GETTMPCMND}\n\r".encode())
time.sleep(GETTEMPTIMESLEEP)
MDreturning = MDserial.read_until(" >").decode(errors="ignore")
MDict = {}
# Sanitise output
MDsanit = MDreturning.splitlines()
#if there is smth do smth
if MDreturning:
for line in MDsanit:
if ">" in line or "b'" in line:
continue
matchstm = line[2:6]
match matchstm:
case "BP_1":
MDict["bp1"] = int(line[12:14])
case "BP_2":
MDict["bp2"] = int(line[12:14])
case "SIM0":
MDict["sim0"] = int(line[12:14])
case "SIM1":
MDict["sim1"] = int(line[12:14])
case "EXP0":
MDict["exp0"] = int(line[12:14])
case "EXP1":
MDict["exp1"] = int(line[12:14])
# case "AVG":
# MDict["avg"] = int(line[12:14])
# MDict["avg"] = int(line.strip().split("=")[1].strip().replace("c", ""))
# try:
# # Extract number from e.g. ' AVG = 40c'
# temp = int(line.strip().split("=")[1].strip().replace("c", ""))
# MDict["avg"] = temp
# except Exception as e:
# # print(f"[WARN] Failed to parse AVG line: {line} ({e})", flush=True)
# pass
case _:
# try to catch the AVG line like: " AVG = 40c"
stripped = line.strip()
if stripped.startswith("AVG"):
try:
temp = int(stripped.split("=")[1].strip().replace("c", ""))
MDict["avg"] = temp
except Exception as e:
print(f"Could not parse AVG line: {line} ({e})", flush=True)
continue
# continue
return MDict
else:
return {"error": "unidentified"}
def setSpeed(inSpeeDict: dict) -> int:
bpavrg = 0
# default
outfanprcntg = 0
# get backplanbe average
if "bp1" in inSpeeDict and "bp2" in inSpeeDict:
bpavrg = (inSpeeDict["bp1"] + inSpeeDict["bp2"]) /2
outfanprcntg = int((bpavrg / (HIGH_FAN_TRSHD - LOW_FAN_TRSHD)) * TEMP_FACTOR)
# os.system(f"echo setting {outfanprcntg}%")
return outfanprcntg
# Set fan speed
if outfanprcntg >= 20:
MDserial.write((f"{SETFANCMND} {str(outfanprcntg)} \n\r").encode())
print(f"setting {outfanprcntg}%", flush=True)
return outfanprcntg
else:
# Set default value
MDserial.write((f"{SETFANCMND} {str(DEFOUTPRCNTG)} \n\r").encode())
return DEFOUTPRCNTG
# If something goes super wrong
return 1
# Check if UART is used
# Not neede because when defining MDserial it gets automatically opened
# Will leave it here anyway
# try:
# MDserial.open()
# except serial.serialutil.SerialException:
# # MDserial.close()
# # MDserial.open()
# print("Port allready opened.\nTry closing it first")
# Init
MDtempDict = getTemp()
lastTempReading = time.time()
try:
while True:
# https://stackoverflow.com/questions/52578122/not-able-to-send-the-enter-command-on-pyserial
# get temperature data, wait for MD1200 to answer and store
currentTime = time.time()
if currentTime - lastTempReading >= TEMPREADINTERVAL:
MDtempDict = getTemp()
lastTempReading = currentTime
if MDtempDict:
setSpeedrcode = setSpeed(MDtempDict)
# good
if setSpeedrcode == 0:
pass
# print("Were mint", flush=True)
# time.sleep(EPPYSLEEPY)
# not good
elif setSpeedrcode == 1:
print("Ambigous temperature readings.\nFalling back to safe values.", flush=True)
# time.sleep(EPPYSLEEPY)
# very not good
elif setSpeedrcode == -1:
print("o nyo", flush=True)
exit()
# very very very not good
else:
print("idk", flush=True)
exit()
else:
print(f"temperature not yet pulled.\nFalling back do default fan speed", flush=True)
# os.system(f"echo temperature not yet pulled.\nFalling back do default fan speed")
MDserial.write((f"{SETFANCMND} {str(DEFOUTPRCNTG)} \n\r").encode())
time.sleep(EPPYSLEEPY)
except KeyboardInterrupt:
print("\n[INFO] KeyboardInterrupt detected. Exiting gracefully...")
MDserial.close()
exit()
finally:
print("closing port")
MDserial.close()
print("closing port")
MDserial.close()

View File

@@ -0,0 +1,6 @@
baud_rate=
serial_adapter=
wait_time=
temp_factor=
lower_treshold=
upper_treshold=

View File

@@ -0,0 +1 @@
.mypy_cache

View File

@@ -0,0 +1,28 @@
FROM alpine:latest
# https://docs.docker.com/reference/dockerfile/#environment-replacement
# ENV MD1200BAUD=38400
# ENV SERIALADAPTER=/dev/ttyUSB0
# ENV EPPYSLEEPY=300
# VOLUME [""]
RUN apk update && \
apk add python3 py3-pip
RUN mkdir /etc/MD1200FAN/
WORKDIR /etc/MD1200FAN/
COPY ./mainDocker.py /etc/MD1200FAN/
# COPY ./requirements.txt /etc/MD1200FAN/
RUN python3 -m venv venv && \
venv/bin/python3 -m pip install --upgrade pip && \
venv/bin/pip3 install PySerial influxdb_client
# venv/bin/pip3 install -r requirements.txt
# VOLUME ["/etc/MD1200FAN/"]
CMD ["venv/bin/python3", "mainDocker.py"]
# CMD ["/etc/MD1200FAN/venv/bin/python3", "/etc/MD1200FAN/mainDocker.py"]

View File

@@ -0,0 +1,11 @@
---
services:
mdfanchanger:
container_name: MD_Fan_Changer
image: yuruc3/md1200_fan_controll:v2-flux
env_file: md1200.env
devices:
- /dev/ttyUSB0:/dev/ttyUSB0
restart: unless-stopped
privileged: false

View File

@@ -0,0 +1,55 @@
import time, os, influxdb_client
from influxdb_client import InfluxDBClient, Point, WritePrecision
from influxdb_client.client.write_api import SYNCHRONOUS, ASYNCHRONOUS, WriteOptions
from datetime import timedelta
from concurrent.futures import ThreadPoolExecutor
from typing import Final
# INFLUXDB config
# token = "apg1gysUeCcxdcRTMmosJTenbEppmUNi9rXlANDB2oNadBdWAu2GVTDc_q_dyo0iyYsckKaOvPRm6ba2NK0y_A=="
INFLUXTOKEN: Final[str] = os.getenv("INFLUX_TOKEN")
# bucket = "JBOD"
# INFLUXBUCKET: Final[str] = os.getenv("INFLUX_BUCKET")
# org = "staging"
INFLUXORG: Final[str] = os.getenv("INFLUX_ORG")
# url = "http://localhost:8086"
INFLUXURL: Final[str] = os.getenv("INFLUX_URL")
# measurement = "MD1200"
measurement = os.getenv("INFLUX_MEASUREMENT")
# MACHINE_TAG = "CHONGUS1200"
MACHINE_TAG = os.getenv("INFLUX_MACHINE_TAG")
# LOCATION = "HQ"
LOCATION = os.getenv("INFLUX_LOCATION")
# INFLX_SEPARATE_POINTS = 0.1
INFLUX_SEPARATE_POINTS = int(os.getenv("INFLUX_SEPARATE_POINTS"))
# Initialize InfluxDB client and influxdb API
inflxdb_client = influxdb_client.InfluxDBClient(url=INFLUXURL, token=INFLUXTOKEN, org=INFLUXORG)
#write_api = inflxdb_client.write_api(write_options=SYNCHRONOUS)
write_api = inflxdb_client.write_api(write_options=WriteOptions(batch_size=500, flush_interval=1000))
# Threaded flow processor
def process_temps(inEntry):
global MDict
# ---LeData---
# {'bp1': 35, 'bp2': 29, 'sim0': 35, 'sim1': 34, 'exp0': 56, 'exp1': 54}
# ---LeData---
# Prep InfluxDB data
inflxdb_Data_To_Send = (
influxdb_client.Point(f"{measurement}-script")
.tag("MACHINE", MACHINE_TAG)
.tag("LOCATION", LOCATION)
.field("Backplane1", inEntry["bp1"])
.field("Backplane2", inEntry["bp2"])
.field("SASIntModule0", inEntry["sim0"])
.field("SASIntModule1", inEntry["sim1"])
.field("Expander0", inEntry["exp0"])
.field("Expander1", inEntry["exp1"])
.field("Average", inEntry["avg"])
)
print("----------------")
return ()

View File

@@ -0,0 +1,350 @@
import time, os, influxdb_client, serial, threading
from influxdb_client import InfluxDBClient, Point, WritePrecision
from influxdb_client.client.write_api import SYNCHRONOUS, ASYNCHRONOUS, WriteOptions
from concurrent.futures import ThreadPoolExecutor
from typing import Final
# setting consts that can be customized
# baud rate. Prob not needed as 38400 is standard
MD1200BAUD: Final[int] = int(os.getenv("MD1200BAUD", "38400"))
# used if you want to run it on multiple JBODs
SERIALADAPTER: Final[str] = os.getenv("SERIALADAPTER", "/dev/ttyUSB0")
# Factor that defines how aggressive the temperature curve is
TEMP_FACTOR: Final[float] = float(os.getenv("TEMP_FACTOR", "19"))
# time between sending command to get temp and storing it. It's there to allow JBOD to answer
EPPYSLEEPY: Final[float] = float(os.getenv("EPPYSLEEPY", "1"))
LOW_FAN_TRSHD: Final[float] = float(os.getenv("LOW_FAN_TRSHD", "21"))
HIGH_FAN_TRSHD: Final[float] = float(os.getenv("HIGH_FAN_TRSHD", "40"))
GETTMPCMND: Final[str] = os.getenv("GETTMPCMND", "_temp_rd")
SETFANCMND: Final[str] = os.getenv("SETFANCMND", "set_speed")
DEFOUTPRCNTG: Final[float] = float(os.getenv("DEFOUTPRCNTG", "24"))
MDSERIALTIMEOUT: Final[float] = float(os.getenv("MDSERIALTIMEOUT", "1"))
TEMPREADINTERVAL: Final[float] = float(os.getenv("TEMPREADINTERVAL", "15"))
# If True or yes then we good
TEMPSETING = os.getenv("TEMPSETING", "1").strip().lower() in ("1", "true", "yes", "on")
PROCESSTEMPWAITTIME: Final[float] = float(os.getenv("PROCESSTEMPWAITTIME", "0.75"))
BACKOFFTIME: Final[float] = float(os.getenv("BACKOFFTIME", "5"))
INFLUX_MAX_RETRIES: Final[int] = int(os.getenv("INFLUX_MAX_RETRIES", "3"))
# INFLUXDB config
# should Influx be used?
USEINFLUX: Final[bool] = os.getenv("USEINFLUX", "True").strip().lower() in ("1", "true", "yes", "on")
# token = "apg1gysUeCcxdcRTMmosJTenbEppmUNi9rXlANDB2oNadBdWAu2GVTDc_q_dyo0iyYsckKaOvPRm6ba2NK0y_A=="
INFLUXTOKEN: Final[str] = str(os.getenv("INFLUX_TOKEN", "0"))
# bucket = "JBOD"
INFLUXBUCKET: Final[str] = str(os.getenv("INFLUX_BUCKET", "0"))
# org = "staging"
INFLUXORG: Final[str] = str(os.getenv("INFLUX_ORG", "0"))
# url = "http://localhost:8086"
INFLUXURL: Final[str] = str(os.getenv("INFLUX_URL", "0"))
# measurement = "MD1200"
INFLUXMEASUREMENT: Final[str] = str(os.getenv("INFLUX_MEASUREMENT", "0"))
# MACHINE_TAG = "CHONGUS1200"
MACHINE_TAG: Final[str] = str(os.getenv("INFLUX_MACHINE_TAG", "0"))
# LOCATION = "HQ"
LOCATION: Final[str] = str(os.getenv("INFLUX_LOCATION", "0"))
# INFLX_SEPARATE_POINTS = 0.1
# INFLUX_SEPARATE_POINTS = float(os.getenv("INFLUX_SEPARATE_POINTS"), 0.1)
# init
MDserial = serial.Serial(
port=SERIALADAPTER,\
baudrate=MD1200BAUD,\
parity=serial.PARITY_NONE,\
stopbits=serial.STOPBITS_ONE,\
bytesize=serial.EIGHTBITS,\
timeout=MDSERIALTIMEOUT)
# lastTempReading: float = time.time()
setSpeedrcode = 21
MDtempDict: dict = {}
MDict: dict = {}
currentSerialUsage = threading.Lock()
fluxSending: bool = False
currentTime: float = 0
lastTempReading: float = 0
# Initialize InfluxDB client and influxdb API
# ---------------------UNCOMMENT-----------------------
if USEINFLUX:
inflxdb_client = influxdb_client.InfluxDBClient(url=INFLUXURL, token=INFLUXTOKEN, org=INFLUXORG)
write_api = inflxdb_client.write_api(write_options=SYNCHRONOUS)
inflxdb_LeData: list = []
# ---------------------UNCOMMENT-----------------------
# Just a helper function for process_temps to avoid db errors on flapping network
def process_temps_dbsend(inpdatatosend) -> bool:
if not USEINFLUX:
return True
try:
write_api.write(bucket=INFLUXBUCKET, org=INFLUXORG, record=inpdatatosend)
return True
except Exception as e:
print(f"Influx write error {e}", flush=True)
return False
def getTemp() -> dict:
global MDict, fluxSending
with currentSerialUsage:
MDserial.write(f"{GETTMPCMND}\n\r".encode())
time.sleep(1)
MDreturning = MDserial.read_until(" >").decode()
# MDict = {}
# Sanitise output
MDsanit = MDreturning.splitlines()
#if there is smth do smth
if MDreturning:
for line in MDsanit:
if ">" in line or "b'" in line:
continue
matchstm = line[2:6]
match matchstm:
case "BP_1":
MDict["bp1"] = int(line[12:14])
case "BP_2":
MDict["bp2"] = int(line[12:14])
case "SIM0":
MDict["sim0"] = int(line[12:14])
case "SIM1":
MDict["sim1"] = int(line[12:14])
case "EXP0":
MDict["exp0"] = int(line[12:14])
case "EXP1":
MDict["exp1"] = int(line[12:14])
# case "AVG":
# MDict["avg"] = int(line[12:14])
# MDict["avg"] = int(line.strip().split("=")[1].strip().replace("c", ""))
# try:
# # Extract number from e.g. ' AVG = 40c'
# temp = int(line.strip().split("=")[1].strip().replace("c", ""))
# MDict["avg"] = temp
# except Exception as e:
# # print(f"[WARN] Failed to parse AVG line: {line} ({e})", flush=True)
# pass
case _:
# try to catch the AVG line like: " AVG = 40c"
stripped = line.strip()
if stripped.startswith("AVG"):
try:
temp = int(stripped.split("=")[1].strip().replace("c", ""))
MDict["avg"] = temp
except Exception as e:
print(f"[WARN] Could not parse AVG line: {line} ({e})", flush=True)
continue
# continue
# {'bp1': 35, 'bp2': 29, 'sim0': 35, 'sim1': 33, 'exp0': 56, 'exp1': 54, 'avg': 40}
# process_temps(MDict)
fluxSending = True
return MDict
else:
return {"error": "unidentified"}
def setSpeed(inSpeeDict: dict) -> int:
bpavrg = 0
# default
outfanprcntg = 0
# get backplanbe average
if "bp1" in inSpeeDict and "bp2" in inSpeeDict:
bpavrg = (inSpeeDict["bp1"] + inSpeeDict["bp2"]) /2
#outfanprcntg = int((bpavrg / (HIGH_FAN_TRSHD - LOW_FAN_TRSHD)) * TEMP_FACTOR)
outfanprcntg = int((bpavrg / (HIGH_FAN_TRSHD - LOW_FAN_TRSHD)) * TEMP_FACTOR)
# os.system(f"echo setting {outfanprcntg}%")
with currentSerialUsage:
# Set fan speed
if outfanprcntg >= 20:
MDserial.write((f"{SETFANCMND} {str(outfanprcntg)} \n\r").encode())
print(f"setting {outfanprcntg}%", flush=True)
return outfanprcntg
else:
# Set default value
MDserial.write((f"{SETFANCMND} {str(DEFOUTPRCNTG)} \n\r").encode())
return outfanprcntg
# If something goes super wrong
return 1
# Check if UART is used
# Not neede because when defining MDserial it gets automatically opened
# Will leave it here anyway
# try:
# MDserial.open()
# except serial.serialutil.SerialException:
# # MDserial.close()
# # MDserial.open()
# print("Port allready opened.\nTry closing it first")
# Threaded flow processor
def process_temps() -> None:
global MDict, fluxSending, setSpeedrcode
while True:
# ---LeData---
# {'bp1': 35, 'bp2': 29, 'sim0': 35, 'sim1': 34, 'exp0': 56, 'exp1': 54}
# ---LeData---
if fluxSending:
# Prep InfluxDB data
# build local copy in case MDict changes while executing
MDictLocalCopy = MDict.copy()
try:
inflxdb_Data_To_Send = (
influxdb_client.Point(f"{INFLUXMEASUREMENT}-script")
.tag("MACHINE", MACHINE_TAG)
.tag("LOCATION", LOCATION)
.field("Backplane1", MDictLocalCopy["bp1"])
.field("Backplane2", MDictLocalCopy["bp2"])
.field("SASIntModule0", MDictLocalCopy["sim0"])
.field("SASIntModule1", MDictLocalCopy["sim1"])
.field("Expander0", MDictLocalCopy["exp0"])
.field("Expander1", MDictLocalCopy["exp1"])
.field("Average", MDictLocalCopy["avg"])
.field("FanSpeed", setSpeedrcode)
)
except KeyError:
time.sleep(BACKOFFTIME)
continue
# Prep/append data
inflxdb_LeData.append(inflxdb_Data_To_Send)
# Issue YuruC3/MD1200#7 fix
if not process_temps_dbsend(inflxdb_Data_To_Send):
i = 0
while i < INFLUX_MAX_RETRIES:
if process_temps_dbsend(inflxdb_Data_To_Send):
print("Sending data to InfluxDB", flush=True)
break
else:
time.sleep(1)
i += 1
else:
print(f"Failed to send data to InfluxDB after {INFLUX_MAX_RETRIES} retires", flush=True)
else:
print("Sending data to InfluxDB", flush=True)
# Clean up before another lo#op, 0.75
inflxdb_LeData.clear()
fluxSending = False
else:
time.sleep(PROCESSTEMPWAITTIME)
# Init
MDict = getTemp()
lastTempReading = time.time()
def mainCodeHere() -> None:
global setSpeedrcode
while True:
global MDict, fluxSending, currentTime, lastTempReading
# https://stackoverflow.com/questions/52578122/not-able-to-send-the-enter-command-on-pyserial
# get temperature data, wait for MD1200 to answer and store
currentTime = time.time()
if currentTime - lastTempReading >= TEMPREADINTERVAL:
getTemp()
lastTempReading = currentTime
if MDict and TEMPSETING:
setSpeedrcode = setSpeed(MDict)
# good
if setSpeedrcode == 0:
pass
# print("Were mint", flush=True)
# time.sleep(EPPYSLEEPY)
# not good
elif setSpeedrcode == 1:
print("Ambigous temperature readings.\nFalling back to safe values.", flush=True)
# time.sleep(EPPYSLEEPY)
# very not good
elif setSpeedrcode == -1:
print("o nyo", flush=True)
exit()
# very very very not good
else:
print("idk", flush=True)
exit()
elif TEMPSETING == False:
print(f"Waiting to get temp", flush=True)
pass
else:
print(f"temperature not yet pulled.\nFalling back do default fan speed", flush=True)
# os.system(f"echo temperature not yet pulled.\nFalling back do default fan speed")
with currentSerialUsage:
MDserial.write((f"{SETFANCMND} {str(DEFOUTPRCNTG)} \n\r").encode())
time.sleep(EPPYSLEEPY)
# Prepare threads and launch them
# daemon to make docker exit smoother
thread_main = threading.Thread(target=mainCodeHere, daemon=True)
threads = [thread_main]
if USEINFLUX:
thread_flux = threading.Thread(target=process_temps, daemon=True)
threads.append(thread_flux)
for thr in threads:
thr.start()
# Join both (this will block forever, which is fine for a daemon)
for thr in threads:
thr.join()

View File

@@ -0,0 +1,21 @@
# MD1200BAUD=
SERIALADAPTER=/dev/ttyUSB0
# These are (for me) working values
EPPYSLEEPY=1.5
MDSERIALTIMEOUT=2
TEMPREADINTERVAL=30
TEMP_FACTOR=16
# LOW_FAN_TRSHD=21
# HIGH_FAN_TRSHD=40
# Influxdb config
INFLUX_TOKEN=-3rZgq6EprG9i-gKqBDSFCC3hTS3U49fxGkg==
INFLUX_BUCKET=JBOD
INFLUX_ORG=FUBUKUS
INFLUX_URL=http://192.168.1.101:8086
INFLUX_MEASUREMENT=MD1200
INFLUX_MACHINE_TAG=CHONGUS1200
INFLUX_LOCATION=HQ

View File

@@ -0,0 +1,19 @@
MD1200BAUD=38400
SERIALADAPTER=/dev/ttyUSB0
EPPYSLEEPY=1
MDSERIALTIMEOUT=1
TEMPREADINTERVAL=15
TEMP_FACTOR=19
LOW_FAN_TRSHD=21
HIGH_FAN_TRSHD=40
# Influxdb config
INFLUX_TOKEN===
INFLUX_BUCKET=JBOD
INFLUX_ORG=staging
INFLUX_URL=http://localhost:8086
INFLUX_MEASUREMENT=MD1200
INFLUX_MACHINE_TAG=CHONGUS1200
INFLUX_LOCATION=HQ

131
PC_CONTROL_CODE/main.py Normal file
View File

@@ -0,0 +1,131 @@
import serial, time
# CONST
MD1200BAUD = 38400
SERIALADAPTER = "/dev/ttyUSB0"
GETTEMP = "_temp_rd"
SETFANPRCNT = "set_speed"
EPPYSLEEPY = 1 # 1 second
#EPPYSLEEPY = 150 # 2,5 minutes
# init
MDserial = serial.Serial(
port=SERIALADAPTER,\
baudrate=MD1200BAUD,\
parity=serial.PARITY_NONE,\
stopbits=serial.STOPBITS_ONE,\
bytesize=serial.EIGHTBITS,\
timeout=1)
def getTemp(inpMDreturning):
MDict = {}
# Sanitise output
MDsanit = inpMDreturning.splitlines()
#if there is smth do smth
if inpMDreturning:
for line in MDsanit:
if ">" in line or "b'" in line:
continue
matchstm = line[2:6]
match matchstm:
case "BP_1":
MDict["bp1"] = int(line[12:14])
case "BP_2":
MDict["bp2"] = int(line[12:14])
case "SIM0":
MDict["sim0"] = int(line[12:14])
case "SIM1":
MDict["sim1"] = int(line[12:14])
case "EXP0":
MDict["exp0"] = int(line[12:14])
case "EXP1":
MDict["exp1"] = int(line[12:14])
case "AVG":
MDict["avg"] = int(line[12:14])
case _:
continue
return MDict
def setSpeed(inSpeeDict: dict):
bpavrg = 0
# Some safe fan speedvalue
defoutprntg = 27
# default
outfanprcntg = 0
# Decide on fan speeds
LOW_FAN_TRSHD = 21
HIGH_FAN_TRSHD = 40
TEMP_FACTOR = 21
# get backplanbe average
if inSpeeDict["bp1"] and inSpeeDict["bp2"]:
bpavrg = (inSpeeDict["bp1"] + inSpeeDict["bp2"]) /2
outfanprcntg = int((bpavrg / (HIGH_FAN_TRSHD - LOW_FAN_TRSHD)) * TEMP_FACTOR)
# Set fan speed
if outfanprcntg >= 20:
MDserial.write(("set_speed " + str(outfanprcntg) + " \n\r").encode())
print(f"setting {outfanprcntg}%")
return 0
else:
# Set default value
MDserial.write(("set_speed " + str(defoutprntg) + " \n\r").encode())
return 1
# If something goes super wrong
return -1
# Check if UART is used
# Not neede because when defining MDserial it gets automatically opened
# Will leave it here anyway
# try:
# MDserial.open()
# except serial.serialutil.SerialException:
# # MDserial.close()
# # MDserial.open()
# print("Port allready opened.\nTry closing it first")
while True:
# https://stackoverflow.com/questions/52578122/not-able-to-send-the-enter-command-on-pyserial
MDserial.write("_temp_rd\n\r".encode())
time.sleep(1)
MDreturning = MDserial.read_until(" >").decode()
MDtempDict = getTemp(MDreturning)
setSpeedrcode = setSpeed(MDtempDict)
# good
if setSpeedrcode == 0:
# print("Were mint")
time.sleep(EPPYSLEEPY)
# not good
elif setSpeedrcode == 1:
print("Ambigous temperature readings.\nFalling back to safe values.")
time.sleep(EPPYSLEEPY)
# very not good
elif setSpeedrcode == -1:
print("o nyo")
exit()
# very very very not good
else:
print("idk")
exit()
print("closing port")
MDserial.close()

View File

@@ -0,0 +1 @@
pySerial == 3.5

View File

@@ -0,0 +1,26 @@
[Unit]
Description=Adjust MD1200/MD1220 fan speeds
After=multi-user.target
# Place in /etc/systemd/system/
[Service]
User=yuru
Group=yuru
Type=simple
Restart=on-failure
# EnvironmentFile=/etc/NetFlux/netflow.env
# User=myuser
WorkingDirectory=/etc/MD1200FAN/
ExecStart=/etc/MD1200FAN/venv/bin/python3 /etc/MD1200FAN/main.py --serve-in-foreground
#StandardInput=tty-force
# Log file will be create if it doesn't exist
StandardOutput=append:/var/log/MD1200FAN.py.log
StandardError=append:/var/log/MD1200FAN.py.errlog
# StandardOutput=syslog
# StandardError=syslog
# SyslogIdentifier=NetFlowInflux
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,26 @@
[Unit]
Description=Adjust MD1200/MD1220 fan speeds
After=multi-user.target
# Place in /etc/systemd/system/
[Service]
User=<CHANGE>
Group=<CHANGE>
Type=simple
Restart=on-failure
# EnvironmentFile=/etc/NetFlux/netflow.env
# User=myuser
WorkingDirectory=/dir/to/script/
ExecStart=/dir/to/script'sVENV/venv/bin/python3 /dir/to/script/main.py --serve-in-foreground
#StandardInput=tty-force
# Log file will be create if it doesn't exist
StandardOutput=append:/var/log/MD1200FAN.py.log
StandardError=append:/var/log/MD1200FAN.py.errlog
# StandardOutput=syslog
# StandardError=syslog
# SyslogIdentifier=NetFlowInflux
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,8 @@
# What you need to prepare
virtual enviroment in python with installed packages
python script and InfluxDB
## Change stuff
In .service file change everything that has <SOMETHIN_LIKE_THIS> and /path/to/dir

226
PC_CONTROL_CODE/test.py Normal file
View File

@@ -0,0 +1,226 @@
import serial, time
# CONST
MD1200BAUD = 38400
SERIALADAPTER = "/dev/ttyUSB0"
GETTEMP = "_temp_rd"
SETFANPRCNT = "set_speed"
EPYSLEEPY = 300 # 5 minutes
#EPYSLEEPY = 150 # 2,5 minutes
# init
# MDserial = serial.Serial(
# port=SERIALADAPTER,\
# baudrate=MD1200BAUD,\
# parity=serial.PARITY_NONE,\
# stopbits=serial.STOPBITS_ONE,\
# bytesize=serial.EIGHTBITS,\
# timeout=1)
# After running .encode() on output
MDreturn = "b'\r\nBlueDress.105.001 >_temp_rd\r\n\r\n BP_1[2] = 24c\r\n BP_2[3] = 24c\r\n SIM0[0] = 26c\r\n SIM1[1] = 29c\r\n EXP0[4] = 43c\r\n EXP1[5] = 47c\r\n\r\n AVG = 32c\r\n\r\nBlueDress.105.001 >'"
# before running .encode() on output
TrueMDeturn = MDreturn.encode()
def getTemp(inpMDreturning):
# bp1 = 0
# bp2 = 0
# exp0 = 0
# exp1 = 0
# simm0 = 0
# simm1 = 0
# averr = 0
MDict = {}
# print("1")
# Sanitise output
MDsanit = inpMDreturning.splitlines()
#if there is smth do smth
if inpMDreturning:
# print("2")
# print(MDsanit)
for line in MDsanit:
# print(line)
if ">" in line or "b'" in line:
continue
# print(line[2:])
# if "BP_1" in line[2:]:
# print("yeeee")
matchstm = line[2:6]
# print(matchstm)
match matchstm:
case "BP_1":
# print("BP_1 " + line[12:14])
# bp1 = line[12:14]
MDict["bp1"] = int(line[12:14])
case "BP_2":
# print("BP_2 " + line[12:14])
# bp2 = line[12:14]
MDict["bp2"] = int(line[12:14])
case "SIM0":
# print("SIM0 " + line[12:14])
# simm0 = line[12:14]
MDict["sim0"] = int(line[12:14])
case "SIM1":
# print("SIM1 " + line[12:14])
# simm1 = line[12:14]
MDict["sim1"] = int(line[12:14])
case "EXP0":
# print("EXP0 " + line[12:14])
# exp0 = line[12:14]
MDict["exp0"] = int(line[12:14])
case "EXP1":
# print("EXP1 " + line[12:14])
# exp1 = line[12:14]
MDict["exp1"] = int(line[12:14])
case "AVG":
# print("AVG " + line[12:14])
# averr = line[12:14]
MDict["avg"] = int(line[12:14])
case _:
print("ay men")
# continue
# for thing in line.split(" ")[2:]:
# print(thing)
# print(line[12:14])
# print(MDsanit.split("\n"))
return MDict
# print(MDict)
# for key, thing in getTemp(MDreturn).items():
# print(key, thing)
def setSpeed(inSpeeDict: dict):
print("skibidi")
bpavrg = 0
# Some safe fan speedvalue
defoutprntg = 27
# default
outfanprcntg = 0
# Decide on fan speeds
LOW_FAN_TRSHD = 21
HIGH_FAN_TRSHD = 40
TEMP_FACTOR = 21
# DEBUG
# for key, thing in inSpeeDict.items():
# print(key, thing)
# get backplanbe average
if inSpeeDict["bp1"] and inSpeeDict["bp2"]:
bpavrg = (inSpeeDict["bp1"] + inSpeeDict["bp2"]) /2
outfanprcntg = int((bpavrg / (HIGH_FAN_TRSHD - LOW_FAN_TRSHD)) * TEMP_FACTOR)
print(f"outfanprcntg is {outfanprcntg}")
# Set fan speed
if outfanprcntg >= 20:
# MDserial.write(("set_speed " + str(outfanprcntg) + " \n\r").encode())
return 0
else:
# Set default value
# MDserial.write(("set_speed " + str(defoutprntg) + " \n\r").encode())
return 1
# If something goes super wrong
return -1
while True:
# if True:
# MDserial.write("_temp_rd\n\r".encode())
# CHANGE AFTER TESTING
# MDreturning = MDserial.read_until(" >").decode()
MDreturning = MDreturn
# sleep(50)
MDtempDict = getTemp(MDreturning)
# setSpeed(MDtempDict)
setSpeedrcode = setSpeed(MDtempDict)
# good
if setSpeedrcode == 0:
print("Were mint")
time.sleep(EPYSLEEPY)
# not good
elif setSpeedrcode == 1:
print("Ambigous temperature readings.\nFalling back to safe values.")
time.sleep(EPYSLEEPY)
# very not good
elif setSpeedrcode == -1:
print("o nyo")
exit()
# very very very not good
else:
print("idk")
# def getTemp():
# bp1 = 0
# bp2 = 0
# #exp0 = 0
# #exp1 = 0
# #simm0 = 0
# #simm1 = 0
# getMD1200tempReturn = ""
# MDserial.write(GETTEMP.encode())
# print(MDserial.readlines())
# MDreturning = MDserial.readlines().decode()
# #if there is smth do smth
# if len(MDreturning) >= 1:
# print("skibidi")
# return MDreturning
# try:
# MDserial.open()
# except serial.serialutil.SerialException:
# print("Port allready opened.\nTry closing it first")
# # https://stackoverflow.com/questions/52578122/not-able-to-send-the-enter-command-on-pyserial
# MDserial.write("_temp_rd\n\r".encode())
# print(MDserial.read_until(" >"))
# fanprct = 23
# MDserial.write(f"set_speed {fanprct}\n\r".encode())
# MDserial.close()

View File

@@ -1,8 +1,58 @@
# Python scripts for changing fan speed on MD1200
# MD1200 fan noise reduction
This adjusts fan speed "dynamically" based on average backplane temperature reading.
A set of scripts that automagically set fan speed on a MD1200 (probably MD1220 as well) based on internal temperature readings.
## PC
### Docker
## STM32F103C6T6
In .env file change:
```serial_adapter``` which is a serial port you're using.
On linux it is /dev/ttyUSBx and on windows it is COMx
```wait_time``` is the interval in which script is checking temperature. By default it is 300 seconds, which is 5 minutes.
Then run with ```sudo docker-compose up -d```
To see output run ```sudo docker container logs MD_Fan_Changer```
### Systemd
First create virtual enviroment
```
python3 -m venv venv
```
Then install required modules
```
venv/bin/pip3 install PySerial
```
After that you just need to change a few things
```SERIALADAPTER``` to a port you're using.
On linux it is /dev/ttyUSBx and on windows it is COMx
```EPPYSLEEPY``` is the interval in which script is checking temperature. By default it is 300 seconds, which is 5 minutes.
### Proxmox LXC
You can also run it in LXC container on your Proxmox host. Just follow the [systemd](###systemd) instructions.
Here you will also need to add ```/dev/ttyUSBx``` to your LXC container. You do it under Resources -> Add -> Device Passthrough -> ```/dev/ttyUSBx``` as Device Path.
## STM32F103C6T6
I think it needs a MAX2323 between MD1200.
Will look into that.
## Arduino Nano
Same here
### FAQ
dc: yuruc3

10
STM32_CONTROL/.vscode/extensions.json vendored Normal file
View File

@@ -0,0 +1,10 @@
{
// See http://go.microsoft.com/fwlink/?LinkId=827846
// for the documentation about the extensions.json format
"recommendations": [
"platformio.platformio-ide"
],
"unwantedRecommendations": [
"ms-vscode.cpptools-extension-pack"
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 MiB

View File

@@ -8,11 +8,13 @@
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html
[env:genericSTM32F103C6]
[env:bluepill_f103c6]
platform = ststm32
board = genericSTM32F103C6
framework = arduino
board = bluepill_f103c6
framework = stm32cube
lib_deps = adafruit/DHT sensor library@^1.4.6
debug_tool = stlink
upload_protocol = stlink
; Enable RX and TX 3
; build_flags = -D SERIAL_USB -D ENABLE_HWSERIAL3
; build_flags = -D SERIAL_USB -D ENABLE_HWSERIAL1 -D ENABLE_HWSERIAL2
; build_flags = -DENABLE_HWSERIAL1

View File

@@ -1,207 +1,130 @@
#include <Arduino.h>
#include <HardwareSerial.h>
// VARS
const int MD1200BAUDS = 38400; // From what I've read it is always 38400
//const int EPYSLEEPY = 600000; / 10 minutes
const int EPYSLEEPY = 300000; // 5 minutes
//const int EPYSLEEPY = 150000; // 2,5 minutes
HardwareSerial Serial1(31, 30);
#include <stm32f1xx_hal.h>
// declarations
int getTemp();
int setFanTrsh(int);
UART_HandleTypeDef huart1;
void setup() {
// Setup connection to MD1200
// Serial1 because we're using RX/TX pins
Serial1.begin(MD1200BAUDS);
// --- Function prototypes ---
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART1_UART_Init(void);
// Just debug
Serial.begin(9600);
Serial.print("skibidi");
// --- Optional RX buffer for interrupt ---
uint8_t rx_byte;
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init();
char msg[] = "UART started at 38400 baud\r\n";
// Send a startup message
HAL_UART_Transmit(&huart1, (uint8_t*)msg, sizeof(msg)-1, HAL_MAX_DELAY);
// Start interrupt-based receive of one byte
HAL_UART_Receive_IT(&huart1, &rx_byte, 1);
while (1)
{
// Send a heartbeat message every second
char heartbeat[] = "Ping\r\n";
HAL_UART_Transmit(&huart1, (uint8_t*)heartbeat, sizeof(heartbeat)-1, HAL_MAX_DELAY);
HAL_Delay(1000);
}
}
void loop() {
int fanPercnt = getTemp();
if (fanPercnt < 10) {
setFanTrsh(fanPercnt);
}
/*
check temperature and
set fan speed every X minutes
*/
delay(EPYSLEEPY);
// -----------------------------------------------------------------------------
// Error handler
// -----------------------------------------------------------------------------
void Error_Handler(void)
{
while (1) { }
}
// Get current temperature
int getTemp() {
int bp1 = 0;
int bp2 = 0;
int exp0 = 0;
int exp1 = 0;
int simm0 = 0;
int simm1 = 0;
String MD1200output;
Serial1.println("_temp_rd");
// -----------------------------------------------------------------------------
// UART1 initialization (38400 baud)
// -----------------------------------------------------------------------------
static void MX_USART1_UART_Init(void)
{
huart1.Instance = USART1;
huart1.Init.BaudRate = 38400;
huart1.Init.WordLength = UART_WORDLENGTH_8B;
huart1.Init.StopBits = UART_STOPBITS_1;
huart1.Init.Parity = UART_PARITY_NONE;
huart1.Init.Mode = UART_MODE_TX_RX;
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart1.Init.OverSampling = UART_OVERSAMPLING_16;
// wait for MD1200 to answer
delay(30);
while (Serial1.available()) {
MD1200output = Serial1.readStringUntil('\n');
// Check backplane 1
if (MD1200output.startsWith("BP_1")) {
// check index number of =
int eq = MD1200output.indexOf('=');
// check index number of c
int c = MD1200output.indexOf('c');
// check if both exists
if (eq != -1 && c != -1) {
/*
take value between "= " and "c".
NOTICE that eq + 1 is there because in
"BP_1[2] = 25c" there is a space between = and 25.
*/
bp1 = MD1200output.substring(eq + 1, c).toInt();
}
if (HAL_UART_Init(&huart1) != HAL_OK)
{
Error_Handler();
}
// Check backplane 2
if (MD1200output.startsWith("BP_2")) {
int eq = MD1200output.indexOf('=');
int c = MD1200output.indexOf('c');
if (eq != -1 && c != -1) {
bp2 = MD1200output.substring(eq + 1, c).toInt();
}
}
// Uncomment if you want to also get temperature for expanders
/*
// Check expander 0
if (MD1200output.startsWith("EXP0")) {
int eq = MD1200output.indexOf('=');
int c = MD1200output.indexOf('c');
if (eq != -1 && c != -1) {
exp0 = MD1200output.substring(eq + 1, c).toInt();
}
}
// Check expander 1
if (MD1200output.startsWith("EXP1")) {
int eq = MD1200output.indexOf('=');
int c = MD1200output.indexOf('c');
if (eq != -1 && c != -1) {
exp1 = MD1200output.substring(eq + 1, c).toInt();
}
}
// Check controller 0
if (MD1200output.startsWith("SIM0")) {
int eq = MD1200output.indexOf('=');
int c = MD1200output.indexOf('c');
if (eq != -1 && c != -1) {
simm0 = MD1200output.substring(eq + 1, c).toInt();
}
}
// Check controller 1
if (MD1200output.startsWith("SIM1")) {
int eq = MD1200output.indexOf('=');
int c = MD1200output.indexOf('c');
if (eq != -1 && c != -1) {
simm1 = MD1200output.substring(eq + 1, c).toInt();
}
}
*/
// Stop when prompt returns
if (MD1200output.endsWith(">")) {
break;
}
}
// Do (BP_1 + BP_2) / 2 to get the average of backplane
if (bp1 != -1 && bp2 != -1) {
int bpAvg = (bp1 + bp2) / 2;
// define default
int outPrcntg = 21;
// a
switch (bpAvg) {
case 23:
outPrcntg = 21;
break;
// Minimum is 21 (akhsually 20)
case 25:
outPrcntg = 23;
break;
case 27:
outPrcntg = 24;
break;
case 29:
outPrcntg = 26;
break;
case 31:
outPrcntg = 27;
break;
case 33:
outPrcntg = 30;
break;
case 35:
outPrcntg = 34;
break;
case 37:
outPrcntg = 38;
break;
/*
I don't wan't to become deaf so max is 40.
*/
default:
return -1;
}
return outPrcntg;
} else {
return -1; // failed to read temp
}
/*
BP_1[2] - Back plane, maybe left
BP_2[3] - Back plane, maybe right
SIM0[0] - Controller A
SIM1[1] - Controller B
EXP0[4] - Expander 0
EXP1[5] - Expander 1
AVG - average of all sensors
*/
}
// take percentage as int and set it.
int setFanTrsh(int fanTrshInp) {
String outputStatement = "set_speed " + String(fanTrshInp);
// -----------------------------------------------------------------------------
// GPIO for UART1 pins (PA9 TX, PA10 RX)
// -----------------------------------------------------------------------------
static void MX_GPIO_Init(void)
{
__HAL_RCC_GPIOA_CLK_ENABLE();
Serial.println("Sending " + outputStatement + " to MD1200");
GPIO_InitTypeDef GPIO_InitStruct = {0};
if (Serial1.println(outputStatement)) {
return 1;
}
else {
return -1;
}
// PA9 -> USART1_TX (Alternate Function Push-Pull)
GPIO_InitStruct.Pin = GPIO_PIN_9;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
// PA10 -> USART1_RX (Input)
GPIO_InitStruct.Pin = GPIO_PIN_10;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}
// -----------------------------------------------------------------------------
// Interrupt callback for UART receive
// -----------------------------------------------------------------------------
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if (huart->Instance == USART1) {
// Echo received byte back
HAL_UART_Transmit(&huart1, &rx_byte, 1, HAL_MAX_DELAY);
// Re-enable reception of next byte
HAL_UART_Receive_IT(&huart1, &rx_byte, 1);
}
}
// -----------------------------------------------------------------------------
// Basic system clock (HSE not required for 38400 UART)
// -----------------------------------------------------------------------------
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
HAL_RCC_OscConfig(&RCC_OscInitStruct);
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK |
RCC_CLOCKTYPE_SYSCLK |
RCC_CLOCKTYPE_PCLK1 |
RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0);
}
}

View File

@@ -0,0 +1,24 @@
// #include <Arduino.h>
#include <stm32f1xx_hal.h>
#define PIN_SERIAL3_RX PB11
#define PIN_SERIAL3_TX PB10
// UART2 (PA3 = RX, PA2 = TX)
HardwareSerial Serial1(PA10, PA9);
// HardwareSerial sigma(PA9, )
void setup() {
Serial1.begin(38400);
}
void loop() {
Serial1.write("skibidi");
delay(5000); // wait before next loop
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,219 @@
#include <Arduino.h>
#include <HardwareSerial.h>
// VARS
const int MD1200BAUDS = 38400; // From what I've read it is always 38400
//const int EPYSLEEPY = 600000; / 10 minutes
const int EPYSLEEPY = 300000; // 5 minutes
//const int EPYSLEEPY = 150000; // 2,5 minutes
HardwareSerial MDSerial(PA3, PA2); // Rx Tx
HardwareSerial DeBug(PA10, PA9); // Rx Tx
// HardwareSerial DeBug(USART1); // use USART1
// declarations
int getTemp();
int setFanTrsh(int);
void setup() {
// Setup connection to MD1200
// MDSerial because we're using RX/TX pins
MDSerial.begin(MD1200BAUDS);
// Just debug
DeBug.begin(9600);
DeBug.println("skibidi");
delay(15000);
}
void loop() {
DeBug.println("Starting loop");
int fanPercnt = getTemp();
if (fanPercnt < 10) {
DeBug.println("Executing setFanTrsh");
setFanTrsh(fanPercnt);
}
/*
check temperature and
set fan speed every X minutes
*/
delay(EPYSLEEPY);
DeBug.println("Ending loop");
}
// Get current temperature
int getTemp() {
DeBug.println("Getting Temperature");
int bp1 = 0;
int bp2 = 0;
int exp0 = 0;
int exp1 = 0;
int simm0 = 0;
int simm1 = 0;
String MD1200output;
MDSerial.println("_temp_rd");
// wait for MD1200 to answer
delay(30);
DeBug.println("getTemp logic start");
while (MDSerial.available()) {
MD1200output = MDSerial.readStringUntil('\n');
// Check backplane 1
if (MD1200output.startsWith("BP_1")) {
// check index number of =
int eq = MD1200output.indexOf('=');
// check index number of c
int c = MD1200output.indexOf('c');
// check if both exists
if (eq != -1 && c != -1) {
/*
take value between "= " and "c".
NOTICE that eq + 1 is there because in
"BP_1[2] = 25c" there is a space between = and 25.
*/
bp1 = MD1200output.substring(eq + 1, c).toInt();
}
}
// Check backplane 2
if (MD1200output.startsWith("BP_2")) {
int eq = MD1200output.indexOf('=');
int c = MD1200output.indexOf('c');
if (eq != -1 && c != -1) {
bp2 = MD1200output.substring(eq + 1, c).toInt();
}
}
// Uncomment if you want to also get temperature for expanders
/*
// Check expander 0
if (MD1200output.startsWith("EXP0")) {
int eq = MD1200output.indexOf('=');
int c = MD1200output.indexOf('c');
if (eq != -1 && c != -1) {
exp0 = MD1200output.substring(eq + 1, c).toInt();
}
}
// Check expander 1
if (MD1200output.startsWith("EXP1")) {
int eq = MD1200output.indexOf('=');
int c = MD1200output.indexOf('c');
if (eq != -1 && c != -1) {
exp1 = MD1200output.substring(eq + 1, c).toInt();
}
}
// Check controller 0
if (MD1200output.startsWith("SIM0")) {
int eq = MD1200output.indexOf('=');
int c = MD1200output.indexOf('c');
if (eq != -1 && c != -1) {
simm0 = MD1200output.substring(eq + 1, c).toInt();
}
}
// Check controller 1
if (MD1200output.startsWith("SIM1")) {
int eq = MD1200output.indexOf('=');
int c = MD1200output.indexOf('c');
if (eq != -1 && c != -1) {
simm1 = MD1200output.substring(eq + 1, c).toInt();
}
}
*/
// Stop when prompt returns
if (MD1200output.endsWith(">")) {
DeBug.println("getTemp got all temp info");
break;
}
}
// Do (BP_1 + BP_2) / 2 to get the average of backplane
if (bp1 != -1 && bp2 != -1) {
int bpAvg = (bp1 + bp2) / 2;
// define default
int outPrcntg = 21;
// a
DeBug.println("getTemp choosing fan speed");
switch (bpAvg) {
case 23:
outPrcntg = 21;
break;
// Minimum is 21 (akhsually 20)
case 25:
outPrcntg = 23;
break;
case 27:
outPrcntg = 24;
break;
case 29:
outPrcntg = 26;
break;
case 31:
outPrcntg = 27;
break;
case 33:
outPrcntg = 30;
break;
case 35:
outPrcntg = 34;
break;
case 37:
outPrcntg = 38;
break;
/*
I don't wan't to become deaf so max is 40.
*/
default:
return -1;
DeBug.println("getTemp error returning -1");
}
DeBug.println("getTemp return " + String(outPrcntg));
return outPrcntg;
} else {
return -1; // failed to read temp
}
/*
BP_1[2] - Back plane, maybe left
BP_2[3] - Back plane, maybe right
SIM0[0] - Controller A
SIM1[1] - Controller B
EXP0[4] - Expander 0
EXP1[5] - Expander 1
AVG - average of all sensors
*/
}
// take percentage as int and set it.
int setFanTrsh(int fanTrshInp) {
String outputStatement = "set_speed " + String(fanTrshInp);
DeBug.println("Sending " + outputStatement + " to MD1200");
if (MDSerial.println(outputStatement)) {
return 1;
}
else {
return -1;
}
}

View File

@@ -2,6 +2,7 @@
#include <Adafruit_Sensor.h>
#include <DHT.h>
#include <DHT_U.h>
#include <HardwareSerial.h>
// -------------------------------------------------------------
// DHT PreConfiguration
@@ -22,6 +23,7 @@ const int MD1200BAUDS = 38400; // From what I've read it is always 38400
//const int EPYSLEEPY = 600000; / 10 minutes
const int EPYSLEEPY = 300000; // 5 minutes
//const int EPYSLEEPY = 150000; // 2,5 minutes
HardwareSerial MDSerial(PA3, PA2); // Rx Tx
// declarations
int getTemp();
@@ -30,8 +32,8 @@ float dhtRead();
void setup() {
// Setup connection to MD1200
// Serial1 because we're using RX/TX pins
Serial1.begin(MD1200BAUDS);
// MDSerial because we're using RX/TX pins
MDSerial.begin(MD1200BAUDS);
// Just debug
Serial.begin(9600);
@@ -66,13 +68,13 @@ int getTemp() {
int simm1 = 0;
String MD1200output;
Serial1.println("_temp_rd");
MDSerial.println("_temp_rd");
// wait for MD1200 to answer
delay(30);
while (Serial1.available()) {
MD1200output = Serial1.readStringUntil('\n');
while (MDSerial.available()) {
MD1200output = MDSerial.readStringUntil('\n');
// Check backplane 1
if (MD1200output.startsWith("BP_1")) {
@@ -221,7 +223,7 @@ int setFanTrsh(int fanTrshInp) {
Serial.println("Sending " + outputStatement + " to MD1200");
if (Serial1.println(outputStatement)) {
if (MDSerial.println(outputStatement)) {
return 1;
}
else {

232
dashboard.json Normal file
View File

@@ -0,0 +1,232 @@
{
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": {
"type": "grafana",
"uid": "-- Grafana --"
},
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"type": "dashboard"
}
]
},
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
"id": 38,
"links": [],
"panels": [
{
"collapsed": false,
"gridPos": {
"h": 1,
"w": 24,
"x": 0,
"y": 0
},
"id": 2,
"panels": [],
"title": "MD1200-1",
"type": "row"
},
{
"datasource": {
"type": "influxdb",
"uid": "bdp80jf4cy328f"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"barWidthFactor": 0.6,
"drawStyle": "line",
"fillOpacity": 0,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green"
},
{
"color": "red",
"value": 80
}
]
},
"unit": "celsius"
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "Average {LOCATION=\"HQ\", MACHINE=\"CHONGUS1200\"}"
},
"properties": [
{
"id": "displayName",
"value": "Average"
}
]
},
{
"matcher": {
"id": "byName",
"options": "Backplane1 {LOCATION=\"HQ\", MACHINE=\"CHONGUS1200\"}"
},
"properties": [
{
"id": "displayName",
"value": "Backplane 1"
}
]
},
{
"matcher": {
"id": "byName",
"options": "Backplane2 {LOCATION=\"HQ\", MACHINE=\"CHONGUS1200\"}"
},
"properties": [
{
"id": "displayName",
"value": "Backplane 2"
}
]
},
{
"matcher": {
"id": "byName",
"options": "Expander0 {LOCATION=\"HQ\", MACHINE=\"CHONGUS1200\"}"
},
"properties": [
{
"id": "displayName",
"value": "Expander 0"
}
]
},
{
"matcher": {
"id": "byName",
"options": "Expander1 {LOCATION=\"HQ\", MACHINE=\"CHONGUS1200\"}"
},
"properties": [
{
"id": "displayName",
"value": "Expander 1"
}
]
},
{
"matcher": {
"id": "byName",
"options": "SASIntModule0 {LOCATION=\"HQ\", MACHINE=\"CHONGUS1200\"}"
},
"properties": [
{
"id": "displayName",
"value": "SAS interface module 0"
}
]
},
{
"matcher": {
"id": "byName",
"options": "SASIntModule1 {LOCATION=\"HQ\", MACHINE=\"CHONGUS1200\"}"
},
"properties": [
{
"id": "displayName",
"value": "SAS interface module 1"
}
]
}
]
},
"gridPos": {
"h": 10,
"w": 24,
"x": 0,
"y": 1
},
"id": 1,
"options": {
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"hideZeros": false,
"mode": "single",
"sort": "none"
}
},
"pluginVersion": "12.0.2",
"targets": [
{
"datasource": {
"type": "influxdb",
"uid": "bdp80jf4cy328f"
},
"query": "from(bucket: \"JBOD\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"MD1200-script\")\n |> filter(fn: (r) => r[\"LOCATION\"] == \"HQ\")\n |> filter(fn: (r) => r[\"MACHINE\"] == \"CHONGUS1200\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: \"mean\")",
"refId": "A"
}
],
"title": "Temperatures",
"type": "timeseries"
}
],
"preload": false,
"schemaVersion": 41,
"tags": [],
"templating": {
"list": []
},
"time": {
"from": "now-3h",
"to": "now"
},
"timepicker": {},
"timezone": "browser",
"title": "JBODs",
"uid": "1ee6cf76-943c-45e0-8d6f-c92db9691459",
"version": 3
}

18
docker-compose.yml Normal file
View File

@@ -0,0 +1,18 @@
---
services:
mdfanchanger:
container_name: MD_Fan_Changer
image: yuruc3/md1200_fan_controll:v1.2.1
environment:
# - MD1200BAUD=
- SERIALADAPTER=/dev/ttyUSB0
- TEMP_FACTOR=17
- EPPYSLEEPY=0.25
- MDSERIALTIMEOUT=0.75
# - LOW_FAN_TRSHD=
# - HIGH_FAN_TRSHD=
devices:
- /dev/ttyUSB0:/dev/ttyUSB0
restart: unless-stopped
privileged: false