Skip to content

Instantly share code, notes, and snippets.

@hrafnkelle
Last active December 7, 2017 13:22
Show Gist options
  • Save hrafnkelle/390f62842468048a94baa67a2081f1df to your computer and use it in GitHub Desktop.
Save hrafnkelle/390f62842468048a94baa67a2081f1df to your computer and use it in GitHub Desktop.
Experiment with asyncio reading of w1 sensor and aiohttp interface
.vscode
Include
Lib
Scripts
tcl
sensors:
- RecircTemp:
id: 28-000004b8240b
offset: 1.2
actors:
- Heater:
gpio: 18
pwmFrequency: 2
- Pump:
gpio: 17
pwmFrequency: 2
control:
agitator: Pump
heater: Heater
sensor: RecircTemp
logic: pid
import asyncio
import aiofiles
from aiohttp import web
import time
import sys
import os
from ruamel.yaml import YAML
# import RPi.GPIO as GPIO
# GPIO.setmode(GPIO.BCM)
# print("Setting BCM mode")
class PIDArduino(object):
def __init__(self, sampleTimeSec, kp, ki, kd, outputMin=float('-inf'),
outputMax=float('inf'), getTimeMs=None):
if kp is None:
raise ValueError('kp must be specified')
if ki is None:
raise ValueError('ki must be specified')
if kd is None:
raise ValueError('kd must be specified')
if sampleTimeSec <= 0:
raise ValueError('sampleTimeSec must be greater than 0')
if outputMin >= outputMax:
raise ValueError('outputMin must be less than outputMax')
self._Kp = kp
self._Ki = sampleTimeSec / ki
self._Kd = kd / sampleTimeSec
self._sampleTime = sampleTimeSec * 1000
self._outputMin = outputMin
self._outputMax = outputMax
self._iTerm = 0
self._lastInput = 0
self._lastOutput = 0
self._lastCalc = 0
if getTimeMs is None:
self._getTimeMs = self._currentTimeMs
else:
self._getTimeMs = getTimeMs
def reset():
self._iTerm = 0.0
def calc(self, inputValue, setpoint):
now = self._getTimeMs()
if (now - self._lastCalc) < self._sampleTime:
return self._lastOutput
# Compute all the working error variables
error = setpoint - inputValue
dInput = inputValue - self._lastInput
# In order to prevent windup, only integrate if the process is not saturated
if self._lastOutput < self._outputMax and self._lastOutput > self._outputMin:
self._iTerm += self._Ki * error
self._iTerm = min(self._iTerm, self._outputMax)
self._iTerm = max(self._iTerm, self._outputMin)
p = self._Kp * error
i = self._iTerm
d = -(self._Kd * dInput)
# Compute PID Output
self._lastOutput = p + i + d
self._lastOutput = min(self._lastOutput, self._outputMax)
self._lastOutput = max(self._lastOutput, self._outputMin)
# Remember some variables for next time
self._lastInput = inputValue
self._lastCalc = now
return self._lastOutput
def _currentTimeMs(self):
return time.time() * 1000
class Runnable:
def run(self, app):
pass
class Actor:
def __init__(self, name, pin, pwmFrequency):
self.name = name
self.power = 0.0
self.pin = pin
self.frequency = pwmFrequency
# GPIO.setup(self.pin, GPIO.OUT)
# self.p = GPIO.PWM(self.pin, self.frequency)
# self.p.start(self.power)
def updatePower(self, power):
self.power = power
# self.p.ChangeDutyCycle(self.power)
async def get(self, request):
return web.Response(text="%f"%self.power)
async def postPower(self, request):
power_str = await request.text()
self.updatePower(float(power_str))
return web.Response()
async def postOnOff(self, request):
self.state = request.match_info['state']
if self.state == 'off':
self.updatePower(0.0)
elif self.state == 'on':
self.updatePower(100.0)
return web.Response(text=self.state)
class Sensor(Runnable):
def __init__(self, name, sensorId, offset=0):
self.name = name
self.sensorId = sensorId
self.offset = offset
self.lastTemp = 0.0
async def run(self, app):
while True:
self.lastTemp = await self.readTemp() + self.offset
await asyncio.sleep(2)
async def readTemp(self):
await asyncio.sleep(2)
return 24.0
# async with aiofiles.open('/sys/bus/w1/devices/%s/w1_slave'% self.sensorId, mode='r') as sensor_file:
# contents = await sensor_file.read()
# if (contents.split('\n')[0].split(' ')[11] == "YES"):
# temp = float(contents.split("=")[-1]) / 1000
# return temp
# else:
# return -100
def get(self, request):
return web.Response(text="%f"%self.lastTemp)
class Controller(Runnable):
def __init__(self, sensor, actor):
self.state = 'off'
self.sensor = sensor
self.actor = actor
self.targetTemp = 0.0
self.pid = PIDArduino(10, 50, 2, 10, 0, 100)
async def getState(self, request):
return web.Response(text=self.state)
async def postState(self, request):
self.state = request.match_info['state']
if self.state == 'off':
self.actor.updatePower(0.0)
return web.Response(text=self.state)
async def postTemp(self, request):
self.targetTemp = float(await request.text())
print("Target temp set to: %f"%self.targetTemp)
return web.Response(text=str(self.targetTemp))
async def getTemp(self, request):
return web.Response(text=str(self.targetTemp))
async def run(self, app):
while True:
if self.state == 'on':
output = self.pid.calc(self.sensor.lastTemp, self.targetTemp)
self.actor.updatePower(output)
await asyncio.sleep(1)
else:
self.actor.updatePower(0)
await asyncio.sleep(1)
#sensor = Sensor("28-000004b8240b")
#heater = Actor(pin=18)
#pump = Actor(pin=17)
async def index(request):
return web.FileResponse('index.html')
sensors = {}
actors = {}
yaml = YAML(typ='safe') # default, if not specfied, is 'rt' (round-trip)
config = yaml.load(open('config.yaml',mode='r'))
for sensor in config['sensors']:
for name, attribs in sensor.items():
sensors[name] = Sensor(name, attribs['id'], attribs['offset'])
for actor in config['actors']:
for name, attribs in actor.items():
actors[name] = Actor(name, attribs['gpio'], attribs['pwmFrequency'])
sensor = sensors['RecircTemp']
heater = actors['Heater']
pump = actors['Pump']
ctrl = Controller(sensor, heater)
async def start_background_tasks(app):
app['controller_runner'] = app.loop.create_task(ctrl.run(app))
for name, sensor in sensors.items():
app[name] = app.loop.create_task(sensor.run(app))
async def cleanup_background_tasks(app):
for name, sensor in sensors.items():
app[name].cancel()
await app[name]
app['controller_runner'].cancel()
await app['controller_runner']
app = web.Application()
app.on_startup.append(start_background_tasks)
app.on_cleanup.append(cleanup_background_tasks)
app.router.add_get('/sensor', sensor.get)
app.router.add_routes([web.get('/heater', heater.get), web.post('/heater', heater.postPower)])
app.router.add_get('/controller', ctrl.getState)
app.router.add_post('/controller/target', ctrl.postTemp)
app.router.add_get('/controller/target', ctrl.getTemp)
app.router.add_post('/controller/{state}',ctrl.postState)
app.router.add_post('/pump/{state}', pump.postOnOff)
app.router.add_get('/',index)
web.run_app(app)
<html>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular.min.js"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/css/bootstrap.min.css" integrity="sha384-PsH8R72JQ3SOdhVi3uxftmaW6Vc51MKb0q5P2rRUpPvrszuE4W1povHYgTpBfshb" crossorigin="anonymous">
<body>
<h1>TF-Brew</h1>
<div ng-app="tfbrew" ng-controller="tfbrewCtrl">
<p>Target temp: <input type="number" ng-model="targetTemp"/> <button class="btn btn-primary" ng-click="submitTargetTemp()">Update</button></p>
<p>Actual temp: {{actualTemp}} C</p>
<p>Controller Enabled: <button class="btn btn-info" ng-class="{'active': controllerState === true}" ng-click="toggleController()">{{controllerState}}</button> Controller power: {{power}}</p>
<p><button class="btn btn-info" ng-class="{'active': pumpState === true}" ng-click="togglePump()">{{pumpState}}</button></button></p>
</div>
<script>
var app = angular.module('tfbrew', []);
app.controller('tfbrewCtrl', function($scope, $http, $interval) {
$scope.controllerState = false;
$scope.pumpState = false;
$http.get("/controller")
.then(function(response) {
if(response.data == 'off') {
$scope.controllerState = false;
}
else if (response.data == 'on') {
$scope.controllerState = true;
}
});
$http.get("/controller/target")
.then(function(response) {
$scope.targetTemp = response.data;
});
$scope.toggleController = function() {
if ($scope.controllerState === true) {
$http.post("/controller/off","")
}
else {
$http.post("/controller/on","")
}
$scope.controllerState = !$scope.controllerState;
}
$scope.togglePump = function() {
if ($scope.pumpState === true) {
$http.post("/pump/off","")
}
else {
$http.post("/pump/on","")
}
$scope.pumpState = !$scope.pumpState;
}
$scope.submitTargetTemp = function() {
$http.post("/controller/target",$scope.targetTemp.toString());
}
var updateData = function() {
$http.get("/sensor")
.then(function(response) {
$scope.actualTemp = response.data;
});
$http.get("/heater")
.then(function(response) {
$scope.power = response.data;
});
}
$interval(updateData, 2000);
});
</script>
</body>
</html>
{"last_check":"2017-12-04T16:47:03Z","pypi_version":"9.0.1"}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment