I was following this post to setup a brewpi remix arduino with an on-board ESP8266 using firmware version 0.2.13, which as far as I can tell is the last version of brewpi remix legacy that you can run on Arduino. In order to get this to work for me I had to adapt them a little to use esptool (as Im running on Linux) and use different code to properly handle communications over a TCVP socket as well as usb.
But wait. Before you take a read, be aware although I once was, I am no longer a developer and I claim no python skills whatsoever as its quite new to me. I may also not be doing this the right way. And someone may well have done this before but a better way.
All I’m saying is in the time I had available between searching the internet, reading and then coding and testing, this is how I ended up with a working brewpi legacy running over wifi to my Ubuntu Studio web server. I found it quicker than trawling through various scattered posts to find an answer for an old an unsupported product.
I also take no credit whatsoever for the code, all of which belongs to the original authors you will find on the brewPi website. Very clever and generous folk. You should be following that guide, which is all Ive done, unless you are stuck needing modified code to connect to the ESP, or cant find how to use esptool. Pretty much, noone should need this page, so what are you doing here?
Anyway, I managed to get this running by flashing the arduino Uno, after correctly setting the jumper switches (00110000), with:
avrdude -F -e -p atmega328p -c arduino -b 115200 -P /dev/ttyUSB1 -U flash:w:"/home/master/brewpi-tools-rmx/brewpi-arduino-uno-revc-0.2.13+da7e14a9.hex" -C /usr/share/arduino/hardware/tools/avrdude.conf
After resetting the jumpers to address the esp8266 (00001110) I used esptool to flash the following:
esptool.py --port /dev/ttyUSB0 --baud 460800 write_flash -fs detect 0x00000 boot_v1.4\(b1\).bin 0x1000 user1.bin 0x3FC000 esp_init_data_default.bin 0x3FE000 blank.bin
Those settings worked for an 8Mb board, but with -fs detect I would expect it to work with other boards. Obviously you’ll need to use the file locations and arduino IDE setup for your system. You can get the latter by using the IDE to download a blank program and grap the avrdude syntax from the output logs.
After correctly setting the jumpers (11000000) and setting up the wifi as per esp_link instructions, I was left with a working arduino connected to wifi, but with no web interface because the script wont connect as it stands. As the guide notes explain, changes to the python code are required. That’s because it cant handle an IP socket when you change the port in config.cfg to reflect the IP address of the esp8266.
I modified the code to add this support, which includes some additional string processing and error handling I found necessary. The code requiring an update is backgroundserial.py in the brewpi directory. I replaced its contents with this:
#!/usr/bin/env python3
# Copyright (C) 2018, 2019 Lee C. Bussy (@LBussy)
# This file is part of LBussy's BrewPi Script Remix (BrewPi-Script-RMX).
#
# BrewPi Script RMX is free software: you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# BrewPi Script RMX is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BrewPi Script RMX. If not, see <https://www.gnu.org/licenses/>.
# These scripts were originally a part of brewpi-script, a part of
# the BrewPi project. Legacy support (for the very popular Arduino
# controller) seems to have been discontinued in favor of new hardware.
# All credit for the original brewpi-script goes to @elcojacobs,
# @m-mcgowan, @rbrady, @steersbob, @glibersat, @Niels-R and I'm sure
# many more contributors around the world. My apologies if I have
# missed anyone; those were the names listed as contributors on the
# Legacy branch.
# See: 'original-license.md' for notes about the original project's
# license and credits. */
# Standard Imports
import threading # for running the serial read in a separate thread
import queue # for handling the serial read and write operations in a thread-safe manner
import sys # for system-specific parameters and functions
import time # for handling time-related operations
import socket # for creating socket connections
# BrewPi specific imports
from BrewPiUtil import printStdErr, logMessage # utility functions for logging and error handling
from serial import Serial, SerialException # for handling serial communications
from expandLogMessage import filterOutLogMessages # for filtering log messages
from packaging.version import Version # for version handling
import BrewPiUtil # additional utility functions
# Class for handling socket serial communication
class SocketSerial:
def __init__(self, host, port):
self.host = host # Host address
self.port = port # Port number
self.sock = None # Socket object
self.open() # Open the socket connection
def open(self):
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # Create a TCP/IP socket
self.sock.connect((self.host, self.port)) # Connect to the specified host and port
self.sock.settimeout(2) # Set timeout for read/write operations
def close(self):
if self.sock: # If socket is open
self.sock.close() # Close the socket
def write(self, data):
self.sock.sendall(data.encode('utf-8')) # Send data encoded in UTF-8
def read(self, size=1024):
return self.sock.recv(size) # Receive data from the socket
def readline(self):
data = b'' # Initialize data buffer
while True:
chunk = self.read(1) # Read one byte at a time
if chunk == b'\n': # If newline character is found, break the loop
break
data += chunk # Append byte to data buffer
return data.decode('utf-8', errors='replace') # Decode buffer to string
def isOpen(self):
try:
self.sock.send(b'') # Try to send empty byte
except socket.error:
return False # If error occurs, socket is not open
return True # If no error, socket is open
# Class for handling background serial communication
class BackGroundSerial:
def __init__(self, serial_port):
self.buffer = '' # Data buffer
self.ser = serial_port # Serial port object
self.queue = queue.Queue() # Queue for storing read lines
self.messages = queue.Queue() # Queue for storing log messages
self.thread = None # Background thread
self.error = False # Error flag
self.fatal_error = None # Fatal error message
self.run = False # Run flag
def start(self):
self.ser.write_timeout = 2 # Set write timeout
self.run = True # Set run flag to true
if not self.thread: # If thread is not already running
self.thread = threading.Thread(target=self.__listenThread) # Create new thread for listening
self.thread.setDaemon(True) # Set thread as daemon
self.thread.start() # Start the thread
def stop(self):
self.run = False # Set run flag to false
if self.thread: # If thread is running
self.thread.join() # Wait for the thread to terminate
self.thread = None # Clear the thread reference
def read_line(self):
self.exit_on_fatal_error() # Check for fatal errors
try:
return self.queue.get_nowait() # Return next line from queue if available
except queue.Empty:
return None # Return None if queue is empty
def read_message(self):
self.exit_on_fatal_error() # Check for fatal errors
try:
return self.messages.get_nowait() # Return next message from queue if available
except queue.Empty:
return None # Return None if queue is empty
def writeln(self, data):
self.write(data + "\n") # Write data followed by newline
def write(self, data):
self.exit_on_fatal_error() # Check for fatal errors
if not self.error: # If no error
try:
if hasattr(data, 'encode'): # If data is a string
self.ser.write(data.encode(encoding='cp437')) # Encode data and write to serial port
else:
self.ser.write(data) # Write data directly if not a string
except (IOError, OSError, SerialException, socket.error) as e:
logMessage('Serial/Socket Error: {0})'.format(str(e))) # Log the error
self.error = True # Set error flag to true
def exit_on_fatal_error(self):
if self.fatal_error is not None: # If a fatal error has occurred
self.stop() # Stop the background serial communication
logMessage(self.fatal_error) # Log the fatal error
if self.ser is not None: # If serial port is open
self.ser.close() # Close the serial port
del self.ser # Delete the serial port reference
sys.exit("Terminating due to fatal serial/socket error") # Exit the program
def __listenThread(self):
lastReceive = time.time() # Timestamp of last received data
while self.run: # While the run flag is true
in_waiting = None # Initialize in_waiting variable
new_data = None # Initialize new_data variable
if not self.error: # If no error
try:
in_waiting = self.ser.readline() # Read a line from serial port
if in_waiting: # If data is received
new_data = in_waiting # Set new_data to received data
lastReceive = time.time() # Update lastReceive timestamp
except (IOError, OSError, SerialException, socket.error) as e:
logMessage('Serial/Socket Error: {0})'.format(str(e))) # Log the error
self.error = True # Set error flag to true
if new_data: # If new data is received
if isinstance(new_data, bytes): # Ensure new_data is decoded to string
new_data = new_data.decode('utf-8', errors='replace')
self.buffer = self.buffer + new_data # Append new_data to buffer
line = self.__get_line_from_buffer() # Extract line from buffer
if line:
self.queue.put(line) # Add line to queue
if self.error: # If an error has occurred
try:
self.ser.close() # Close the serial port
self.ser.open() # Reopen the serial port
self.error = False # Clear the error flag
except (ValueError, OSError, SerialException, socket.error) as e:
self.ser.close() # Close the serial port
self.fatal_error = 'Lost serial/socket connection. Error: {0})'.format(str(e)) # Set fatal error message
self.run = False # Set run flag to false
time.sleep(0.01) # Sleep for a short time to avoid high CPU usage
def __get_line_from_buffer(self):
while '\n' in self.buffer: # While there is a newline character in the buffer
stripped_buffer, messages = filterOutLogMessages(self.buffer) # Filter out log messages
if len(messages) > 0: # If there are messages
for message in messages:
self.messages.put(message[2:]) # Remove "D:" and add to queue
self.buffer = stripped_buffer # Update buffer
continue
lines = self.buffer.partition('\n') # Partition buffer at newline character
if lines[1] == '': # If no newline character found
self.buffer = lines[0] # Update buffer
return None
else:
self.buffer = lines[2] # Update buffer
return self.__asciiToUnicode(lines[0]) # Convert line to Unicode and return
def __asciiToUnicode(self, s):
return BrewPiUtil.asciiToUnicode(s) # Convert ASCII string to Unicode
def setupConnection(config):
port = config.get('port') # Get port from config
if port.startswith('socket://'): # If port is a socket
host, port = port.split('://')[1].split(':') # Parse host and port
port = int(port) # Convert port to integer
return SocketSerial(host, port) # Return SocketSerial object
else:
return Serial(port, baudrate=57600, timeout=10) # Return Serial object with specified parameters
if __name__ == '__main__':
import simplejson # For handling JSON data
import time # For handling time-related operations
import BrewPiUtil as util # Additional utility functions
config_file = util.addSlash(sys.path[0]) + 'settings/config.cfg' # Config file path
config = util.readCfgWithDefaults(config_file) # Read config with defaults
ser = setupConnection(config) # Setup connection
if not ser: # If connection setup fails
printStdErr("Could not open Serial/Socket Port") # Print error message
exit() # Exit the program
bg_ser = BackGroundSerial(ser) # Create BackGroundSerial object
bg_ser.start() # Start background serial communication
success = 0 # Initialize success count
fail = 0 # Initialize fail count
for i in range(1, 5): # Loop 4 times
bg_ser.write('v') # Write 'v' to serial port
bg_ser.write('v') # Write 'v' to serial port
bg_ser.write('v') # Write 'v' to serial port
bg_ser.write('v') # Write 'v' to serial port
bg_ser.write('v') # Write 'v' to serial port
line = True # Initialize line variable
while(line): # While line is True
line = bg_ser.read_line() # Read line from background serial
if line: # If line is not None
if line[0] == 'V': # If line starts with 'V'
try:
decoded = simplejson.loads(line[2:]) # Decode JSON from line
print("Success") # Print success message
success += 1 # Increment success count
except simplejson.JSONDecodeError:
logMessage("Error: invalid JSON parameter string received: " + line) # Log error message
fail += 1 # Increment fail count
else:
print(line) # Print the line
time.sleep(5) # Sleep for 5 seconds
print("Successes: {0}, Fails: {1}".format(success, fail)) # Print summary of successes and fails
