Last time device moved

dsegura7 months ago

Hello everyone.

I've been using traccar for a while, really like it.
I had one issue with combined reports, where the devices trip wouldn't "end" till another position was sent, so it looked like the device had been running all night. I understand this is not the case, but to my clients it was not that obvious, so I had to change it. I understood this happened because my device stopped sending positions too early, so the stop didn't have time to register. My solution was to change the configuration(Teltonika FMC230) to send positions even while stopped, and it worked. But this generated a different issue.
I had changed the frontEnd of Traccar in map/devices/deviceRow to show the last time the device had moved, or if it was online, by using the Date of lastUpdate and the deviceStatus, but since the device now updates even while stopped, my devices only appear offline as long as I have set the devices to send positions, then appear online again.
Is there any way to track the last time the device moved, that is already implemented? I've been trying to use the motionStreak and MotionState, but these seem inconsistent, at least in the trys I've made. I asssume adding a column or a variable that stores the last DeviceMoving Event could be a solution, just don't want to waste time if it is already done in a place I'm not seeing. Any help is apprecciated, thank you very much!

Anton Tananaev7 months ago

There's nothing that's already available.

dsegura7 months ago

All right, thanks for clarifying that. I'll get right on it, and if I find a solution, I'll post it here for anyone interested!

dsegura6 months ago

So, I managed to do this. I have added an attribute to Device.Java like this

    private Date stopTime;

    @QueryIgnore
    public Date getStopTime() {
        return stopTime;
    }

    public void setStopTime(Date stopTime) {
        this.stopTime = stopTime;
    }

Then, I made this piece of code in DeviceUtil.java:

    public static void deviceToExtendedDevice(Storage storage, Device device) {
        try {

            // Ultimo evento de parada del device
            Event lastStopEvent = storage.getObject(Event.class, new Request(
                    new Columns.All(),
                    new Condition.And(
                            new Condition.Equals("type", Event.TYPE_DEVICE_STOPPED),
                            new Condition.Equals("deviceid", device.getId())),
                    new Order("eventtime", true, 1)));

            if (lastStopEvent != null) {
                device.setStopTime(lastStopEvent.getEventTime());
            }

        }
        catch (StorageException e) {
            LOGGER.error("deviceToExtendedDevice storage exception: " + e.getMessage());
        }
    }

Wich is used in ConnectionManager.java:

    @Override
    public synchronized void updateDevice(boolean local, Device device) {
        if (local) {
            broadcastService.updateDevice(true, device);
        } else if (Device.STATUS_ONLINE.equals(device.getStatus())) {
            timeouts.remove(device.getId());
            removeDeviceSession(device.getId());
        }
        for (long userId : deviceUsers.getOrDefault(device.getId(), Collections.emptySet())) {
            if (listeners.containsKey(userId)) {
                for (UpdateListener listener : listeners.get(userId)) {
                    DeviceUtil.deviceToExtendedDevice(storage, device);
                    listener.onUpdateDevice(device);
                }
            }
        }
    }

And DeviceResource.java:

    @Path("extended")
    @GET
    public Collection<Device> getExtended(
            @QueryParam("all") boolean all,
            @QueryParam("userId") long userId,
            @QueryParam("uniqueId") List<String> uniqueIds,
            @QueryParam("id") List<Long> deviceIds) throws StorageException {

        Collection<Device> devices = get(all, userId, uniqueIds, deviceIds);
        for (Device device : devices) {
            DeviceUtil.deviceToExtendedDevice(storage, device);
        }

        return devices;
    }

And when it's finally updated, timne to show i in deviceRow.jsx:

// Función para definir el color dependiendo del estado y de la última conexión
  const getTimeColor = (stopTime) => {
    const minutesSinceUpdate = dayjs().diff(dayjs(stopTime), 'minutes');

    if (minutesSinceUpdate <= 5) {
      return classes.green; // Si han pasado menos de 5 minutos: Verde
    } else if (minutesSinceUpdate > 5 && minutesSinceUpdate <= 60) {
      return classes.yellow; // Si han pasado más de 5 minutos: Amarillo
    } else if (minutesSinceUpdate > 60 && minutesSinceUpdate <= 24 * 60) {
      return classes.red; // Si han pasado más de 1 hora: Rojo
    } else {
      return classes.grey; // Si han pasado más de 1 día: Gris
    }
  };


  // Función para mostrar al lado del nombre del dispositivo si está online o hace cuanto que está offline
  const secondaryText = () => {
    let moving = 0;
    if (item.motionStreak && item.status === 'online') {
      moving = formatStatus(item.status, t);
      return (
        <>
          {deviceSecondary && item[deviceSecondary] && `${item[deviceSecondary]} • `}
          <span className={classes.green}>{moving}</span>
        </>
      )
    } else if(!item.lastUpdate) {
      moving = formatStatus(item.status, t);
      return (
        <>
          {deviceSecondary && item[deviceSecondary] && `${item[deviceSecondary]} • `}
          <span className={getTimeColor(item.stopTime)}>{moving}</span>
        </>
      )
    } else {
      moving = dayjs(item.stopTime).fromNow(true);
        return (
          <>
            {deviceSecondary && item[deviceSecondary] && `${item[deviceSecondary]} • `}
            <span className={getTimeColor(item.stopTime)}>{moving}</span>
          </>
        );
    }
  };

And in the return in the desired place, we call the function:

            <ListItemText
              primary={item[devicePrimary]}
              primaryTypographyProps={{
                noWrap: false,
                className: desktop ? classes.desktopDeviceName : classes.mobileDeviceName
              }}
              secondary={secondaryText()}
              secondaryTypographyProps={{
                noWrap: true,
                className: classes.clickable
              }}
              onClick={(e) => {
                e.stopPropagation();
                dispatch(devicesActions.selectId(item.id));
                navigate('/reports/combined');
              }}
            />

Hope this helps someone, its been a great change for me and my clients. any help I can provide, happy to

Dave4 months ago

Hi dsegura, thanks for your solution. I can’t find Device.java , DeviceUtil.java, ConnectionManager.java or deviceResource.java files or folders anywhere in the code. Are these new files that need to be made? I’m on the latest version 6.2

Anton Tananaev4 months ago

You must be looking in the wrong source.

Dave4 months ago

Can I ask what version you are running as the classes colours use the old description ie green instead of success

Anton Tananaev4 months ago

That's completely unrelated to the topic.

dsegura4 months ago

Hello Dave. Like Anton said, you must be looking at a wrong source since they are not particularly hidden, For me, these folders where at

/home/traccar/traccar_ngpro/src/main/java/org/traccar/model/Device.java
/home/traccar/traccar_ngpro/src/main/java/org/traccar/helper/model/DeviceUtil.java
/home/traccar/traccar_ngpro/src/main/java/org/traccar/session/ConnectionManager.java
/home/traccar/traccar_ngpro/src/main/java/org/traccar/api/resource/DeviceResource.java

Hope this helps you!

Dave4 months ago

Thanks dsegura, when calling the function in deviceRow.jsx I’m getting an error that it doesn’t recognise ‘desktop’ as a classname. Is this something that’s been changed recently? Thats more the reason why I asked what version this will work on.

dsegura4 months ago
  const desktop= useMediaQuery(theme.breakpoints.down("md"));

Desktop was just a local variable to verify if it was a mobile phone or not.

While I understand wanting to use a solution right away, I don't advice straight copypasting my code. I selected only what I thought was related to the topic, but my company has heavily modified traccar's source code, to fit our necessities, thats why I didn't provide the version number, we don't update it, nor check it, since we make our own changes. Sorry I can't help you in that end, the original version we used is unkown to me, though if it's that important I'll try and track it down, it's not bad knowledge for me to have either

Dave3 months ago

Thanks dsegura, seeing as you have a heavily modified version the number won’t be necessary, did you add a js file named ‘dayjs’ for this function?

const minutesSinceUpdate = dayjs().diff(dayjs(stopTime), 'minutes');
dsegura3 months ago

I don't recall creating it so it may have been done by my predecessor. It was under traccar_web/modern/node_modules/dayjs.

It does not seem like a variable we have created but rather an imported library. Not sure if with current traccar version you can follow this path, as I have seen, modern no longer exists since it has been updated.

Davea month ago

Did you have any issues with slowing the performance of the server as it computes the stopTime for each device on every pc or app at different times, then stopTime defaults at (a month) when the page is refreshed until devices have loaded new position, is this expected behaviour?

dseguraa month ago

Hi @Dave, no, it is not expected behaviour. When we create a device and it gives position but doesn't make enough distance in those few meters to be considered a trip, it does default to a month. However once it makes a trip, the stopTime begins to be the true one.
Regarding performance, we have upwards of 400 devices and did not notice an slower connection.

But if you have a more refined code that you think is more efficient, please do share it, faster server is (usually) welcomed