Raspberry Pi i båt (del 1 - bygging og programmering hjemme)

Det hele begynte med at jeg kunne tenke meg tankmåling på septiktanken for å følge med på mengden møkk i den. Nå er det en Bavaria vi har, og de er normalt utstyrt med måler ved 3/4 tank og full tank. Men like vanlig er det at , på Bavariaer, så fungerer ikke disse målerene.

Videre er det kjekt å få noen pekepinner før tanken er så full som 3/4. Vi regner nemlig tanken som full når vannstanden er i underkant av mannlokket. Dette fordi man kan risikere å måtte åpne dette lokket dersom staking blir nødvendig. Vi fyller over dette noen ganger, men normalt holder vi oss under.

Men nå var det ikke mannskit denne tråden skulle handle om, den skulle handle om Raspberry Pi i båt. For jeg så at slike kapasitive tankmålere koster en formue i innkjøp, og kom dermed på idèen om å bygge dette selv. Jeg hadde hørt om Raspberry Pi. Jeg har nesten null erfaring med programmering. Jeg tok grunnleggende informatikk på universitetet for 13 år siden, og har skrevet noen få setninger i Java. Men det meste av det ligger nå i forrige utgave av glemmeboken. Raspberryen, sensorer og diverse, er enda ikke montert i båten. Jeg er nettopp ferdig med å bygge og teste dette hjemme. Det vil komme en ny tråd når alt er ferdig montert i båt.

Først innkjøp: 
- Raspberry Pi: Kr 330,- på Power (Expert)
- Adeept learning kit: Kr 130,- på ebay
- 4 temperatursensorer: Kr 160,- på ebay
- USB/seriell adapter: Kr 300,- på Clas Ohlson (var 50% når jeg kjøpte den. Dvs kr 150,-)
- TFT Nextion display: 230,- på ebay
- Kapasitive tanksensorer 4 stk: Kr 160,- på ebay
- Diverse koplingsmateriell, RJ45 plugger, etc: ca kr 600,- på Clas Ohlson.
- Mus og tastatur: Hadde jeg fra før.
- Strømforsyning: Hadde jeg fra før.
- HDMI kabel: Hadde jeg fra før.
- Plexiglass til å bygge kabinett: Hadde fra før.
- 4G modem: Hadde jeg fra før. (denne trenger man selvsagt kun dersom man ønsker å styre det hele hjemmefra, eller hvor som helst ellers i verden)

Totalt innkjøp ca kr 1760,-

Raspberry Pi montert på plexiplate. Her kommer ei plexiplate til som lokk. Tenker å bruke RJ45 plugger til de fleste interface, slik at jeg enkelt kan kople RPi fra dersom jeg vil ta den med hjem.

USB/seriell adapter. Denne bruker jeg for å hente navigasjonsdata fra kartplotter. Jeg får fart, dybde, kurs, vinddata, posisjon, osv levert til min Raspberry Pi. 

1W temperatursensorer. Disse er veldig kjekke. Hver sensor har egen adresse. De koples i parallell. Dvs man går ut fra RPi med tre ledere. 5V, jord og signal. Samtlige sensorer sender sine data tilbake gjennom en og samme leder. Veldig veldig enkelt oppsett. Genialt!!

TFT skjerm. Denne viser IKKE RPis skjermbilde. På denne laster man inn det man ønsker av bakgrunnsbilder. Så henter man de data man ønsker fra RPi som da vises oppå den bakgrunnen man laster inn. Også denne var enkel å konfigurere og sette opp. 

Adeept Learning kit. Et must. Her følger med ledninger, LCD display, og en hel haug duppeditter som LED, resistorer, transistorer, knapper, etc, etc. Og hovedgrunnen til at jeg kjøpte det: koplingsbrettet. Dette MÅ man ha når man skal eksperimentere. 

Det var innkjøpene. Så hva vil jeg så mer med denne søte lille dingsen enn å måle møkkanivå i septiktanken?
Mulighetene er uendelige. Man kan styre, måle og kontrollere nær sagt hva som helst. Men det tar tid. Spesielt når man må lære seg alt. Men jeg har landet på følgende funksjoner nå i år, så kan det godt være jeg bygger ut med mer funksjonalitet neste vinter.
- Tankmåling på septiktank ved å benytte kapasitive sensorer
- Tankmåling på ferskvannstank ved å benytte eksisterende resistiv måler
- Temperaturmåling i salong, maskinrom, kjøl og frys ved 1W sensorer
- Mulighet for avlesning av posisjon, fart og kurs ved kartbord. (Det er den infoen man trenger i nød når man driver)
- Styring av Eberspächer via 4G modem. Dvs at jeg, på kalde vinterdager, kan sitte hjemme og lese av temperatur i båten, og fjernstarte Eberen. Her kan man også programmere inn automatisk start dersom temperaturen faller under et gitt nivå, men i første omgang legger jeg kun inn fjernstyring.

Mulige funksjoner jeg vil programmere inn til vinteren:
- Temperaturføler utendørs
- Vinddata fra kartplotter
- Strømdata fra Victron batterimåler
- Releer/fjernstyring av varmtvannstank, kjøl, etc.
- Videoovervåkning
- Røyk/flamme overvåkning
- Nivåmåling dieseltank
- Motordata.... (om det er mulig...)
- Overføring av overnevnte data via NMEA2000 til kartplotter

HMI (human machine interface)
Hva skal man med dette uten HMI? På en eller annen måte må man lese av disse kjekke dataene, og kommunisere med denne flotte maskinen. Her har veien blitt litt til mens stien er tråkket. Jeg har landet på følgende.
Fysiske HMI (altså hardware):
- Båtens TV skjerm via HMDI kabel
- Ipad, via wifi access point (her kan ipad brukes som skjerm uten internettdekning)
- Ipad eller mobil via VNC (tilgang til RPi fra hvor som helst)
- LCD display som fulgte med Adeept learning kit (denne har jeg tatt bort etter at jeg skaffet tft display. Men styringen av det ligger fremdeles i scriptet mitt, sånn i tilfelle jeg vil ta det i bruk igjen.)
- TFT display

Men en ting er skjermene. Man må også ha software i orden. TFT displayet programmerer man enkelt med medfølgende programvare. LCD displayet tok mye tid. Det programmeres i script. På TV skjerm, Ipad, osv, så må man bruke tilgjengelig programvare. Jeg lastet ned Rasbian + Openplotter som operativsystem, og her er flere muligheter. Jeg nevner i fleng:
- Signal K instrumentpanel
- Node Red dashboard
- Freeboard
- Open CPN
Jeg har prøvd meg frem med disse forskjellige, men har landet på Node Red dashboard.

Testbenk. LCD displayet på koplingsbrett, og temperatursensorer på bordet. 

TFT display. Her vises tanknivå for septik og ferskvann. Samt temperaturer. Dette kan utvides med flere sider. Displayet er touch, og man kan også programmere inn touch funksjoner. Det har ikke jeg gjort foreløpig. Det er dette displayet som blir hoved HMI, og dette vil bli montert ved kartbord.

Node Red dashboard. Her kan man legge til mye rart. Jeg har valgt å ta inn tankmåling, temperaturer, styring av Eber samt noe navigasjonsdata. Det er denne skjermen som vises på TVen, eller på Ipad eller mobil ved VCN tilkopling. Her kan man også programmere RPi til å automatisk starte denne siden i fullskjermmodus ved oppstart. Dersom jeg vil fjernstyre Eber hjemmefra så kopler jeg meg opp via VCN, og trykker på Eberknappen like nedenfor temperaturene. Det er dette som blir hoved-HMI fra alle andre steder enn ombord i båten. 

Programmering og dataflyt
- 1W temperatur og navigasjonsdata via NMEA0183 hentes inn via SignalK, og overføres derfra videre til script og Node Red via websocket.
- Nivåmåling hentes inn fra GPIO direkte til script, og sendes derfra til Node Red via websocket.
- Styring av Eber skjer utelukkende i Node Red, da jeg ikke har behov for noe data her verken på display eller SignalK.

Under her er mine tre sider med noder i Node Red.




Skisse som viser fysiske tilkoplinger til RPi. Denne blir kjekk å ha nå når jeg skal nappe ut alle ledninger og bygge det hele opp igjen i båten. Jeg ser også nå i ettertid at det er en feil på denne skissen. Jeg har tegnet inn ar jeg bruker 5V inn på gpio. Det stemmer ikke. Jeg bruker 5V til de kapasitive sensorene og til tft skjermen. Men det er 3,3V som benyttes til alt annet. Gpio tåler ikke 5V sies det. . 

Så det var vel egentlig det jeg ønsket å formidle i denne omgang. Det vil komme mer om fysisk oppkopling, bruk av RJ45 kontakter, osv i neste tråd, når jeg har fått det hele på plass i båten. De kapasitive sensorene er allerede montert i båten. De er limt fast på utsiden av septiktank, så jeg prioriterte å få dem på plass først. Under her finner dere hele scriptet mitt. Jeg har valgt å ha det hele samlet i ett script. Det blir stort og uoversiktlig, men det funker bra for meg. Scriptet er heller ikke vakkert utført. Jeg kunne nok ha gjort det mye ryddigere. Men husk på at her har jeg lært mens jeg har jobbet. Jeg begynte for et par måneder siden som komplett noob. Store deler av scriptet (ca den første halvdel) er styringen av LCD display. Dette er forsåvidt irrelevant, ettersom jeg har lagt LCD displayet tilside. Men jeg lar det bli der inntil videre, i tilfelle jeg skulle ønske å ta LCD displayet i bruk igjen.



#!/usr/bin/python

from time import sleep


#serial port to tft display on GPIO 8 and 10
import serial
ser=serial.Serial(
 port='/dev/ttyS0',
 baudrate=9600,
 parity = serial.PARITY_NONE,
 stopbits=serial.STOPBITS_ONE,
 bytesize=serial.EIGHTBITS,
 timeout=1
)

#setup of LCD display. Normally not in use
class Adafruit_CharLCD:

 #commands
 LCD_CLEARDISPLAY =0x01
 LCD_RETURNHOME  =0x02
 LCD_ENTRYMODESET =0x04
 LCD_DISPLAYCONTROL =0x08
 LCD_CURSORSHIFT  =0x10
 LCD_FUNCTIONSET  =0x20
 LCD_SETCGRAMADDR =0X40
 LCD_SETDDRAMADDR =0x80

 #flags for display entry mode
 LCD_ENTRYRIGHT  =0x00
 LCD_ENTRYLEFT  =0X02
 LCD_ENTRYSHIFTINCREMENT =0x01
 LCD_ENTRYSHIFTDECREMENT =0x00

 #flags for display on/off control
 LCD_DISPLAYON  =0x04
 LCD_DISPLAYOFF  =0x00
 LCD_CURSORON  =0x02
 LCD_CURSOROFF  =0x00
 LCD_BLINKON  =0x01
 LCD_BLINKOFF  =0x00

 #flags for display/cursor shift
 LCD_DISPLAYMOVE  =0x08
 LCD_CURSORMOVE  =0x00

 #flags for display/cursor shift
 LCD_DISPLAYMOVE  =0x08
 LCD_CURSORMOVE  =0x00
 LCD_MOVERIGHT  =0x04
 LCD_MOVELEFT  =0x00

 #flags for function set
 LCD_8BITMODE  =0X10
 LCD_4BITMODE  =0X00
 LCD_2LINE  =0x08
 LCD_1LINE  =0X00
 LCD_5x10DOTS  =0x04
 LCD_5x8DOTS  =0x00


 def __init__(self, pin_rs=24, pin_e=23, pins_db=[17, 18, 27, 22], GPIO = None):
  if not GPIO:
   import RPi.GPIO as GPIO
   GPIO.setwarnings(False)
  self.GPIO = GPIO
  self.pin_rs = pin_rs
  self.pin_e = pin_e
  self.pins_db = pins_db
  
  self.GPIO.setmode(GPIO.BCM)
  self.GPIO.setup(self.pin_e, GPIO.OUT)
  self.GPIO.setup(self.pin_rs, GPIO.OUT)
  
  for pin in self.pins_db:
   self.GPIO.setup(pin, GPIO.OUT)

  self.write4bits(0x33)
  self.write4bits(0x32)
  self.write4bits(0x28)
  self.write4bits(0x0C)
  self.write4bits(0x06)

  self.displaycontrol = self.LCD_DISPLAYON | self.LCD_CURSOROFF | self.LCD_BLINKOFF

  self.displayfunction = self.LCD_4BITMODE | self.LCD_1LINE | self.LCD_5x8DOTS
  self.displayfunction |= self.LCD_2LINE

  """ Initialize to default text direction (for romance languages) """
  self.displaymode = self.LCD_ENTRYLEFT | self.LCD_ENTRYSHIFTDECREMENT
  self.write4bits(self.LCD_ENTRYMODESET | self.displaymode)

  self.clear()

 def begin(self, cols, lines):

  if (lines> 1):
   self.numlines = lines
   self.displayfunction |= self.LCD_2LINE
   self.currline = 0

 def home(self):

  self.write4bits(self.LCD_RETURNHOME)
  self.delayMicroseconds(3000)

 def clear(self):

  self.write4bits(self.LCD_CLEARDISPLAY)
  self.delayMicroseconds(3000)

 def setCursos(self, col, row):

  self.row_offsets = [0x00, 0x40, 0x14, 0x54]

  if ( row > self.numlines ):
   row = self.numlines - 1

  self.write4bits(self.LCD_SETDDRAMADDR | (col + self.row_offsets[row]))

 def noDisplay(self):
  """ Turn the display off (quickly) """

  self.displaycontrol &= -self.LCD.DISPLAYON
  self.write4bits(self.LCD_DISPLAYCONTROL | self.displaycontrol)

 def display(self):
  """ Turn th display on (quickly) """

  self.displaycontrol |= self.LCD_DISPLAYON
  self.write4bits(self.LCD.DISPLAYCONTROL | self.displaycontrol)

 def cursos(self):
  """ Cursor on """

  self.displaycontrol |= self.LCD_CURSORON
  self.write4bits(self.LCD_DISPLAYCONTROL | self.displaycontrol)

 def noBlink(self):
  """ Turn on and off the blinking cursor """

  self.displaycontrol &= -self.LCD_BLINKON
  self.write4bits(self.LCD_DISPLAYCONTROL | self.displaycontrol)

 def DisplayLeft(self):
  """ These commands scroll the display without changing the RAM """
  
  self.write4bits(self.LCD_CURSORSHIFT | self.LCD_DISPLAYMOVE | self.LCD_MOVELEFT)

 def scrollDisplayRight(self):
  """ These commands scroll the display without changing the RAM """

  self.write4bits(self.LCD_CURSORSHIFT | self.LCD_DISPLAYMOVE | self.LCD_MOVERIGHT);

 def leftToRight(self):
  """ This is for text that flows Left to Right """

  self.displaymode |= self.LCD_ENTRYLEFT
  self.write4bits(self.LCD_ENTRYMODESET | self.displaymode);

 def rightToLeft(self):
  """ This is for text that flows Right to Left """

  self.displaymode &= -self.LCD_ENTRYLEFT
  self.write4bits(self.LCD_ENTRYMODESET| self.displaymode)

 def autoscroll(self):
  """ This will 'right justify' text from the cursor """

  self.displaymode &= -self.LCD_ENTRYSHIFTINCREMENT
  self.write4bits(self.LCD_ENTRYMODESET | self.displaymode)

 def noAutoscroll(self):
  """ This will 'lef justify' text from the cursor """

  self.displaymode &= -self.LCD_ENTRYSHIFTINCREMENT
  self.write4bits(self.LCD_ENTRYMODESET | self.displaymode)

 def write4bits(self, bits, char_mode=False):
  """ Send command to LCD """

  self.delayMicroseconds(1000)

  bits=bin(bits)[2:].zfill(8)

  self.GPIO.output(self.pin_rs, char_mode)

  for pin in self.pins_db:
   self.GPIO.output(pin, False)

  for i in range(4):
   if bits[i] == "1":
    self.GPIO.output(self.pins_db[::-1][i], True)

  self.pulseEnable()

  for pin in self.pins_db:
   self.GPIO.output(pin, False)

  for i in range(4,8):
   if bits[i] == "1":
    self.GPIO.output(self.pins_db[::-1][i-4], True)

  self.pulseEnable()

 def delayMicroseconds(self, microseconds):
  seconds = microseconds / float(1000000)
  sleep(seconds)

 def pulseEnable(self):
  self.GPIO.output(self.pin_e, False)
  self.delayMicroseconds(1)
  self.GPIO.output(self.pin_e, True)
  self.delayMicroseconds(1)
  self.GPIO.output(self.pin_e, False)
  self.delayMicroseconds(1)

 def message(self, text):
  """ Send string to LCD. Newline wraps to second line"""

  for char in text:
   if char == '\n':
    self.write4bits(0xC0)
   else:
    self.write4bits(ord(char),True)

#script for controlling temp sensors and tank level sensors starting here

import RPi.GPIO as GPIO
import time

#setting the tank level sensors. Don't ask why they are called buttons. The first part here was
#written in a very early stage of my own development. This is my first and only script ever. But it works. 

#sewage tank
BTN_100=19
BTN_75=13
BTN_50=6
BTN_25=5

#fresh water tank
BTN_F100=12
BTN_F75=16
BTN_F50=20
BTN_F25=21

def setup():
 GPIO.setmode(GPIO.BCM)
 GPIO.setup(BTN_100, GPIO.IN, pull_up_down=GPIO.PUD_UP)
 GPIO.setup(BTN_75, GPIO.IN, pull_up_down=GPIO.PUD_UP)
 GPIO.setup(BTN_50, GPIO.IN, pull_up_down=GPIO.PUD_UP)
 GPIO.setup(BTN_25, GPIO.IN, pull_up_down=GPIO.PUD_UP)
 GPIO.setup(BTN_F100, GPIO.IN, pull_up_down=GPIO.PUD_UP)
 GPIO.setup(BTN_F75, GPIO.IN, pull_up_down=GPIO.PUD_UP)
 GPIO.setup(BTN_F50, GPIO.IN, pull_up_down=GPIO.PUD_UP)
 GPIO.setup(BTN_F25, GPIO.IN, pull_up_down=GPIO.PUD_UP)

#using socket to communicate to and from node red
import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
UDP_IP = "127.0.0.1"
UDP_PORT = 5005

interval=20
intervalF=25

#loop starts here
def loop():
 lcd = Adafruit_CharLCD()
 SignalK=""
 while True: 
  #sewage tank
  level=20
  if GPIO.input(BTN_25) == 0:
   level=level+interval
  if GPIO.input(BTN_50) == 0:
   level=level+interval
  if GPIO.input(BTN_75) == 0:
   level=level+interval
  if GPIO.input(BTN_100) == 0:
   level=level+interval
  if level == 20:
   level = 0
  
  #fresh water tank
  levelF=0
  if GPIO.input(BTN_F25) == 0:
   levelF=levelF+intervalF
  if GPIO.input(BTN_F50) == 0:
   levelF=levelF+intervalF
  if GPIO.input(BTN_F75) == 0:
   levelF=levelF+intervalF
  if GPIO.input(BTN_F100) == 0:
   levelF=levelF+intervalF  
  
  #send values to LCD
  lcd.clear()
  str1 = 'Septik: %s'%level
  str2 = '\nFerskvann: %s' %levelF
  str3 = '%'
  mystring=str1+str3+str2+str3
  str4 = 'W %s'%level
  str5 = 'F %s'%levelF
  str6 = " "
  lcd.message(mystring)

  #send values to tft
  ser.write(b'j0.val=')
  ser.write(str(level))
  ser.write('\xff\xff\xff')

  ser.write(b'j1.val=')
  ser.write(str(levelF))
  ser.write('\xff\xff\xff')

  #send values to node red
  MESSAGE = str4
  "UDP target IP:", UDP_IP
  "UDP target port:", UDP_PORT
  "message:", MESSAGE
  sock.sendto(MESSAGE, (UDP_IP, UDP_PORT))
  sleep(5)
 
  MESSAGE = str5
  "UDP target IP:", UDP_IP
  "UDP target port:", UDP_PORT
  "message:", MESSAGE
  sock.sendto(MESSAGE, (UDP_IP, UDP_PORT))
  sleep(5)

  #get 1w temperature values from node red
  import socket
  ip = '127.0.0.1'
  port = 5006
  s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
  
  s.bind((ip, port))
  data, addr = s.recvfrom(20)
  str7 = 'Maskin: '
  str8 = '%.4s' %data
  str9 = 'C'
  str108 = '%.2s' %data

  import socket
  ip = '127.0.0.1'
  port = 5007
  s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
  
  s.bind((ip, port))
  data, addr = s.recvfrom(20)
  str10 = '\nSalong: '
  str11 = '%.4s' %data
  str12 = 'C'
  str111 = '%.2s' %data
  
  #send temperatures to LCD
  mystring=str7+str8+str9+str10+str11+str12
  lcd.clear()
  lcd.message(mystring)
  sleep(5)
  
  import socket
  ip = '127.0.0.1'
  port = 5008
  s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
  
  s.bind((ip, port))
  data, addr = s.recvfrom(20)
  str13 = 'Frys: '
  str14 = '%.4s' %data
  str15 = 'C'
  str114 = '%.2s' %data

  import socket
  ip = '127.0.0.1'
  port = 5009
  s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
  
  s.bind((ip, port))
  data, addr = s.recvfrom(20)
  str16 = '\nKjoel: '
  str17 = '%.4s' %data
  str18 = 'C'
  str117 = '%.2s' %data

  #send temperatures to LCD
  mystring=str13+str14+str15+str16+str17+str18
  lcd.clear()
  lcd.message(mystring)
  sleep(5)

  #send temperatures to tft
  ser.write(b'n0.val=')
  ser.write(str111)
  ser.write('\xff\xff\xff')

  ser.write(b'n1.val=')
  ser.write(str108)
  ser.write('\xff\xff\xff')

  ser.write(b'n2.val=')
  ser.write(str117)
  ser.write('\xff\xff\xff')

  ser.write(b'n3.val=')
  ser.write(str114)
  ser.write('\xff\xff\xff')


def destroy():
 GPIO.cleanup()

if __name__ == '__main__':
 setup()
 try:
  loop()
 except KeyboardInterrupt:
  destroy()



Kommentarer

Populære innlegg fra denne bloggen

Vårpuss: Rubbing og polering av skuteside - forbannet mikrofiber

Lei av frossen melk og tomme batterier? - Nytt kjøleaggregat