Commit 76910731 authored by Pekka Lundstrom's avatar Pekka Lundstrom

Merge branch 'next'

parents d2f56726 e041c8b4
# Package name and version
AC_INIT(dsme, 0.64.8)
AC_INIT(dsme, 0.65.0)
AM_INIT_AUTOMAKE
......@@ -156,6 +156,19 @@ AS_IF([test "x$enable_bootreason_logger" != xno],
[AC_DEFINE([DSME_BOOTREASON_LOGGER], [1])])
AM_CONDITIONAL([WANT_BOOTREASON_LOGGER], [test x$enable_bootreason_logger != xno])
#
# Shutdown feedback
#
AC_ARG_ENABLE([shutdown-feedback],
[AS_HELP_STRING([--disable-shutdown-feedback],
[disable bootreason logger (libshutdownfeedback)])],
[],
[enable_shutdown_feedback=yes])
AS_IF([test "x$enable_shutdown_feedback" != xno],
[AC_DEFINE([DSME_SHUTDOWN_FEEDBACK], [1])])
AM_CONDITIONAL([WANT_SHUTDOWN_FEEDBACK], [test x$enable_shutdown_feedback != xno])
#
# Compiler and linker flags
#
......@@ -198,6 +211,9 @@ AM_PROG_CC_C_O()
PKG_CHECK_MODULES(GLIB, glib-2.0)
PKG_CHECK_MODULES(DBUS, dbus-1)
PKG_CHECK_MODULES(DBUSGLIB, dbus-glib-1)
if test "x$enable_shutdown_feedback" != xno; then
PKG_CHECK_MODULES(LIBNGF, libngf0)
fi
# Check libs (that are not yet checked)
# Whitespaces in 'action-if-found' fields in order to not (auto)update LIBS variable
......
......@@ -75,6 +75,10 @@ if WANT_BOOTREASON_LOGGER
pkglib_LTLIBRARIES += bootreasonlogger.la
endif
if WANT_SHUTDOWN_FEEDBACK
pkglib_LTLIBRARIES += shutdownfeedback.la
endif
startup_la_SOURCES = startup.c
# TODO: remove this
......@@ -154,7 +158,7 @@ if WANT_HW_THERMAL_MGMT
thermalobject_hw_la_SOURCES = thermalobject_hw.c \
thermalsensor_hw.c \
thermalsensor_hw.h
thermalobject_hw_la_CFLAGS = $(AM_CFLAGS) $(GLIB_CFLAGS)
thermalobject_hw_la_CFLAGS = $(AM_CFLAGS) $(GLIB_CFLAGS) $(DBUS_CFLAGS)
thermalobject_hw_la_LIBADD = $(GLIB_LIBS)
endif
......@@ -199,3 +203,9 @@ bootreasonlogger_la_SOURCES = bootreasonlogger.c
bootreasonlogger_la_CFLAGS = $(AM_CFLAGS) $(GLIB_CFLAGS) $(DBUS_CFLAGS) -D_GNU_SOURCE
bootreasonlogger_la_LIBADD = $(GLIB_LIBS)
endif
if WANT_SHUTDOWN_FEEDBACK
shutdownfeedback_la_SOURCES = shutdownfeedback.c
shutdownfeedback_la_CFLAGS = $(AM_CFLAGS) $(GLIB_CFLAGS) $(DBUS_CFLAGS) $(LIBNGF_CFLAGS) -D_GNU_SOURCE
shutdownfeedback_la_LIBADD = $(GLIB_LIBS) $(LIBNGF_LIBS)
endif
......@@ -47,6 +47,14 @@
#define CHARGING_INFO_PATH "/run/state/namespaces/Battery/IsCharging"
#define BATTERY_LEVEL_CONFIG_FILE "/etc/dsme/battery_levels.conf"
/**
* Timer value for alarm shutdown timer. This is how long we wait before reporting
* empty battery when phone woke up for alarm and battery is empty.
* Note that actually user has little bit more because battery level is not checked
* during first wakeup minute.
*/
#define ALARM_DELAYED_TIMEOUT 60
typedef enum {
BATTERY_STATUS_FULL,
BATTERY_STATUS_NORMAL,
......@@ -91,6 +99,10 @@ typedef struct battery_state_t {
static battery_state_t battery_state;
static dsme_state_t dsme_state = DSME_STATE_NOT_SET;
static bool battery_temp_normal = true;
static bool alarm_active = false;
static dsme_timer_t alarm_delayed_empty_timer = 0;
static int delayed_empty_fn(void* unused);
static void read_config_file(void)
{
......@@ -124,10 +136,10 @@ static void read_config_file(void)
new_levels[i].wakeup = (wakeup != 0);
/* Do some sanity checking for values
* Battery level values should be between 2-99, and in descending order.
* Battery level values should be between 1-99, and in descending order.
* Polling times should also make sense 10-1000s
*/
if (((i < BATTERY_STATUS_EMPTY) && (new_levels[i].min_level < 2)) ||
if (((i < BATTERY_STATUS_EMPTY) && (new_levels[i].min_level < 1)) ||
(new_levels[i].min_level > 99 ) ||
((i>0) && (new_levels[i].min_level >= new_levels[i-1].min_level)) ||
(new_levels[i].polling_time < 10 ) ||
......@@ -271,12 +283,25 @@ static void send_empty_if_needed()
if (!battery_state.is_charging) {
/* Charging is not goig on. Request shutdown */
request_shutdown = true;
/* Except when phone woke up and alarm is active.
* In that case we wait couple of minutes extra before we report empty
*/
if ((dsme_state == DSME_STATE_ACTDEAD) && alarm_active) {
if (! alarm_delayed_empty_timer) {
alarm_delayed_empty_timer = dsme_create_timer(ALARM_DELAYED_TIMEOUT,
delayed_empty_fn, NULL);
dsme_log(LOG_INFO, "batterytracker: Battery empty but shutdown delayed because of active alarm");
}
if (alarm_delayed_empty_timer)
request_shutdown = false;
}
} else if (dsme_state != DSME_STATE_ACTDEAD) {
/* If charging in USER state, make sure level won't drop more and always keep min 1% */
if ((battery_state.percentance < battery_level_when_empty_seen) ||
(battery_state.percentance < 1)) {
request_shutdown = true;
dsme_log(LOG_DEBUG, "batterytracker: Battery level keeps dropping. Must shutdown");
dsme_log(LOG_INFO, "batterytracker: Battery level keeps dropping. Must shutdown");
} else {
dsme_log(LOG_DEBUG, "batterytracker: Charging is going on. We don't shutdown");
}
......@@ -379,6 +404,24 @@ DSME_HANDLER(DSM_MSGTYPE_STATE_CHANGE_IND, server, msg)
dsme_state = msg->state;
}
DSME_HANDLER(DSM_MSGTYPE_SET_ALARM_STATE, conn, msg)
{
dsme_log(LOG_DEBUG,
"batterytracker: alarm %s state received",
msg->alarm_set ? "set" : "not set");
alarm_active = msg->alarm_set;
/* When alarm was active, we might have postponed shutdown.
* Check current status now and stop possible timer
*/
if ((!alarm_active) && alarm_delayed_empty_timer) {
dsme_destroy_timer(alarm_delayed_empty_timer);
alarm_delayed_empty_timer = 0;
dsme_log(LOG_INFO, "batterytracker: Empty state was earlier delayed due alarm. Now alarm is off");
send_empty_if_needed();
}
}
DSME_HANDLER(DSM_MSGTYPE_WAKEUP, client, msg)
{
dsme_log(LOG_DEBUG, "batterytracker: WAKEUP");
......@@ -390,9 +433,19 @@ module_fn_info_t message_handlers[] = {
DSME_HANDLER_BINDING(DSM_MSGTYPE_WAKEUP),
DSME_HANDLER_BINDING(DSM_MSGTYPE_STATE_CHANGE_IND),
DSME_HANDLER_BINDING(DSM_MSGTYPE_SET_THERMAL_STATUS),
DSME_HANDLER_BINDING(DSM_MSGTYPE_SET_ALARM_STATE),
{ 0 }
};
static int delayed_empty_fn(void* unused)
{
alarm_delayed_empty_timer = 0;
alarm_active = false;
dsme_log(LOG_INFO, "batterytracker: Alarm hold off timeout is over");
send_empty_if_needed();
return 0; /* stop the interval */
}
static void query_current_state(void)
{
......
......@@ -44,6 +44,7 @@
typedef enum {
SD_REASON_UNKNOWN,
SD_SW_REBOOT,
SD_DBUS_FAILED_REBOOT,
SD_SW_SHUTDOWN,
SD_DEVICE_OVERHEAT,
SD_BATTERY_EMPTY,
......@@ -55,6 +56,7 @@ typedef enum {
static const char* const shutdown_reason_string[SD_REASON_COUNT] = {
"Reason Unknown",
"SW reboot request",
"Dbus failed, reboot",
"SW shutdown request",
"Device overheated",
"Battery empty",
......@@ -102,20 +104,20 @@ static const char* state_name(dsme_state_t state)
static bool sw_update_running(void)
{
if (access("/tmp/os-update-running", F_OK) == 0)
return TRUE;
else
return FALSE;
return (access("/tmp/os-update-running", F_OK) == 0);
}
static bool system_still_booting(void)
{
/* Once system boot is over, init-done flag is set */
/* If file is not there, we are still booting */
if (access("/run/systemd/boot-status/init-done", F_OK) != 0)
return TRUE;
else
return FALSE;
return (access("/run/systemd/boot-status/init-done", F_OK) != 0);
}
static bool dbus_has_failed(void)
{
/* If dbus fails, dsme dbus has noticed it, marked and requested reboot */
return (access("/run/systemd/boot-status/dbus-failed", F_OK) == 0);
}
static const char * get_timestamp(void)
......@@ -280,8 +282,12 @@ DSME_HANDLER(DSM_MSGTYPE_REBOOT_REQ, conn, msg)
char* sender = endpoint_name(conn);
write_log("Received: reboot request from", sender ? sender : "(unknown)");
if (saved_shutdown_reason == SD_REASON_UNKNOWN)
saved_shutdown_reason = SD_SW_REBOOT;
if (saved_shutdown_reason == SD_REASON_UNKNOWN) {
if (dbus_has_failed())
saved_shutdown_reason = SD_DBUS_FAILED_REBOOT;
else
saved_shutdown_reason = SD_SW_REBOOT;
}
free(sender);
}
......
......@@ -31,6 +31,7 @@
#include "dsme/logging.h"
#include "dsme/modules.h"
#include "dsme/modulebase.h"
#include "dsme/state.h"
#include <glib.h>
#include <dbus/dbus.h>
......@@ -82,13 +83,18 @@ static bool dsme_dbus_check_arg_type(DBusMessageIter* iter, int want_type)
return false;
}
static DBusHandlerResult
dsme_dbus_filter(DBusConnection *con, DBusMessage *msg, void *aptr)
{
FILE* f;
if( dbus_message_is_signal(msg, DBUS_INTERFACE_LOCAL, "Disconnected") ) {
dsme_log(LOG_CRIT, "Disconnected from system bus; terminating");
dsme_exit(EXIT_FAILURE);
dsme_log(LOG_CRIT, "Disconnected from system bus; rebooting");
/* mark failure and request reboot */
if ((f = fopen(DBUS_FAILED_FILE, "w+")) != NULL)
fclose(f);
DSM_MSGTYPE_REBOOT_REQ req = DSME_MSG_INIT(DSM_MSGTYPE_REBOOT_REQ);
broadcast_internally(&req);
}
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
......
......@@ -28,6 +28,9 @@
#include <stdbool.h>
#include <dbus/dbus.h>
#define DBUS_FAILED_FILE "/run/systemd/boot-status/dbus-failed"
typedef struct DsmeDbusMessage DsmeDbusMessage;
typedef void DsmeDbusMethod(const DsmeDbusMessage* request,
......
......@@ -80,6 +80,8 @@ static bool change_runlevel(dsme_runlevel_t runlevel)
*/
static void shutdown(dsme_runlevel_t runlevel)
{
char command[64];
if ((runlevel != DSME_RUNLEVEL_REBOOT) &&
(runlevel != DSME_RUNLEVEL_SHUTDOWN) &&
(runlevel != DSME_RUNLEVEL_MALF))
......@@ -92,10 +94,25 @@ static void shutdown(dsme_runlevel_t runlevel)
runlevel == DSME_RUNLEVEL_REBOOT ? "Reboot" :
"Malf");
/* If we have systemd, use systemctl commands */
if (access("/bin/systemctl", X_OK) == 0) {
if (runlevel == DSME_RUNLEVEL_SHUTDOWN) {
snprintf(command, sizeof(command), "/bin/systemctl --no-block poweroff");
} else if (runlevel == DSME_RUNLEVEL_REBOOT) {
snprintf(command, sizeof(command), "/bin/systemctl --no-block reboot");
} else {
dsme_log(LOG_WARNING, "MALF not supported by our systemd implementation");
goto fail_and_exit;
}
dsme_log(LOG_NOTICE, "Issuing %s", command);
if (system(command) != 0) {
dsme_log(LOG_WARNING, "command %s failed: %m", command);
/* We ignore error. No retry or anything else */
}
}
/* If runlevel change fails, handle the shutdown/reboot by DSME */
if (!change_runlevel(runlevel))
else if (!change_runlevel(runlevel))
{
char command[32];
dsme_log(LOG_CRIT, "Doing forced shutdown/reboot");
sync();
......
/**
@file shutdownfeedback.c
Play vibra when shutting down
<p>
Copyright (C) 2013 Jolla Oy.
@author Pekka Lundstrom <pekka.lundstrom@jolla.com>
This file is part of Dsme.
Dsme is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License
version 2.1 as published by the Free Software Foundation.
Dsme is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with Dsme. If not, see <http://www.gnu.org/licenses/>.
*/
#include "dbusproxy.h"
#include "dsme_dbus.h"
#include "dsme/modules.h"
#include "dsme/logging.h"
#include <dsme/state.h>
#include <dsme/protocol.h>
#include <libngf/ngf.h>
#include <stdio.h>
#include <stdlib.h>
#include <glib.h>
#include <dbus/dbus.h>
#include <dbus/dbus-glib-lowlevel.h>
#define PFIX "shutdownfeedback: "
#define NGF_PWROFF_EVENT "pwroff"
static NgfClient *ngf_client = NULL;
static DBusConnection *dbus_connection = NULL;
static uint32_t playing_event_id = 0;
static void ngf_callback(NgfClient *client, uint32_t id, NgfEventState state, void *data);
static void create_ngf_client(void)
{
if (ngf_client) {
/* We already have a connection */
return;
}
if (!dbus_connection) {
dsme_log(LOG_WARNING, PFIX"No dbus connection. Can't connect to ngf");
return;
}
if ((ngf_client = ngf_client_create(NGF_TRANSPORT_DBUS, dbus_connection)) == NULL) {
dsme_log(LOG_ERR, PFIX"Can't create ngf client");
} else {
ngf_client_set_callback(ngf_client, ngf_callback, NULL);
}
}
static void destroy_ngf_client(void)
{
/* we should do something like this *
* if (ngf_client) {
* ngf_client_destroy(ngf_client);
* ngf_client = NULL;
* playing_event_id = 0;
* }
* but shutdown is already going, ngfd is already down, same as dbus
* so there is no point of doing clean destroy.
* Let system go down and forget about destroy
*/
}
static void
ngf_callback(NgfClient *client, uint32_t event_id, NgfEventState state, void *userdata)
{
(void) client;
(void) userdata;
const char *state_name;
bool play_done = false;
switch (state) {
case NGF_EVENT_FAILED:
state_name = "Failed";
play_done = true;
break;
case NGF_EVENT_COMPLETED:
state_name = "Completed";
play_done = true;
break;
case NGF_EVENT_PLAYING:
state_name = "Playing"; break;
case NGF_EVENT_PAUSED:
state_name = "Paused"; break;
case NGF_EVENT_BUSY:
state_name = "Busy"; break;
case NGF_EVENT_LONG:
state_name = "Long"; break;
case NGF_EVENT_SHORT:
state_name = "Short"; break;
default:
state_name = "Unknown";
play_done = true;
break;
}
dsme_log(LOG_DEBUG, PFIX"%s(%s, %d)", __FUNCTION__, state_name, event_id);
if (play_done) {
playing_event_id = 0;
}
}
static void play_vibra(void)
{
static char event[] = NGF_PWROFF_EVENT;
if (playing_event_id) {
/* We already are playing an event, don't start new one */
// dsme_log(LOG_DEBUG, PFIX"Play already going, skip");
return;
}
if (!ngf_client) {
create_ngf_client();
}
if (!ngf_client) {
dsme_log(LOG_ERR, PFIX"Can't play vibra. We don't have ngf client");
return;
}
playing_event_id = ngf_client_play_event (ngf_client, event, NULL);
dsme_log(LOG_DEBUG, PFIX"PLAY(%s, %d)", event, playing_event_id);
}
DSME_HANDLER(DSM_MSGTYPE_STATE_CHANGE_IND, conn, msg)
{
if ((msg->state == DSME_STATE_SHUTDOWN) ||
(msg->state == DSME_STATE_REBOOT)) {
//dsme_log(LOG_DEBUG, PFIX"shutdown/reboot state received");
play_vibra();
}
}
DSME_HANDLER(DSM_MSGTYPE_REBOOT_REQ, conn, msg)
{
// dsme_log(LOG_DEBUG, PFIX"reboot reques received");
play_vibra();
}
DSME_HANDLER(DSM_MSGTYPE_SHUTDOWN_REQ, conn, msg)
{
//dsme_log(LOG_DEBUG, PFIX"shutdown reques received");
play_vibra();
}
DSME_HANDLER(DSM_MSGTYPE_DBUS_CONNECT, conn, msg)
{
DBusError err = DBUS_ERROR_INIT;
//dsme_log(LOG_INFO, PFIX"DBUS_CONNECT");
if (!(dbus_connection = dsme_dbus_get_connection(&err))) {
dsme_log(LOG_WARNING, PFIX"can't connect to systembus: %s: %s",
err.name, err.message);
goto cleanup;
}
dbus_connection_setup_with_g_main(dbus_connection, NULL);
cleanup:
dbus_error_free(&err);
}
DSME_HANDLER(DSM_MSGTYPE_DBUS_DISCONNECT, conn, msg)
{
//dsme_log(LOG_INFO, PFIX"DBUS_DISCONNECT");
destroy_ngf_client();
if (dbus_connection) {
dbus_connection_unref(dbus_connection);
dbus_connection = NULL;
}
}
module_fn_info_t message_handlers[] = {
DSME_HANDLER_BINDING(DSM_MSGTYPE_SHUTDOWN_REQ),
DSME_HANDLER_BINDING(DSM_MSGTYPE_REBOOT_REQ),
DSME_HANDLER_BINDING(DSM_MSGTYPE_STATE_CHANGE_IND),
DSME_HANDLER_BINDING(DSM_MSGTYPE_DBUS_CONNECT),
DSME_HANDLER_BINDING(DSM_MSGTYPE_DBUS_DISCONNECT),
{0}
};
void module_init(module_t* handle)
{
dsme_log(LOG_DEBUG, "shutdownfeedback.so loaded");
}
void module_fini(void)
{
dsme_log(LOG_DEBUG, "shutdownfeedback.so unloaded");
}
......@@ -114,6 +114,9 @@ const char *modules[] = {
"dbusautoconnector.so",
#ifdef DSME_PWRKEY_MONITOR
"pwrkeymonitor.so",
#endif
#ifdef DSME_SHUTDOWN_FEEDBACK
"shutdownfeedback.so",
#endif
NULL
};
......
......@@ -30,6 +30,10 @@
#include "dsme/modules.h"
#include "dsme/logging.h"
#include "dsme_dbus.h"
#include "dbusproxy.h"
#include <dsme/thermalmanager_dbus_if.h>
static bool core_registered = false;
static bool battery_registered = false;
......@@ -78,6 +82,64 @@ static thermal_object_t battery_thermal_object = {
0
};
static int measured_core_temp = INVALID_TEMPERATURE;
static int measured_battery_temp = INVALID_TEMPERATURE;
static void core_temp_cb(thermal_object_t* thermal_object, int temp)
{
(void) thermal_object;
measured_core_temp = temp;
}
static void battery_temp_cb(thermal_object_t* thermal_object, int temp)
{
(void) thermal_object;
measured_battery_temp = temp;
}
static void core_temperature(const DsmeDbusMessage* request,
DsmeDbusMessage** reply)
{
dsme_hw_get_core_temperature(NULL, core_temp_cb);
*reply = dsme_dbus_reply_new(request);
dsme_dbus_message_append_int(*reply, measured_core_temp);
}
static void battery_temperature(const DsmeDbusMessage* request,
DsmeDbusMessage** reply)
{
dsme_hw_get_battery_temperature(NULL, battery_temp_cb);
*reply = dsme_dbus_reply_new(request);
dsme_dbus_message_append_int(*reply, measured_battery_temp);
}
static const dsme_dbus_binding_t methods[] = {
{ core_temperature, "core_temperature" },
{ battery_temperature, "battery_temperature" },
{ 0, 0 }
};
static bool bound = false;
static const char* const service = thermalmanager_service;
static const char* const interface = thermalmanager_interface;
DSME_HANDLER(DSM_MSGTYPE_DBUS_CONNECT, client, msg)
{
dsme_log(LOG_DEBUG, "thermalobject_hw: DBUS_CONNECT");
dsme_dbus_bind_methods(&bound, methods, service, interface);
}
DSME_HANDLER(DSM_MSGTYPE_DBUS_DISCONNECT, client, msg)
{
dsme_log(LOG_DEBUG, "thermalobject_hw: DBUS_DISCONNECT");
dsme_dbus_unbind_methods(&bound, methods, service, interface);
}
module_fn_info_t message_handlers[] = {
DSME_HANDLER_BINDING(DSM_MSGTYPE_DBUS_CONNECT),
DSME_HANDLER_BINDING(DSM_MSGTYPE_DBUS_DISCONNECT),
{ 0 }
};
void module_init(module_t* handle)
{
......
[Unit]
Description=DSME
DefaultDependencies=no
After=local-fs.target syslog.target usb-moded.service
Requires=dbus.service
After=local-fs.target usb-moded.service dbus.service
Conflicts=shutdown.target
[Service]
......@@ -9,7 +10,6 @@ Type=notify
# When starting dsme gets initial runlevel from the bootstate file
# If it doesn't exist, we default to USER
# This works because EnvironmentFile overrides Environment
ControlGroup=cpu:/
Environment=BOOTSTATE=USER
EnvironmentFile=-/run/systemd/boot-status/bootstate
EnvironmentFile=-/var/lib/environment/dsme/*.conf
......
Name: dsme
Summary: Device State Management Entity
Version: 0.64.7
Version: 0.65.0
Release: 0
Group: System/System Control
License: LGPLv2+
......@@ -10,6 +10,7 @@ Source1: dsme.service
Source2: dsme-rpmlintrc
Requires: systemd
Requires: statefs
Requires: ngfd
Requires(preun): systemd
Requires(post): systemd
Requires(postun): systemd
......@@ -20,6 +21,7 @@ BuildRequires: pkgconfig(libiphb)
BuildRequires: pkgconfig(dsme) >= 0.62.0
BuildRequires: pkgconfig(systemd)
BuildRequires: pkgconfig(mce) >= 1.12.3
BuildRequires: pkgconfig(libngf0)
BuildRequires: python
BuildRequires: autoconf
BuildRequires: libtool
......
#!/bin/sh
export PERIOD=5
PERIOD=5
echo Displaying elapsed time and estimated surface temperature every $PERIOD seconds
echo Displaying date, core and battery temperature every $PERIOD seconds
export STARTTIME=$(date +"%s")
while true; do
dbus-send --system --print-reply --dest=com.nokia.thermalmanager /com/nokia/thermalmanager com.nokia.thermalmanager.estimate_surface_temperature | grep int32 | while read HABA TEMPERATURE; do echo $(expr $(date +"%s") - $STARTTIME) $TEMPERATURE; done
sleep 5
CORE_TEMP=$(dbus-send --system --print-reply --dest=com.nokia.thermalmanager /com/nokia/thermalmanager com.nokia.thermalmanager.core_temperature | grep int32 | tr -s ' ' | cut -d ' ' -f3)
BATTERY_TEMP=$(dbus-send --system --print-reply --dest=com.nokia.thermalmanager /com/nokia/thermalmanager com.nokia.thermalmanager.battery_temperature | grep int32 | tr -s ' ' | cut -d ' ' -f3)
echo "`date +\"%x %T\"` core=$CORE_TEMP battery=$BATTERY_TEMP"
sleep $PERIOD
done
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment