diff --git a/lib/counter/GwCounter.h b/lib/counter/GwCounter.h
new file mode 100644
index 0000000..4a66aea
--- /dev/null
+++ b/lib/counter/GwCounter.h
@@ -0,0 +1,61 @@
+#ifndef _GWCOUNTER_H
+#define _GWCOUNTER_H
+#include <map>
+#include "ArduinoJson.h"
+template<class T> class GwCounter{
+    private:
+        typedef std::map<T,unsigned long> CounterMap;
+        CounterMap okCounter;
+        CounterMap failCounter;
+        unsigned long globalOk=0;
+        unsigned long globalFail=0;
+        String name;
+    public:
+        GwCounter(String name){
+            this->name=name;
+        };
+        void reset(){
+            okCounter.clear();
+            failCounter.clear();
+            globalFail=0;
+            globalOk=0;
+        }
+        void add(T key){
+            globalOk++;
+            auto it=okCounter.find(key);
+            if (it == okCounter.end()){
+                okCounter[key]=1;
+            }
+            else{
+                it->second++;
+            }
+        }
+        void addFail(T key){
+            globalFail++;
+            auto it=failCounter.find(key);
+            if (it == failCounter.end()){
+                failCounter[key]=1;
+            }
+            else{
+                it->second++;
+            }
+        }
+        int getJsonSize(){
+            return JSON_OBJECT_SIZE(4)+JSON_OBJECT_SIZE(okCounter.size()+1)+
+                JSON_OBJECT_SIZE(failCounter.size()+1);
+        }
+        void toJson(JsonDocument &json){
+            JsonObject jo=json.createNestedObject(name);
+            jo["sumOk"]=globalOk;
+            jo["sumFail"]=globalFail;
+            JsonObject jok=jo.createNestedObject("ok");
+            for (auto it=okCounter.begin();it!=okCounter.end();it++){
+                jok[String(it->first)]=it->second;
+            }
+            JsonObject jfail=jo.createNestedObject("fail");
+            for (auto it=failCounter.begin();it!=failCounter.end();it++){
+                jfail[String(it->first)]=it->second;
+            }
+        }
+};
+#endif
\ No newline at end of file
diff --git a/src/main.cpp b/src/main.cpp
index 043e11c..79b21ba 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -46,6 +46,7 @@ const unsigned long HEAP_REPORT_TIME=2000; //set to 0 to disable heap reporting
 #include "GwApi.h"
 #include "GwButtons.h"
 #include "GwLeds.h"
+#include "GwCounter.h"
 
 
 //NMEA message channels
@@ -72,10 +73,6 @@ GwSocketServer socketServer(&config,&logger,MIN_TCP_CHANNEL_ID);
 GwBoatData boatData(&logger);
 
 
-//counter
-int numCan=0;
-
-
 int NodeAddress;  // To store last Node Address
 
 Preferences preferences;             // Nonvolatile storage on ESP32 - To store LastDeviceAddress
@@ -88,6 +85,41 @@ void SendNMEA0183Message(const tNMEA0183Msg &NMEA0183Msg,int id);
 GwRequestQueue mainQueue(&logger,20);
 GwWebServer webserver(&logger,&mainQueue,80);
 
+GwCounter<unsigned long> countNMEA2KIn("count2Kin");
+GwCounter<unsigned long> countNMEA2KOut("count2kout");
+
+GwCounter<String> countUSBIn("countUSBin");
+GwCounter<String> countUSBOut("countUSBout");
+GwCounter<String> countTCPIn("countTCPin");
+GwCounter<String> countTCPOut("countTCPout");
+GwCounter<String> countSerialIn("countSerialIn");
+GwCounter<String> countSerialOut("countSerialOut");
+
+void updateNMEACounter(int id,const char *msg,bool incoming,bool fail=false){
+  //we rely on the msg being long enough
+  char key[6];
+  if (msg[0] == '$') {
+    strncpy(key,&msg[3],3);
+    key[3]=0;
+  }
+  else if(msg[0] == '!'){
+    strncpy(key,&msg[1],5);
+    key[5]=0;
+  }
+  else return;
+  GwCounter<String> *counter=NULL;
+  if (id == USB_CHANNEL_ID) counter=incoming?&countUSBIn:&countUSBOut;
+  if (id == SERIAL1_CHANNEL_ID) counter=incoming?&countSerialIn:&countSerialOut;
+  if (id >= MIN_TCP_CHANNEL_ID) counter=incoming?&countTCPIn:&countTCPOut;
+  if (! counter) return;
+  if (fail){
+    counter->addFail(key);
+  }
+  else{
+    counter->add(key);
+  }
+}
+
 //configs that we need in main
 
 
@@ -211,14 +243,30 @@ protected:
   virtual void processRequest()
   {
     int numPgns = nmea0183Converter->numPgns();
-    DynamicJsonDocument status(256 + numPgns * 50);
-    status["numcan"] = numCan;
+    DynamicJsonDocument status(256 + 
+      countNMEA2KIn.getJsonSize()+
+      countNMEA2KOut.getJsonSize() +
+      countUSBIn.getJsonSize()+
+      countUSBOut.getJsonSize()+
+      countSerialIn.getJsonSize()+
+      countSerialOut.getJsonSize()+
+      countTCPIn.getJsonSize()+
+      countTCPOut.getJsonSize()
+      );
     status["version"] = VERSION;
     status["wifiConnected"] = gwWifi.clientConnected();
     status["clientIP"] = WiFi.localIP().toString();
     status["numClients"] = socketServer.numClients();
     status["apIp"] = gwWifi.apIP();
-    nmea0183Converter->toJson(status);
+    //nmea0183Converter->toJson(status);
+    countNMEA2KIn.toJson(status);
+    countNMEA2KOut.toJson(status);
+    countUSBIn.toJson(status);
+    countUSBOut.toJson(status);
+    countSerialIn.toJson(status);
+    countSerialOut.toJson(status);
+    countTCPIn.toJson(status);
+    countTCPOut.toJson(status);
     serializeJson(status, result);
   }
 };
@@ -444,6 +492,7 @@ void setup() {
 
   toN2KConverter= NMEA0183DataToN2K::create(&logger,&boatData,[](const tN2kMsg &msg)->bool{
     logger.logDebug(GwLog::DEBUG+2,"send N2K %ld",msg.PGN);
+    countNMEA2KOut.add(msg.PGN);
     NMEA2000.SendMsg(msg);
     return true;
   });  
@@ -494,7 +543,7 @@ void setup() {
   NMEA2000.ExtendTransmitMessages(pgns);
   NMEA2000.ExtendReceiveMessages(nmea0183Converter->handledPgns());
   NMEA2000.SetMsgHandler([](const tN2kMsg &n2kMsg){
-    numCan++;
+    countNMEA2KIn.add(n2kMsg.PGN);
     if ( sendSeasmart->asBoolean() ) {
       char buf[MAX_NMEA2000_MESSAGE_SEASMART_SIZE];
       if ( N2kToSeasmart(n2kMsg, millis(), buf, MAX_NMEA2000_MESSAGE_SEASMART_SIZE) == 0 ) return;
@@ -519,12 +568,15 @@ void setup() {
 void sendBufferToChannels(const char * buffer, int sourceId){
   if (sendTCP->asBoolean() && checkFilter(buffer,MIN_TCP_CHANNEL_ID,false)){
     socketServer.sendToClients(buffer,sourceId);
+    updateNMEACounter(MIN_TCP_CHANNEL_ID,buffer,false);
   }
   if (sendUsb->asBoolean() && checkFilter(buffer,USB_CHANNEL_ID,false)){
     usbSerial->sendToClients(buffer,sourceId);
+    updateNMEACounter(USB_CHANNEL_ID,buffer,false);
   }
   if (serial1 && serCanWrite && checkFilter(buffer,SERIAL1_CHANNEL_ID,false)){
     serial1->sendToClients(buffer,sourceId);
+    updateNMEACounter(SERIAL1_CHANNEL_ID,buffer,false);
   }
 }
 
@@ -543,6 +595,7 @@ void SendNMEA0183Message(const tNMEA0183Msg &NMEA0183Msg, int sourceId) {
 }
 
 void handleReceivedNmeaMessage(const char *buf, int sourceId){
+  updateNMEACounter(sourceId,buf,true);
   if (! checkFilter(buf,sourceId,true)) return;
   if ((sourceId == USB_CHANNEL_ID && n2kFromUSB->asBoolean())||
       (sourceId >= MIN_TCP_CHANNEL_ID && n2kFromTCP->asBoolean())||
diff --git a/web/index.html b/web/index.html
index 14d075a..a61da02 100644
--- a/web/index.html
+++ b/web/index.html
@@ -15,6 +15,12 @@
     if (parent)parent.appendChild(el);
     return el;
   }
+  function forEl(query,callback){
+    let all=document.querySelectorAll(query);
+    for (let i=0;i<all.length;i++){
+      callback(all[i]);
+    }
+  }
   function alertRestart(){
     reloadConfig=true;
     alert("Board reset triggered, reconnect WLAN if necessary");
@@ -41,8 +47,18 @@
     getJson('/api/status')
       .then(function(jsonData){
         for (let k in jsonData){
-          if (k == 'cnv'){
-            updateCanDetails(jsonData[k]);
+          if (typeof(jsonData[k]) === 'object'){
+            for (let sk in jsonData[k]){
+              let key=k+"."+sk;
+              if (typeof(jsonData[k][sk]) === 'object'){
+                //msg details
+                updateMsgDetails(key,jsonData[k][sk]);
+              }
+              else{
+                let el=document.getElementById(key);
+                if (el) el.textContent=jsonData[k][sk];
+              }
+            }
           }
           else{
             let el=document.getElementById(k);
@@ -131,36 +147,24 @@
         alertRestart(); 
       })
   }
-  function showCanDetails(on){
-    let el=document.getElementById('canDetails');
-    if (!el) return;
-    if (on) el.classList.add('visible');
-    else(el.classList).remove('visible');
-  }
-  function updateCanDetails(details){
-    let frame=document.getElementById('canDetails');
-    if (! frame) return;
-    for (let k in details){
-      let el=frame.querySelector("[data-id=\""+k+"\"] ");
-      if (!el){
-        el=document.createElement('div');
-        el.classList.add('row');
-        let cv=document.createElement('span');
-        cv.classList.add('label');
-        cv.textContent="PGN"+k;
-        el.appendChild(cv);
-        cv=document.createElement('span');
-        cv.classList.add('value');
-        cv.setAttribute('data-id',k);
-        cv.textContent=details[k];
-        el.appendChild(cv);
-        frame.appendChild(el);
-      }
-      else{
-        el.textContent=details[k];
-      }
+  
+  function updateMsgDetails(key, details) {
+      forEl('.msgDetails', function (frame) {
+        if (frame.getAttribute('id') !== key) return;
+        for (let k in details) {
+          let el = frame.querySelector("[data-id=\"" + k + "\"] ");
+          if (!el) {
+            el = addEl('div','row',frame);
+            let cv = addEl('span','label',el,k);
+            cv = addEl('span','value',el,details[k]);
+            cv.setAttribute('data-id', k);
+          }
+          else {
+            el.textContent = details[k];
+          }
+        }
+      });
     }
-  }
   function showOverlay(text,isHtml){
     let el=document.getElementById('overlayContent');
     if (isHtml){
@@ -451,9 +455,15 @@
       let be=buttons[i];
       be.onclick=window[be.id]; //assume a function with the button id
     }
-    let cd=document.getElementById("showCanDetails");
-    cd.addEventListener('change',function(ev){
-      showCanDetails(ev.target.checked);
+    forEl('.showMsgDetails',function(cd){
+      cd.addEventListener('change',function(ev){
+        let key=ev.target.getAttribute('data-key');
+        if (! key) return;
+        let el=document.getElementById(key);
+        if (!el) return;
+        if (ev.target.checked) el.classList.remove('hidden');
+        else(el.classList).add('hidden');
+      });
     });
     let tabs=document.querySelectorAll('.tab');
     for (let i=0;i<tabs.length;i++){
@@ -544,13 +554,13 @@ button.infoButton {
 .hidden{
   display: none !important;
 }
-#canDetails{
-  display:none;
+.msgDetails .value {
+    width: 5em;
+    text-align: right;
 }
-#canDetails.visible{
-  display: block;
+.msgDetails .label {
+    width: 5em;
 }
-
 .overlayContainer {
     position: fixed;
     top: 0;
@@ -669,29 +679,97 @@ div#dashboardPage {
   <span class="label">Access Point IP</span>
   <span class="value" id="apIp">---</span>
 </div>
-<div class="row">
-  <span class="label"># NMEA2000 messages</span>
-  <span class="value" id="numcan">---</span>
+<div class="row ">
+  <span class="label">wifi client connected</span>
+  <span class="value" id="wifiConnected">---</span>
 </div>
 <div class="row even">
-  <span class="label">NMEA2000 details</span>
-  <input type="checkbox" id="showCanDetails"></span>
+  <span class="label">wifi client IP</span>
+  <span class="value" id="clientIP">---</span>
 </div>
-
-<div class="row even" id="canDetails">
-
+<div class="row">
+  <span class="label"># NMEA2000 in</span>
+  <span class="value" id="count2Kin.sumOk">---</span>
+</div>
+<div class="row even">
+  <span class="label">NMEA2000 in details</span>
+  <input type="checkbox" class="showMsgDetails" data-key="count2Kin.ok"></span>
+</div>
+<div class="row even msgDetails hidden" id="count2Kin.ok" >
+</div>
+<div class="row">
+  <span class="label"># NMEA2000 out</span>
+  <span class="value" id="count2Kout.sumOk">---</span>
+</div>
+<div class="row even">
+  <span class="label">NMEA2000 out details</span>
+  <input type="checkbox" class="showMsgDetails" data-key="count2Kout.ok"></span>
+</div>
+<div class="row even msgDetails hidden" id="count2Kout.ok" >
 </div>
 <div class="row">
   <span class="label"># TCP clients</span>
   <span class="value" id="numClients">---</span>
 </div>
 <div class="row even">
-  <span class="label">wifi client connected</span>
-  <span class="value" id="wifiConnected">---</span>
+  <span class="label"># TCP in</span>
+  <span class="value" id="countTCPin.sumOk">---</span>
 </div>
-<div class="row">
-  <span class="label">wifi client IP</span>
-  <span class="value" id="clientIP">---</span>
+<div class="row ">
+  <span class="label">TCP in details</span>
+  <input type="checkbox" class="showMsgDetails" data-key="countTCPin.ok"></span>
+</div>
+<div class="row msgDetails hidden" id="countTCPin.ok" >
+</div>
+<div class="row even">
+  <span class="label"># TCP out</span>
+  <span class="value" id="countTCPout.sumOk">---</span>
+</div>
+<div class="row ">
+  <span class="label">TCP out details</span>
+  <input type="checkbox" class="showMsgDetails" data-key="countTCPout.ok"></span>
+</div>
+<div class="row msgDetails hidden" id="countTCPout.ok" >
+</div>
+<div class="row even">
+  <span class="label"># USB in</span>
+  <span class="value" id="countUSBin.sumOk">---</span>
+</div>
+<div class="row ">
+  <span class="label">USB in details</span>
+  <input type="checkbox" class="showMsgDetails" data-key="countUSBin.ok"></span>
+</div>
+<div class="row msgDetails hidden" id="countUSBin.ok" >
+</div>
+<div class="row even">
+  <span class="label"># USB out</span>
+  <span class="value" id="countUSBout.sumOk">---</span>
+</div>
+<div class="row ">
+  <span class="label">USB out details</span>
+  <input type="checkbox" class="showMsgDetails" data-key="countUSBout.ok"></span>
+</div>
+<div class="row msgDetails hidden" id="countUSBout.ok" >
+</div>
+<div class="row even">
+  <span class="label"># Serial in</span>
+  <span class="value" id="countSerialin.sumOk">---</span>
+</div>
+<div class="row ">
+  <span class="label">Serial in details</span>
+  <input type="checkbox" class="showMsgDetails" data-key="countSerialin.ok"></span>
+</div>
+<div class="row msgDetails hidden" id="countSerialin.ok" >
+</div>
+<div class="row even">
+  <span class="label"># Serial out</span>
+  <span class="value" id="countSerialout.sumOk">---</span>
+</div>
+<div class="row ">
+  <span class="label">Serial out details</span>
+  <input type="checkbox" class="showMsgDetails" data-key="countSerialout.ok"></span>
+</div>
+<div class="row msgDetails hidden" id="countSerialout.ok" >
 </div>
 <button id="reset" >Reset</button>
 </div>