Skip to content

Instantly share code, notes, and snippets.

@unreal4u
Created August 28, 2018 20:57
Show Gist options
  • Save unreal4u/431e8b0263793e822037b0f971bb33ec to your computer and use it in GitHub Desktop.
Save unreal4u/431e8b0263793e822037b0f971bb33ec to your computer and use it in GitHub Desktop.
Turns on or off a relay depending on the value that the MQTT broker sends.
<?php
/**
* This script is a quick and dirty way to turn a relay on or off in the basement
* of my house. It will listen to a certain topic and activate the commands that
* are sent in that topic. It has a built-in failsave so that the fan turns off
* automatically after 45 minutes.
*/
declare(strict_types=1);
use Monolog\Handler\RotatingFileHandler;
use Monolog\Handler\StreamHandler;
use Monolog\Logger;
use PiPHP\GPIO\GPIO;
use PiPHP\GPIO\Pin\PinInterface;
use unreal4u\MQTT\Application\Message;
use unreal4u\MQTT\Client;
use unreal4u\MQTT\Protocol\Connect;
use unreal4u\MQTT\Protocol\Connect\Parameters;
use unreal4u\MQTT\Protocol\Publish;
use unreal4u\MQTT\Protocol\Subscribe;
use unreal4u\MQTT\Protocol\Subscribe\Topic;
function writeStateFile(\DateTimeImmutable $date, string $command, Logger $logger): bool
{
$logger->debug('Writing statefile', ['date' => $date->format('c'), 'command' => $command]);
file_put_contents('statefile.json', json_encode(['commandDate' => $date, 'command' => $command]));
return true;
}
function getStateFile(Logger $logger): array
{
$logger->debug('Getting statefile');
$stateFile = json_decode(file_get_contents('statefile.json'), true);
return [
'commandDate' => new \DateTimeImmutable(
$stateFile['commandDate']['date'],
new \DateTimeZone($stateFile['commandDate']['timezone'])
),
'command' => $stateFile['command'],
];
}
function failsaveFanShutdown(Logger $logger): bool
{
$stateFile = getStateFile($logger);
if ($stateFile['command'] === 'start') {
$logger->debug('Fan on, checking whether it is time to force fan shutdown');
$now = new \DateTimeImmutable('now', new \DateTimeZone('UTC'));
/** @var \DateTimeImmutable $optionalFutureDate */
$optionalFutureDate = $stateFile['commandDate']->add(new \DateInterval('PT45M'));
$logger->info('Dates', [
'now' => $now->format('YmdHis'),
'optionalFuture' => $optionalFutureDate->format('YmdHis'),
'diff' => $optionalFutureDate > $now
]);
if ($now > $optionalFutureDate) {
$logger->debug('Fan has been on for more than 45 minutes, sending shut down command');
return true;
}
$logger->debug('Not time yet to shut fan down', [
'turnedOn' => $stateFile['commandDate']->format('Y-m-d H:i'),
'turnOffTime' => $optionalFutureDate->format('Y-m-d H:i'),
]);
}
return false;
}
chdir(__DIR__ . '/../');
include 'vendor/autoload.php';
// Which pin controls this relay
const RELAY_PIN = 14;
// Generate an unique 6 character string id for this run (doesn't need to be cryptographically secure)
$uniqId = substr(uniqid('fbr' . mt_rand(0, 255), true), -8);
$logger = new Logger('main-' . $uniqId);
$logger->pushHandler(new StreamHandler('php://stdout', Logger::DEBUG));
$logger->pushHandler(new RotatingFileHandler('logs/FanBaseroomController.log', 14, Logger::DEBUG));
$logger->info('Program startup', ['uniqueId' => $uniqId]);
$connectionParameters = new Parameters('fbr-' . $uniqId, '192.168.1.1');
$connectionParameters->setKeepAlivePeriod(30);
$connectionParameters->setUsername('xxxx');
$connectionParameters->setPassword('xxxx');
$connect = new Connect();
$connect->setConnectionParameters($connectionParameters);
try {
$client = new Client();
$client->sendData($connect);
} catch (\Exception $e) {
printf($e->getMessage());
die();
}
$logger->info('Client connected, continuing...');
if (!file_exists('statefile.json')) {
writeStateFile(new \DateTimeImmutable('now', new \DateTimeZone('UTC')), 'stop', $logger);
}
if (failsaveFanShutdown($logger) === true) {
$logger->notice('Forcing shutdown of fan');
$message = new Message();
$message->setTopicName('commands/fan/kelder');
$message->setQoSLevel(1);
$message->setPayload(json_encode([
'commandDate' => new \DateTimeImmutable('now', new \DateTimeZone('UTC')),
'command' => 'stop'
]));
$publish = new Publish();
$publish->setMessage($message);
$client->sendData($publish);
}
$lockFile = fopen('commandReaderVentilator.lock', 'wb+');
if (!flock($lockFile, LOCK_EX | LOCK_NB)) {
$logger->notice('Could not adquire lock, creating notification and dying');
$message = new Message();
$message->setTopicName('notifications/telegram');
$message->setPayload('[RPi3] Could not adquire lock on file ' . __FILE__ . ':' . __LINE__);
$publish = new Publish();
$publish->setMessage($message);
$client->sendData($publish);
die();
}
$subscribe = new Subscribe();
$subscribe->addTopics(new Topic('commands/fan/kelder'));
$logger->info('Everything ready, subscribing to topic and waiting...');
foreach ($subscribe->loop($client) as $message) {
$decodedObject = json_decode($message->getPayload(), true);
$commandDate = new \DateTimeImmutable(
$decodedObject['commandDate']['date'],
new \DateTimezone($decodedObject['commandDate']['timezone'])
);
$command = $decodedObject['command'];
$stateFile = getStateFile($logger);
$stateFileDate = $stateFile['commandDate'];
$logger->debug('Reading from statefile', [
'date' => $stateFileDate->format('c'),
'command' => $stateFile['command']
]);
if ($stateFile['command'] !== $command) {
$logger->info('Got change in command', [
'stateFileCommand' => $stateFile['command'],
'mqttCommand' => $command,
]);
writeStateFile($commandDate, $command, $logger);
$gpio = new GPIO();
$pin = $gpio->getOutputPin(RELAY_PIN);
if ($command === 'start') {
$pin->setValue(PinInterface::VALUE_LOW);
} else {
$pin->setValue(PinInterface::VALUE_HIGH);
}
$logger->notice('Fan ' . $command);
$message = new Message();
$message->setTopicName('status/fan/kelder');
$message->setRetainFlag(true);
$message->setQoSLevel(1);
$message->setPayload(json_encode(['date' => $commandDate, 'status' => ($command === 'start') ? 'on' : 'off']));
$publish = new Publish();
$publish->setMessage($message);
$client->sendData($publish);
} else {
$logger->warning('Statefile and mqtt command are the same!');
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment