Jan-27-2018, 06:45 PM
Hi everyone,
I am trying to learn a bit of python and picked up a little project to give the coding a purpose. I am trying to make a temperature stabilized water bath using a temperature sensor, a heater and a raspberry pi (and some more stuff, like an oled display and a rotary encoder). I tried out bokeh to create a web interface and everything was going pretty ok until I asked my girlfriend to take a look on her phone while I had the web interface open on my pc. She opened the url that runs the python script and caused all kinds of errors because she was initializing all the hardware again. My mistake was to not separate the hardware controlling part of the script from the data visualization. If I could use bokeh to just run a script that plots the data and gets fed the temperature readings from the hardware controlling script it could be run as many times as it needs to be. I would run the hardware script and one plotting script as the pi starts up. As more people look at the url the plotting part gets executed multiple times, but the hardware script keeps being the only instance.
I have no idea how to do that, though. I looked at nmap and subprocess, but I don't really get it. My data is currently a dictionary, but I am not necessarily insisting on that...
Could be that there are other solutions to my problem as well... Maybe bokeh can be configured/run in a way that just shows the same thing to multiple users and is not running the script again. Maybe there is a clever way to do what I want to do from a single script without running into problems. I am happy with any solution, but I thought the piping of data from one script to another could be an interesting thing to learn as well.
I am trying to learn a bit of python and picked up a little project to give the coding a purpose. I am trying to make a temperature stabilized water bath using a temperature sensor, a heater and a raspberry pi (and some more stuff, like an oled display and a rotary encoder). I tried out bokeh to create a web interface and everything was going pretty ok until I asked my girlfriend to take a look on her phone while I had the web interface open on my pc. She opened the url that runs the python script and caused all kinds of errors because she was initializing all the hardware again. My mistake was to not separate the hardware controlling part of the script from the data visualization. If I could use bokeh to just run a script that plots the data and gets fed the temperature readings from the hardware controlling script it could be run as many times as it needs to be. I would run the hardware script and one plotting script as the pi starts up. As more people look at the url the plotting part gets executed multiple times, but the hardware script keeps being the only instance.
I have no idea how to do that, though. I looked at nmap and subprocess, but I don't really get it. My data is currently a dictionary, but I am not necessarily insisting on that...
Could be that there are other solutions to my problem as well... Maybe bokeh can be configured/run in a way that just shows the same thing to multiple users and is not running the script again. Maybe there is a clever way to do what I want to do from a single script without running into problems. I am happy with any solution, but I thought the piping of data from one script to another could be an interesting thing to learn as well.
import RPi.GPIO as GPIO from threading import Lock, Thread from functools import partial from concurrent.futures import ThreadPoolExecutor from time import sleep, time from bokeh.layouts import column from bokeh.models import ColumnDataSource from bokeh.document import without_document_lock from bokeh.plotting import figure, curdoc import Adafruit_MCP9808.MCP9808 as MCP9808 from PID import PID from tornado import gen from TempDisp import TempDisp # GPIO Ports Enc_A = 27 # Encoder input A: input GPIO 4 Enc_B = 17 # Encoder input B: input GPIO 14 button = 18 SSR = 26 Rotary_counter = 0 # Start counting from 0 Current_A = 1 # Assume that rotary switch is not Current_B = 1 # moving while we init software running = False pwm_frequency = 100 t0 = time() tsens = MCP9808.MCP9808() P = 20 I = 0 D = 0 pid = PID(P,I,D) pid.SetPoint = 0 pid.setSampleTime = 0.2 disp = TempDisp() LockRotary = Lock() # create lock for rotary switch executor = ThreadPoolExecutor(max_workers=2) def start_stop_pwm(channel): global pwm, running, pidOut if running == True: pwm.stop() running = False else: pwm.start(pidOut) running = True # initialize interrupt handlers def init(): GPIO.setwarnings(True) GPIO.setmode(GPIO.BCM) # I used BOARD (not BCM) mode, but that was causing problems with the oled library # define the Encoder switch inputs GPIO.setup(SSR,GPIO.OUT) GPIO.setup(Enc_A, GPIO.IN, pull_up_down = GPIO.PUD_UP) GPIO.setup(Enc_B, GPIO.IN, pull_up_down = GPIO.PUD_UP) GPIO.setup(button, GPIO.IN, pull_up_down=GPIO.PUD_UP) #define "button" pin as input and set up internal pullup resis$ # setup callback thread for the A and B encoder # use interrupts for all inputs GPIO.add_event_detect(Enc_A, GPIO.RISING, callback=rotary_interrupt) # NO bouncetime GPIO.add_event_detect(Enc_B, GPIO.RISING, callback=rotary_interrupt) # NO bouncetime GPIO.add_event_detect(button, GPIO.FALLING, callback=start_stop_pwm, bouncetime=200) tsens.begin() return # Rotarty encoder interrupt: # this one is called for both inputs from rotary switch (A and B) def rotary_interrupt(A_or_B): global Rotary_counter, Current_A, Current_B, LockRotary # read both of the switches Switch_A = GPIO.input(Enc_A) Switch_B = GPIO.input(Enc_B) # now check if state of A or B has changed # if not that means that bouncing caused it if Current_A == Switch_A and Current_B == Switch_B: # Same interrupt as before (Bouncing)? return # ignore interrupt! Current_A = Switch_A # remember new state Current_B = Switch_B # for next bouncing check if (Switch_A and Switch_B): # Both one active? Yes -> end of sequence LockRotary.acquire() # get lock if A_or_B == Enc_B: # Turning direction depends on Rotary_counter += 1 # which input gave last interrupt else: # so depending on direction either Rotary_counter -= 1 # increase or decrease counter LockRotary.release() # and release lock return # THAT'S IT doc = curdoc() @gen.coroutine def update_data(x,y1,y2): new_data=dict(t = [x], tarTemp = [y1], temp = [y2]) data.stream(new_data, 10000) # Main loop. Demonstrate reading, direction and speed of turning left/rignt @gen.coroutine @without_document_lock def main(): global tarTemp, pwm, Rotary_counter, LockRotary, SSR, pwm_frequency, t0, tsens, pid, pidOut pidOut = 0 tarTemp = 0 # Current tarTemp NewCounter = 0 # for faster reading with locks init() # Init interrupts, GPIO, ... pwm = GPIO.PWM(SSR,pwm_frequency) #allows pwm.start(DutyCycle) followed by pwm.ChangeDutyCycle(DutyCycle) and pwm.ChangeFrequency(pwm_frequency) try: while True : # start test sleep(0.2) # sleep 200 msec # because of threading make sure no thread # changes value until we get them # and reset them LockRotary.acquire() # get lock for rotary switch NewCounter = Rotary_counter # get counter value Rotary_counter = 0 # RESET IT TO 0 LockRotary.release() # and release lock if (NewCounter !=0): # Counter has CHANGED tarTemp = tarTemp + NewCounter*abs(NewCounter) # Decrease or increase tarTemp if tarTemp < 0: # limit tarTemp to 0...100 tarTemp = 0 elif tarTemp > 100: # limit tarTemp to 0...100 tarTemp = 100 pid.SetPoint = tarTemp pidOut=pid.output # !!! MAPPING NEEDS FIXING !!! if pidOut > 100: pidOut = 100 elif pidOut < 0: pidOut = 0 print(pid.output,pidOut) pwm.ChangeDutyCycle(pidOut) newT = tsens.readTempC() newTemp = round(newT,1) disp.Display(tarTemp,newTemp) pid.update(newTemp) doc.add_next_tick_callback(partial(update_data, x=time()-t0, y1=tarTemp, y2=newTemp)) except KeyboardInterrupt: print('interrupted with keyboard') except: print('unspecified error') finally: GPIO.cleanup() data=ColumnDataSource(dict(t=[], tarTemp=[], temp=[])) fig=figure(logo=None) fig.xaxis.axis_label = 'Time (s)' fig.line(source=data, x='t', y='tarTemp', line_width=2, alpha=.85, color='pink') fig.line(source=data, x='t', y='temp', line_width=2, alpha=.85, color='purple') doc.add_root(fig) thread = Thread(target=main) thread.start()Lots of this code is copy pasted from solutions I found on the internet. I am not claiming that any of this is my intellectual property.