RV-C connection for SeeLevel tank monitor

Ideas and discussion of what to do with the CAN Bus ( i.e. XMDirect, iPod, Carputer, etc... )
JimmyM
What's hacking?
Posts: 16
Joined: 2020 May 04 14:20

Re: RV-C connection for SeeLevel tank monitor

Post by JimmyM »

Yup. Page 177. Got it.

OK. The counter is a sort of hysteresis then to prevent borderline readings from flipping back and forth too quickly and make sure that full really means full.
Below are some changes I've made (Not complete code).
Eventually I'm going to have a single pin drive a few APA106 LEDs as tank alarm indicators, but I don't have that code built yet.

Code: Select all

typedef struct { // structure changed to suit new requirements for alarm conditions and level
  boolean alarm       = false;
  unsigned char alarm_trip = 0;
  unsigned char level = 0;
} tank;

tank grey;
tank fresh;
tank black;

  fresh.alarm_trip = 10;
  grey.alarm_trip = 80;
  black.alarm_trip = 80;
  
void parseTank() {
  if (buf[0] == 0) { // Fresh water tank
    fresh.level = (100*(uint16_t)buf[1]) / buf[2];
    if (fresh.level < fresh.alarm_trip){ // If fresh tank level is below the alarm trip point, set alarm parameter true
        fresh.alarm = true;
    }
    else if (fresh.level > (fresh.alarm_trip+5) ){ // Hysteresis to clear alarm condition
        fresh.alarm = false;
    }
  }
  if (buf[0] == 1) { // Black water tank
    black.level = (100*(uint16_t)buf[1]) / buf[2];
    if (black.level > black.alarm_trip){ // If black tank level is above the alarm trip point, set alarm parameter true
        black.alarm = true;
    }
    else if (black.level < (black.alarm_trip+5) ){ // Hysteresis to clear alarm condition
        black.alarm = false;
    }
  }
  if (buf[0] == 2) { // Grey water tank
    grey.level = (100*(uint16_t)buf[1]) / buf[2];
    if (grey.level > grey.alarm_trip){ // If grey tank level is above the alarm trip point, set alarm parameter true
        grey.alarm = true;
    }
    else if (grey.level < (grey.alarm_trip+5) ){ // Hysteresis to clear alarm condition
        grey.alarm = false;
    }
  }
}
JimmyM
What's hacking?
Posts: 16
Joined: 2020 May 04 14:20

Re: RV-C connection for SeeLevel tank monitor

Post by JimmyM »

What connector is used on the back of the 709-RVC for the RV-C connection? What one did you use?
Their manual says that it conforms to the RV-C standards, but the RV-C spec 4-pin connector is a 2x2 4 pin. The Seelevel unit has a 4-pin flat connector.
I've got an email out to them, but I just figured I'd ask you as well.
User avatar
linuxkidd
Site Admin
Posts: 364
Joined: 2005 Jul 22 15:48
Location: Anywhere, USA
Contact:

Re: RV-C connection for SeeLevel tank monitor

Post by linuxkidd »

Hi JimmyM,
The connector is a 3M 4 pin connector... Here's a link to the item on Mouser:
https://www.mouser.com/ProductDetail/51 ... 2165-000-1
LK
If you can read this, the light is still red.
JimmyM
What's hacking?
Posts: 16
Joined: 2020 May 04 14:20

Re: RV-C connection for SeeLevel tank monitor

Post by JimmyM »

Great thanks. I've orders some hardware and will start building something to test code on.
Once it's done, I'll post commented code if anyone else is interested.
JimmyM
What's hacking?
Posts: 16
Joined: 2020 May 04 14:20

Re: RV-C connection for SeeLevel tank monitor

Post by JimmyM »

My parts have come in.
Do you have a Pinout for the RV-C plug?
User avatar
linuxkidd
Site Admin
Posts: 364
Joined: 2005 Jul 22 15:48
Location: Anywhere, USA
Contact:

Re: RV-C connection for SeeLevel tank monitor

Post by linuxkidd »

Hi JimmyM, the Pinout starting from the release clip side is:
Release clip -> (12v) (CAN-Hi) (CAN-Lo) (Gnd)

The cable I use, that produces color pattern:
Release clip -> (Red) (White) (Green) (Black)

LK
If you can read this, the light is still red.
JimmyM
What's hacking?
Posts: 16
Joined: 2020 May 04 14:20

Re: RV-C connection for SeeLevel tank monitor

Post by JimmyM »

Perfect. Thanks.
JimmyM
What's hacking?
Posts: 16
Joined: 2020 May 04 14:20

Re: RV-C connection for SeeLevel tank monitor

Post by JimmyM »

Alrighty.
The code I have written listens for CAN messages and parses the tank values when the DGN is 1FFB7 and reports the tank levels to the screen via serial monitor.

However there's a bunch of messages like below with the DGN of 0FECA. But this doesn't seem to coordinate with the RV-C spec

Code: Select all

13:37:08.189 -> 221241,6,0FECA,AF,7,4548010105FFFF
13:37:09.175 -> 222241,6,0FECA,AF,7,4548010205FFFF
13:37:09.175 -> 222243,6,0FECA,AF,7,4549010305FFFF
13:37:10.127 -> 223199,6,0FECA,AF,7,4548010205FFFF
13:37:10.195 -> 223243,6,1FFB7,AF,8,000018FFFFFFFFFF

[\code]
User avatar
linuxkidd
Site Admin
Posts: 364
Joined: 2005 Jul 22 15:48
Location: Anywhere, USA
Contact:

Re: RV-C connection for SeeLevel tank monitor

Post by linuxkidd »

Hey JimmyM, congrats on the progress! The 0FECA DGN is an off-spec duplicate of 1FECA which is a diagnostic message ( DM_RV ). Completely safe to ignore.

LK
If you can read this, the light is still red.
User avatar
linuxkidd
Site Admin
Posts: 364
Joined: 2005 Jul 22 15:48
Location: Anywhere, USA
Contact:

Re: RV-C connection for SeeLevel tank monitor

Post by linuxkidd »

FYI -- For any other off-spec things you find, there's probably an entry for it in the rvc-spec.yml file from rvc-proxy project:
https://github.com/rvc-proxy/coachproxy ... c-spec.yml
If you can read this, the light is still red.
JimmyM
What's hacking?
Posts: 16
Joined: 2020 May 04 14:20

Re: RV-C connection for SeeLevel tank monitor

Post by JimmyM »

It's just strange. The SeeLevel is connected directly to the MCP2515 adapter. No other modules on the bus.
I'm using the DGN Filter for 1FFB7 and it functions as intended. I was just curious.
JimmyM
What's hacking?
Posts: 16
Joined: 2020 May 04 14:20

Re: RV-C connection for SeeLevel tank monitor

Post by JimmyM »

OK. All is working now. The Arduino and MCP2515 listens successfully for messages from the SeeLevel.
I have it driving 4 APA106 LEDs using the FastLED library and reading a button.
The LEDs indicate 3 tank levels and flash an alarm LED when any of the tanks read above/below a certain level. I've installed the LEDs in a Marine switch blank cover. The SeeLevel can't be seen from the kitchen. So I'll mount this display in easy view of the kitchen.

The installed hardware will be a 16MHz Arduino Pro Micro and a MCP25625 (code compatible with MCP2515) shield that will fit on the Pro Micro board.

There's still some code that you provided that isn't necessarily required for what I need. But it's still in there.

Code: Select all

/* 
   SeeLevel Alarm monitor for 5V/16MHz Pro Mini or Pro Micro
     by: Jim Minihane
    rev: 1.0
   date: 2020-05-14
  Based on...   
   RV-C CAN_Bus_Monitor
     by: Michael J. Kidd <linuxkidd@gmail.com>
    Rev: 1.0
   Date: 2015-11-23
*/

#include <FastLED.h>
#include <mcp_can.h>
#include <SPI.h>

#define DEBUG  // Enable debug output
   
#define BUTTON_PIN 7
// FastLED defines
#define NUM_LEDS 4
#define DATA_PIN 8
#define RGB_ORDER RGB
#define ALARM_ON_COUNTS 100
#define ALARM_OFF_COUNTS 100
#define tankGREEN 0x000F00
#define tankYELLOW 0x0F0F00
#define tankRED 0x0F0000
#define tankOFF 0x000000
#define alarmCOLOR1 0xFF0000
#define alarmCOLOR2 0x0000FF
#define alarmOFF 0x000000
#define ledALARM 0
#define ledFRESH 1
#define ledGREY 2
#define ledBLACK 3
                
#define SPI_CS_PIN 10 // Which pin to use for SPI CS with CAN bus interface

/*
 * Variables below this point are used internally and should not be changed.
 */


uint8_t len = 0;
uint8_t buf[8];
uint8_t alarm_state = 0;
uint8_t alarm_updown = 0;
uint16_t alarm_counter = 0;

uint32_t canId = 0x000;
char outbuf[8];
char outbuf2[32];
char hexDGN[6];

unsigned int bin2int(char * digits);
char* int2bin(uint32_t d);

void parseTank();

CRGB leds[NUM_LEDS]; // Led pixel array
  
MCP_CAN CAN(SPI_CS_PIN); // Set CS pin

typedef struct {
  uint8_t level_alert = 0;
  uint8_t level_alarm = 0;
  uint8_t level_num = 0;
  uint8_t alarm_last = 0;
  uint8_t alarm_current = 0;
} tank;

tank fresh;
tank grey;
tank black;

typedef struct {
  char prio[4];
  char dgnhi[10];
  char dgnlo[9];
  char srcAD[9];
} packetmeta;


void setup() {

  pinMode(BUTTON_PIN, INPUT_PULLUP);

  FastLED.addLeds<WS2812B, DATA_PIN, RGB_ORDER>(leds, NUM_LEDS);
  FastLED.setCorrection(0xFFFFFF); // TypicalLEDStrip);
  FastLED.setTemperature(Tungsten100W);
  FastLED.setDither(0);
  FastLED.setBrightness(255);
  fill_solid(leds, NUM_LEDS, CRGB::Black);
  FastLED.show();
  
#ifdef DEBUG
  Serial.begin(115200);
#endif


START_INIT:
#ifdef DEBUG
  Serial.println(F("CAN Begin..."));
#endif
  if (CAN_OK == CAN.begin(CAN_250KBPS, MCP_8MHz)){
    CAN.init_Mask(0, 1, 0);
    CAN.init_Mask(1, 1, 0);

    for (int i = 0; i < 6; i++) {
      CAN.init_Filt(i, 1, 0);
    }

#ifdef DEBUG
    Serial.println(F("CAN BUS Shield init ok!"));
#endif
  }
  else {
#ifdef DEBUG
    Serial.println(F("CAN BUS Shield init fail"));
    Serial.println(F("Init CAN BUS Shield again"));
#endif
    delay(100);
    goto START_INIT;
  }

  fresh.level_alarm = 17;
  fresh.level_alert = 35;
  grey.level_alarm = 80;
  grey.level_alert = 60;
  black.level_alarm = 80;
  black.level_alert = 60;
} // Setup

void loop() {

if (digitalRead(BUTTON_PIN) == 1) {
   alarm_state = 0;
} 

if (CAN_MSGAVAIL == CAN.checkReceive()) {
   CAN.readMsgBuf(&len, buf);
   canId = CAN.getCanId();
   char *binCanID=int2bin(canId);

   packetmeta p;

   // Separate the overloaded canID
   memcpy(p.prio,  &binCanID[0], 3);
   memcpy(p.dgnhi, &binCanID[4], 9);
   memcpy(p.dgnlo, &binCanID[13],8);
   memcpy(p.srcAD, &binCanID[21],8);

   // Terminate all the char strings
   p.prio[3]='\0';
   p.dgnhi[9]='\0';
   p.dgnlo[8]='\0';
   p.srcAD[8]='\0';

   free(binCanID);

   sprintf(hexDGN, "%03X%02X", // Construct full hexDGN
           bin2int(p.dgnhi),
           bin2int(p.dgnlo));
/*
#ifdef DEBUG
    sprintf(outbuf2, "%X,%03X%02X,%02X,%d,",
           bin2int(p.prio),
           bin2int(p.dgnhi),
           bin2int(p.dgnlo),
           bin2int(p.srcAD),
           len);

   //Serial.print(millis()); Serial.print(",");
   Serial.println(canId, HEX);
   Serial.print(outbuf2);
   for (int i = 0; i < len; i++) {
      sprintf(outbuf, "%02X", buf[i]);
      Serial.print(outbuf);
   }
   Serial.println();
#endif
*/
 if (strncmp(hexDGN,"1FFB7", 5) == 0 ){
      
   parseTank();
    
#ifdef DEBUG
   Serial.print(F("black.level_num: "));
   Serial.println(black.level_num,DEC);
      
   Serial.print(F("grey.level_num: "));
   Serial.println(grey.level_num,DEC);
      
   Serial.print(F("fresh.level_num: "));
   Serial.println(fresh.level_num,DEC);
#endif      
   if (fresh.level_num < fresh.level_alarm){
       fresh.alarm_current = 1;
       if (fresh.alarm_current > fresh.alarm_last){
           alarm_state = 1;
           fresh.alarm_last = fresh.alarm_current;
       }
       leds[ledFRESH] = CRGB(tankRED);
   }
   else if (fresh.level_num < fresh.level_alert){
       fresh.alarm_current = 0;
       if (fresh.alarm_current < fresh.alarm_last){
           fresh.alarm_last = fresh.alarm_current;
       }
       leds[ledFRESH] = CRGB(tankYELLOW);
   }
   else {
       fresh.alarm_current = 0;
       if (fresh.alarm_current < fresh.alarm_last){
           fresh.alarm_last = fresh.alarm_current;
       }
       leds[ledFRESH] = CRGB(tankGREEN);
   }
      
   if (grey.level_num > grey.level_alarm){
       grey.alarm_current = 1;
       if (grey.alarm_current > grey.alarm_last){
           alarm_state = 1;
           grey.alarm_last = grey.alarm_current;
       }
       leds[ledGREY] = CRGB(tankRED);
   }
   else if (grey.level_num > grey.level_alert){
       grey.alarm_current = 0;
       if (grey.alarm_current < grey.alarm_last){
           grey.alarm_last = grey.alarm_current;
       }
       leds[ledGREY] = CRGB(tankYELLOW);
   }
   else {
       grey.alarm_current = 0;
       if (grey.alarm_current < grey.alarm_last){
           grey.alarm_last = grey.alarm_current;
       }
       leds[ledGREY] = CRGB(tankGREEN);
   }
   
   if (black.level_num > black.level_alarm){
       black.alarm_current = 1;
       if (black.alarm_current > black.alarm_last){
           alarm_state = 1;
           black.alarm_last = black.alarm_current;
       }
       leds[ledBLACK] = CRGB(tankRED);
   }
   else if (black.level_num > black.level_alert){
       black.alarm_current = 0;
       if (black.alarm_current < black.alarm_last){
           black.alarm_last = black.alarm_current;
       }
       leds[ledBLACK] = CRGB(tankYELLOW);
   }
   else {
       black.alarm_current = 0;
       if (black.alarm_current < black.alarm_last){
           black.alarm_last = black.alarm_current;
       }
       leds[ledBLACK] = CRGB(tankGREEN);
   }

   if ((fresh.level_num > fresh.level_alert) && (grey.level_num < grey.level_alert) && (black.level_num < black.level_alert))
    {
      alarm_state = 0;
   }
      
   FastLED.show();
      
 } //hexDGN,"1FFB7"

} // CAN Message Avail

if (alarm_state == 1){
   if (alarm_updown == 0){
       alarm_counter++;
       leds[ledALARM] = CRGB(alarmCOLOR1);
       if (alarm_counter == ALARM_ON_COUNTS){ //ON Time
           alarm_updown = 1;
           alarm_counter = 0;
       }
   }
   else {
       alarm_counter++;
       leds[ledALARM] = CRGB(alarmCOLOR2);
       if (alarm_counter == ALARM_OFF_COUNTS){ //OFF Time
           alarm_updown = 0;
           alarm_counter = 0;
       }
   }
            
   FastLED.show();
}
else {
   leds[ledALARM] = CRGB(alarmOFF);
   FastLED.show();
} // alarm_state
} // End loop


/*
   unsigned int bin2int(char * digits)
     This function converts a string of binary bits to an int.
*/
unsigned int bin2int(char * digits) {
  unsigned int res = 0;
  int digits_len = strlen(digits);

  for (int i = 0; i < digits_len; i++) {
    if ((digits[digits_len - (i + 1)]) == '1')
      res += (1 << i);
  }
  return res;
}


/*
 * char* int2bin(uint32_t d)
 *    This funciton converts an Unsigned 32 bit int into a CHAR string of binary
 */
char* int2bin(uint32_t d) {
  const char* mybins = "01";
  int pos = 0;
  // unsigned long int b;
  char b[29];
  char *c = (char*)malloc(29);
  while(d > 0) {
    b[pos]=mybins[d % 2];
    d /= 2;
    pos++;
  }
  while(pos<29) {
    b[pos]=mybins[0];
    pos++;
  }
  b[pos]='\0';
  pos--;
  for(int i=pos;i>=0;i--) {
    c[pos-i]=b[i];
  }
  pos++;
  c[pos]='\0';
  return c;
}

/*
 * parseTank()
 * Reads Tank level and sets <tank>.level and <tank>.alarm values
*/
void parseTank() {
    
  if (buf[0] == 0) { // Fresh water tank
    fresh.level_num = (100*(uint16_t)buf[1]) / buf[2];

  }
  
  if (buf[0] == 1) { // Black water tank
    black.level_num = (100*(uint16_t)buf[1]) / buf[2];

  }
  
  if (buf[0] == 2) { // Grey water tank
    grey.level_num = (100*(uint16_t)buf[1]) / buf[2];
    
  }
}

/*********************************************************************************************************
  END FILE
*********************************************************************************************************/

User avatar
linuxkidd
Site Admin
Posts: 364
Joined: 2005 Jul 22 15:48
Location: Anywhere, USA
Contact:

Re: RV-C connection for SeeLevel tank monitor

Post by linuxkidd »

Nicely done, JimmyM.

Some thoughts on other features you may consider -- code this up on an ESP32 (or esp8266) with a small web page and MQTT support. This way, you could always display the tank level on any Web browser, or feed it into home automation or other alerting systems ( Thinking HomeAssistant, NodeRed, etc ).

Again, nice work.
LK
If you can read this, the light is still red.
User avatar
linuxkidd
Site Admin
Posts: 364
Joined: 2005 Jul 22 15:48
Location: Anywhere, USA
Contact:

Re: RV-C connection for SeeLevel tank monitor

Post by linuxkidd »

Ooh... you could also get a small e-paper display and put it in the kitchen, to display actual % levels (not just 4 segments).

Just ideas to spin your propeller :D
If you can read this, the light is still red.
JimmyM
What's hacking?
Posts: 16
Joined: 2020 May 04 14:20

Re: RV-C connection for SeeLevel tank monitor

Post by JimmyM »

I guess I never hit submit before.
Stop spinning my propeller! I hardly need encouragement to over complicate a simple system. HA!
I do have a few esp8266s around. I was thinking of using one to drive my WS2811 12V under awning strip with a web interface. I've been using a home brewed IR controller using an Arduino pro mini and a Teensy LC.
But I've never used e-paper or e-Ink before. But I DO have a few small LCD lying about.
Post Reply