Created
August 28, 2018 20:57
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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