• What is so unique about this clock?

    Besides the modern, clean, very legible look - designed 70+ years ago by Hans Hilfiker - it also moves in a very unique way that makes it very noticeable when exactly a new minute brakes, almost like a shot that starts a race, but silent.

    The second hand is (was) run by a synchron electric motor by the AC frequency of the power grid, and geared the way that it runs the minute in about 59 seconds and then stops. A central clock then fired a pulse to all clocks to begin the next minute.In other words, the seconds in each minute are controlled by the AC power grid locally, where as the start of a new minute is centrally managed.

    • 1. In the first face of the picture the time looks like 35 minutes - purple arc - and :57 seconds - 'white pie'.
    • 2. When the second hand reaches :60 seconds - indicating the 36th minute is about to complete - it stops for about second - waiting for the minute pulse from the central clock.
    • 3. On the pulse, the mine hand jumps to the 36th minute and the second hand is at the begin of the race thru the next minute
    • 4. The second hand is about 4 seconds into the new minute... since it is the full round is done in 59 seconds, it is a tad less than 4 seconds... actually about 50ms less...

    Since at the time of the original implementation things were working mostly analog and continuously... In the BangleJS implementation the second hand is not continuously moving, nor does the hour hand. The second hand jumps by the second and the hour hand by every 12 minutes (the angle of a common clock face minute). Nicely shown is the jump form actual completion of the minute / 60 seconds, because the value of the 360 degrees Circular Gauge changes from 60 to 0. At this time also the minute hand jumps - and ever 12th minutes - the hours hand as well.

    And here is the code. When started, it takes the time from the current time. Since the start is usually during a minute and not at the begin, the rest of the minute is implemented as a timeout that that then sets the second hand value to 0 and starts starts the new minutes with an interval. The second hand circular gauge jumps every 984ms (983.3333 would be exact) as an interval and clears itself after reaching the value of 60.

    The other arcs are just residues from the development of the Circular Gauge... The code can already now be uploaded onto the BangleJS and it behaves nicely. At some time, the clock may make it into an actual, properly sized BangleJS clock face.

    // Swiss Federal Railway Clock implemented with
    // cgauge.js - circular gauge prototype / 'class'
    // allObjects - forum.espruino.com
    // 2020-11-28
    
    // left/top = x/y = 0/0
    
    // Circular Gauge cg0:
    // - visual: 270 degree clock-wise gauge starting mid left bottom quadrant
    // - graphics: ...starting mid 2nd quadrant ending mid 1st quadrant
    // - showing values from 0..300 clockwise with initial value of 100
    
    var cg0
      , cg1,cg1c,cg1f,cg1t,cg1h
      , cg2,cg2c,cg2f,cg2t,cg2h,cg2i,cg2j,cg2u,cg2k
      , cg3,cg3c,cg3f,cg3t,cg3h
      , cg4
      , cg5
      , cg6
      , b1w,b2w,b3w
      ;
    function run() {
      halt();
      cg0=new CGauge("cg0",0,0,300,[0,  1,  0],[1  ,0  ,0  ],135,270,null,120,140,100,96);
      cg1=new CGauge("cg1",0,0, 30,[1,  1,  0],[0  ,1  ,1  ],180,180,null, 80,212, 16,13);
      cg2=new CGauge("cg2",0,0, 60,[1,  1,  1],[0.5,0.5,0.5],270,360,null,120,142, 18, 1); // sec
      cg3=new CGauge("cg3",0,0, 30,[1,  0,  1],[0  ,0  ,1  ],180,180,null,160,212, 16, 0);
      cg4=new CGauge("cg4",0,0, 40,[1,  0,  1],[0.6,0.6,0.6],120,-60,null,120,  8, 70,64);
      cg5=new CGauge("cg5",0,0, 60,[1,0.5,  1],[0.5,0.5,0.5],270,360,null,120,142, 53,42); // min
      cg6=new CGauge("cg6",0,0, 60,[1,  1,0.5],[0  ,0  ,0  ],270,360,null,120,142, 28,22); // hour
      cg0.setVal(100,-1);
      cg1.setVal( 20,-1);
      cg2.setVal(  0,-1); cg5.setVal(  0,-1); cg6.setVal(  0,-1); // sec, min, hour
      cg2.tikL=0;         cg5.tikL=0;         cg6.tikL=0;         // nbo ticks
      cg3.setVal( 15,-1);
      cg4.setVal( 10,-1);
      drawAll();
      cg1c=2; cg1f=function(){ if (!cg1.setVal(cg1.val+cg1c)) cg1c=-cg1c; };
      cg1t=750;
      cg2c=1; cg2f=function(){ var v=cg2.val+cg2c;  // +1 - seconds
                       if (v>=60 && cg2h) cg2h=clearInterval(cg2h);
                       cg2.setVal(v); };
      cg2i=function(){ cg2j(); cg2k=setInterval(cg2j,cg2u); }; // finished min
      cg2j=function(){ cg2h=setInterval(cg2f,cg2t); cg2.setVal(0); // minutes
                       cg5.setVal((cg5.val+1)%60); 
                       if (cg5.val%12==0) cg6.setVal((cg6.val+1)%60); };
      cg2t=984;cg2u=60000;
      cg3c=5; cg3f=function(){ if (!cg3.setVal(cg3.val+cg3c)) cg3c=-cg3c; };
      cg3t=1700;
      if (!b1w) b1w=setWatch(cg1f,BTN1,{edge:"rising",repeat:true});
      if (!b2w) b2w=setWatch(cg2f,BTN2,{edge:"rising",repeat:true});
      if (!b3w) b3w=setWatch(cg3f,BTN3,{edge:"rising",repeat:true});
      cont();
    }
    function cont() { var d;
       cg1h=setInterval(cg1f,cg1t);
       d=new Date();
       cg2.setVal(d.getSeconds());
       cg5.setVal(d.getMinutes());
       cg6.setVal(d.getHours()%12*5+Math.floor(cg5.val/12));
       cg2k=(cg2.val!=0)?setTimeout(cg2i,cg2u-cg2.val*cg2t)
                        :setInterval(cg2j,cg2u);
       if (cg2.val<cg2.maxV) cg2h=setInterval(cg2f,cg2t);
       cg3h=setInterval(cg3f,cg3t);
    }
    function halt() {
      if (cg1h) cg1h=clearInterval(cg1h);
      if (cg2h) cg2h=clearInterval(cg2h);
      if (cg2k) cg2k=clearInterval(cg2k);
      if (cg3h) cg3h=clearInterval(cg3h);
    }
    
    function drawAll() { 
      g.clear(); setTimeout(function() {
          cg0.draw(1); cg1.draw(1);
          for (var i=1;i<=12;i++) { cg2.drawTick(cg2.x,cg2.y,cg5.rIn-5,
            (cg6.rOut+5)/(cg5.rIn-5),cg2.rad(270+i*30),[1,0.8,0.8]); }
          cg2.draw(1); cg5.draw(1); cg6.draw(1);
          cg3.draw(1); cg4.draw(1); }
        , 1000); }
    
    var p; // temp for prototype references
    function CGauge(id,val,minV,maxV,color,fColor,begDeg,degs,deg0,x,y,rOuter,rInner) {
      var _=0||this;
      _.mxXY=239;    // x, y max graph coord - defaults for BangleJS Graphics
      _.pps=2;       // 'pixel per segment'/jaggedness/graphical precision/resolution
      _.tikL=6;      // tick-length (set to 0 post construction for no ticks drawing)
      _.id=id;       // id of the circular gauge
      _.val=null;    // temporary, set at end of construction
      _.minV=minV;   // minimum value (arc all in fColor)
      _.maxV=maxV;   // maximum value (arc all in color)
      _.clr=color;   // color - as required by Graphics - for the value arc
      _.fClr=fColor; // color - as required by Graphics - for to complete the arc
      _.begD=begDeg; // 0 degrees: +x-Axis
      _.degs=degs;   // gauge full arc in degrees -/+ = counter/clockwise
      _.deg0=(deg0)?deg0:begDeg; // for 0/center value mark; falsy defaults to begDeg
      _.x=x;         // center x
      _.y=y;         // center y
      _.rOut=rOuter; // radius outer
      _.rIn=rInner;  // radius inner (optional)
      _.begR=_.rad(_.begD);                              // begin radian
      _.arcR=(_.degs==360)?Math.PI*2:_.rad(_.degs);      // arc rad used for sCnt only
      _.segR=(Math.PI/(4/_.pps)/_.rOut)*((degs>0)?1:-1); // segment radian
      _.sCnt=Math.round(Math.abs(_.arcR/_.segR));        // segment count in arc
      _.cUp=[];                                          // clean up vertices 
      _.setVal(val,-1); // set value only
    } p=CGauge.prototype;
    p.setVal=function(v,opt) { // --- set min/max adj'd val, draw != && opt=0 || opt>0; 
      var chd = (v=(v<this.minV)?this.minV:(v>this.maxV)?this.maxV:v)!=this.val; // ret
      if (opt<0) { this.val=v; // update value only, NO drawing
      } else if (v!=this.val||opt>0) { this.val=v; this.draw(opt); }
      return chd; };
    p.draw=function(o) { // --- draw circular gauge (o-pt w/ extras, such as 0-tick)
      var s=this.sCnt,v=Math.round(s/(this.maxV-this.minV)*this.val),h=!!this.rIn,vs;
      if (o) { if (this.tikL) this.drawTicks(h); } // console.log(this.id,this.val,v,s,this.cUp);
      g.setColor(0,0,0); while (this.cUp.length) g.drawLine.apply(g,this.cUp.pop());
      if (v<s) g.setColor.apply(g,this.fClr).drawPoly(this._pvs(v,s,0),h);
      vs=this._pvs(0,v,1);
      g.setColor.apply(g,this.clr).drawPoly(vs,h); };
    p.drawTicks=function(h) { // --- draw ticks, begin and end and 0-tick
      var x=this.x,y=this.y,rTO=(h)?this.rIn:this.rOut,rTI=rTO-this.tikL,bR=this.begR
        , eR=bR+this.sCnt*this.segR,rTS=((rTI<0)?0:rTI)/rTO; // console.log(this.id,rTO,rTI,rTS);
      this.drawTick(x,y,rTO,rTS,eR,this.fClr);this.drawTick(x,y,rTO,rTS,bR,this.clr);
      if (this.deg0!=this.begD) this.drawTick(x,y,rTO,rTS,bR,this.clr); };
    p.drawTick=function(x,y,t,s,r,c) { // --- draw tick;
      var vX=t*Math.cos(r),vY=t*Math.sin(r); g.setColor.apply(g,c); g.drawLine(
        Math.round(x+vX),Math.round(y+vY),Math.round(x+vX*s),Math.round(y+vY*s)); };
    p._pvs=function(f,t,c) { // --- calc polygon vertices from..to
      var x=this.x, y=this.y, rO=this.rOut, rI=this.rIn, bR=this.begR, sR=this.segR
        , l=(t-f+1)*2*((rI)?2:1) // len of array for vertices (double w/ inner radius
        , v=((this.mxXY<=355) ? new Uint8Array(l) : new Uint16Array(l)) // vertices array
        , s=f-1  // segment index 
        , i=-1,j // vertices array index (running and 'turn around'/last outer)
        , r      // radian
        ; // console.log(x,y,rO,rI,bR,sR,f,t,s,i);
      while(++s<=t) { r=bR+s*sR;
        v[++i]=Math.round(x+rO*Math.cos(r));
        v[++i]=Math.round(y+rO*Math.sin(r)); } // console.log(this.id,s,r,v[i-1],v[i]); }
      if (rI) { j=i;
        while (--s>=f) { r=bR+s*sR;
          v[++i]=Math.round(x+rI*Math.cos(r));
          v[++i]=Math.round(y+rI*Math.sin(r)); }
        this.cUp.push((c)?v.slice(j-1,j+3):v.slice(0,2).concat(v.slice(-2)));
      } // console.log(this.id,c,j,i,v.slice(0,4),this.cUp);
      return v; };
    p.rad=function(degrs) { return 2*Math.PI*(degrs%360)/360; }; // radian <-- degrees
    // p.v=function(x,y,r,rO,ri) { // <--- vertice
    //  return [
    
    function r() { run(); }
    function h() { halt(); }
    function c() { cont(); }
    
    setTimeout(run,999);
    
About

Avatar for allObjects @allObjects started